ACM2 integration with NetEq 4.
nack{.cc, .h, _unittest.cc} are basically copies from main/source/ folder, with cpplint warning cleaned up.
BUG=
R=andrew@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/2190009
git-svn-id: http://webrtc.googlecode.com/svn/trunk@4736 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc
new file mode 100644
index 0000000..fb3fe3e
--- /dev/null
+++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/audio_coding/main/source/acm_receiver.h"
+
+#include <stdlib.h> // malloc
+
+#include <algorithm> // sort
+#include <vector>
+
+#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/audio_coding/main/source/acm_common_defs.h"
+#include "webrtc/modules/audio_coding/main/source/acm_resampler.h"
+#include "webrtc/modules/audio_coding/main/source/nack.h"
+#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h"
+#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h"
+#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/interface/logging.h"
+#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h"
+#include "webrtc/system_wrappers/interface/tick_util.h"
+#include "webrtc/system_wrappers/interface/trace.h"
+
+namespace webrtc {
+
+namespace {
+
+const int kRtpHeaderSize = 12;
+const int kNeteqInitSampleRateHz = 16000;
+const int kNackThresholdPackets = 2;
+
+// |vad_activity_| field of |audio_frame| is set to |previous_audio_activity_|
+// before the call to this function.
+void SetAudioFrameActivityAndType(bool vad_enabled,
+ NetEqOutputType type,
+ AudioFrame* audio_frame) {
+ if (vad_enabled) {
+ switch (type) {
+ case kOutputNormal: {
+ audio_frame->vad_activity_ = AudioFrame::kVadActive;
+ audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
+ break;
+ }
+ case kOutputVADPassive: {
+ audio_frame->vad_activity_ = AudioFrame::kVadPassive;
+ audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
+ break;
+ }
+ case kOutputCNG: {
+ audio_frame->vad_activity_ = AudioFrame::kVadPassive;
+ audio_frame->speech_type_ = AudioFrame::kCNG;
+ break;
+ }
+ case kOutputPLC: {
+ // Don't change |audio_frame->vad_activity_|, it should be the same as
+ // |previous_audio_activity_|.
+ audio_frame->speech_type_ = AudioFrame::kPLC;
+ break;
+ }
+ case kOutputPLCtoCNG: {
+ audio_frame->vad_activity_ = AudioFrame::kVadPassive;
+ audio_frame->speech_type_ = AudioFrame::kPLCCNG;
+ break;
+ }
+ default:
+ assert(false);
+ }
+ } else {
+ // Always return kVadUnknown when receive VAD is inactive
+ audio_frame->vad_activity_ = AudioFrame::kVadUnknown;
+ switch (type) {
+ case kOutputNormal: {
+ audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
+ break;
+ }
+ case kOutputCNG: {
+ audio_frame->speech_type_ = AudioFrame::kCNG;
+ break;
+ }
+ case kOutputPLC: {
+ audio_frame->speech_type_ = AudioFrame::kPLC;
+ break;
+ }
+ case kOutputPLCtoCNG: {
+ audio_frame->speech_type_ = AudioFrame::kPLCCNG;
+ break;
+ }
+ case kOutputVADPassive: {
+ // Normally, we should no get any VAD decision if post-decoding VAD is
+ // not active. However, if post-decoding VAD has been active then
+ // disabled, we might be here for couple of frames.
+ audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
+ LOG_F(LS_WARNING) << "Post-decoding VAD is disabled but output is "
+ << "labeled VAD-passive";
+ break;
+ }
+ default:
+ assert(false);
+ }
+ }
+}
+
+// Is the given codec a CNG codec?
+bool IsCng(int codec_id) {
+ return (codec_id == ACMCodecDB::kCNNB || codec_id == ACMCodecDB::kCNWB ||
+ codec_id == ACMCodecDB::kCNSWB || codec_id == ACMCodecDB::kCNFB);
+}
+
+} // namespace
+
+AcmReceiver::AcmReceiver()
+ : id_(0),
+ neteq_(NetEq::Create(kNeteqInitSampleRateHz)),
+ last_audio_decoder_(-1), // Invalid value.
+ decode_lock_(RWLockWrapper::CreateRWLock()),
+ neteq_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ vad_enabled_(false),
+ previous_audio_activity_(AudioFrame::kVadUnknown),
+ current_sample_rate_hz_(kNeteqInitSampleRateHz),
+ nack_(),
+ nack_enabled_(false),
+ av_sync_(false),
+ initial_delay_manager_(),
+ missing_packets_sync_stream_(),
+ late_packets_sync_stream_() {
+ for (int n = 0; n < ACMCodecDB::kMaxNumCodecs; ++n) {
+ decoders_[n].registered = false;
+ }
+
+ // Make sure we are on the same page as NetEq, although the default behavior
+ // for NetEq has been VAD disabled.
+ if (vad_enabled_)
+ neteq_->EnableVad();
+ else
+ neteq_->DisableVad();
+}
+
+AcmReceiver::~AcmReceiver() {
+ delete neteq_;
+ delete decode_lock_;
+ delete neteq_crit_sect_;
+}
+
+int AcmReceiver::SetMinimumDelay(int delay_ms) {
+ if (neteq_->SetMinimumDelay(delay_ms))
+ return 0;
+ LOG_FERR1(LS_ERROR, "AcmReceiver::SetExtraDelay", delay_ms);
+ return -1;
+}
+
+int AcmReceiver::SetInitialDelay(int delay_ms) {
+ if (delay_ms < 0 || delay_ms > 10000) {
+ return -1;
+ }
+ CriticalSectionScoped lock(neteq_crit_sect_);
+
+ if (delay_ms == 0) {
+ av_sync_ = false;
+ initial_delay_manager_.reset();
+ missing_packets_sync_stream_.reset();
+ late_packets_sync_stream_.reset();
+ neteq_->SetMinimumDelay(0);
+ return 0;
+ }
+
+ if (av_sync_ && initial_delay_manager_->PacketBuffered()) {
+ // Too late for this API. Only works before a call is started.
+ return -1;
+ }
+
+ // Most of places NetEq calls are not within AcmReceiver's critical section to
+ // improve performance. Here, this call has to be placed before the following
+ // block, therefore, we keep it inside critical section. Otherwise, we have to
+ // release |neteq_crit_sect_| and acquire it again, which seems an overkill.
+ if (neteq_->SetMinimumDelay(delay_ms) < 0)
+ return -1;
+
+ const int kLatePacketThreshold = 5;
+ av_sync_ = true;
+ initial_delay_manager_.reset(new InitialDelayManager(delay_ms,
+ kLatePacketThreshold));
+ missing_packets_sync_stream_.reset(new InitialDelayManager::SyncStream);
+ late_packets_sync_stream_.reset(new InitialDelayManager::SyncStream);
+ return 0;
+}
+
+int AcmReceiver::SetMaximumDelay(int delay_ms) {
+ if (neteq_->SetMaximumDelay(delay_ms))
+ return 0;
+ LOG_FERR1(LS_ERROR, "AcmReceiver::SetExtraDelay", delay_ms);
+ return -1;
+}
+
+int AcmReceiver::LeastRequiredDelayMs() const {
+ return neteq_->LeastRequiredDelayMs();
+}
+
+int AcmReceiver::current_sample_rate_hz() const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ return current_sample_rate_hz_;
+}
+
+// TODO(turajs): use one set of enumerators, e.g. the one defined in
+// common_types.h
+void AcmReceiver::SetPlayoutMode(AudioPlayoutMode mode) {
+ enum NetEqPlayoutMode playout_mode = kPlayoutOn;
+ enum NetEqBackgroundNoiseMode bgn_mode = kBgnOn;
+ switch (mode) {
+ case voice:
+ playout_mode = kPlayoutOn;
+ bgn_mode = kBgnOn;
+ break;
+ case fax: // No change to background noise mode.
+ playout_mode = kPlayoutFax;
+ bgn_mode = neteq_->BackgroundNoiseMode();
+ break;
+ case streaming:
+ playout_mode = kPlayoutStreaming;
+ bgn_mode = kBgnOff;
+ break;
+ case off:
+ playout_mode = kPlayoutOff;
+ bgn_mode = kBgnOff;
+ break;
+ }
+ neteq_->SetPlayoutMode(playout_mode);
+ neteq_->SetBackgroundNoiseMode(bgn_mode);
+}
+
+AudioPlayoutMode AcmReceiver::PlayoutMode() const {
+ AudioPlayoutMode acm_mode = voice;
+ NetEqPlayoutMode mode = neteq_->PlayoutMode();
+ switch (mode) {
+ case kPlayoutOn:
+ acm_mode = voice;
+ break;
+ case kPlayoutOff:
+ acm_mode = off;
+ break;
+ case kPlayoutFax:
+ acm_mode = fax;
+ break;
+ case kPlayoutStreaming:
+ acm_mode = streaming;
+ break;
+ default:
+ assert(false);
+ }
+ return acm_mode;
+}
+
+int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header,
+ const uint8_t* incoming_payload,
+ int length_payload) {
+ uint32_t receive_timestamp = 0;
+ InitialDelayManager::PacketType packet_type =
+ InitialDelayManager::kUndefinedPacket;
+ bool new_codec = false;
+ const RTPHeader* header = &rtp_header.header; // Just a shorthand.
+
+ {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+
+ int codec_id = RtpHeaderToCodecIndex(*header, incoming_payload);
+ if (codec_id < 0) {
+ LOG_F(LS_ERROR) << "Payload-type " << header->payloadType
+ << " is not registered.";
+ return -1;
+ }
+ assert(codec_id < ACMCodecDB::kMaxNumCodecs);
+ const int sample_rate_hz = ACMCodecDB::CodecFreq(codec_id);
+ receive_timestamp = NowInTimestamp(sample_rate_hz);
+
+ if (IsCng(codec_id)) {
+ // If this is a CNG while the audio codec is not mono skip pushing in
+ // packets into NetEq.
+ if (last_audio_decoder_ >= 0 &&
+ decoders_[last_audio_decoder_].channels > 1)
+ return 0;
+ packet_type = InitialDelayManager::kCngPacket;
+ } else if (codec_id == ACMCodecDB::kAVT) {
+ packet_type = InitialDelayManager::kAvtPacket;
+ } else {
+ if (codec_id != last_audio_decoder_) {
+ // This is either the first audio packet or send codec is changed.
+ // Therefore, either NetEq buffer is empty or will be flushed when this
+ // packet inserted. Note that |last_audio_decoder_| is initialized to
+ // an invalid value (-1), hence, the above condition is true for the
+ // very first audio packet.
+ new_codec = true;
+
+ // Updating NACK'sampling rate is required, either first packet is
+ // received or codec is changed. Furthermore, reset is required if codec
+ // is changed (NetEq flushes its buffer so NACK should reset its list).
+ if (nack_enabled_) {
+ assert(nack_.get());
+ nack_->Reset();
+ nack_->UpdateSampleRate(sample_rate_hz);
+ }
+ last_audio_decoder_ = codec_id;
+ }
+ packet_type = InitialDelayManager::kAudioPacket;
+ }
+
+ if (nack_enabled_) {
+ assert(nack_.get());
+ nack_->UpdateLastReceivedPacket(header->sequenceNumber,
+ header->timestamp);
+ }
+
+ if (av_sync_) {
+ assert(initial_delay_manager_.get());
+ assert(missing_packets_sync_stream_.get());
+ // This updates |initial_delay_manager_| and specifies an stream of
+ // sync-packets, if required to be inserted. We insert the sync-packets
+ // when AcmReceiver lock is released and |decoder_lock_| is acquired.
+ initial_delay_manager_->UpdateLastReceivedPacket(
+ rtp_header, receive_timestamp, packet_type, new_codec, sample_rate_hz,
+ missing_packets_sync_stream_.get());
+ }
+ }
+
+ {
+ WriteLockScoped lock_codecs(*decode_lock_); // Lock to prevent an encoding.
+
+ // If |missing_packets_sync_stream_| is allocated then we are in AV-sync and
+ // we may need to insert sync-packets. We don't check |av_sync_| as we are
+ // outside AcmReceiver's critical section.
+ if (missing_packets_sync_stream_.get()) {
+ InsertStreamOfSyncPackets(missing_packets_sync_stream_.get());
+ }
+
+ if (neteq_->InsertPacket(rtp_header, incoming_payload, length_payload,
+ receive_timestamp) < 0) {
+ LOG_FERR1(LS_ERROR, "AcmReceiver::InsertPacket", header->payloadType) <<
+ " Failed to insert packet";
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) {
+ enum NetEqOutputType type;
+ int16_t* ptr_audio_buffer = audio_frame->data_;
+ int samples_per_channel;
+ int num_channels;
+ bool return_silence = false;
+
+ {
+ // Accessing members, take the lock.
+ CriticalSectionScoped lock(neteq_crit_sect_);
+
+ if (av_sync_) {
+ assert(initial_delay_manager_.get());
+ assert(late_packets_sync_stream_.get());
+ return_silence = GetSilence(desired_freq_hz, audio_frame);
+ uint32_t timestamp_now = NowInTimestamp(current_sample_rate_hz_);
+ initial_delay_manager_->LatePackets(timestamp_now,
+ late_packets_sync_stream_.get());
+ }
+
+ if (!return_silence) {
+ // This is our initial guess regarding whether a resampling will be
+ // required. It is based on previous sample rate of netEq. Most often,
+ // this is a correct guess, however, in case that incoming payload changes
+ // the resampling might might be needed. By doing so, we avoid an
+ // unnecessary memcpy().
+ if (desired_freq_hz != -1 &&
+ current_sample_rate_hz_ != desired_freq_hz) {
+ ptr_audio_buffer = audio_buffer_;
+ }
+ }
+ }
+
+ {
+ WriteLockScoped lock_codecs(*decode_lock_); // Lock to prevent an encoding.
+
+ // If |late_packets_sync_stream_| is allocated then we have been in AV-sync
+ // mode and we might have to insert sync-packets.
+ if (late_packets_sync_stream_.get()) {
+ InsertStreamOfSyncPackets(late_packets_sync_stream_.get());
+ if (return_silence) // Silence generated, don't pull from NetEq.
+ return 0;
+ }
+
+ if (neteq_->GetAudio(AudioFrame::kMaxDataSizeSamples,
+ ptr_audio_buffer,
+ &samples_per_channel,
+ &num_channels, &type) != NetEq::kOK) {
+ LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "NetEq Failed.";
+ return -1;
+ }
+ }
+
+ // Accessing members, take the lock.
+ CriticalSectionScoped lock(neteq_crit_sect_);
+
+ // Update NACK.
+ int decoded_sequence_num = 0;
+ uint32_t decoded_timestamp = 0;
+ bool update_nack = nack_enabled_ && // Update NACK only if it is enabled.
+ neteq_->DecodedRtpInfo(&decoded_sequence_num, &decoded_timestamp);
+ if (update_nack) {
+ assert(nack_.get());
+ nack_->UpdateLastDecodedPacket(decoded_sequence_num, decoded_timestamp);
+ }
+
+ // NetEq always returns 10 ms of audio.
+ current_sample_rate_hz_ = samples_per_channel * 100;
+
+ // Update if resampling is required.
+ bool need_resampling = (desired_freq_hz != -1) &&
+ (current_sample_rate_hz_ != desired_freq_hz);
+
+ if (ptr_audio_buffer == audio_buffer_) {
+ // Data is written to local buffer.
+ if (need_resampling) {
+ samples_per_channel = resampler_.Resample10Msec(
+ audio_buffer_, current_sample_rate_hz_, desired_freq_hz,
+ num_channels, audio_frame->data_);
+ if (samples_per_channel < 0) {
+ LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "Resampler Failed.";
+ return -1;
+ }
+ } else {
+ // We might end up here ONLY if codec is changed.
+ memcpy(audio_frame->data_, audio_buffer_, samples_per_channel *
+ num_channels * sizeof(int16_t));
+ }
+ } else {
+ // Data is written into |audio_frame|.
+ if (need_resampling) {
+ // We might end up here ONLY if codec is changed.
+ samples_per_channel = resampler_.Resample10Msec(
+ audio_frame->data_, current_sample_rate_hz_, desired_freq_hz,
+ num_channels, audio_buffer_);
+ if (samples_per_channel < 0) {
+ LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "Resampler Failed.";
+ return -1;
+ }
+ memcpy(audio_frame->data_, audio_buffer_, samples_per_channel *
+ num_channels * sizeof(int16_t));
+ }
+ }
+
+ audio_frame->num_channels_ = num_channels;
+ audio_frame->samples_per_channel_ = samples_per_channel;
+ audio_frame->sample_rate_hz_ = samples_per_channel * 100;
+
+ // Should set |vad_activity| before calling SetAudioFrameActivityAndType().
+ audio_frame->vad_activity_ = previous_audio_activity_;
+ SetAudioFrameActivityAndType(vad_enabled_, type, audio_frame);
+ previous_audio_activity_ = audio_frame->vad_activity_;
+ return 0;
+}
+
+int32_t AcmReceiver::AddCodec(int acm_codec_id,
+ uint8_t payload_type,
+ int channels,
+ AudioDecoder* audio_decoder) {
+ assert(acm_codec_id >= 0 && acm_codec_id < ACMCodecDB::kMaxNumCodecs);
+ NetEqDecoder neteq_decoder = ACMCodecDB::neteq_decoders_[acm_codec_id];
+
+ CriticalSectionScoped lock(neteq_crit_sect_);
+
+ // The corresponding NetEq decoder ID.
+ // If this coder has been registered before.
+ if (decoders_[acm_codec_id].registered) {
+ if (decoders_[acm_codec_id].payload_type == payload_type) {
+ // Re-registering the same codec with the same payload-type. Do nothing
+ // and return.
+ return 0;
+ }
+
+ // Changing the payload-type of this codec. First unregister. Then register
+ // with new payload-type.
+ if (neteq_->RemovePayloadType(decoders_[acm_codec_id].payload_type) !=
+ NetEq::kOK) {
+ LOG_F(LS_ERROR) << "Cannot remover payload "
+ << decoders_[acm_codec_id].payload_type;
+ return -1;
+ }
+ }
+
+ int ret_val;
+ if (!audio_decoder) {
+ ret_val = neteq_->RegisterPayloadType(neteq_decoder, payload_type);
+ } else {
+ ret_val = neteq_->RegisterExternalDecoder(
+ audio_decoder, neteq_decoder,
+ ACMCodecDB::database_[acm_codec_id].plfreq, payload_type);
+ }
+ if (ret_val != NetEq::kOK) {
+ LOG_FERR3(LS_ERROR, "AcmReceiver::AddCodec", acm_codec_id, payload_type,
+ channels);
+ // Registration failed, delete the allocated space and set the pointer to
+ // NULL, for the record.
+ decoders_[acm_codec_id].registered = false;
+ return -1;
+ }
+
+ decoders_[acm_codec_id].registered = true;
+ decoders_[acm_codec_id].payload_type = payload_type;
+ decoders_[acm_codec_id].channels = channels;
+ return 0;
+}
+
+void AcmReceiver::EnableVad() {
+ neteq_->EnableVad();
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ vad_enabled_ = true;
+}
+
+void AcmReceiver::DisableVad() {
+ neteq_->DisableVad();
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ vad_enabled_ = false;
+}
+
+void AcmReceiver::FlushBuffers() {
+ neteq_->FlushBuffers();
+}
+
+// If failed in removing one of the codecs, this method continues to remove as
+// many as it can.
+int AcmReceiver::RemoveAllCodecs() {
+ int ret_val = 0;
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ for (int n = 0; n < ACMCodecDB::kMaxNumCodecs; ++n) {
+ if (decoders_[n].registered) {
+ if (neteq_->RemovePayloadType(decoders_[n].payload_type) == 0) {
+ decoders_[n].registered = false;
+ } else {
+ LOG_F(LS_ERROR) << "Cannot remove payload "
+ << decoders_[n].payload_type;
+ ret_val = -1;
+ }
+ }
+ }
+ return ret_val;
+}
+
+int AcmReceiver::RemoveCodec(uint8_t payload_type) {
+ int codec_index = PayloadType2CodecIndex(payload_type);
+ if (codec_index < 0) { // Such a payload-type is not registered.
+ LOG(LS_ERROR) << "payload_type " << payload_type << " is not registered"
+ " to be removed.";
+ return -1;
+ }
+ if (neteq_->RemovePayloadType(payload_type) != NetEq::kOK) {
+ LOG_FERR1(LS_ERROR, "AcmReceiver::RemoveCodec", payload_type);
+ return -1;
+ }
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ decoders_[codec_index].registered = false;
+ return 0;
+}
+
+void AcmReceiver::set_id(int id) {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ id_ = id;
+}
+
+uint32_t AcmReceiver::PlayoutTimestamp() {
+ if (av_sync_) {
+ assert(initial_delay_manager_.get());
+ if (initial_delay_manager_->buffering())
+ return initial_delay_manager_->playout_timestamp();
+ }
+ return neteq_->PlayoutTimestamp();
+}
+
+int AcmReceiver::last_audio_codec_id() const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ return last_audio_decoder_;
+}
+
+int AcmReceiver::last_audio_payload_type() const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ if (last_audio_decoder_ < 0)
+ return -1;
+ assert(decoders_[last_audio_decoder_].registered);
+ return decoders_[last_audio_decoder_].payload_type;
+}
+
+int AcmReceiver::RedPayloadType() const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ if (!decoders_[ACMCodecDB::kRED].registered) {
+ LOG_F(LS_WARNING) << "RED is not registered.";
+ return -1;
+ }
+ return decoders_[ACMCodecDB::kRED].payload_type;
+}
+
+int AcmReceiver::LastAudioCodec(CodecInst* codec) const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ if (last_audio_decoder_ < 0) {
+ LOG_F(LS_WARNING) << "No audio payload is received, yet.";
+ return -1;
+ }
+ assert(decoders_[last_audio_decoder_].registered);
+ memcpy(codec, &ACMCodecDB::database_[last_audio_decoder_], sizeof(CodecInst));
+ codec->pltype = decoders_[last_audio_decoder_].payload_type;
+ codec->channels = decoders_[last_audio_decoder_].channels;
+ return 0;
+}
+
+void AcmReceiver::NetworkStatistics(ACMNetworkStatistics* acm_stat) {
+ NetEqNetworkStatistics neteq_stat;
+ // NetEq function always returns zero, so we don't check the return value.
+ neteq_->NetworkStatistics(&neteq_stat);
+
+ acm_stat->currentBufferSize = neteq_stat.current_buffer_size_ms;
+ acm_stat->preferredBufferSize = neteq_stat.preferred_buffer_size_ms;
+ acm_stat->jitterPeaksFound = neteq_stat.jitter_peaks_found;
+ acm_stat->currentPacketLossRate = neteq_stat.packet_loss_rate;
+ acm_stat->currentDiscardRate = neteq_stat.packet_discard_rate;
+ acm_stat->currentExpandRate = neteq_stat.expand_rate;
+ acm_stat->currentPreemptiveRate = neteq_stat.preemptive_rate;
+ acm_stat->currentAccelerateRate = neteq_stat.accelerate_rate;
+ acm_stat->clockDriftPPM = neteq_stat.clockdrift_ppm;
+
+ std::vector<int> waiting_times;
+ neteq_->WaitingTimes(&waiting_times);
+ size_t size = waiting_times.size();
+ if (size == 0) {
+ acm_stat->meanWaitingTimeMs = -1;
+ acm_stat->medianWaitingTimeMs = -1;
+ acm_stat->minWaitingTimeMs = -1;
+ acm_stat->maxWaitingTimeMs = -1;
+ } else {
+ std::sort(waiting_times.begin(), waiting_times.end());
+ if ((size & 0x1) == 0) {
+ acm_stat->medianWaitingTimeMs = (waiting_times[size / 2 - 1] +
+ waiting_times[size / 2]) / 2;
+ } else {
+ acm_stat->medianWaitingTimeMs = waiting_times[size / 2];
+ }
+ acm_stat->minWaitingTimeMs = waiting_times.front();
+ acm_stat->maxWaitingTimeMs = waiting_times.back();
+ double sum = 0;
+ for (size_t i = 0; i < size; ++i) {
+ sum += waiting_times[i];
+ }
+ acm_stat->meanWaitingTimeMs = static_cast<int>(sum / size);
+ }
+}
+
+int AcmReceiver::DecoderByPayloadType(uint8_t payload_type,
+ CodecInst* codec) const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ int codec_index = PayloadType2CodecIndex(payload_type);
+ if (codec_index < 0) {
+ LOG_FERR1(LS_ERROR, "AcmReceiver::DecoderByPayloadType", payload_type);
+ return -1;
+ }
+ memcpy(codec, &ACMCodecDB::database_[codec_index], sizeof(CodecInst));
+ codec->pltype = decoders_[codec_index].payload_type;
+ codec->channels = decoders_[codec_index].channels;
+ return 0;
+}
+
+int AcmReceiver::PayloadType2CodecIndex(uint8_t payload_type) const {
+ for (int n = 0; n < ACMCodecDB::kMaxNumCodecs; ++n) {
+ if (decoders_[n].registered && decoders_[n].payload_type == payload_type) {
+ return n;
+ }
+ }
+ return -1;
+}
+
+int AcmReceiver::EnableNack(size_t max_nack_list_size) {
+ // Don't do anything if |max_nack_list_size| is out of range.
+ if (max_nack_list_size == 0 || max_nack_list_size > Nack::kNackListSizeLimit)
+ return -1;
+
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ if (!nack_enabled_) {
+ nack_.reset(Nack::Create(kNackThresholdPackets));
+ nack_enabled_ = true;
+
+ // Sampling rate might need to be updated if we change from disable to
+ // enable. Do it if the receive codec is valid.
+ if (last_audio_decoder_ >= 0) {
+ nack_->UpdateSampleRate(
+ ACMCodecDB::database_[last_audio_decoder_].plfreq);
+ }
+ }
+ return nack_->SetMaxNackListSize(max_nack_list_size);
+}
+
+void AcmReceiver::DisableNack() {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ nack_.reset(); // Memory is released.
+ nack_enabled_ = false;
+}
+
+std::vector<uint16_t> AcmReceiver::GetNackList(
+ int round_trip_time_ms) const {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ if (round_trip_time_ms < 0) {
+ WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_,
+ "GetNackList: round trip time cannot be negative."
+ " round_trip_time_ms=%d", round_trip_time_ms);
+ }
+ if (nack_enabled_ && round_trip_time_ms >= 0) {
+ assert(nack_.get());
+ return nack_->GetNackList(round_trip_time_ms);
+ }
+ std::vector<uint16_t> empty_list;
+ return empty_list;
+}
+
+void AcmReceiver::ResetInitialDelay() {
+ {
+ CriticalSectionScoped lock(neteq_crit_sect_);
+ av_sync_ = false;
+ initial_delay_manager_.reset(NULL);
+ missing_packets_sync_stream_.reset(NULL);
+ late_packets_sync_stream_.reset(NULL);
+ }
+ neteq_->SetMinimumDelay(0);
+ // TODO(turajs): Should NetEq Buffer be flushed?
+}
+
+// This function is called within critical section, no need to acquire a lock.
+bool AcmReceiver::GetSilence(int desired_sample_rate_hz, AudioFrame* frame) {
+ assert(av_sync_);
+ assert(initial_delay_manager_.get());
+ if (!initial_delay_manager_->buffering()) {
+ return false;
+ }
+
+ // We stop accumulating packets, if the number of packets or the total size
+ // exceeds a threshold.
+ int num_packets;
+ int max_num_packets;
+ int buffer_size_byte;
+ int max_buffer_size_byte;
+ const float kBufferingThresholdScale = 0.9;
+ neteq_->PacketBufferStatistics(&num_packets, &max_num_packets,
+ &buffer_size_byte, &max_buffer_size_byte);
+ if (num_packets > max_num_packets * kBufferingThresholdScale ||
+ buffer_size_byte > max_buffer_size_byte * kBufferingThresholdScale) {
+ initial_delay_manager_->DisableBuffering();
+ return false;
+ }
+
+ // Set the values if already got a packet, otherwise set to default values.
+ if (last_audio_decoder_ >= 0) {
+ current_sample_rate_hz_ = ACMCodecDB::database_[last_audio_decoder_].plfreq;
+ frame->num_channels_ = decoders_[last_audio_decoder_].channels;
+ } else {
+ current_sample_rate_hz_ = kNeteqInitSampleRateHz;
+ frame->num_channels_ = 1;
+ }
+
+ // Set the audio frame's sampling frequency.
+ if (desired_sample_rate_hz > 0) {
+ frame->sample_rate_hz_ = desired_sample_rate_hz;
+ } else {
+ frame->sample_rate_hz_ = current_sample_rate_hz_;
+ }
+
+ frame->samples_per_channel_ = frame->sample_rate_hz_ / 100; // Always 10 ms.
+ frame->speech_type_ = AudioFrame::kCNG;
+ frame->vad_activity_ = AudioFrame::kVadPassive;
+ frame->energy_ = 0;
+ int samples = frame->samples_per_channel_ * frame->num_channels_;
+ memset(frame->data_, 0, samples * sizeof(int16_t));
+ return true;
+}
+
+NetEqBackgroundNoiseMode AcmReceiver::BackgroundNoiseModeForTest() const {
+ return neteq_->BackgroundNoiseMode();
+}
+
+int AcmReceiver::RtpHeaderToCodecIndex(
+ const RTPHeader &rtp_header, const uint8_t* payload) const {
+ uint8_t payload_type = rtp_header.payloadType;
+ if (decoders_[ACMCodecDB::kRED].registered &&
+ payload_type == decoders_[ACMCodecDB::kRED].payload_type) {
+ // This is a RED packet, get the payload of the audio codec.
+ payload_type = payload[0] & 0x7F;
+ }
+
+ // Check if the payload is registered.
+ return PayloadType2CodecIndex(payload_type);
+}
+
+uint32_t AcmReceiver::NowInTimestamp(int decoder_sampling_rate) const {
+ // Down-cast the time to (32-6)-bit since we only care about
+ // the least significant bits. (32-6) bits cover 2^(32-6) = 67108864 ms.
+ // We masked 6 most significant bits of 32-bit so there is no overflow in
+ // the conversion from milliseconds to timestamp.
+ const uint32_t now_in_ms = static_cast<uint32_t>(
+ TickTime::MillisecondTimestamp() & 0x03ffffff);
+ return static_cast<uint32_t>(
+ (decoder_sampling_rate / 1000) * now_in_ms);
+}
+
+// This function only interacts with |neteq_|, therefore, it does not have to
+// be within critical section of AcmReceiver. It is inserting packets
+// into NetEq, so we call it when |decode_lock_| is acquired. However, this is
+// not essential as sync-packets do not interact with codecs (especially BWE).
+void AcmReceiver::InsertStreamOfSyncPackets(
+ InitialDelayManager::SyncStream* sync_stream) {
+ assert(sync_stream);
+ assert(av_sync_);
+ for (int n = 0; n < sync_stream->num_sync_packets; ++n) {
+ neteq_->InsertSyncPacket(sync_stream->rtp_info,
+ sync_stream->receive_timestamp);
+ ++sync_stream->rtp_info.header.sequenceNumber;
+ sync_stream->rtp_info.header.timestamp += sync_stream->timestamp_step;
+ sync_stream->receive_timestamp += sync_stream->timestamp_step;
+ }
+}
+
+} // namespace webrtc