summaryrefslogtreecommitdiffstats
path: root/src/audio/audio_rx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/audio_rx.cpp')
-rw-r--r--src/audio/audio_rx.cpp915
1 files changed, 915 insertions, 0 deletions
diff --git a/src/audio/audio_rx.cpp b/src/audio/audio_rx.cpp
new file mode 100644
index 0000000..bb6db2b
--- /dev/null
+++ b/src/audio/audio_rx.cpp
@@ -0,0 +1,915 @@
+/*
+ Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com>
+
+ 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 <iostream>
+#include <cstdio>
+#include <ctime>
+#include <cstdlib>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <cc++/config.h>
+
+#include "audio_rx.h"
+#include "log.h"
+#include "phone.h"
+#include "rtp_telephone_event.h"
+#include "userintf.h"
+#include "line.h"
+#include "sys_settings.h"
+#include "sequence_number.h"
+#include "audits/memman.h"
+
+extern t_phone *phone;
+
+#define SAMPLE_BUF_SIZE (audio_encoder->get_ptime() * audio_encoder->get_sample_rate()/1000 *\
+ AUDIO_SAMPLE_SIZE/8)
+
+// Debug macro to print timestamp
+#define DEBUG_TS(s) { gettimeofday(&debug_timer, NULL);\
+ cout << "DEBUG: ";\
+ cout << debug_timer.tv_sec * 1000 +\
+ debug_timer.tv_usec / 1000;\
+ cout << " " << (s) << endl;\
+ }
+
+//////////
+// PRIVATE
+//////////
+
+bool t_audio_rx::get_sound_samples(unsigned short &sound_payload_size, bool &silence) {
+ int status;
+ struct timespec sleeptimer;
+ //struct timeval debug_timer;
+
+ silence = false;
+
+ mtx_3way.lock();
+
+ if (is_3way && !is_main_rx_3way) {
+ // We are not the main receiver in a 3-way call, so
+ // get the sound samples from the local media buffer.
+ // This buffer will be filled by the main receiver.
+ if (!media_3way_peer_rx->get(input_sample_buf, SAMPLE_BUF_SIZE)) {
+ // The mutex is unlocked before going to sleep.
+ // First I had the mutex unlock after the sleep.
+ // That worked fine with LinuxThreading, but it does
+ // not work with NPTL. It causes a deadlock when
+ // the main receiver calls post_media_peer_rx_3way
+ // as NPTL does not fair scheduling. This thread
+ // simly gets the lock again and the main receiver
+ // dies from starvation.
+ mtx_3way.unlock();
+
+ // There is not enough data yet. Sleep for 1 ms.
+ sleeptimer.tv_sec = 0;
+ sleeptimer.tv_nsec = 1000000;
+ nanosleep(&sleeptimer, NULL);
+ return false;
+ }
+
+ mtx_3way.unlock();
+ } else {
+ // Don't keep the 3way mutex locked while waiting for the DSP.
+ mtx_3way.unlock();
+
+ // Get the sound samples from the DSP
+ status = input_device->read(input_sample_buf, SAMPLE_BUF_SIZE);
+
+ if (status != SAMPLE_BUF_SIZE) {
+ if (!logged_capture_failure) {
+ // Log this failure only once
+ log_file->write_header("t_audio_rx::get_sound_samples",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": sound capture failed.\n");
+ log_file->write_raw("Status: ");
+ log_file->write_raw(status);
+ log_file->write_endl();
+ log_file->write_footer();
+ logged_capture_failure = true;
+ }
+
+ stop_running = true;
+ return false;
+ }
+
+ // If line is muted, then fill sample buffer with silence.
+ // Note that we keep reading the dsp, to prevent the DSP buffers
+ // from filling up.
+ if (get_line()->get_is_muted()) {
+ memset(input_sample_buf, 0, SAMPLE_BUF_SIZE);
+ }
+ }
+
+ // Convert buffer to a buffer of shorts as the samples are 16 bits
+ short *sb = (short *)input_sample_buf;
+
+ mtx_3way.lock();
+ if (is_3way) {
+ // Send the sound samples to the other receiver if we
+ // are the main receiver.
+ // There may be no other receiver when one of the far-ends
+ // has put the call on-hold.
+ if (is_main_rx_3way && peer_rx_3way) {
+ peer_rx_3way->post_media_peer_rx_3way(input_sample_buf, SAMPLE_BUF_SIZE,
+ audio_encoder->get_sample_rate());
+ }
+
+ // Mix the sound samples with the 3rd party
+ if (media_3way_peer_tx->get(mix_buf_3way, SAMPLE_BUF_SIZE)) {
+ short *mix_sb = (short *)mix_buf_3way;
+ for (int i = 0; i < SAMPLE_BUF_SIZE / 2; i++) {
+ sb[i] = mix_linear_pcm(sb[i], mix_sb[i]);
+ }
+ }
+ }
+
+ mtx_3way.unlock();
+
+ /*** PREPROCESSING & ENCODING ***/
+
+ bool preprocessing_silence = false;
+
+#ifdef HAVE_SPEEX
+ // speex acoustic echo cancellation
+ if (audio_session->get_do_echo_cancellation() && !audio_session->get_echo_captured_last()) {
+
+ spx_int16_t *input_buf = new spx_int16_t[SAMPLE_BUF_SIZE/2];
+ MEMMAN_NEW_ARRAY(input_buf);
+
+ for (int i = 0; i < SAMPLE_BUF_SIZE / 2; i++) {
+ input_buf[i] = sb[i];
+ }
+
+ speex_echo_capture(audio_session->get_speex_echo_state(), input_buf, sb);
+ audio_session->set_echo_captured_last(true);
+
+ MEMMAN_DELETE_ARRAY(input_buf);
+ delete [] input_buf;
+ }
+
+ // preprocessing
+ preprocessing_silence = !speex_preprocess_run(speex_preprocess_state, sb);
+
+ // According to the speex API documentation the return value
+ // from speex_preprocess_run() is only defined when VAD is
+ // enabled. So to be safe, reset the return value, if VAD is
+ // disabled.
+ if (!speex_dsp_vad) preprocessing_silence = false;
+#endif
+
+ // encoding
+ sound_payload_size = audio_encoder->encode(sb, nsamples, payload, payload_size, silence);
+
+ // recognizing silence (both from preprocessing and encoding)
+ silence = silence || preprocessing_silence;
+
+ return true;
+}
+
+bool t_audio_rx::get_dtmf_event(void) {
+ // DTMF events are not supported in a 3-way conference
+ if (is_3way) return false;
+
+ if (!sema_dtmf_q.try_down()) {
+ // No DTMF event available
+ return false;
+ }
+
+ // Get next DTMF event
+ mtx_dtmf_q.lock();
+ t_dtmf_event dtmf_event = dtmf_queue.front();
+ dtmf_queue.pop();
+ mtx_dtmf_q.unlock();
+
+ ui->cb_async_send_dtmf(get_line()->get_line_number(), dtmf_event.dtmf_tone);
+
+ // Create DTMF player
+ if (dtmf_event.inband) {
+ dtmf_player = new t_inband_dtmf_player(this, audio_encoder, user_config,
+ dtmf_event.dtmf_tone, timestamp, nsamples);
+ MEMMAN_NEW(dtmf_player);
+
+ // Log DTMF event
+ log_file->write_header("t_audio_rx::get_dtmf_event", LOG_NORMAL);
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": start inband DTMF tone - ");
+ log_file->write_raw(dtmf_event.dtmf_tone);
+ log_file->write_endl();
+ log_file->write_footer();
+ } else {
+ // The telephone events may have a different sampling rate than
+ // the audio codec. Change nsamples accordingly.
+ nsamples = audio_sample_rate(CODEC_TELEPHONE_EVENT)/1000 *
+ audio_encoder->get_ptime();
+
+ dtmf_player = new t_rtp_event_dtmf_player(this, audio_encoder, user_config,
+ dtmf_event.dtmf_tone, timestamp, nsamples);
+ MEMMAN_NEW(dtmf_player);
+
+ // Log DTMF event
+ log_file->write_header("t_audio_rx::get_dtmf_event", LOG_NORMAL);
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": start DTMF event - ");
+ log_file->write_raw(dtmf_event.dtmf_tone);
+ log_file->write_endl();
+ log_file->write_raw("Payload type: ");
+ log_file->write_raw(pt_telephone_event);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ // Set RTP payload format
+ // HACK: the sample rate for telephone events is 8000, but the
+ // ccRTP stack does not handle it well when the sample rate
+ // changes. When the sample rate of the audio codec is kept
+ // on the ccRTP session settings, then all works fine.
+ rtp_session->setPayloadFormat(DynamicPayloadFormat(pt_telephone_event,
+ audio_encoder->get_sample_rate()));
+ // should be this: audio_sample_rate(CODEC_TELEPHONE_EVENT)
+
+ // As all RTP event contain the same timestamp, the ccRTP stack will
+ // discard packets when the timestamp gets to old.
+ // Increase the expire timeout value to prevent this.
+ rtp_session->setExpireTimeout((JITTER_BUF_MS +
+ user_config->get_dtmf_duration() + user_config->get_dtmf_pause()) * 1000);
+ }
+
+ return true;
+}
+
+void t_audio_rx::set_sound_payload_format(void) {
+ nsamples = audio_encoder->get_sample_rate()/1000 * audio_encoder->get_ptime();
+ rtp_session->setPayloadFormat(DynamicPayloadFormat(audio_encoder->get_payload_id(),
+ audio_encoder->get_sample_rate()));
+}
+
+//////////
+// PUBLIC
+//////////
+
+t_audio_rx::t_audio_rx(t_audio_session *_audio_session,
+ t_audio_io *_input_device, t_twinkle_rtp_session *_rtp_session,
+ t_audio_codec _codec, unsigned short _payload_id,
+ unsigned short _ptime) : sema_dtmf_q(0)
+{
+ audio_session = _audio_session;
+
+ user_config = audio_session->get_line()->get_user();
+ assert(user_config);
+
+ input_device = _input_device;
+ rtp_session = _rtp_session;
+ dtmf_player = NULL;
+ is_running = false;
+ stop_running = false;
+ logged_capture_failure = false;
+ use_nat_keepalive = phone->use_nat_keepalive(user_config);
+
+ pt_telephone_event = -1;
+
+ // Create audio encoder
+ switch (_codec) {
+ case CODEC_G711_ALAW:
+ audio_encoder = new t_g711a_audio_encoder(_payload_id, _ptime, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_G711_ULAW:
+ audio_encoder = new t_g711u_audio_encoder(_payload_id, _ptime, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_GSM:
+ audio_encoder = new t_gsm_audio_encoder(_payload_id, _ptime, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+#ifdef HAVE_SPEEX
+ case CODEC_SPEEX_NB:
+ audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime,
+ t_speex_audio_encoder::MODE_NB, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_SPEEX_WB:
+ audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime,
+ t_speex_audio_encoder::MODE_WB, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_SPEEX_UWB:
+ audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime,
+ t_speex_audio_encoder::MODE_UWB, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+#endif
+#ifdef HAVE_ILBC
+ case CODEC_ILBC:
+ audio_encoder = new t_ilbc_audio_encoder(_payload_id, _ptime, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+#endif
+ case CODEC_G726_16:
+ audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime,
+ t_g726_audio_encoder::BIT_RATE_16, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_G726_24:
+ audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime,
+ t_g726_audio_encoder::BIT_RATE_24, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_G726_32:
+ audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime,
+ t_g726_audio_encoder::BIT_RATE_32, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ case CODEC_G726_40:
+ audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime,
+ t_g726_audio_encoder::BIT_RATE_40, user_config);
+ MEMMAN_NEW(audio_encoder);
+ break;
+ default:
+ assert(false);
+ }
+
+ payload_size = audio_encoder->get_max_payload_size();
+
+ input_sample_buf = new unsigned char[SAMPLE_BUF_SIZE];
+ MEMMAN_NEW_ARRAY(input_sample_buf);
+
+ payload = new unsigned char[payload_size];
+ MEMMAN_NEW_ARRAY(payload);
+ nsamples = audio_encoder->get_sample_rate()/1000 * audio_encoder->get_ptime();
+
+ // Initialize 3-way settings to 'null'
+ media_3way_peer_tx = NULL;
+ media_3way_peer_rx = NULL;
+ peer_rx_3way = NULL;
+ mix_buf_3way = NULL;
+ is_3way = false;
+ is_main_rx_3way = false;
+
+#ifdef HAVE_SPEEX
+ // initializing speex preprocessing state
+ speex_preprocess_state = speex_preprocess_state_init(nsamples, audio_encoder->get_sample_rate());
+
+ int arg;
+ float farg;
+
+ // Noise reduction
+ arg = (user_config->get_speex_dsp_nrd() ? 1 : 0);
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_DENOISE, &arg);
+ arg = -30;
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &arg);
+
+ // Automatic gain control
+ arg = (user_config->get_speex_dsp_agc() ? 1 : 0);
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC, &arg);
+ farg = (float) (user_config->get_speex_dsp_agc_level()) * 327.68f;
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &farg);
+ arg = 30;
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &arg);
+
+ // Voice activity detection
+ arg = (user_config->get_speex_dsp_vad() ? 1 : 0);
+ speex_dsp_vad = (bool)arg;
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_VAD, &arg);
+
+ // Acoustic echo cancellation
+ if (audio_session->get_do_echo_cancellation()) {
+ speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_ECHO_STATE,
+ audio_session->get_speex_echo_state());
+ }
+#endif
+}
+
+t_audio_rx::~t_audio_rx() {
+ struct timespec sleeptimer;
+
+ if (is_running) {
+ stop_running = true;
+ do {
+ sleeptimer.tv_sec = 0;
+ sleeptimer.tv_nsec = 10000000;
+ nanosleep(&sleeptimer, NULL);
+ } while (is_running);
+ }
+
+#ifdef HAVE_SPEEX
+ // cleaning speex preprocessing
+ if (audio_session->get_do_echo_cancellation()) {
+ speex_echo_state_reset(audio_session->get_speex_echo_state());
+ }
+ speex_preprocess_state_destroy(speex_preprocess_state);
+#endif
+
+ MEMMAN_DELETE_ARRAY(input_sample_buf);
+ delete [] input_sample_buf;
+
+ MEMMAN_DELETE_ARRAY(payload);
+ delete [] payload;
+
+ MEMMAN_DELETE(audio_encoder);
+ delete audio_encoder;
+
+ // Clean up resources for 3-way conference calls
+ if (media_3way_peer_tx) {
+ MEMMAN_DELETE(media_3way_peer_tx);
+ delete media_3way_peer_tx;
+ }
+ if (media_3way_peer_rx) {
+ MEMMAN_DELETE(media_3way_peer_rx);
+ delete media_3way_peer_rx;
+ }
+ if (mix_buf_3way) {
+ MEMMAN_DELETE_ARRAY(mix_buf_3way);
+ delete [] mix_buf_3way;
+ }
+
+ if (dtmf_player) {
+ MEMMAN_DELETE(dtmf_player);
+ delete dtmf_player;
+ }
+}
+
+void t_audio_rx::set_running(bool running) {
+ is_running = running;
+}
+
+// NOTE: no operations on the phone object are allowed inside the run() method.
+// Such an operation needs a lock on the transaction layer. The destructor
+// on audio_rx is called while this lock is locked. The destructor waits
+// in a busy loop for the run() method to finish. If the run() method would
+// need the phone lock, this would lead to a dead lock (and a long trip
+// in debug hell!)
+void t_audio_rx::run(void) {
+ //struct timeval debug_timer;
+ unsigned short sound_payload_size;
+ uint32 dtmf_rtp_timestamp;
+
+ phone->add_prohibited_thread();
+ ui->add_prohibited_thread();
+
+ // This flag indicates if we are currently in a silence period.
+ // The start of a new stream is assumed to start in silence, such
+ // that the very first RTP packet will be marked.
+ bool silence_period = true;
+ uint64 silence_nsamples = 0; // duration in samples
+
+ // This flag indicates if a sound frame can be suppressed
+ bool suppress_samples = false;
+
+ // The running flag is set already in t_audio_session::run to prevent
+ // a crash when the thread gets destroyed before it starts running.
+ // is_running = true;
+
+ // For a 3-way conference only the main receiver has access
+ // to the dsp.
+ if (!is_3way || is_main_rx_3way) {
+ // Enable recording
+ if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(),
+ sys_config->get_dev_mic()))
+ {
+ input_device->enable(true, true);
+ } else {
+ input_device->enable(false, true);
+ }
+
+ // If the stream is stopped for call-hold, then the buffer might
+ // be filled with old sound samples.
+ input_device->flush(false, true);
+ }
+
+ // Synchronize the timestamp driven by the sampling rate
+ // of the recording with the timestamp of the RTP session.
+ // As the RTP session is already created in advance, the
+ // RTP clock is a bit ahead already.
+ timestamp = rtp_session->getCurrentTimestamp() + nsamples;
+
+ // This loop keeps running until the stop_running flag is set to true.
+ // When a call is being released the stop_running flag is set to true.
+ // At that moment the lock on the transaction layer (phone) is taken.
+ // So do not use operations that take the phone lock, otherwise a
+ // dead lock may occur during call release.
+ while (true) {
+ if (stop_running) break;
+
+ if (dtmf_player) {
+ rtp_session->setMark(false);
+ // Skip samples from sound card
+ input_device->read(input_sample_buf, SAMPLE_BUF_SIZE);
+ sound_payload_size = dtmf_player->get_payload(
+ payload, payload_size, timestamp, dtmf_rtp_timestamp);
+ silence_period = false;
+ } else if (get_dtmf_event()) {
+ // RFC 2833
+ // Set marker in first RTP packet of a DTMF event
+ rtp_session->setMark(true);
+ // Skip samples from sound card
+ input_device->read(input_sample_buf, SAMPLE_BUF_SIZE);
+ assert(dtmf_player);
+ sound_payload_size = dtmf_player->get_payload(
+ payload, payload_size, timestamp, dtmf_rtp_timestamp);
+ silence_period = false;
+ } else if (get_sound_samples(sound_payload_size, suppress_samples)) {
+ if (suppress_samples && use_nat_keepalive) {
+ if (!silence_period) silence_nsamples = 0;
+
+ // Send a silence packet at the NAT keep alive interval
+ // to keep the NAT bindings for RTP fresh.
+ silence_nsamples += SAMPLE_BUF_SIZE / 2;
+ if (silence_nsamples >
+ (uint64_t)user_config->get_timer_nat_keepalive() * 1000 *
+ audio_encoder->get_sample_rate())
+ {
+ suppress_samples = false;
+ }
+ }
+
+ if (silence_period && !suppress_samples) {
+ // RFC 3551 4.1
+ // Set marker bit in first RTP packet after silence
+ rtp_session->setMark(true);
+ } else {
+ rtp_session->setMark(false);
+ }
+ silence_period = suppress_samples;
+ } else {
+ continue;
+ }
+
+ // If timestamp is more than 1 payload size ahead of the clock of
+ // the ccRTP stack, then drop the current payload and do not advance
+ // the timestamp. This will happen if the DSP delivers more
+ // sound samples than the set sample rate. To compensate for this
+ // samples must be dropped.
+ uint32 current_timestamp = rtp_session->getCurrentTimestamp();
+ if (seq32_t(timestamp) <= seq32_t(current_timestamp + nsamples)) {
+ if (dtmf_player) {
+ // Send DTMF payload
+ rtp_session->putData(dtmf_rtp_timestamp, payload,
+ sound_payload_size);
+
+ // If DTMF has ended then set payload back to sound
+ if (dtmf_player->finished()) {
+ set_sound_payload_format();
+ MEMMAN_DELETE(dtmf_player);
+ delete dtmf_player;
+ dtmf_player = NULL;
+ }
+ } else if (!suppress_samples) {
+ // Send sound samples
+ // Set the expire timeout to the jitter buffer size.
+ // This allows for old packets still to be sent out.
+ rtp_session->setExpireTimeout(MAX_OUT_AUDIO_DELAY_MS * 1000);
+ rtp_session->putData(timestamp, payload, sound_payload_size);
+ }
+
+ timestamp += nsamples;
+ } else {
+ log_file->write_header("t_audio_rx::run", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": discarded surplus of sound samples.\n");
+ log_file->write_raw("Timestamp: ");
+ log_file->write_raw(timestamp);
+ log_file->write_endl();
+ log_file->write_raw("Current timestamp: ");
+ log_file->write_raw(current_timestamp);
+ log_file->write_endl();
+ log_file->write_raw("nsamples: ");
+ log_file->write_raw(nsamples);
+ log_file->write_endl();
+ log_file->write_footer();
+ }
+
+ // If there is enough data in the DSP buffers to fill another
+ // RTP packet then do not sleep, but immediately go to the
+ // next cycle to play out the data. Probably this thread did
+ // not get enough time, so the buffer filled up. The far end
+ // jitter buffer has to cope with the jitter caused by this.
+ if (is_3way && !is_main_rx_3way) {
+ if (media_3way_peer_rx->size_content() >= SAMPLE_BUF_SIZE) {
+ continue;
+ }
+ } else {
+ if (input_device->get_buffer_space(true) >= SAMPLE_BUF_SIZE) continue;
+ }
+
+ // There is no data left in the DSP buffers to play out anymore.
+ // So the timestamp must be in sync with the clock of the ccRTP
+ // stack. It might get behind if the sound cards samples a bit
+ // slower than the set sample rate. Advance the timestamp to get
+ // in sync again.
+ current_timestamp = rtp_session->getCurrentTimestamp();
+ if (seq32_t(timestamp) <= seq32_t(current_timestamp -
+ (JITTER_BUF_MS / audio_encoder->get_ptime()) * nsamples))
+ {
+ timestamp += nsamples * (JITTER_BUF_MS / audio_encoder->get_ptime());
+ log_file->write_header("t_audio_rx::run", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": timestamp forwarded by ");
+ log_file->write_raw(nsamples * (JITTER_BUF_MS /
+ audio_encoder->get_ptime()));
+ log_file->write_endl();
+ log_file->write_raw("Timestamp: ");
+ log_file->write_raw(timestamp);
+ log_file->write_endl();
+ log_file->write_raw("Current timestamp: ");
+ log_file->write_raw(current_timestamp);
+ log_file->write_endl();
+ log_file->write_raw("nsamples: ");
+ log_file->write_raw(nsamples);
+ log_file->write_endl();
+ log_file->write_footer();
+ }
+ }
+
+ phone->remove_prohibited_thread();
+ ui->remove_prohibited_thread();
+ is_running = false;
+}
+
+void t_audio_rx::set_pt_telephone_event(int pt) {
+ pt_telephone_event = pt;
+}
+
+void t_audio_rx::push_dtmf(char digit, bool inband) {
+ // Ignore invalid DTMF digits
+ if (!VALID_DTMF_SYM(digit)) return;
+
+ // Ignore DTMF tones in a 3-way conference
+ if (is_3way) return;
+
+ t_dtmf_event dtmf_event;
+ dtmf_event.dtmf_tone = char2dtmf_ev(digit);
+ dtmf_event.inband = inband;
+
+ mtx_dtmf_q.lock();
+ dtmf_queue.push(dtmf_event);
+ mtx_dtmf_q.unlock();
+ sema_dtmf_q.up();
+}
+
+t_line *t_audio_rx::get_line(void) const {
+ return audio_session->get_line();
+}
+
+void t_audio_rx::join_3way(bool main_rx, t_audio_rx *peer_rx) {
+ mtx_3way.lock();
+
+ if (is_3way) {
+ log_file->write_header("t_audio_rx::join_3way", LOG_NORMAL);
+ log_file->write_raw("ERROR: audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(" - 3way is already active.\n");
+ log_file->write_footer();
+ mtx_3way.unlock();
+ return;
+ }
+
+ // Logging
+ log_file->write_header("t_audio_rx::join_3way");
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": join 3-way.\n");
+ if (main_rx) {
+ log_file->write_raw("Role is: mixer.\n");
+ } else {
+ log_file->write_raw("Role is: non-mixing.\n");
+ }
+ if (peer_rx) {
+ log_file->write_raw("A peer receiver already exists.\n");
+ } else {
+ log_file->write_raw("A peer receiver does not exist.\n");
+ }
+ log_file->write_footer();
+
+ // Create media buffers for the 2 far-ends of a 3-way call.
+ // The size of the media buffer is the size of the jitter buffer.
+ // This allows for jitter in the RTP streams and also for
+ // incompatible payload sizes. Eg. 1 far-end may send 20ms paylaods,
+ // while the other sends 30ms payloads. The outgoing RTP stream might
+ // even have another payload size.
+ // When the data has been captured from the soundcard, it will be
+ // checked if there is enough data available in the media buffers, i.e.
+ // the same amount of data as captured from the soundcard for mixing.
+ // If there is it will be retrieved and mixed.
+ // If there isn't the captured sound will simply be sent on its own
+ // to the far-end. Meanwhile the buffer will fill up with data such
+ // that from the next captured sample there will be sufficient data
+ // for mixing.
+ media_3way_peer_tx = new t_media_buffer(
+ JITTER_BUF_SIZE(audio_encoder->get_sample_rate()));
+ MEMMAN_NEW(media_3way_peer_tx);
+ media_3way_peer_rx = new t_media_buffer(
+ JITTER_BUF_SIZE(audio_encoder->get_sample_rate()));
+ MEMMAN_NEW(media_3way_peer_rx);
+
+ // Create a mix buffer for one sample frame.
+ mix_buf_3way = new unsigned char[SAMPLE_BUF_SIZE];
+ MEMMAN_NEW_ARRAY(mix_buf_3way);
+
+ peer_rx_3way = peer_rx;
+
+ is_3way = true;
+ is_main_rx_3way = main_rx;
+
+ // Stop DTMF tones as these are not supported in a 3way
+ if (dtmf_player) {
+ MEMMAN_DELETE(dtmf_player);
+ delete dtmf_player;
+ dtmf_player = NULL;
+ }
+
+ mtx_3way.unlock();
+}
+
+void t_audio_rx::set_peer_rx_3way(t_audio_rx *peer_rx) {
+ mtx_3way.lock();
+
+ if (!is_3way) {
+ mtx_3way.unlock();
+ return;
+ }
+
+ // Logging
+ log_file->write_header("t_audio_rx::set_peer_rx_3way");
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ if (peer_rx) {
+ log_file->write_raw(": set peer receiver.\n");
+ } else {
+ log_file->write_raw(": erase peer receiver.\n");
+ }
+ if (is_main_rx_3way) {
+ log_file->write_raw("Role is: mixer.\n");
+ } else {
+ log_file->write_raw("Role is: non-mixing.\n");
+ }
+ log_file->write_footer();
+
+ peer_rx_3way = peer_rx;
+
+ mtx_3way.unlock();
+}
+
+void t_audio_rx::set_main_rx_3way(bool main_rx) {
+ mtx_3way.lock();
+
+ if (!is_3way) {
+ mtx_3way.unlock();
+ return;
+ }
+
+ // Logging
+ log_file->write_header("t_audio_rx::set_main_rx_3way");
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ if (main_rx) {
+ log_file->write_raw(": change role to: mixer.\n");
+ } else {
+ log_file->write_raw(": change role to: non-mixing.\n");
+ }
+ log_file->write_footer();
+
+
+ // Initialize the DSP if we become the mixer and we were not before
+ if (main_rx && !is_main_rx_3way) {
+ // Enable recording
+ if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(),
+ sys_config->get_dev_mic()))
+ {
+ input_device->enable(true, true);
+ } else {
+ input_device->enable(false, true);
+ }
+
+ // If the stream is stopped for call-hold, then the buffer might
+ // be filled with old sound samples.
+ input_device->flush(false, true);
+ }
+
+ is_main_rx_3way = main_rx;
+
+ mtx_3way.unlock();
+}
+
+void t_audio_rx::stop_3way(void) {
+ mtx_3way.lock();
+
+ if (!is_3way) {
+ log_file->write_header("t_audio_rx::stop_3way");
+ log_file->write_raw("ERROR: audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(" - 3way is not active.\n");
+ log_file->write_footer();
+ mtx_3way.unlock();
+ return;
+ }
+
+ // Logging
+ log_file->write_header("t_audio_rx::stop_3way");
+ log_file->write_raw("Audio rx line ");
+ log_file->write_raw(get_line()->get_line_number()+1);
+ log_file->write_raw(": stop 3-way.\n");
+ log_file->write_footer();
+
+ is_3way = false;
+ is_main_rx_3way = false;
+
+ peer_rx_3way = NULL;
+
+ MEMMAN_DELETE(media_3way_peer_tx);
+ delete media_3way_peer_tx;
+ media_3way_peer_tx = NULL;
+ MEMMAN_DELETE(media_3way_peer_rx);
+ delete media_3way_peer_rx;
+ media_3way_peer_rx = NULL;
+ MEMMAN_DELETE_ARRAY(mix_buf_3way);
+ delete [] mix_buf_3way;
+ mix_buf_3way = NULL;
+
+ mtx_3way.unlock();
+}
+
+void t_audio_rx::post_media_peer_tx_3way(unsigned char *media, int len,
+ unsigned short peer_sample_rate)
+{
+ mtx_3way.lock();
+
+ if (!is_3way) {
+ // This is not a 3-way call. This is not necessarily an
+ // error condition. The 3rd party may be in the process of
+ // leaving the conference.
+ // Simply discard the posted media
+ mtx_3way.unlock();
+ return;
+ }
+
+ if (peer_sample_rate != audio_encoder->get_sample_rate()) {
+ // Resample media from peer to sample rate of this receiver
+ int output_len = (len / 2) * audio_encoder->get_sample_rate() / peer_sample_rate;
+ short *output_buf = new short[output_len];
+ MEMMAN_NEW_ARRAY(output_buf);
+ int resample_len = resample((short *)media, len / 2, peer_sample_rate,
+ output_buf, output_len, audio_encoder->get_sample_rate());
+ media_3way_peer_tx->add((unsigned char *)output_buf, resample_len * 2);
+ MEMMAN_DELETE_ARRAY(output_buf);
+ delete [] output_buf;
+ } else {
+ media_3way_peer_tx->add(media, len);
+ }
+
+ mtx_3way.unlock();
+}
+
+void t_audio_rx::post_media_peer_rx_3way(unsigned char *media, int len,
+ unsigned short peer_sample_rate)
+{
+ mtx_3way.lock();
+
+ if (!is_3way) {
+ // This is not a 3-way call. This is not necessarily an
+ // error condition. The 3rd party may be in the process of
+ // leaving the conference.
+ // Simply discard the posted media
+ mtx_3way.unlock();
+ return;
+ }
+
+ if (peer_sample_rate != audio_encoder->get_sample_rate()) {
+ // Resample media from peer to sample rate of this receiver
+ int output_len = (len / 2) * audio_encoder->get_sample_rate() / peer_sample_rate;
+ short *output_buf = new short[output_len];
+ MEMMAN_NEW_ARRAY(output_buf);
+ int resample_len = resample((short *)media, len / 2, peer_sample_rate,
+ output_buf, output_len, audio_encoder->get_sample_rate());
+ media_3way_peer_rx->add((unsigned char *)output_buf, resample_len * 2);
+ MEMMAN_DELETE_ARRAY(output_buf);
+ delete [] output_buf;
+ } else {
+ media_3way_peer_rx->add(media, len);
+ }
+
+ mtx_3way.unlock();
+}
+
+bool t_audio_rx::get_is_main_rx_3way(void) const {
+ return is_main_rx_3way;
+}