/* Copyright (C) 2005-2009 Michel de Boer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "twinkle_config.h" #include #include #include #include #include #include "audio_session.h" #include "line.h" #include "log.h" #include "sys_settings.h" #include "translator.h" #include "user.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #ifdef HAVE_ZRTP #include "twinkle_zrtp_ui.h" #endif static t_audio_session *_audio_session; /////////// // PRIVATE /////////// bool t_audio_session::is_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); return p->part_of_3way(l->get_line_number()); } t_audio_session *t_audio_session::get_peer_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); t_line *peer_line = p->get_3way_peer_line(l->get_line_number()); return peer_line->get_audio_session(); } bool t_audio_session::open_dsp(void) { if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(), sys_config->get_dev_mic())) { return open_dsp_full_duplex(); } return open_dsp_speaker() && open_dsp_mic(); } bool t_audio_session::open_dsp_full_duplex(void) { // Open audio device speaker = t_audio_io::open(sys_config->get_dev_speaker(), true, true, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!speaker) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_full_duplex", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } // Disable recording // If recording is not disabled, then the capture buffers will // already fill with data. Then when the audio_rx thread starts // to read blocks of 160 samples, it gets all these initial blocks // very quickly 1 per 12 ms I have seen. And hence the timestamps // for these blocks get out of sync with the RTP stack. // Also a large delay is introduced by this. So recording should // be enabled just before the data is read from the device. speaker->enable(true, false); mic = speaker; return true; } bool t_audio_session::open_dsp_speaker(void) { speaker = t_audio_io::open(sys_config->get_dev_speaker(), true, false, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!speaker) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_speaker", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } return true; } bool t_audio_session::open_dsp_mic(void) { mic = t_audio_io::open(sys_config->get_dev_mic(), false, true, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!mic) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_mic", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } // Disable recording // If recording is not disabled, then the capture buffers will // already fill with data. Then when the audio_rx thread starts // to read blocks of 160 samples, it gets all these initial blocks // very quickly 1 per 12 ms I have seen. And hence the timestamps // for these blocks get out of sync with the RTP stack. // Also a large delay is introduced by this. So recording should // be enabled just before the data is read from the device. speaker->enable(true, false); return true; } /////////// // PUBLIC /////////// t_audio_session::t_audio_session(t_session *_session, const string &_recv_host, unsigned short _recv_port, const string &_dst_host, unsigned short _dst_port, t_audio_codec _codec, unsigned short _ptime, const map &recv_payload2ac, const map &send_ac2payload, bool encrypt) { valid = false; session = _session; audio_rx = NULL; audio_tx = NULL; thr_audio_rx = NULL; thr_audio_tx = NULL; speaker = NULL; mic = NULL; codec = _codec; ptime = _ptime; is_encrypted = false; zrtp_sas.clear(); // Assume the SAS is confirmed. When a SAS is received from the ZRTP // stack, the confirmed flag will be cleared. zrtp_sas_confirmed = true; srtp_cipher_mode.clear(); log_file->write_header("t_audio_session::t_audio_session"); log_file->write_raw("Receive RTP from: "); log_file->write_raw(_recv_host); log_file->write_raw(":"); log_file->write_raw(_recv_port); log_file->write_endl(); log_file->write_raw("Send RTP to: "); log_file->write_raw(_dst_host); log_file->write_raw(":"); log_file->write_raw(_dst_port); log_file->write_endl(); log_file->write_footer(); t_user *user_config = get_line()->get_user(); // Create RTP session try { if (_recv_host.empty() || _recv_port == 0) { rtp_session = new t_twinkle_rtp_session( InetHostAddress("0.0.0.0")); MEMMAN_NEW(rtp_session); } else { rtp_session = new t_twinkle_rtp_session( InetHostAddress(_recv_host.c_str()), _recv_port); MEMMAN_NEW(rtp_session); } #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque && rtp_session->is_zrtp_initialized()) { zque->setEnableZrtp(encrypt); if (user_config->get_zrtp_enabled()) { // Create the ZRTP call back interface TwinkleZrtpUI* twui = new TwinkleZrtpUI(this); // The ZrtpQueue keeps track of the twui - the destructor of // ZrtpQueue (aka t_twinkle_rtp_session) deletes this object, // thus no other management is required. zque->setUserCallback(twui); } } #endif } catch(...) { // If the RTPSession constructor throws an exception, no // object is created, so clear the pointer. rtp_session = NULL; string msg(TRANSLATE2("CoreAudio", "Failed to create a UDP socket (RTP) on port %1")); msg = replace_first(msg, "%1", int2str(_recv_port)); log_file->write_report(msg, "t_audio_session::t_audio_session", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); return; } if (!_dst_host.empty() && _dst_port != 0) { rtp_session->addDestination( InetHostAddress(_dst_host.c_str()), _dst_port); } // Set payload format for outgoing RTP packets map::const_iterator it; it = send_ac2payload.find(codec); assert(it != send_ac2payload.end()); unsigned short payload_id = it->second; rtp_session->setPayloadFormat(DynamicPayloadFormat( payload_id, audio_sample_rate(codec))); // Open and initialize sound card t_audio_session *as_peer; if (is_3way() && (as_peer = get_peer_3way())) { speaker = as_peer->get_dsp_speaker(); mic = as_peer->get_dsp_mic(); if (!speaker || !mic) return; } else { if (!open_dsp()) return; } #ifdef HAVE_SPEEX // Speex AEC auxiliary data initialization do_echo_cancellation = false; if (user_config->get_speex_dsp_aec()) { int nsamples = audio_sample_rate(codec) / 1000 * ptime; speex_echo_state = speex_echo_state_init(nsamples, 5*nsamples); do_echo_cancellation = true; echo_captured_last = true; } #endif // Create recorder if (!_recv_host.empty() && _recv_port != 0) { audio_rx = new t_audio_rx(this, mic, rtp_session, codec, payload_id, ptime); MEMMAN_NEW(audio_rx); // Setup 3-way configuration if this audio session is part of // a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (!peer || !peer->audio_rx) { // There is no peer rx yet, so become the main rx audio_rx->join_3way(true, NULL); if (peer && peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(audio_rx); } } else { // There is a peer rx already so that must be the // main rx. audio_rx->join_3way(false, peer->audio_rx); peer->audio_rx->set_peer_rx_3way(audio_rx); if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(audio_rx); } } } } // Create player if (!_dst_host.empty() && _dst_port != 0) { audio_tx = new t_audio_tx(this, speaker, rtp_session, codec, recv_payload2ac, ptime); MEMMAN_NEW(audio_tx); // Setup 3-way configuration if this audio session is part of // a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (!peer) { // There is no peer tx yet, so become the mixer tx audio_tx->join_3way(true, NULL, NULL); } else if (!peer->audio_tx) { // There is a peer audio session, but no peer tx, // so become the mixer tx audio_tx->join_3way(true, NULL, peer->audio_rx); } else { // There is a peer tx already. That must be the // mixer. audio_tx->join_3way( false, peer->audio_tx, peer->audio_rx); } } } valid = true; } t_audio_session::~t_audio_session() { // Delete of the audio_rx and audio_tx objects will terminate // thread execution. if (audio_rx) { // Reconfigure 3-way configuration if this audio session is // part of a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (peer) { // Make the peer audio rx the main rx and remove // reference to this audio rx if (peer->audio_rx) { peer->audio_rx->set_peer_rx_3way(NULL); peer->audio_rx->set_main_rx_3way(true); } // Remove reference to this audio rx if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(NULL); } } } MEMMAN_DELETE(audio_rx); delete audio_rx; } if (audio_tx) { // Reconfigure 3-way configuration if this audio session is // part of a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (peer) { // Make the peer audio tx the mixer and remove // reference to this audio tx if (peer->audio_tx) { peer->audio_tx->set_peer_tx_3way(NULL); peer->audio_tx->set_mixer_3way(true); } } } MEMMAN_DELETE(audio_tx); delete audio_tx; } if (thr_audio_rx) { MEMMAN_DELETE(thr_audio_rx); delete thr_audio_rx; } if (thr_audio_tx) { MEMMAN_DELETE(thr_audio_tx); delete thr_audio_tx; } if (rtp_session) { log_file->write_header("t_audio_session::~t_audio_session"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": stopping RTP session.\n"); log_file->write_footer(); MEMMAN_DELETE(rtp_session); delete rtp_session; log_file->write_header("t_audio_session::~t_audio_session"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": RTP session stopped.\n"); log_file->write_footer(); } if (speaker && (!is_3way() || !get_peer_3way())) { if (mic == speaker) mic = 0; MEMMAN_DELETE(speaker); delete speaker; speaker = 0; } if (mic && (!is_3way() || !get_peer_3way())) { MEMMAN_DELETE(mic); delete mic; mic = 0; } #ifdef HAVE_SPEEX // cleaning speech AEC if (do_echo_cancellation) { speex_echo_state_destroy(speex_echo_state); } #endif } void t_audio_session::set_session(t_session *_session) { mtx_session.lock(); session = _session; mtx_session.unlock(); } void t_audio_session::run(void) { _audio_session = this; log_file->write_header("t_audio_session::run"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": starting RTP session.\n"); log_file->write_footer(); rtp_session->startRunning(); log_file->write_header("t_audio_session::run"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": RTP session started.\n"); log_file->write_footer(); if (audio_rx) { try { // Set the running flag now instead of at the start of // t_audio_tx::run as due to race conditions the thread might // get destroyed before the run method starts running. The // destructor still has to wait on the thread to finish. audio_rx->set_running(true); thr_audio_rx = new t_thread(main_audio_rx, NULL); MEMMAN_NEW(thr_audio_rx); // thr_audio_rx->set_sched_fifo(90); thr_audio_rx->detach(); } catch (int) { audio_rx->set_running(false); string msg(TRANSLATE2("CoreAudio", "Failed to create audio receiver thread.")); log_file->write_report(msg, "t_audio_session::run", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } if (audio_tx) { try { // See comment above for audio_rx audio_tx->set_running(true); thr_audio_tx = new t_thread(main_audio_tx, NULL); MEMMAN_NEW(thr_audio_tx); // thr_audio_tx->set_sched_fifo(90); thr_audio_tx->detach(); } catch (int) { audio_tx->set_running(false); string msg(TRANSLATE2("CoreAudio", "Failed to create audio transmitter thread.")); log_file->write_report(msg, "t_audio_session::run", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } } void t_audio_session::set_pt_out_dtmf(unsigned short pt) { if (audio_rx) audio_rx->set_pt_telephone_event(pt); } void t_audio_session::set_pt_in_dtmf(unsigned short pt, unsigned short pt_alt) { if (audio_tx) audio_tx->set_pt_telephone_event(pt, pt_alt); } void t_audio_session::send_dtmf(char digit, bool inband) { if (audio_rx) audio_rx->push_dtmf(digit, inband); } t_line *t_audio_session::get_line(void) const { t_line *line; mtx_session.lock(); line = session->get_line(); mtx_session.unlock(); return line; } void t_audio_session::start_3way(void) { if (audio_rx) { audio_rx->join_3way(true, NULL); } if (audio_tx) { audio_tx->join_3way(true, NULL, NULL); } } void t_audio_session::stop_3way(void) { if (audio_rx) { t_audio_session *peer = get_peer_3way(); if (peer) { if (peer->audio_rx) { peer->audio_rx->set_peer_rx_3way(NULL); } if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(NULL); } } audio_rx->stop_3way(); } if (audio_tx) { t_audio_session *peer = get_peer_3way(); if (peer) { if (peer->audio_tx) { peer->audio_tx->set_peer_tx_3way(NULL); } } audio_tx->stop_3way(); } } bool t_audio_session::is_valid(void) const { return valid; } t_audio_io* t_audio_session::get_dsp_speaker(void) const { return speaker; } t_audio_io* t_audio_session::get_dsp_mic(void) const { return mic; } bool t_audio_session::matching_sample_rates(void) const { int codec_sample_rate = audio_sample_rate(codec); return (speaker->get_sample_rate() == codec_sample_rate && mic->get_sample_rate() == codec_sample_rate); } void t_audio_session::confirm_zrtp_sas(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->SASVerified(); set_zrtp_sas_confirmed(true); } #endif } void t_audio_session::reset_zrtp_sas_confirmation(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->resetSASVerified(); set_zrtp_sas_confirmed(false); } #endif } void t_audio_session::enable_zrtp(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->setEnableZrtp(true); } #endif } void t_audio_session::zrtp_request_go_clear(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->requestGoClear(); } #endif } void t_audio_session::zrtp_go_clear_ok(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->goClearOk(); } #endif } bool t_audio_session::get_is_encrypted(void) const { mtx_zrtp_data.lock(); bool b = is_encrypted; mtx_zrtp_data.unlock(); return b; } string t_audio_session::get_zrtp_sas(void) const { mtx_zrtp_data.lock(); string s = zrtp_sas; mtx_zrtp_data.unlock(); return s; } bool t_audio_session::get_zrtp_sas_confirmed(void) const { mtx_zrtp_data.lock(); bool b = zrtp_sas_confirmed; mtx_zrtp_data.unlock(); return b; } string t_audio_session::get_srtp_cipher_mode(void) const { mtx_zrtp_data.lock(); string s = srtp_cipher_mode; mtx_zrtp_data.unlock(); return s; } void t_audio_session::set_is_encrypted(bool on) { mtx_zrtp_data.lock(); is_encrypted = on; mtx_zrtp_data.unlock(); } void t_audio_session::set_zrtp_sas(const string &sas) { mtx_zrtp_data.lock(); zrtp_sas = sas; mtx_zrtp_data.unlock(); } void t_audio_session::set_zrtp_sas_confirmed(bool confirmed) { mtx_zrtp_data.lock(); zrtp_sas_confirmed = confirmed; mtx_zrtp_data.unlock(); } void t_audio_session::set_srtp_cipher_mode(const string &cipher_mode) { mtx_zrtp_data.lock(); srtp_cipher_mode = cipher_mode; mtx_zrtp_data.unlock(); } #ifdef HAVE_SPEEX bool t_audio_session::get_do_echo_cancellation(void) const { return do_echo_cancellation; } bool t_audio_session::get_echo_captured_last(void) { return echo_captured_last; } void t_audio_session::set_echo_captured_last(bool value) { echo_captured_last = value; } SpeexEchoState *t_audio_session::get_speex_echo_state(void) { return speex_echo_state; } #endif void *main_audio_rx(void *arg) { _audio_session->audio_rx->run(); return NULL; } void *main_audio_tx(void *arg) { _audio_session->audio_tx->run(); return NULL; }