diff options
Diffstat (limited to 'webrtc/modules/audio_coding/acm2')
29 files changed, 7399 insertions, 0 deletions
diff --git a/webrtc/modules/audio_coding/acm2/acm_codec_database.cc b/webrtc/modules/audio_coding/acm2/acm_codec_database.cc new file mode 100644 index 0000000000..5f3c07802b --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_codec_database.cc @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2012 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. + */ + +/* + * This file generates databases with information about all supported audio + * codecs. + */ + +// TODO(tlegrand): Change constant input pointers in all functions to constant +// references, where appropriate. +#include "webrtc/modules/audio_coding/acm2/acm_codec_database.h" + +#include <assert.h> + +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_coding/acm2/acm_common_defs.h" +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { + +namespace acm2 { + +namespace { + +// Checks if the bitrate is valid for iSAC. +bool IsISACRateValid(int rate) { + return (rate == -1) || ((rate <= 56000) && (rate >= 10000)); +} + +// Checks if the bitrate is valid for iLBC. +bool IsILBCRateValid(int rate, int frame_size_samples) { + if (((frame_size_samples == 240) || (frame_size_samples == 480)) && + (rate == 13300)) { + return true; + } else if (((frame_size_samples == 160) || (frame_size_samples == 320)) && + (rate == 15200)) { + return true; + } else { + return false; + } +} + +// Checks if the bitrate is valid for Opus. +bool IsOpusRateValid(int rate) { + return (rate >= 6000) && (rate <= 510000); +} + +} // namespace + +// Not yet used payload-types. +// 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, +// 67, 66, 65 + +const CodecInst ACMCodecDB::database_[] = { +#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) + {103, "ISAC", 16000, kIsacPacSize480, 1, kIsacWbDefaultRate}, +# if (defined(WEBRTC_CODEC_ISAC)) + {104, "ISAC", 32000, kIsacPacSize960, 1, kIsacSwbDefaultRate}, +# endif +#endif + // Mono + {107, "L16", 8000, 80, 1, 128000}, + {108, "L16", 16000, 160, 1, 256000}, + {109, "L16", 32000, 320, 1, 512000}, + // Stereo + {111, "L16", 8000, 80, 2, 128000}, + {112, "L16", 16000, 160, 2, 256000}, + {113, "L16", 32000, 320, 2, 512000}, + // G.711, PCM mu-law and A-law. + // Mono + {0, "PCMU", 8000, 160, 1, 64000}, + {8, "PCMA", 8000, 160, 1, 64000}, + // Stereo + {110, "PCMU", 8000, 160, 2, 64000}, + {118, "PCMA", 8000, 160, 2, 64000}, +#ifdef WEBRTC_CODEC_ILBC + {102, "ILBC", 8000, 240, 1, 13300}, +#endif +#ifdef WEBRTC_CODEC_G722 + // Mono + {9, "G722", 16000, 320, 1, 64000}, + // Stereo + {119, "G722", 16000, 320, 2, 64000}, +#endif +#ifdef WEBRTC_CODEC_OPUS + // Opus internally supports 48, 24, 16, 12, 8 kHz. + // Mono and stereo. + {120, "opus", 48000, 960, 2, 64000}, +#endif + // Comfort noise for four different sampling frequencies. + {13, "CN", 8000, 240, 1, 0}, + {98, "CN", 16000, 480, 1, 0}, + {99, "CN", 32000, 960, 1, 0}, +#ifdef ENABLE_48000_HZ + {100, "CN", 48000, 1440, 1, 0}, +#endif + {106, "telephone-event", 8000, 240, 1, 0}, +#ifdef WEBRTC_CODEC_RED + {127, "red", 8000, 0, 1, 0}, +#endif + // To prevent compile errors due to trailing commas. + {-1, "Null", -1, -1, 0, -1} +}; + +// Create database with all codec settings at compile time. +// Each entry needs the following parameters in the given order: +// Number of allowed packet sizes, a vector with the allowed packet sizes, +// Basic block samples, max number of channels that are supported. +const ACMCodecDB::CodecSettings ACMCodecDB::codec_settings_[] = { +#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) + {2, {kIsacPacSize480, kIsacPacSize960}, 0, 1}, +# if (defined(WEBRTC_CODEC_ISAC)) + {1, {kIsacPacSize960}, 0, 1}, +# endif +#endif + // Mono + {4, {80, 160, 240, 320}, 0, 2}, + {4, {160, 320, 480, 640}, 0, 2}, + {2, {320, 640}, 0, 2}, + // Stereo + {4, {80, 160, 240, 320}, 0, 2}, + {4, {160, 320, 480, 640}, 0, 2}, + {2, {320, 640}, 0, 2}, + // G.711, PCM mu-law and A-law. + // Mono + {6, {80, 160, 240, 320, 400, 480}, 0, 2}, + {6, {80, 160, 240, 320, 400, 480}, 0, 2}, + // Stereo + {6, {80, 160, 240, 320, 400, 480}, 0, 2}, + {6, {80, 160, 240, 320, 400, 480}, 0, 2}, +#ifdef WEBRTC_CODEC_ILBC + {4, {160, 240, 320, 480}, 0, 1}, +#endif +#ifdef WEBRTC_CODEC_G722 + // Mono + {6, {160, 320, 480, 640, 800, 960}, 0, 2}, + // Stereo + {6, {160, 320, 480, 640, 800, 960}, 0, 2}, +#endif +#ifdef WEBRTC_CODEC_OPUS + // Opus supports frames shorter than 10ms, + // but it doesn't help us to use them. + // Mono and stereo. + {4, {480, 960, 1920, 2880}, 0, 2}, +#endif + // Comfort noise for three different sampling frequencies. + {1, {240}, 240, 1}, + {1, {480}, 480, 1}, + {1, {960}, 960, 1}, +#ifdef ENABLE_48000_HZ + {1, {1440}, 1440, 1}, +#endif + {1, {240}, 240, 1}, +#ifdef WEBRTC_CODEC_RED + {1, {0}, 0, 1}, +#endif + // To prevent compile errors due to trailing commas. + {-1, {-1}, -1, 0} +}; + +// Create a database of all NetEQ decoders at compile time. +const NetEqDecoder ACMCodecDB::neteq_decoders_[] = { +#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) + NetEqDecoder::kDecoderISAC, +# if (defined(WEBRTC_CODEC_ISAC)) + NetEqDecoder::kDecoderISACswb, +# endif +#endif + // Mono + NetEqDecoder::kDecoderPCM16B, NetEqDecoder::kDecoderPCM16Bwb, + NetEqDecoder::kDecoderPCM16Bswb32kHz, + // Stereo + NetEqDecoder::kDecoderPCM16B_2ch, NetEqDecoder::kDecoderPCM16Bwb_2ch, + NetEqDecoder::kDecoderPCM16Bswb32kHz_2ch, + // G.711, PCM mu-las and A-law. + // Mono + NetEqDecoder::kDecoderPCMu, NetEqDecoder::kDecoderPCMa, + // Stereo + NetEqDecoder::kDecoderPCMu_2ch, NetEqDecoder::kDecoderPCMa_2ch, +#ifdef WEBRTC_CODEC_ILBC + NetEqDecoder::kDecoderILBC, +#endif +#ifdef WEBRTC_CODEC_G722 + // Mono + NetEqDecoder::kDecoderG722, + // Stereo + NetEqDecoder::kDecoderG722_2ch, +#endif +#ifdef WEBRTC_CODEC_OPUS + // Mono and stereo. + NetEqDecoder::kDecoderOpus, +#endif + // Comfort noise for three different sampling frequencies. + NetEqDecoder::kDecoderCNGnb, NetEqDecoder::kDecoderCNGwb, + NetEqDecoder::kDecoderCNGswb32kHz, +#ifdef ENABLE_48000_HZ + NetEqDecoder::kDecoderCNGswb48kHz, +#endif + NetEqDecoder::kDecoderAVT, +#ifdef WEBRTC_CODEC_RED + NetEqDecoder::kDecoderRED, +#endif +}; + +// Enumerator for error codes when asking for codec database id. +enum { + kInvalidCodec = -10, + kInvalidPayloadtype = -30, + kInvalidPacketSize = -40, + kInvalidRate = -50 +}; + +// Gets the codec id number from the database. If there is some mismatch in +// the codec settings, the function will return an error code. +// NOTE! The first mismatch found will generate the return value. +int ACMCodecDB::CodecNumber(const CodecInst& codec_inst) { + // Look for a matching codec in the database. + int codec_id = CodecId(codec_inst); + + // Checks if we found a matching codec. + if (codec_id == -1) { + return kInvalidCodec; + } + + // Checks the validity of payload type + if (!RentACodec::IsPayloadTypeValid(codec_inst.pltype)) { + return kInvalidPayloadtype; + } + + // Comfort Noise is special case, packet-size & rate is not checked. + if (STR_CASE_CMP(database_[codec_id].plname, "CN") == 0) { + return codec_id; + } + + // RED is special case, packet-size & rate is not checked. + if (STR_CASE_CMP(database_[codec_id].plname, "red") == 0) { + return codec_id; + } + + // Checks the validity of packet size. + if (codec_settings_[codec_id].num_packet_sizes > 0) { + bool packet_size_ok = false; + int i; + int packet_size_samples; + for (i = 0; i < codec_settings_[codec_id].num_packet_sizes; i++) { + packet_size_samples = + codec_settings_[codec_id].packet_sizes_samples[i]; + if (codec_inst.pacsize == packet_size_samples) { + packet_size_ok = true; + break; + } + } + + if (!packet_size_ok) { + return kInvalidPacketSize; + } + } + + if (codec_inst.pacsize < 1) { + return kInvalidPacketSize; + } + + // Check the validity of rate. Codecs with multiple rates have their own + // function for this. + if (STR_CASE_CMP("isac", codec_inst.plname) == 0) { + return IsISACRateValid(codec_inst.rate) ? codec_id : kInvalidRate; + } else if (STR_CASE_CMP("ilbc", codec_inst.plname) == 0) { + return IsILBCRateValid(codec_inst.rate, codec_inst.pacsize) + ? codec_id : kInvalidRate; + } else if (STR_CASE_CMP("opus", codec_inst.plname) == 0) { + return IsOpusRateValid(codec_inst.rate) + ? codec_id : kInvalidRate; + } + + return database_[codec_id].rate == codec_inst.rate ? codec_id : kInvalidRate; +} + +// Looks for a matching payload name, frequency, and channels in the +// codec list. Need to check all three since some codecs have several codec +// entries with different frequencies and/or channels. +// Does not check other codec settings, such as payload type and packet size. +// Returns the id of the codec, or -1 if no match is found. +int ACMCodecDB::CodecId(const CodecInst& codec_inst) { + return (CodecId(codec_inst.plname, codec_inst.plfreq, + codec_inst.channels)); +} + +int ACMCodecDB::CodecId(const char* payload_name, + int frequency, + size_t channels) { + for (const CodecInst& ci : RentACodec::Database()) { + bool name_match = false; + bool frequency_match = false; + bool channels_match = false; + + // Payload name, sampling frequency and number of channels need to match. + // NOTE! If |frequency| is -1, the frequency is not applicable, and is + // always treated as true, like for RED. + name_match = (STR_CASE_CMP(ci.plname, payload_name) == 0); + frequency_match = (frequency == ci.plfreq) || (frequency == -1); + // The number of channels must match for all codecs but Opus. + if (STR_CASE_CMP(payload_name, "opus") != 0) { + channels_match = (channels == ci.channels); + } else { + // For opus we just check that number of channels is valid. + channels_match = (channels == 1 || channels == 2); + } + + if (name_match && frequency_match && channels_match) { + // We have found a matching codec in the list. + return &ci - RentACodec::Database().data(); + } + } + + // We didn't find a matching codec. + return -1; +} +// Gets codec id number from database for the receiver. +int ACMCodecDB::ReceiverCodecNumber(const CodecInst& codec_inst) { + // Look for a matching codec in the database. + return CodecId(codec_inst); +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_codec_database.h b/webrtc/modules/audio_coding/acm2/acm_codec_database.h new file mode 100644 index 0000000000..6c2db9cfc8 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_codec_database.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012 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. + */ + +/* + * This file generates databases with information about all supported audio + * codecs. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_CODEC_DATABASE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_CODEC_DATABASE_H_ + +#include "webrtc/common_types.h" +#include "webrtc/engine_configurations.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" +#include "webrtc/modules/audio_coding/neteq/include/neteq.h" + +namespace webrtc { + +namespace acm2 { + +// TODO(tlegrand): replace class ACMCodecDB with a namespace. +class ACMCodecDB { + public: + // kMaxNumCodecs - Maximum number of codecs that can be activated in one + // build. + // kMaxNumPacketSize - Maximum number of allowed packet sizes for one codec. + // These might need to be increased if adding a new codec to the database + static const int kMaxNumCodecs = 50; + static const int kMaxNumPacketSize = 6; + + // Codec specific settings + // + // num_packet_sizes - number of allowed packet sizes. + // packet_sizes_samples - list of the allowed packet sizes. + // basic_block_samples - assigned a value different from 0 if the codec + // requires to be fed with a specific number of samples + // that can be different from packet size. + // channel_support - number of channels supported to encode; + // 1 = mono, 2 = stereo, etc. + struct CodecSettings { + int num_packet_sizes; + int packet_sizes_samples[kMaxNumPacketSize]; + int basic_block_samples; + size_t channel_support; + }; + + // Returns codec id from database, given the information received in the input + // [codec_inst]. + // Input: + // [codec_inst] - Information about the codec for which we require the + // database id. + // Return: + // codec id if successful, otherwise < 0. + static int CodecNumber(const CodecInst& codec_inst); + static int CodecId(const CodecInst& codec_inst); + static int CodecId(const char* payload_name, int frequency, size_t channels); + static int ReceiverCodecNumber(const CodecInst& codec_inst); + + // Databases with information about the supported codecs + // database_ - stored information about all codecs: payload type, name, + // sampling frequency, packet size in samples, default channel + // support, and default rate. + // codec_settings_ - stored codec settings: number of allowed packet sizes, + // a vector with the allowed packet sizes, basic block + // samples, and max number of channels that are supported. + // neteq_decoders_ - list of supported decoders in NetEQ. + static const CodecInst database_[kMaxNumCodecs]; + static const CodecSettings codec_settings_[kMaxNumCodecs]; + static const NetEqDecoder neteq_decoders_[kMaxNumCodecs]; +}; + +} // namespace acm2 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_CODEC_DATABASE_H_ diff --git a/webrtc/modules/audio_coding/acm2/acm_common_defs.h b/webrtc/modules/audio_coding/acm2/acm_common_defs.h new file mode 100644 index 0000000000..483bdd93f1 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_common_defs.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_COMMON_DEFS_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_COMMON_DEFS_H_ + +#include "webrtc/engine_configurations.h" + +// Checks for enabled codecs, we prevent enabling codecs which are not +// compatible. +#if ((defined WEBRTC_CODEC_ISAC) && (defined WEBRTC_CODEC_ISACFX)) +#error iSAC and iSACFX codecs cannot be enabled at the same time +#endif + +namespace webrtc { + +// General codec specific defines +const int kIsacWbDefaultRate = 32000; +const int kIsacSwbDefaultRate = 56000; +const int kIsacPacSize480 = 480; +const int kIsacPacSize960 = 960; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_COMMON_DEFS_H_ diff --git a/webrtc/modules/audio_coding/acm2/acm_neteq_unittest.cc b/webrtc/modules/audio_coding/acm2/acm_neteq_unittest.cc new file mode 100644 index 0000000000..607b933deb --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_neteq_unittest.cc @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2012 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. + */ + +// This file contains unit tests for ACM's NetEQ wrapper (class ACMNetEQ). + +namespace webrtc { + +namespace acm2 {} // namespace diff --git a/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.cc b/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.cc new file mode 100644 index 0000000000..855a39e675 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2014 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/acm2/acm_receive_test_oldapi.h" + +#include <assert.h> +#include <stdio.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_sink.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" + +namespace webrtc { +namespace test { + +namespace { +// Returns true if the codec should be registered, otherwise false. Changes +// the number of channels for the Opus codec to always be 1. +bool ModifyAndUseThisCodec(CodecInst* codec_param) { + if (STR_CASE_CMP(codec_param->plname, "CN") == 0 && + codec_param->plfreq == 48000) + return false; // Skip 48 kHz comfort noise. + + if (STR_CASE_CMP(codec_param->plname, "telephone-event") == 0) + return false; // Skip DTFM. + + return true; +} + +// Remaps payload types from ACM's default to those used in the resource file +// neteq_universal_new.rtp. Returns true if the codec should be registered, +// otherwise false. The payload types are set as follows (all are mono codecs): +// PCMu = 0; +// PCMa = 8; +// Comfort noise 8 kHz = 13 +// Comfort noise 16 kHz = 98 +// Comfort noise 32 kHz = 99 +// iLBC = 102 +// iSAC wideband = 103 +// iSAC super-wideband = 104 +// AVT/DTMF = 106 +// RED = 117 +// PCM16b 8 kHz = 93 +// PCM16b 16 kHz = 94 +// PCM16b 32 kHz = 95 +// G.722 = 94 +bool RemapPltypeAndUseThisCodec(const char* plname, + int plfreq, + size_t channels, + int* pltype) { + if (channels != 1) + return false; // Don't use non-mono codecs. + + // Re-map pltypes to those used in the NetEq test files. + if (STR_CASE_CMP(plname, "PCMU") == 0 && plfreq == 8000) { + *pltype = 0; + } else if (STR_CASE_CMP(plname, "PCMA") == 0 && plfreq == 8000) { + *pltype = 8; + } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 8000) { + *pltype = 13; + } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 16000) { + *pltype = 98; + } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 32000) { + *pltype = 99; + } else if (STR_CASE_CMP(plname, "ILBC") == 0) { + *pltype = 102; + } else if (STR_CASE_CMP(plname, "ISAC") == 0 && plfreq == 16000) { + *pltype = 103; + } else if (STR_CASE_CMP(plname, "ISAC") == 0 && plfreq == 32000) { + *pltype = 104; + } else if (STR_CASE_CMP(plname, "telephone-event") == 0) { + *pltype = 106; + } else if (STR_CASE_CMP(plname, "red") == 0) { + *pltype = 117; + } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 8000) { + *pltype = 93; + } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 16000) { + *pltype = 94; + } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 32000) { + *pltype = 95; + } else if (STR_CASE_CMP(plname, "G722") == 0) { + *pltype = 9; + } else { + // Don't use any other codecs. + return false; + } + return true; +} +} // namespace + +AcmReceiveTestOldApi::AcmReceiveTestOldApi( + PacketSource* packet_source, + AudioSink* audio_sink, + int output_freq_hz, + NumOutputChannels exptected_output_channels) + : clock_(0), + acm_(webrtc::AudioCodingModule::Create(0, &clock_)), + packet_source_(packet_source), + audio_sink_(audio_sink), + output_freq_hz_(output_freq_hz), + exptected_output_channels_(exptected_output_channels) { +} + +void AcmReceiveTestOldApi::RegisterDefaultCodecs() { + CodecInst my_codec_param; + for (int n = 0; n < acm_->NumberOfCodecs(); n++) { + ASSERT_EQ(0, acm_->Codec(n, &my_codec_param)) << "Failed to get codec."; + if (ModifyAndUseThisCodec(&my_codec_param)) { + ASSERT_EQ(0, acm_->RegisterReceiveCodec(my_codec_param)) + << "Couldn't register receive codec.\n"; + } + } +} + +void AcmReceiveTestOldApi::RegisterNetEqTestCodecs() { + CodecInst my_codec_param; + for (int n = 0; n < acm_->NumberOfCodecs(); n++) { + ASSERT_EQ(0, acm_->Codec(n, &my_codec_param)) << "Failed to get codec."; + if (!ModifyAndUseThisCodec(&my_codec_param)) { + // Skip this codec. + continue; + } + + if (RemapPltypeAndUseThisCodec(my_codec_param.plname, + my_codec_param.plfreq, + my_codec_param.channels, + &my_codec_param.pltype)) { + ASSERT_EQ(0, acm_->RegisterReceiveCodec(my_codec_param)) + << "Couldn't register receive codec.\n"; + } + } +} + +int AcmReceiveTestOldApi::RegisterExternalReceiveCodec( + int rtp_payload_type, + AudioDecoder* external_decoder, + int sample_rate_hz, + int num_channels, + const std::string& name) { + return acm_->RegisterExternalReceiveCodec(rtp_payload_type, external_decoder, + sample_rate_hz, num_channels, name); +} + +void AcmReceiveTestOldApi::Run() { + for (rtc::scoped_ptr<Packet> packet(packet_source_->NextPacket()); packet; + packet.reset(packet_source_->NextPacket())) { + // Pull audio until time to insert packet. + while (clock_.TimeInMilliseconds() < packet->time_ms()) { + AudioFrame output_frame; + EXPECT_EQ(0, acm_->PlayoutData10Ms(output_freq_hz_, &output_frame)); + EXPECT_EQ(output_freq_hz_, output_frame.sample_rate_hz_); + const size_t samples_per_block = + static_cast<size_t>(output_freq_hz_ * 10 / 1000); + EXPECT_EQ(samples_per_block, output_frame.samples_per_channel_); + if (exptected_output_channels_ != kArbitraryChannels) { + if (output_frame.speech_type_ == webrtc::AudioFrame::kPLC) { + // Don't check number of channels for PLC output, since each test run + // usually starts with a short period of mono PLC before decoding the + // first packet. + } else { + EXPECT_EQ(exptected_output_channels_, output_frame.num_channels_); + } + } + ASSERT_TRUE(audio_sink_->WriteAudioFrame(output_frame)); + clock_.AdvanceTimeMilliseconds(10); + AfterGetAudio(); + } + + // Insert packet after converting from RTPHeader to WebRtcRTPHeader. + WebRtcRTPHeader header; + header.header = packet->header(); + header.frameType = kAudioFrameSpeech; + memset(&header.type.Audio, 0, sizeof(RTPAudioHeader)); + EXPECT_EQ(0, + acm_->IncomingPacket( + packet->payload(), + static_cast<int32_t>(packet->payload_length_bytes()), + header)) + << "Failure when inserting packet:" << std::endl + << " PT = " << static_cast<int>(header.header.payloadType) << std::endl + << " TS = " << header.header.timestamp << std::endl + << " SN = " << header.header.sequenceNumber; + } +} + +AcmReceiveTestToggleOutputFreqOldApi::AcmReceiveTestToggleOutputFreqOldApi( + PacketSource* packet_source, + AudioSink* audio_sink, + int output_freq_hz_1, + int output_freq_hz_2, + int toggle_period_ms, + NumOutputChannels exptected_output_channels) + : AcmReceiveTestOldApi(packet_source, + audio_sink, + output_freq_hz_1, + exptected_output_channels), + output_freq_hz_1_(output_freq_hz_1), + output_freq_hz_2_(output_freq_hz_2), + toggle_period_ms_(toggle_period_ms), + last_toggle_time_ms_(clock_.TimeInMilliseconds()) { +} + +void AcmReceiveTestToggleOutputFreqOldApi::AfterGetAudio() { + if (clock_.TimeInMilliseconds() >= last_toggle_time_ms_ + toggle_period_ms_) { + output_freq_hz_ = (output_freq_hz_ == output_freq_hz_1_) + ? output_freq_hz_2_ + : output_freq_hz_1_; + last_toggle_time_ms_ = clock_.TimeInMilliseconds(); + } +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.h b/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.h new file mode 100644 index 0000000000..3010ec72b1 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_OLDAPI_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_OLDAPI_H_ + +#include <string> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +class AudioCodingModule; +class AudioDecoder; +struct CodecInst; + +namespace test { +class AudioSink; +class PacketSource; + +class AcmReceiveTestOldApi { + public: + enum NumOutputChannels { + kArbitraryChannels = 0, + kMonoOutput = 1, + kStereoOutput = 2 + }; + + AcmReceiveTestOldApi(PacketSource* packet_source, + AudioSink* audio_sink, + int output_freq_hz, + NumOutputChannels exptected_output_channels); + virtual ~AcmReceiveTestOldApi() {} + + // Registers the codecs with default parameters from ACM. + void RegisterDefaultCodecs(); + + // Registers codecs with payload types matching the pre-encoded NetEq test + // files. + void RegisterNetEqTestCodecs(); + + int RegisterExternalReceiveCodec(int rtp_payload_type, + AudioDecoder* external_decoder, + int sample_rate_hz, + int num_channels, + const std::string& name); + + // Runs the test and returns true if successful. + void Run(); + + protected: + // Method is called after each block of output audio is received from ACM. + virtual void AfterGetAudio() {} + + SimulatedClock clock_; + rtc::scoped_ptr<AudioCodingModule> acm_; + PacketSource* packet_source_; + AudioSink* audio_sink_; + int output_freq_hz_; + NumOutputChannels exptected_output_channels_; + + RTC_DISALLOW_COPY_AND_ASSIGN(AcmReceiveTestOldApi); +}; + +// This test toggles the output frequency every |toggle_period_ms|. The test +// starts with |output_freq_hz_1|. Except for the toggling, it does the same +// thing as AcmReceiveTestOldApi. +class AcmReceiveTestToggleOutputFreqOldApi : public AcmReceiveTestOldApi { + public: + AcmReceiveTestToggleOutputFreqOldApi( + PacketSource* packet_source, + AudioSink* audio_sink, + int output_freq_hz_1, + int output_freq_hz_2, + int toggle_period_ms, + NumOutputChannels exptected_output_channels); + + protected: + void AfterGetAudio() override; + + const int output_freq_hz_1_; + const int output_freq_hz_2_; + const int toggle_period_ms_; + int64_t last_toggle_time_ms_; +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVE_TEST_OLDAPI_H_ diff --git a/webrtc/modules/audio_coding/acm2/acm_receiver.cc b/webrtc/modules/audio_coding/acm2/acm_receiver.cc new file mode 100644 index 0000000000..f45d5d3414 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_receiver.cc @@ -0,0 +1,541 @@ +/* + * 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/acm2/acm_receiver.h" + +#include <stdlib.h> // malloc + +#include <algorithm> // sort +#include <vector> + +#include "webrtc/base/checks.h" +#include "webrtc/base/format_macros.h" +#include "webrtc/base/logging.h" +#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/audio_coding/codecs/audio_decoder.h" +#include "webrtc/modules/audio_coding/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/acm2/call_statistics.h" +#include "webrtc/modules/audio_coding/neteq/include/neteq.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/tick_util.h" +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { + +namespace acm2 { + +namespace { + +// |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(WARNING) << "Post-decoding VAD is disabled but output is " + << "labeled VAD-passive"; + break; + } + default: + assert(false); + } + } +} + +// Is the given codec a CNG codec? +// TODO(kwiberg): Move to RentACodec. +bool IsCng(int codec_id) { + auto i = RentACodec::CodecIdFromIndex(codec_id); + return (i && (*i == RentACodec::CodecId::kCNNB || + *i == RentACodec::CodecId::kCNWB || + *i == RentACodec::CodecId::kCNSWB || + *i == RentACodec::CodecId::kCNFB)); +} + +} // namespace + +AcmReceiver::AcmReceiver(const AudioCodingModule::Config& config) + : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + id_(config.id), + last_audio_decoder_(nullptr), + previous_audio_activity_(AudioFrame::kVadPassive), + audio_buffer_(new int16_t[AudioFrame::kMaxDataSizeSamples]), + last_audio_buffer_(new int16_t[AudioFrame::kMaxDataSizeSamples]), + neteq_(NetEq::Create(config.neteq_config)), + vad_enabled_(config.neteq_config.enable_post_decode_vad), + clock_(config.clock), + resampled_last_output_frame_(true) { + assert(clock_); + memset(audio_buffer_.get(), 0, AudioFrame::kMaxDataSizeSamples); + memset(last_audio_buffer_.get(), 0, AudioFrame::kMaxDataSizeSamples); +} + +AcmReceiver::~AcmReceiver() { + delete neteq_; +} + +int AcmReceiver::SetMinimumDelay(int delay_ms) { + if (neteq_->SetMinimumDelay(delay_ms)) + return 0; + LOG(LERROR) << "AcmReceiver::SetExtraDelay " << delay_ms; + return -1; +} + +int AcmReceiver::SetMaximumDelay(int delay_ms) { + if (neteq_->SetMaximumDelay(delay_ms)) + return 0; + LOG(LERROR) << "AcmReceiver::SetExtraDelay " << delay_ms; + return -1; +} + +int AcmReceiver::LeastRequiredDelayMs() const { + return neteq_->LeastRequiredDelayMs(); +} + +rtc::Optional<int> AcmReceiver::last_packet_sample_rate_hz() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_packet_sample_rate_hz_; +} + +int AcmReceiver::last_output_sample_rate_hz() const { + return neteq_->last_output_sample_rate_hz(); +} + +int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header, + rtc::ArrayView<const uint8_t> incoming_payload) { + uint32_t receive_timestamp = 0; + const RTPHeader* header = &rtp_header.header; // Just a shorthand. + + { + CriticalSectionScoped lock(crit_sect_.get()); + + const Decoder* decoder = RtpHeaderToDecoder(*header, incoming_payload[0]); + if (!decoder) { + LOG_F(LS_ERROR) << "Payload-type " + << static_cast<int>(header->payloadType) + << " is not registered."; + return -1; + } + const int sample_rate_hz = [&decoder] { + const auto ci = RentACodec::CodecIdFromIndex(decoder->acm_codec_id); + return ci ? RentACodec::CodecInstById(*ci)->plfreq : -1; + }(); + receive_timestamp = NowInTimestamp(sample_rate_hz); + + // If this is a CNG while the audio codec is not mono, skip pushing in + // packets into NetEq. + if (IsCng(decoder->acm_codec_id) && last_audio_decoder_ && + last_audio_decoder_->channels > 1) + return 0; + if (!IsCng(decoder->acm_codec_id) && + decoder->acm_codec_id != + *RentACodec::CodecIndexFromId(RentACodec::CodecId::kAVT)) { + last_audio_decoder_ = decoder; + last_packet_sample_rate_hz_ = rtc::Optional<int>(decoder->sample_rate_hz); + } + + } // |crit_sect_| is released. + + if (neteq_->InsertPacket(rtp_header, incoming_payload, receive_timestamp) < + 0) { + LOG(LERROR) << "AcmReceiver::InsertPacket " + << static_cast<int>(header->payloadType) + << " Failed to insert packet"; + return -1; + } + return 0; +} + +int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { + enum NetEqOutputType type; + size_t samples_per_channel; + size_t num_channels; + + // Accessing members, take the lock. + CriticalSectionScoped lock(crit_sect_.get()); + + // Always write the output to |audio_buffer_| first. + if (neteq_->GetAudio(AudioFrame::kMaxDataSizeSamples, + audio_buffer_.get(), + &samples_per_channel, + &num_channels, + &type) != NetEq::kOK) { + LOG(LERROR) << "AcmReceiver::GetAudio - NetEq Failed."; + return -1; + } + + const int current_sample_rate_hz = neteq_->last_output_sample_rate_hz(); + + // Update if resampling is required. + const bool need_resampling = + (desired_freq_hz != -1) && (current_sample_rate_hz != desired_freq_hz); + + if (need_resampling && !resampled_last_output_frame_) { + // Prime the resampler with the last frame. + int16_t temp_output[AudioFrame::kMaxDataSizeSamples]; + int samples_per_channel_int = resampler_.Resample10Msec( + last_audio_buffer_.get(), current_sample_rate_hz, desired_freq_hz, + num_channels, AudioFrame::kMaxDataSizeSamples, temp_output); + if (samples_per_channel_int < 0) { + LOG(LERROR) << "AcmReceiver::GetAudio - " + "Resampling last_audio_buffer_ failed."; + return -1; + } + samples_per_channel = static_cast<size_t>(samples_per_channel_int); + } + + // The audio in |audio_buffer_| is tansferred to |audio_frame_| below, either + // through resampling, or through straight memcpy. + // TODO(henrik.lundin) Glitches in the output may appear if the output rate + // from NetEq changes. See WebRTC issue 3923. + if (need_resampling) { + int samples_per_channel_int = resampler_.Resample10Msec( + audio_buffer_.get(), current_sample_rate_hz, desired_freq_hz, + num_channels, AudioFrame::kMaxDataSizeSamples, audio_frame->data_); + if (samples_per_channel_int < 0) { + LOG(LERROR) << "AcmReceiver::GetAudio - Resampling audio_buffer_ failed."; + return -1; + } + samples_per_channel = static_cast<size_t>(samples_per_channel_int); + resampled_last_output_frame_ = true; + } else { + resampled_last_output_frame_ = false; + // We might end up here ONLY if codec is changed. + memcpy(audio_frame->data_, + audio_buffer_.get(), + samples_per_channel * num_channels * sizeof(int16_t)); + } + + // Swap buffers, so that the current audio is stored in |last_audio_buffer_| + // for next time. + audio_buffer_.swap(last_audio_buffer_); + + audio_frame->num_channels_ = num_channels; + audio_frame->samples_per_channel_ = samples_per_channel; + audio_frame->sample_rate_hz_ = static_cast<int>(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_; + call_stats_.DecodedByNetEq(audio_frame->speech_type_); + + // Computes the RTP timestamp of the first sample in |audio_frame| from + // |GetPlayoutTimestamp|, which is the timestamp of the last sample of + // |audio_frame|. + uint32_t playout_timestamp = 0; + if (GetPlayoutTimestamp(&playout_timestamp)) { + audio_frame->timestamp_ = playout_timestamp - + static_cast<uint32_t>(audio_frame->samples_per_channel_); + } else { + // Remain 0 until we have a valid |playout_timestamp|. + audio_frame->timestamp_ = 0; + } + + return 0; +} + +int32_t AcmReceiver::AddCodec(int acm_codec_id, + uint8_t payload_type, + size_t channels, + int sample_rate_hz, + AudioDecoder* audio_decoder, + const std::string& name) { + const auto neteq_decoder = [acm_codec_id, channels]() -> NetEqDecoder { + if (acm_codec_id == -1) + return NetEqDecoder::kDecoderArbitrary; // External decoder. + const rtc::Optional<RentACodec::CodecId> cid = + RentACodec::CodecIdFromIndex(acm_codec_id); + RTC_DCHECK(cid) << "Invalid codec index: " << acm_codec_id; + const rtc::Optional<NetEqDecoder> ned = + RentACodec::NetEqDecoderFromCodecId(*cid, channels); + RTC_DCHECK(ned) << "Invalid codec ID: " << static_cast<int>(*cid); + return *ned; + }(); + + CriticalSectionScoped lock(crit_sect_.get()); + + // The corresponding NetEq decoder ID. + // If this codec has been registered before. + auto it = decoders_.find(payload_type); + if (it != decoders_.end()) { + const Decoder& decoder = it->second; + if (acm_codec_id != -1 && decoder.acm_codec_id == acm_codec_id && + decoder.channels == channels && + decoder.sample_rate_hz == sample_rate_hz) { + // Re-registering the same codec. Do nothing and return. + return 0; + } + + // Changing codec. First unregister the old codec, then register the new + // one. + if (neteq_->RemovePayloadType(payload_type) != NetEq::kOK) { + LOG(LERROR) << "Cannot remove payload " << static_cast<int>(payload_type); + return -1; + } + + decoders_.erase(it); + } + + int ret_val; + if (!audio_decoder) { + ret_val = neteq_->RegisterPayloadType(neteq_decoder, name, payload_type); + } else { + ret_val = neteq_->RegisterExternalDecoder( + audio_decoder, neteq_decoder, name, payload_type, sample_rate_hz); + } + if (ret_val != NetEq::kOK) { + LOG(LERROR) << "AcmReceiver::AddCodec " << acm_codec_id + << static_cast<int>(payload_type) + << " channels: " << channels; + return -1; + } + + Decoder decoder; + decoder.acm_codec_id = acm_codec_id; + decoder.payload_type = payload_type; + decoder.channels = channels; + decoder.sample_rate_hz = sample_rate_hz; + decoders_[payload_type] = decoder; + return 0; +} + +void AcmReceiver::EnableVad() { + neteq_->EnableVad(); + CriticalSectionScoped lock(crit_sect_.get()); + vad_enabled_ = true; +} + +void AcmReceiver::DisableVad() { + neteq_->DisableVad(); + CriticalSectionScoped lock(crit_sect_.get()); + 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(crit_sect_.get()); + for (auto it = decoders_.begin(); it != decoders_.end(); ) { + auto cur = it; + ++it; // it will be valid even if we erase cur + if (neteq_->RemovePayloadType(cur->second.payload_type) == 0) { + decoders_.erase(cur); + } else { + LOG_F(LS_ERROR) << "Cannot remove payload " + << static_cast<int>(cur->second.payload_type); + ret_val = -1; + } + } + + // No codec is registered, invalidate last audio decoder. + last_audio_decoder_ = nullptr; + last_packet_sample_rate_hz_ = rtc::Optional<int>(); + return ret_val; +} + +int AcmReceiver::RemoveCodec(uint8_t payload_type) { + CriticalSectionScoped lock(crit_sect_.get()); + auto it = decoders_.find(payload_type); + if (it == decoders_.end()) { // Such a payload-type is not registered. + return 0; + } + if (neteq_->RemovePayloadType(payload_type) != NetEq::kOK) { + LOG(LERROR) << "AcmReceiver::RemoveCodec" << static_cast<int>(payload_type); + return -1; + } + if (last_audio_decoder_ == &it->second) { + last_audio_decoder_ = nullptr; + last_packet_sample_rate_hz_ = rtc::Optional<int>(); + } + decoders_.erase(it); + return 0; +} + +void AcmReceiver::set_id(int id) { + CriticalSectionScoped lock(crit_sect_.get()); + id_ = id; +} + +bool AcmReceiver::GetPlayoutTimestamp(uint32_t* timestamp) { + return neteq_->GetPlayoutTimestamp(timestamp); +} + +int AcmReceiver::LastAudioCodec(CodecInst* codec) const { + CriticalSectionScoped lock(crit_sect_.get()); + if (!last_audio_decoder_) { + return -1; + } + *codec = *RentACodec::CodecInstById( + *RentACodec::CodecIdFromIndex(last_audio_decoder_->acm_codec_id)); + codec->pltype = last_audio_decoder_->payload_type; + codec->channels = last_audio_decoder_->channels; + codec->plfreq = last_audio_decoder_->sample_rate_hz; + return 0; +} + +void AcmReceiver::GetNetworkStatistics(NetworkStatistics* 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 ? true : false; + 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->currentSpeechExpandRate = neteq_stat.speech_expand_rate; + acm_stat->currentPreemptiveRate = neteq_stat.preemptive_rate; + acm_stat->currentAccelerateRate = neteq_stat.accelerate_rate; + acm_stat->currentSecondaryDecodedRate = neteq_stat.secondary_decoded_rate; + acm_stat->clockDriftPPM = neteq_stat.clockdrift_ppm; + acm_stat->addedSamples = neteq_stat.added_zero_samples; + acm_stat->meanWaitingTimeMs = neteq_stat.mean_waiting_time_ms; + acm_stat->medianWaitingTimeMs = neteq_stat.median_waiting_time_ms; + acm_stat->minWaitingTimeMs = neteq_stat.min_waiting_time_ms; + acm_stat->maxWaitingTimeMs = neteq_stat.max_waiting_time_ms; +} + +int AcmReceiver::DecoderByPayloadType(uint8_t payload_type, + CodecInst* codec) const { + CriticalSectionScoped lock(crit_sect_.get()); + auto it = decoders_.find(payload_type); + if (it == decoders_.end()) { + LOG(LERROR) << "AcmReceiver::DecoderByPayloadType " + << static_cast<int>(payload_type); + return -1; + } + const Decoder& decoder = it->second; + *codec = *RentACodec::CodecInstById( + *RentACodec::CodecIdFromIndex(decoder.acm_codec_id)); + codec->pltype = decoder.payload_type; + codec->channels = decoder.channels; + codec->plfreq = decoder.sample_rate_hz; + return 0; +} + +int AcmReceiver::EnableNack(size_t max_nack_list_size) { + neteq_->EnableNack(max_nack_list_size); + return 0; +} + +void AcmReceiver::DisableNack() { + neteq_->DisableNack(); +} + +std::vector<uint16_t> AcmReceiver::GetNackList( + int64_t round_trip_time_ms) const { + return neteq_->GetNackList(round_trip_time_ms); +} + +void AcmReceiver::ResetInitialDelay() { + neteq_->SetMinimumDelay(0); + // TODO(turajs): Should NetEq Buffer be flushed? +} + +const AcmReceiver::Decoder* AcmReceiver::RtpHeaderToDecoder( + const RTPHeader& rtp_header, + uint8_t payload_type) const { + auto it = decoders_.find(rtp_header.payloadType); + const auto red_index = + RentACodec::CodecIndexFromId(RentACodec::CodecId::kRED); + if (red_index && // This ensures that RED is defined in WebRTC. + it != decoders_.end() && it->second.acm_codec_id == *red_index) { + // This is a RED packet, get the payload of the audio codec. + it = decoders_.find(payload_type & 0x7F); + } + + // Check if the payload is registered. + return it != decoders_.end() ? &it->second : nullptr; +} + +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>( + clock_->TimeInMilliseconds() & 0x03ffffff); + return static_cast<uint32_t>( + (decoder_sampling_rate / 1000) * now_in_ms); +} + +void AcmReceiver::GetDecodingCallStatistics( + AudioDecodingCallStats* stats) const { + CriticalSectionScoped lock(crit_sect_.get()); + *stats = call_stats_.GetDecodingStatistics(); +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_receiver.h b/webrtc/modules/audio_coding/acm2/acm_receiver.h new file mode 100644 index 0000000000..b150612f69 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_receiver.h @@ -0,0 +1,307 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "webrtc/base/array_view.h" +#include "webrtc/base/optional.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/common_audio/vad/include/webrtc_vad.h" +#include "webrtc/engine_configurations.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/acm2/call_statistics.h" +#include "webrtc/modules/audio_coding/acm2/initial_delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/include/neteq.h" +#include "webrtc/modules/include/module_common_types.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +struct CodecInst; +class CriticalSectionWrapper; +class NetEq; + +namespace acm2 { + +class AcmReceiver { + public: + struct Decoder { + int acm_codec_id; + uint8_t payload_type; + // This field is meaningful for codecs where both mono and + // stereo versions are registered under the same ID. + size_t channels; + int sample_rate_hz; + }; + + // Constructor of the class + explicit AcmReceiver(const AudioCodingModule::Config& config); + + // Destructor of the class. + ~AcmReceiver(); + + // + // Inserts a payload with its associated RTP-header into NetEq. + // + // Input: + // - rtp_header : RTP header for the incoming payload containing + // information about payload type, sequence number, + // timestamp, SSRC and marker bit. + // - incoming_payload : Incoming audio payload. + // - length_payload : Length of incoming audio payload in bytes. + // + // Return value : 0 if OK. + // <0 if NetEq returned an error. + // + int InsertPacket(const WebRtcRTPHeader& rtp_header, + rtc::ArrayView<const uint8_t> incoming_payload); + + // + // Asks NetEq for 10 milliseconds of decoded audio. + // + // Input: + // -desired_freq_hz : specifies the sampling rate [Hz] of the output + // audio. If set -1 indicates to resampling is + // is required and the audio returned at the + // sampling rate of the decoder. + // + // Output: + // -audio_frame : an audio frame were output data and + // associated parameters are written to. + // + // Return value : 0 if OK. + // -1 if NetEq returned an error. + // + int GetAudio(int desired_freq_hz, AudioFrame* audio_frame); + + // + // Adds a new codec to the NetEq codec database. + // + // Input: + // - acm_codec_id : ACM codec ID; -1 means external decoder. + // - payload_type : payload type. + // - sample_rate_hz : sample rate. + // - audio_decoder : pointer to a decoder object. If it's null, then + // NetEq will internally create a decoder object + // based on the value of |acm_codec_id| (which + // mustn't be -1). Otherwise, NetEq will use the + // given decoder for the given payload type. NetEq + // won't take ownership of the decoder; it's up to + // the caller to delete it when it's no longer + // needed. + // + // Providing an existing decoder object here is + // necessary for external decoders, but may also be + // used for built-in decoders if NetEq doesn't have + // all the info it needs to construct them properly + // (e.g. iSAC, where the decoder needs to be paired + // with an encoder). + // + // Return value : 0 if OK. + // <0 if NetEq returned an error. + // + int AddCodec(int acm_codec_id, + uint8_t payload_type, + size_t channels, + int sample_rate_hz, + AudioDecoder* audio_decoder, + const std::string& name); + + // + // Sets a minimum delay for packet buffer. The given delay is maintained, + // unless channel condition dictates a higher delay. + // + // Input: + // - delay_ms : minimum delay in milliseconds. + // + // Return value : 0 if OK. + // <0 if NetEq returned an error. + // + int SetMinimumDelay(int delay_ms); + + // + // Sets a maximum delay [ms] for the packet buffer. The target delay does not + // exceed the given value, even if channel condition requires so. + // + // Input: + // - delay_ms : maximum delay in milliseconds. + // + // Return value : 0 if OK. + // <0 if NetEq returned an error. + // + int SetMaximumDelay(int delay_ms); + + // + // Get least required delay computed based on channel conditions. Note that + // this is before applying any user-defined limits (specified by calling + // (SetMinimumDelay() and/or SetMaximumDelay()). + // + int LeastRequiredDelayMs() const; + + // + // Resets the initial delay to zero. + // + void ResetInitialDelay(); + + // Returns the sample rate of the decoder associated with the last incoming + // packet. If no packet of a registered non-CNG codec has been received, the + // return value is empty. Also, if the decoder was unregistered since the last + // packet was inserted, the return value is empty. + rtc::Optional<int> last_packet_sample_rate_hz() const; + + // Returns last_output_sample_rate_hz from the NetEq instance. + int last_output_sample_rate_hz() const; + + // + // Get the current network statistics from NetEq. + // + // Output: + // - statistics : The current network statistics. + // + void GetNetworkStatistics(NetworkStatistics* statistics); + + // + // Enable post-decoding VAD. + // + void EnableVad(); + + // + // Disable post-decoding VAD. + // + void DisableVad(); + + // + // Returns whether post-decoding VAD is enabled (true) or disabled (false). + // + bool vad_enabled() const { return vad_enabled_; } + + // + // Flushes the NetEq packet and speech buffers. + // + void FlushBuffers(); + + // + // Removes a payload-type from the NetEq codec database. + // + // Input: + // - payload_type : the payload-type to be removed. + // + // Return value : 0 if OK. + // -1 if an error occurred. + // + int RemoveCodec(uint8_t payload_type); + + // + // Remove all registered codecs. + // + int RemoveAllCodecs(); + + // + // Set ID. + // + void set_id(int id); // TODO(turajs): can be inline. + + // + // Gets the RTP timestamp of the last sample delivered by GetAudio(). + // Returns true if the RTP timestamp is valid, otherwise false. + // + bool GetPlayoutTimestamp(uint32_t* timestamp); + + // + // Get the audio codec associated with the last non-CNG/non-DTMF received + // payload. If no non-CNG/non-DTMF packet is received -1 is returned, + // otherwise return 0. + // + int LastAudioCodec(CodecInst* codec) const; + + // + // Get a decoder given its registered payload-type. + // + // Input: + // -payload_type : the payload-type of the codec to be retrieved. + // + // Output: + // -codec : codec associated with the given payload-type. + // + // Return value : 0 if succeeded. + // -1 if failed, e.g. given payload-type is not + // registered. + // + int DecoderByPayloadType(uint8_t payload_type, + CodecInst* codec) const; + + // + // Enable NACK and set the maximum size of the NACK list. If NACK is already + // enabled then the maximum NACK list size is modified accordingly. + // + // Input: + // -max_nack_list_size : maximum NACK list size + // should be positive (none zero) and less than or + // equal to |Nack::kNackListSizeLimit| + // Return value + // : 0 if succeeded. + // -1 if failed + // + int EnableNack(size_t max_nack_list_size); + + // Disable NACK. + void DisableNack(); + + // + // Get a list of packets to be retransmitted. + // + // Input: + // -round_trip_time_ms : estimate of the round-trip-time (in milliseconds). + // Return value : list of packets to be retransmitted. + // + std::vector<uint16_t> GetNackList(int64_t round_trip_time_ms) const; + + // + // Get statistics of calls to GetAudio(). + void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const; + + private: + const Decoder* RtpHeaderToDecoder(const RTPHeader& rtp_header, + uint8_t payload_type) const + EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); + + uint32_t NowInTimestamp(int decoder_sampling_rate) const; + + rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; + int id_; // TODO(henrik.lundin) Make const. + const Decoder* last_audio_decoder_ GUARDED_BY(crit_sect_); + AudioFrame::VADActivity previous_audio_activity_ GUARDED_BY(crit_sect_); + ACMResampler resampler_ GUARDED_BY(crit_sect_); + // Used in GetAudio, declared as member to avoid allocating every 10ms. + // TODO(henrik.lundin) Stack-allocate in GetAudio instead? + rtc::scoped_ptr<int16_t[]> audio_buffer_ GUARDED_BY(crit_sect_); + rtc::scoped_ptr<int16_t[]> last_audio_buffer_ GUARDED_BY(crit_sect_); + CallStatistics call_stats_ GUARDED_BY(crit_sect_); + NetEq* neteq_; + // Decoders map is keyed by payload type + std::map<uint8_t, Decoder> decoders_ GUARDED_BY(crit_sect_); + bool vad_enabled_; + Clock* clock_; // TODO(henrik.lundin) Make const if possible. + bool resampled_last_output_frame_ GUARDED_BY(crit_sect_); + rtc::Optional<int> last_packet_sample_rate_hz_ GUARDED_BY(crit_sect_); +}; + +} // namespace acm2 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RECEIVER_H_ diff --git a/webrtc/modules/audio_coding/acm2/acm_receiver_unittest_oldapi.cc b/webrtc/modules/audio_coding/acm2/acm_receiver_unittest_oldapi.cc new file mode 100644 index 0000000000..24ecc694ff --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_receiver_unittest_oldapi.cc @@ -0,0 +1,398 @@ +/* + * 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/acm2/acm_receiver.h" + +#include <algorithm> // std::min + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/acm2/audio_coding_module_impl.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/test/test_suite.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { + +namespace acm2 { +namespace { + +bool CodecsEqual(const CodecInst& codec_a, const CodecInst& codec_b) { + if (strcmp(codec_a.plname, codec_b.plname) != 0 || + codec_a.plfreq != codec_b.plfreq || + codec_a.pltype != codec_b.pltype || + codec_b.channels != codec_a.channels) + return false; + return true; +} + +struct CodecIdInst { + explicit CodecIdInst(RentACodec::CodecId codec_id) { + const auto codec_ix = RentACodec::CodecIndexFromId(codec_id); + EXPECT_TRUE(codec_ix); + id = *codec_ix; + const auto codec_inst = RentACodec::CodecInstById(codec_id); + EXPECT_TRUE(codec_inst); + inst = *codec_inst; + } + int id; + CodecInst inst; +}; + +} // namespace + +class AcmReceiverTestOldApi : public AudioPacketizationCallback, + public ::testing::Test { + protected: + AcmReceiverTestOldApi() + : timestamp_(0), + packet_sent_(false), + last_packet_send_timestamp_(timestamp_), + last_frame_type_(kEmptyFrame) { + AudioCodingModule::Config config; + acm_.reset(new AudioCodingModuleImpl(config)); + receiver_.reset(new AcmReceiver(config)); + } + + ~AcmReceiverTestOldApi() {} + + void SetUp() override { + ASSERT_TRUE(receiver_.get() != NULL); + ASSERT_TRUE(acm_.get() != NULL); + codecs_ = RentACodec::Database(); + + acm_->InitializeReceiver(); + acm_->RegisterTransportCallback(this); + + rtp_header_.header.sequenceNumber = 0; + rtp_header_.header.timestamp = 0; + rtp_header_.header.markerBit = false; + rtp_header_.header.ssrc = 0x12345678; // Arbitrary. + rtp_header_.header.numCSRCs = 0; + rtp_header_.header.payloadType = 0; + rtp_header_.frameType = kAudioFrameSpeech; + rtp_header_.type.Audio.isCNG = false; + } + + void TearDown() override {} + + void InsertOnePacketOfSilence(int codec_id) { + CodecInst codec = + *RentACodec::CodecInstById(*RentACodec::CodecIdFromIndex(codec_id)); + if (timestamp_ == 0) { // This is the first time inserting audio. + ASSERT_EQ(0, acm_->RegisterSendCodec(codec)); + } else { + auto current_codec = acm_->SendCodec(); + ASSERT_TRUE(current_codec); + if (!CodecsEqual(codec, *current_codec)) + ASSERT_EQ(0, acm_->RegisterSendCodec(codec)); + } + AudioFrame frame; + // Frame setup according to the codec. + frame.sample_rate_hz_ = codec.plfreq; + frame.samples_per_channel_ = codec.plfreq / 100; // 10 ms. + frame.num_channels_ = codec.channels; + memset(frame.data_, 0, frame.samples_per_channel_ * frame.num_channels_ * + sizeof(int16_t)); + packet_sent_ = false; + last_packet_send_timestamp_ = timestamp_; + while (!packet_sent_) { + frame.timestamp_ = timestamp_; + timestamp_ += frame.samples_per_channel_; + ASSERT_GE(acm_->Add10MsData(frame), 0); + } + } + + template <size_t N> + void AddSetOfCodecs(const RentACodec::CodecId(&ids)[N]) { + for (auto id : ids) { + const auto i = RentACodec::CodecIndexFromId(id); + ASSERT_TRUE(i); + ASSERT_EQ( + 0, receiver_->AddCodec(*i, codecs_[*i].pltype, codecs_[*i].channels, + codecs_[*i].plfreq, nullptr, "")); + } + } + + int SendData(FrameType frame_type, + uint8_t payload_type, + uint32_t timestamp, + const uint8_t* payload_data, + size_t payload_len_bytes, + const RTPFragmentationHeader* fragmentation) override { + if (frame_type == kEmptyFrame) + return 0; + + rtp_header_.header.payloadType = payload_type; + rtp_header_.frameType = frame_type; + if (frame_type == kAudioFrameSpeech) + rtp_header_.type.Audio.isCNG = false; + else + rtp_header_.type.Audio.isCNG = true; + rtp_header_.header.timestamp = timestamp; + + int ret_val = receiver_->InsertPacket( + rtp_header_, + rtc::ArrayView<const uint8_t>(payload_data, payload_len_bytes)); + if (ret_val < 0) { + assert(false); + return -1; + } + rtp_header_.header.sequenceNumber++; + packet_sent_ = true; + last_frame_type_ = frame_type; + return 0; + } + + rtc::scoped_ptr<AcmReceiver> receiver_; + rtc::ArrayView<const CodecInst> codecs_; + rtc::scoped_ptr<AudioCodingModule> acm_; + WebRtcRTPHeader rtp_header_; + uint32_t timestamp_; + bool packet_sent_; // Set when SendData is called reset when inserting audio. + uint32_t last_packet_send_timestamp_; + FrameType last_frame_type_; +}; + +#if defined(WEBRTC_ANDROID) +#define MAYBE_AddCodecGetCodec DISABLED_AddCodecGetCodec +#else +#define MAYBE_AddCodecGetCodec AddCodecGetCodec +#endif +TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecGetCodec) { + // Add codec. + for (size_t n = 0; n < codecs_.size(); ++n) { + if (n & 0x1) // Just add codecs with odd index. + EXPECT_EQ(0, + receiver_->AddCodec(n, codecs_[n].pltype, codecs_[n].channels, + codecs_[n].plfreq, NULL, "")); + } + // Get codec and compare. + for (size_t n = 0; n < codecs_.size(); ++n) { + CodecInst my_codec; + if (n & 0x1) { + // Codecs with odd index should match the reference. + EXPECT_EQ(0, receiver_->DecoderByPayloadType(codecs_[n].pltype, + &my_codec)); + EXPECT_TRUE(CodecsEqual(codecs_[n], my_codec)); + } else { + // Codecs with even index are not registered. + EXPECT_EQ(-1, receiver_->DecoderByPayloadType(codecs_[n].pltype, + &my_codec)); + } + } +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_AddCodecChangePayloadType DISABLED_AddCodecChangePayloadType +#else +#define MAYBE_AddCodecChangePayloadType AddCodecChangePayloadType +#endif +TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecChangePayloadType) { + const CodecIdInst codec1(RentACodec::CodecId::kPCMA); + CodecInst codec2 = codec1.inst; + ++codec2.pltype; + CodecInst test_codec; + + // Register the same codec with different payloads. + EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec1.inst.pltype, + codec1.inst.channels, codec1.inst.plfreq, + nullptr, "")); + EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec2.pltype, codec2.channels, + codec2.plfreq, NULL, "")); + + // Both payload types should exist. + EXPECT_EQ(0, + receiver_->DecoderByPayloadType(codec1.inst.pltype, &test_codec)); + EXPECT_EQ(true, CodecsEqual(codec1.inst, test_codec)); + EXPECT_EQ(0, receiver_->DecoderByPayloadType(codec2.pltype, &test_codec)); + EXPECT_EQ(true, CodecsEqual(codec2, test_codec)); +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_AddCodecChangeCodecId DISABLED_AddCodecChangeCodecId +#else +#define MAYBE_AddCodecChangeCodecId AddCodecChangeCodecId +#endif +TEST_F(AcmReceiverTestOldApi, AddCodecChangeCodecId) { + const CodecIdInst codec1(RentACodec::CodecId::kPCMU); + CodecIdInst codec2(RentACodec::CodecId::kPCMA); + codec2.inst.pltype = codec1.inst.pltype; + CodecInst test_codec; + + // Register the same payload type with different codec ID. + EXPECT_EQ(0, receiver_->AddCodec(codec1.id, codec1.inst.pltype, + codec1.inst.channels, codec1.inst.plfreq, + nullptr, "")); + EXPECT_EQ(0, receiver_->AddCodec(codec2.id, codec2.inst.pltype, + codec2.inst.channels, codec2.inst.plfreq, + nullptr, "")); + + // Make sure that the last codec is used. + EXPECT_EQ(0, + receiver_->DecoderByPayloadType(codec2.inst.pltype, &test_codec)); + EXPECT_EQ(true, CodecsEqual(codec2.inst, test_codec)); +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_AddCodecRemoveCodec DISABLED_AddCodecRemoveCodec +#else +#define MAYBE_AddCodecRemoveCodec AddCodecRemoveCodec +#endif +TEST_F(AcmReceiverTestOldApi, MAYBE_AddCodecRemoveCodec) { + const CodecIdInst codec(RentACodec::CodecId::kPCMA); + const int payload_type = codec.inst.pltype; + EXPECT_EQ( + 0, receiver_->AddCodec(codec.id, codec.inst.pltype, codec.inst.channels, + codec.inst.plfreq, nullptr, "")); + + // Remove non-existing codec should not fail. ACM1 legacy. + EXPECT_EQ(0, receiver_->RemoveCodec(payload_type + 1)); + + // Remove an existing codec. + EXPECT_EQ(0, receiver_->RemoveCodec(payload_type)); + + // Ask for the removed codec, must fail. + CodecInst ci; + EXPECT_EQ(-1, receiver_->DecoderByPayloadType(payload_type, &ci)); +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_SampleRate DISABLED_SampleRate +#else +#define MAYBE_SampleRate SampleRate +#endif +TEST_F(AcmReceiverTestOldApi, MAYBE_SampleRate) { + const RentACodec::CodecId kCodecId[] = {RentACodec::CodecId::kISAC, + RentACodec::CodecId::kISACSWB}; + AddSetOfCodecs(kCodecId); + + AudioFrame frame; + const int kOutSampleRateHz = 8000; // Different than codec sample rate. + for (const auto codec_id : kCodecId) { + const CodecIdInst codec(codec_id); + const int num_10ms_frames = codec.inst.pacsize / (codec.inst.plfreq / 100); + InsertOnePacketOfSilence(codec.id); + for (int k = 0; k < num_10ms_frames; ++k) { + EXPECT_EQ(0, receiver_->GetAudio(kOutSampleRateHz, &frame)); + } + EXPECT_EQ(codec.inst.plfreq, receiver_->last_output_sample_rate_hz()); + } +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_PostdecodingVad DISABLED_PostdecodingVad +#else +#define MAYBE_PostdecodingVad PostdecodingVad +#endif +TEST_F(AcmReceiverTestOldApi, MAYBE_PostdecodingVad) { + receiver_->EnableVad(); + EXPECT_TRUE(receiver_->vad_enabled()); + const CodecIdInst codec(RentACodec::CodecId::kPCM16Bwb); + ASSERT_EQ( + 0, receiver_->AddCodec(codec.id, codec.inst.pltype, codec.inst.channels, + codec.inst.plfreq, nullptr, "")); + const int kNumPackets = 5; + const int num_10ms_frames = codec.inst.pacsize / (codec.inst.plfreq / 100); + AudioFrame frame; + for (int n = 0; n < kNumPackets; ++n) { + InsertOnePacketOfSilence(codec.id); + for (int k = 0; k < num_10ms_frames; ++k) + ASSERT_EQ(0, receiver_->GetAudio(codec.inst.plfreq, &frame)); + } + EXPECT_EQ(AudioFrame::kVadPassive, frame.vad_activity_); + + receiver_->DisableVad(); + EXPECT_FALSE(receiver_->vad_enabled()); + + for (int n = 0; n < kNumPackets; ++n) { + InsertOnePacketOfSilence(codec.id); + for (int k = 0; k < num_10ms_frames; ++k) + ASSERT_EQ(0, receiver_->GetAudio(codec.inst.plfreq, &frame)); + } + EXPECT_EQ(AudioFrame::kVadUnknown, frame.vad_activity_); +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_LastAudioCodec DISABLED_LastAudioCodec +#else +#define MAYBE_LastAudioCodec LastAudioCodec +#endif +#if defined(WEBRTC_CODEC_ISAC) +TEST_F(AcmReceiverTestOldApi, MAYBE_LastAudioCodec) { + const RentACodec::CodecId kCodecId[] = { + RentACodec::CodecId::kISAC, RentACodec::CodecId::kPCMA, + RentACodec::CodecId::kISACSWB, RentACodec::CodecId::kPCM16Bswb32kHz}; + AddSetOfCodecs(kCodecId); + + const RentACodec::CodecId kCngId[] = { + // Not including full-band. + RentACodec::CodecId::kCNNB, RentACodec::CodecId::kCNWB, + RentACodec::CodecId::kCNSWB}; + AddSetOfCodecs(kCngId); + + // Register CNG at sender side. + for (auto id : kCngId) + ASSERT_EQ(0, acm_->RegisterSendCodec(CodecIdInst(id).inst)); + + CodecInst codec; + // No audio payload is received. + EXPECT_EQ(-1, receiver_->LastAudioCodec(&codec)); + + // Start with sending DTX. + ASSERT_EQ(0, acm_->SetVAD(true, true, VADVeryAggr)); + packet_sent_ = false; + InsertOnePacketOfSilence(CodecIdInst(kCodecId[0]).id); // Enough to test + // with one codec. + ASSERT_TRUE(packet_sent_); + EXPECT_EQ(kAudioFrameCN, last_frame_type_); + + // Has received, only, DTX. Last Audio codec is undefined. + EXPECT_EQ(-1, receiver_->LastAudioCodec(&codec)); + EXPECT_FALSE(receiver_->last_packet_sample_rate_hz()); + + for (auto id : kCodecId) { + const CodecIdInst c(id); + + // Set DTX off to send audio payload. + acm_->SetVAD(false, false, VADAggr); + packet_sent_ = false; + InsertOnePacketOfSilence(c.id); + + // Sanity check if Actually an audio payload received, and it should be + // of type "speech." + ASSERT_TRUE(packet_sent_); + ASSERT_EQ(kAudioFrameSpeech, last_frame_type_); + EXPECT_EQ(rtc::Optional<int>(c.inst.plfreq), + receiver_->last_packet_sample_rate_hz()); + + // Set VAD on to send DTX. Then check if the "Last Audio codec" returns + // the expected codec. + acm_->SetVAD(true, true, VADAggr); + + // Do as many encoding until a DTX is sent. + while (last_frame_type_ != kAudioFrameCN) { + packet_sent_ = false; + InsertOnePacketOfSilence(c.id); + ASSERT_TRUE(packet_sent_); + } + EXPECT_EQ(rtc::Optional<int>(c.inst.plfreq), + receiver_->last_packet_sample_rate_hz()); + EXPECT_EQ(0, receiver_->LastAudioCodec(&codec)); + EXPECT_TRUE(CodecsEqual(c.inst, codec)); + } +} +#endif + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_resampler.cc b/webrtc/modules/audio_coding/acm2/acm_resampler.cc new file mode 100644 index 0000000000..dfc3ef7e27 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_resampler.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 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/acm2/acm_resampler.h" + +#include <assert.h> +#include <string.h> + +#include "webrtc/common_audio/resampler/include/resampler.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { +namespace acm2 { + +ACMResampler::ACMResampler() { +} + +ACMResampler::~ACMResampler() { +} + +int ACMResampler::Resample10Msec(const int16_t* in_audio, + int in_freq_hz, + int out_freq_hz, + size_t num_audio_channels, + size_t out_capacity_samples, + int16_t* out_audio) { + size_t in_length = in_freq_hz * num_audio_channels / 100; + if (in_freq_hz == out_freq_hz) { + if (out_capacity_samples < in_length) { + assert(false); + return -1; + } + memcpy(out_audio, in_audio, in_length * sizeof(int16_t)); + return static_cast<int>(in_length / num_audio_channels); + } + + if (resampler_.InitializeIfNeeded(in_freq_hz, out_freq_hz, + num_audio_channels) != 0) { + LOG(LS_ERROR) << "InitializeIfNeeded(" << in_freq_hz << ", " << out_freq_hz + << ", " << num_audio_channels << ") failed."; + return -1; + } + + int out_length = + resampler_.Resample(in_audio, in_length, out_audio, out_capacity_samples); + if (out_length == -1) { + LOG(LS_ERROR) << "Resample(" << in_audio << ", " << in_length << ", " + << out_audio << ", " << out_capacity_samples << ") failed."; + return -1; + } + + return static_cast<int>(out_length / num_audio_channels); +} + +} // namespace acm2 +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_resampler.h b/webrtc/modules/audio_coding/acm2/acm_resampler.h new file mode 100644 index 0000000000..268db8b752 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_resampler.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_ + +#include "webrtc/common_audio/resampler/include/push_resampler.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace acm2 { + +class ACMResampler { + public: + ACMResampler(); + ~ACMResampler(); + + int Resample10Msec(const int16_t* in_audio, + int in_freq_hz, + int out_freq_hz, + size_t num_audio_channels, + size_t out_capacity_samples, + int16_t* out_audio); + + private: + PushResampler<int16_t> resampler_; +}; + +} // namespace acm2 +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_RESAMPLER_H_ diff --git a/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.cc b/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.cc new file mode 100644 index 0000000000..3a89a77487 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.cc @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2014 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/acm2/acm_send_test_oldapi.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" + +namespace webrtc { +namespace test { + +AcmSendTestOldApi::AcmSendTestOldApi(InputAudioFile* audio_source, + int source_rate_hz, + int test_duration_ms) + : clock_(0), + acm_(webrtc::AudioCodingModule::Create(0, &clock_)), + audio_source_(audio_source), + source_rate_hz_(source_rate_hz), + input_block_size_samples_( + static_cast<size_t>(source_rate_hz_ * kBlockSizeMs / 1000)), + codec_registered_(false), + test_duration_ms_(test_duration_ms), + frame_type_(kAudioFrameSpeech), + payload_type_(0), + timestamp_(0), + sequence_number_(0) { + input_frame_.sample_rate_hz_ = source_rate_hz_; + input_frame_.num_channels_ = 1; + input_frame_.samples_per_channel_ = input_block_size_samples_; + assert(input_block_size_samples_ * input_frame_.num_channels_ <= + AudioFrame::kMaxDataSizeSamples); + acm_->RegisterTransportCallback(this); +} + +bool AcmSendTestOldApi::RegisterCodec(const char* payload_name, + int sampling_freq_hz, + int channels, + int payload_type, + int frame_size_samples) { + CodecInst codec; + RTC_CHECK_EQ(0, AudioCodingModule::Codec(payload_name, &codec, + sampling_freq_hz, channels)); + codec.pltype = payload_type; + codec.pacsize = frame_size_samples; + codec_registered_ = (acm_->RegisterSendCodec(codec) == 0); + input_frame_.num_channels_ = channels; + assert(input_block_size_samples_ * input_frame_.num_channels_ <= + AudioFrame::kMaxDataSizeSamples); + return codec_registered_; +} + +bool AcmSendTestOldApi::RegisterExternalCodec( + AudioEncoder* external_speech_encoder) { + acm_->RegisterExternalSendCodec(external_speech_encoder); + input_frame_.num_channels_ = external_speech_encoder->NumChannels(); + assert(input_block_size_samples_ * input_frame_.num_channels_ <= + AudioFrame::kMaxDataSizeSamples); + return codec_registered_ = true; +} + +Packet* AcmSendTestOldApi::NextPacket() { + assert(codec_registered_); + if (filter_.test(static_cast<size_t>(payload_type_))) { + // This payload type should be filtered out. Since the payload type is the + // same throughout the whole test run, no packet at all will be delivered. + // We can just as well signal that the test is over by returning NULL. + return NULL; + } + // Insert audio and process until one packet is produced. + while (clock_.TimeInMilliseconds() < test_duration_ms_) { + clock_.AdvanceTimeMilliseconds(kBlockSizeMs); + RTC_CHECK( + audio_source_->Read(input_block_size_samples_, input_frame_.data_)); + if (input_frame_.num_channels_ > 1) { + InputAudioFile::DuplicateInterleaved(input_frame_.data_, + input_block_size_samples_, + input_frame_.num_channels_, + input_frame_.data_); + } + data_to_send_ = false; + RTC_CHECK_GE(acm_->Add10MsData(input_frame_), 0); + input_frame_.timestamp_ += static_cast<uint32_t>(input_block_size_samples_); + if (data_to_send_) { + // Encoded packet received. + return CreatePacket(); + } + } + // Test ended. + return NULL; +} + +// This method receives the callback from ACM when a new packet is produced. +int32_t AcmSendTestOldApi::SendData( + FrameType frame_type, + uint8_t payload_type, + uint32_t timestamp, + const uint8_t* payload_data, + size_t payload_len_bytes, + const RTPFragmentationHeader* fragmentation) { + // Store the packet locally. + frame_type_ = frame_type; + payload_type_ = payload_type; + timestamp_ = timestamp; + last_payload_vec_.assign(payload_data, payload_data + payload_len_bytes); + assert(last_payload_vec_.size() == payload_len_bytes); + data_to_send_ = true; + return 0; +} + +Packet* AcmSendTestOldApi::CreatePacket() { + const size_t kRtpHeaderSize = 12; + size_t allocated_bytes = last_payload_vec_.size() + kRtpHeaderSize; + uint8_t* packet_memory = new uint8_t[allocated_bytes]; + // Populate the header bytes. + packet_memory[0] = 0x80; + packet_memory[1] = static_cast<uint8_t>(payload_type_); + packet_memory[2] = (sequence_number_ >> 8) & 0xFF; + packet_memory[3] = (sequence_number_) & 0xFF; + packet_memory[4] = (timestamp_ >> 24) & 0xFF; + packet_memory[5] = (timestamp_ >> 16) & 0xFF; + packet_memory[6] = (timestamp_ >> 8) & 0xFF; + packet_memory[7] = timestamp_ & 0xFF; + // Set SSRC to 0x12345678. + packet_memory[8] = 0x12; + packet_memory[9] = 0x34; + packet_memory[10] = 0x56; + packet_memory[11] = 0x78; + + ++sequence_number_; + + // Copy the payload data. + memcpy(packet_memory + kRtpHeaderSize, + &last_payload_vec_[0], + last_payload_vec_.size()); + Packet* packet = + new Packet(packet_memory, allocated_bytes, clock_.TimeInMilliseconds()); + assert(packet); + assert(packet->valid_header()); + return packet; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.h b/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.h new file mode 100644 index 0000000000..ce68196a3f --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_OLDAPI_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_OLDAPI_H_ + +#include <vector> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +class AudioEncoder; + +namespace test { +class InputAudioFile; +class Packet; + +class AcmSendTestOldApi : public AudioPacketizationCallback, + public PacketSource { + public: + AcmSendTestOldApi(InputAudioFile* audio_source, + int source_rate_hz, + int test_duration_ms); + virtual ~AcmSendTestOldApi() {} + + // Registers the send codec. Returns true on success, false otherwise. + bool RegisterCodec(const char* payload_name, + int sampling_freq_hz, + int channels, + int payload_type, + int frame_size_samples); + + // Registers an external send codec. Returns true on success, false otherwise. + bool RegisterExternalCodec(AudioEncoder* external_speech_encoder); + + // Returns the next encoded packet. Returns NULL if the test duration was + // exceeded. Ownership of the packet is handed over to the caller. + // Inherited from PacketSource. + Packet* NextPacket(); + + // Inherited from AudioPacketizationCallback. + int32_t SendData(FrameType frame_type, + uint8_t payload_type, + uint32_t timestamp, + const uint8_t* payload_data, + size_t payload_len_bytes, + const RTPFragmentationHeader* fragmentation) override; + + AudioCodingModule* acm() { return acm_.get(); } + + private: + static const int kBlockSizeMs = 10; + + // Creates a Packet object from the last packet produced by ACM (and received + // through the SendData method as a callback). Ownership of the new Packet + // object is transferred to the caller. + Packet* CreatePacket(); + + SimulatedClock clock_; + rtc::scoped_ptr<AudioCodingModule> acm_; + InputAudioFile* audio_source_; + int source_rate_hz_; + const size_t input_block_size_samples_; + AudioFrame input_frame_; + bool codec_registered_; + int test_duration_ms_; + // The following member variables are set whenever SendData() is called. + FrameType frame_type_; + int payload_type_; + uint32_t timestamp_; + uint16_t sequence_number_; + std::vector<uint8_t> last_payload_vec_; + bool data_to_send_; + + RTC_DISALLOW_COPY_AND_ASSIGN(AcmSendTestOldApi); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_ACM_SEND_TEST_OLDAPI_H_ diff --git a/webrtc/modules/audio_coding/acm2/audio_coding_module.cc b/webrtc/modules/audio_coding/acm2/audio_coding_module.cc new file mode 100644 index 0000000000..c4dd349cc4 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/audio_coding_module.cc @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2012 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/include/audio_coding_module.h" + +#include "webrtc/base/checks.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/audio_coding/acm2/audio_coding_module_impl.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { + +// Create module +AudioCodingModule* AudioCodingModule::Create(int id) { + Config config; + config.id = id; + config.clock = Clock::GetRealTimeClock(); + return Create(config); +} + +AudioCodingModule* AudioCodingModule::Create(int id, Clock* clock) { + Config config; + config.id = id; + config.clock = clock; + return Create(config); +} + +AudioCodingModule* AudioCodingModule::Create(const Config& config) { + return new acm2::AudioCodingModuleImpl(config); +} + +int AudioCodingModule::NumberOfCodecs() { + return static_cast<int>(acm2::RentACodec::NumberOfCodecs()); +} + +int AudioCodingModule::Codec(int list_id, CodecInst* codec) { + auto codec_id = acm2::RentACodec::CodecIdFromIndex(list_id); + if (!codec_id) + return -1; + auto ci = acm2::RentACodec::CodecInstById(*codec_id); + if (!ci) + return -1; + *codec = *ci; + return 0; +} + +int AudioCodingModule::Codec(const char* payload_name, + CodecInst* codec, + int sampling_freq_hz, + size_t channels) { + rtc::Optional<CodecInst> ci = acm2::RentACodec::CodecInstByParams( + payload_name, sampling_freq_hz, channels); + if (ci) { + *codec = *ci; + return 0; + } else { + // We couldn't find a matching codec, so set the parameters to unacceptable + // values and return. + codec->plname[0] = '\0'; + codec->pltype = -1; + codec->pacsize = 0; + codec->rate = 0; + codec->plfreq = 0; + return -1; + } +} + +int AudioCodingModule::Codec(const char* payload_name, + int sampling_freq_hz, + size_t channels) { + rtc::Optional<acm2::RentACodec::CodecId> ci = + acm2::RentACodec::CodecIdByParams(payload_name, sampling_freq_hz, + channels); + if (!ci) + return -1; + rtc::Optional<int> i = acm2::RentACodec::CodecIndexFromId(*ci); + return i ? *i : -1; +} + +// Checks the validity of the parameters of the given codec +bool AudioCodingModule::IsCodecValid(const CodecInst& codec) { + bool valid = acm2::RentACodec::IsCodecValid(codec); + if (!valid) + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, -1, + "Invalid codec setting"); + return valid; +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.cc new file mode 100644 index 0000000000..ac302f0fe3 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.cc @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2012 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/acm2/audio_coding_module_impl.h" + +#include <assert.h> +#include <stdlib.h> +#include <vector> + +#include "webrtc/base/checks.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/engine_configurations.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "webrtc/modules/audio_coding/acm2/acm_common_defs.h" +#include "webrtc/modules/audio_coding/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/acm2/call_statistics.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/metrics.h" +#include "webrtc/system_wrappers/include/rw_lock_wrapper.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +namespace acm2 { + +namespace { + +// TODO(turajs): the same functionality is used in NetEq. If both classes +// need them, make it a static function in ACMCodecDB. +bool IsCodecRED(const CodecInst& codec) { + return (STR_CASE_CMP(codec.plname, "RED") == 0); +} + +bool IsCodecCN(const CodecInst& codec) { + return (STR_CASE_CMP(codec.plname, "CN") == 0); +} + +// Stereo-to-mono can be used as in-place. +int DownMix(const AudioFrame& frame, + size_t length_out_buff, + int16_t* out_buff) { + if (length_out_buff < frame.samples_per_channel_) { + return -1; + } + for (size_t n = 0; n < frame.samples_per_channel_; ++n) + out_buff[n] = (frame.data_[2 * n] + frame.data_[2 * n + 1]) >> 1; + return 0; +} + +// Mono-to-stereo can be used as in-place. +int UpMix(const AudioFrame& frame, size_t length_out_buff, int16_t* out_buff) { + if (length_out_buff < frame.samples_per_channel_) { + return -1; + } + for (size_t n = frame.samples_per_channel_; n != 0; --n) { + size_t i = n - 1; + int16_t sample = frame.data_[i]; + out_buff[2 * i + 1] = sample; + out_buff[2 * i] = sample; + } + return 0; +} + +void ConvertEncodedInfoToFragmentationHeader( + const AudioEncoder::EncodedInfo& info, + RTPFragmentationHeader* frag) { + if (info.redundant.empty()) { + frag->fragmentationVectorSize = 0; + return; + } + + frag->VerifyAndAllocateFragmentationHeader( + static_cast<uint16_t>(info.redundant.size())); + frag->fragmentationVectorSize = static_cast<uint16_t>(info.redundant.size()); + size_t offset = 0; + for (size_t i = 0; i < info.redundant.size(); ++i) { + frag->fragmentationOffset[i] = offset; + offset += info.redundant[i].encoded_bytes; + frag->fragmentationLength[i] = info.redundant[i].encoded_bytes; + frag->fragmentationTimeDiff[i] = rtc::checked_cast<uint16_t>( + info.encoded_timestamp - info.redundant[i].encoded_timestamp); + frag->fragmentationPlType[i] = info.redundant[i].payload_type; + } +} +} // namespace + +void AudioCodingModuleImpl::ChangeLogger::MaybeLog(int value) { + if (value != last_value_ || first_time_) { + first_time_ = false; + last_value_ = value; + RTC_HISTOGRAM_COUNTS_SPARSE_100(histogram_name_, value); + } +} + +AudioCodingModuleImpl::AudioCodingModuleImpl( + const AudioCodingModule::Config& config) + : acm_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + id_(config.id), + expected_codec_ts_(0xD87F3F9F), + expected_in_ts_(0xD87F3F9F), + receiver_(config), + bitrate_logger_("WebRTC.Audio.TargetBitrateInKbps"), + previous_pltype_(255), + receiver_initialized_(false), + first_10ms_data_(false), + first_frame_(true), + callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + packetization_callback_(NULL), + vad_callback_(NULL) { + if (InitializeReceiverSafe() < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot initialize receiver"); + } + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceAudioCoding, id_, "Created"); +} + +AudioCodingModuleImpl::~AudioCodingModuleImpl() = default; + +int32_t AudioCodingModuleImpl::Encode(const InputData& input_data) { + AudioEncoder::EncodedInfo encoded_info; + uint8_t previous_pltype; + + // Check if there is an encoder before. + if (!HaveValidEncoder("Process")) + return -1; + + AudioEncoder* audio_encoder = rent_a_codec_.GetEncoderStack(); + // Scale the timestamp to the codec's RTP timestamp rate. + uint32_t rtp_timestamp = + first_frame_ ? input_data.input_timestamp + : last_rtp_timestamp_ + + rtc::CheckedDivExact( + input_data.input_timestamp - last_timestamp_, + static_cast<uint32_t>(rtc::CheckedDivExact( + audio_encoder->SampleRateHz(), + audio_encoder->RtpTimestampRateHz()))); + last_timestamp_ = input_data.input_timestamp; + last_rtp_timestamp_ = rtp_timestamp; + first_frame_ = false; + + encode_buffer_.SetSize(audio_encoder->MaxEncodedBytes()); + encoded_info = audio_encoder->Encode( + rtp_timestamp, rtc::ArrayView<const int16_t>( + input_data.audio, input_data.audio_channel * + input_data.length_per_channel), + encode_buffer_.size(), encode_buffer_.data()); + encode_buffer_.SetSize(encoded_info.encoded_bytes); + bitrate_logger_.MaybeLog(audio_encoder->GetTargetBitrate() / 1000); + if (encode_buffer_.size() == 0 && !encoded_info.send_even_if_empty) { + // Not enough data. + return 0; + } + previous_pltype = previous_pltype_; // Read it while we have the critsect. + + RTPFragmentationHeader my_fragmentation; + ConvertEncodedInfoToFragmentationHeader(encoded_info, &my_fragmentation); + FrameType frame_type; + if (encode_buffer_.size() == 0 && encoded_info.send_even_if_empty) { + frame_type = kEmptyFrame; + encoded_info.payload_type = previous_pltype; + } else { + RTC_DCHECK_GT(encode_buffer_.size(), 0u); + frame_type = encoded_info.speech ? kAudioFrameSpeech : kAudioFrameCN; + } + + { + CriticalSectionScoped lock(callback_crit_sect_.get()); + if (packetization_callback_) { + packetization_callback_->SendData( + frame_type, encoded_info.payload_type, encoded_info.encoded_timestamp, + encode_buffer_.data(), encode_buffer_.size(), + my_fragmentation.fragmentationVectorSize > 0 ? &my_fragmentation + : nullptr); + } + + if (vad_callback_) { + // Callback with VAD decision. + vad_callback_->InFrameType(frame_type); + } + } + previous_pltype_ = encoded_info.payload_type; + return static_cast<int32_t>(encode_buffer_.size()); +} + +///////////////////////////////////////// +// Sender +// + +// Can be called multiple times for Codec, CNG, RED. +int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!codec_manager_.RegisterEncoder(send_codec)) { + return -1; + } + auto* sp = codec_manager_.GetStackParams(); + if (!sp->speech_encoder && codec_manager_.GetCodecInst()) { + // We have no speech encoder, but we have a specification for making one. + AudioEncoder* enc = + rent_a_codec_.RentEncoder(*codec_manager_.GetCodecInst()); + if (!enc) + return -1; + sp->speech_encoder = enc; + } + if (sp->speech_encoder) + rent_a_codec_.RentEncoderStack(sp); + return 0; +} + +void AudioCodingModuleImpl::RegisterExternalSendCodec( + AudioEncoder* external_speech_encoder) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + auto* sp = codec_manager_.GetStackParams(); + sp->speech_encoder = external_speech_encoder; + rent_a_codec_.RentEncoderStack(sp); +} + +// Get current send codec. +rtc::Optional<CodecInst> AudioCodingModuleImpl::SendCodec() const { + CriticalSectionScoped lock(acm_crit_sect_.get()); + auto* ci = codec_manager_.GetCodecInst(); + if (ci) { + return rtc::Optional<CodecInst>(*ci); + } + auto* enc = codec_manager_.GetStackParams()->speech_encoder; + if (enc) { + return rtc::Optional<CodecInst>(CodecManager::ForgeCodecInst(enc)); + } + return rtc::Optional<CodecInst>(); +} + +// Get current send frequency. +int AudioCodingModuleImpl::SendFrequency() const { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, + "SendFrequency()"); + CriticalSectionScoped lock(acm_crit_sect_.get()); + + const auto* enc = rent_a_codec_.GetEncoderStack(); + if (!enc) { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, + "SendFrequency Failed, no codec is registered"); + return -1; + } + + return enc->SampleRateHz(); +} + +void AudioCodingModuleImpl::SetBitRate(int bitrate_bps) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + auto* enc = rent_a_codec_.GetEncoderStack(); + if (enc) { + enc->SetTargetBitrate(bitrate_bps); + } +} + +// Register a transport callback which will be called to deliver +// the encoded buffers. +int AudioCodingModuleImpl::RegisterTransportCallback( + AudioPacketizationCallback* transport) { + CriticalSectionScoped lock(callback_crit_sect_.get()); + packetization_callback_ = transport; + return 0; +} + +// Add 10MS of raw (PCM) audio data to the encoder. +int AudioCodingModuleImpl::Add10MsData(const AudioFrame& audio_frame) { + InputData input_data; + CriticalSectionScoped lock(acm_crit_sect_.get()); + int r = Add10MsDataInternal(audio_frame, &input_data); + return r < 0 ? r : Encode(input_data); +} + +int AudioCodingModuleImpl::Add10MsDataInternal(const AudioFrame& audio_frame, + InputData* input_data) { + if (audio_frame.samples_per_channel_ == 0) { + assert(false); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot Add 10 ms audio, payload length is zero"); + return -1; + } + + if (audio_frame.sample_rate_hz_ > 48000) { + assert(false); + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot Add 10 ms audio, input frequency not valid"); + return -1; + } + + // If the length and frequency matches. We currently just support raw PCM. + if (static_cast<size_t>(audio_frame.sample_rate_hz_ / 100) != + audio_frame.samples_per_channel_) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot Add 10 ms audio, input frequency and length doesn't" + " match"); + return -1; + } + + if (audio_frame.num_channels_ != 1 && audio_frame.num_channels_ != 2) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot Add 10 ms audio, invalid number of channels."); + return -1; + } + + // Do we have a codec registered? + if (!HaveValidEncoder("Add10MsData")) { + return -1; + } + + const AudioFrame* ptr_frame; + // Perform a resampling, also down-mix if it is required and can be + // performed before resampling (a down mix prior to resampling will take + // place if both primary and secondary encoders are mono and input is in + // stereo). + if (PreprocessToAddData(audio_frame, &ptr_frame) < 0) { + return -1; + } + + // Check whether we need an up-mix or down-mix? + const size_t current_num_channels = + rent_a_codec_.GetEncoderStack()->NumChannels(); + const bool same_num_channels = + ptr_frame->num_channels_ == current_num_channels; + + if (!same_num_channels) { + if (ptr_frame->num_channels_ == 1) { + if (UpMix(*ptr_frame, WEBRTC_10MS_PCM_AUDIO, input_data->buffer) < 0) + return -1; + } else { + if (DownMix(*ptr_frame, WEBRTC_10MS_PCM_AUDIO, input_data->buffer) < 0) + return -1; + } + } + + // When adding data to encoders this pointer is pointing to an audio buffer + // with correct number of channels. + const int16_t* ptr_audio = ptr_frame->data_; + + // For pushing data to primary, point the |ptr_audio| to correct buffer. + if (!same_num_channels) + ptr_audio = input_data->buffer; + + input_data->input_timestamp = ptr_frame->timestamp_; + input_data->audio = ptr_audio; + input_data->length_per_channel = ptr_frame->samples_per_channel_; + input_data->audio_channel = current_num_channels; + + return 0; +} + +// Perform a resampling and down-mix if required. We down-mix only if +// encoder is mono and input is stereo. In case of dual-streaming, both +// encoders has to be mono for down-mix to take place. +// |*ptr_out| will point to the pre-processed audio-frame. If no pre-processing +// is required, |*ptr_out| points to |in_frame|. +int AudioCodingModuleImpl::PreprocessToAddData(const AudioFrame& in_frame, + const AudioFrame** ptr_out) { + const auto* enc = rent_a_codec_.GetEncoderStack(); + const bool resample = in_frame.sample_rate_hz_ != enc->SampleRateHz(); + + // This variable is true if primary codec and secondary codec (if exists) + // are both mono and input is stereo. + // TODO(henrik.lundin): This condition should probably be + // in_frame.num_channels_ > enc->NumChannels() + const bool down_mix = in_frame.num_channels_ == 2 && enc->NumChannels() == 1; + + if (!first_10ms_data_) { + expected_in_ts_ = in_frame.timestamp_; + expected_codec_ts_ = in_frame.timestamp_; + first_10ms_data_ = true; + } else if (in_frame.timestamp_ != expected_in_ts_) { + // TODO(turajs): Do we need a warning here. + expected_codec_ts_ += + (in_frame.timestamp_ - expected_in_ts_) * + static_cast<uint32_t>(static_cast<double>(enc->SampleRateHz()) / + static_cast<double>(in_frame.sample_rate_hz_)); + expected_in_ts_ = in_frame.timestamp_; + } + + + if (!down_mix && !resample) { + // No pre-processing is required. + expected_in_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_); + expected_codec_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_); + *ptr_out = &in_frame; + return 0; + } + + *ptr_out = &preprocess_frame_; + preprocess_frame_.num_channels_ = in_frame.num_channels_; + int16_t audio[WEBRTC_10MS_PCM_AUDIO]; + const int16_t* src_ptr_audio = in_frame.data_; + int16_t* dest_ptr_audio = preprocess_frame_.data_; + if (down_mix) { + // If a resampling is required the output of a down-mix is written into a + // local buffer, otherwise, it will be written to the output frame. + if (resample) + dest_ptr_audio = audio; + if (DownMix(in_frame, WEBRTC_10MS_PCM_AUDIO, dest_ptr_audio) < 0) + return -1; + preprocess_frame_.num_channels_ = 1; + // Set the input of the resampler is the down-mixed signal. + src_ptr_audio = audio; + } + + preprocess_frame_.timestamp_ = expected_codec_ts_; + preprocess_frame_.samples_per_channel_ = in_frame.samples_per_channel_; + preprocess_frame_.sample_rate_hz_ = in_frame.sample_rate_hz_; + // If it is required, we have to do a resampling. + if (resample) { + // The result of the resampler is written to output frame. + dest_ptr_audio = preprocess_frame_.data_; + + int samples_per_channel = resampler_.Resample10Msec( + src_ptr_audio, in_frame.sample_rate_hz_, enc->SampleRateHz(), + preprocess_frame_.num_channels_, AudioFrame::kMaxDataSizeSamples, + dest_ptr_audio); + + if (samples_per_channel < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot add 10 ms audio, resampling failed"); + return -1; + } + preprocess_frame_.samples_per_channel_ = + static_cast<size_t>(samples_per_channel); + preprocess_frame_.sample_rate_hz_ = enc->SampleRateHz(); + } + + expected_codec_ts_ += + static_cast<uint32_t>(preprocess_frame_.samples_per_channel_); + expected_in_ts_ += static_cast<uint32_t>(in_frame.samples_per_channel_); + + return 0; +} + +///////////////////////////////////////// +// (RED) Redundant Coding +// + +bool AudioCodingModuleImpl::REDStatus() const { + CriticalSectionScoped lock(acm_crit_sect_.get()); + return codec_manager_.GetStackParams()->use_red; +} + +// Configure RED status i.e on/off. +int AudioCodingModuleImpl::SetREDStatus(bool enable_red) { +#ifdef WEBRTC_CODEC_RED + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!codec_manager_.SetCopyRed(enable_red)) { + return -1; + } + auto* sp = codec_manager_.GetStackParams(); + if (sp->speech_encoder) + rent_a_codec_.RentEncoderStack(sp); + return 0; +#else + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, + " WEBRTC_CODEC_RED is undefined"); + return -1; +#endif +} + +///////////////////////////////////////// +// (FEC) Forward Error Correction (codec internal) +// + +bool AudioCodingModuleImpl::CodecFEC() const { + CriticalSectionScoped lock(acm_crit_sect_.get()); + return codec_manager_.GetStackParams()->use_codec_fec; +} + +int AudioCodingModuleImpl::SetCodecFEC(bool enable_codec_fec) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!codec_manager_.SetCodecFEC(enable_codec_fec)) { + return -1; + } + auto* sp = codec_manager_.GetStackParams(); + if (sp->speech_encoder) + rent_a_codec_.RentEncoderStack(sp); + if (enable_codec_fec) { + return sp->use_codec_fec ? 0 : -1; + } else { + RTC_DCHECK(!sp->use_codec_fec); + return 0; + } +} + +int AudioCodingModuleImpl::SetPacketLossRate(int loss_rate) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (HaveValidEncoder("SetPacketLossRate")) { + rent_a_codec_.GetEncoderStack()->SetProjectedPacketLossRate(loss_rate / + 100.0); + } + return 0; +} + +///////////////////////////////////////// +// (VAD) Voice Activity Detection +// +int AudioCodingModuleImpl::SetVAD(bool enable_dtx, + bool enable_vad, + ACMVADMode mode) { + // Note: |enable_vad| is not used; VAD is enabled based on the DTX setting. + RTC_DCHECK_EQ(enable_dtx, enable_vad); + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!codec_manager_.SetVAD(enable_dtx, mode)) { + return -1; + } + auto* sp = codec_manager_.GetStackParams(); + if (sp->speech_encoder) + rent_a_codec_.RentEncoderStack(sp); + return 0; +} + +// Get VAD/DTX settings. +int AudioCodingModuleImpl::VAD(bool* dtx_enabled, bool* vad_enabled, + ACMVADMode* mode) const { + CriticalSectionScoped lock(acm_crit_sect_.get()); + const auto* sp = codec_manager_.GetStackParams(); + *dtx_enabled = *vad_enabled = sp->use_cng; + *mode = sp->vad_mode; + return 0; +} + +///////////////////////////////////////// +// Receiver +// + +int AudioCodingModuleImpl::InitializeReceiver() { + CriticalSectionScoped lock(acm_crit_sect_.get()); + return InitializeReceiverSafe(); +} + +// Initialize receiver, resets codec database etc. +int AudioCodingModuleImpl::InitializeReceiverSafe() { + // If the receiver is already initialized then we want to destroy any + // existing decoders. After a call to this function, we should have a clean + // start-up. + if (receiver_initialized_) { + if (receiver_.RemoveAllCodecs() < 0) + return -1; + } + receiver_.set_id(id_); + receiver_.ResetInitialDelay(); + receiver_.SetMinimumDelay(0); + receiver_.SetMaximumDelay(0); + receiver_.FlushBuffers(); + + // Register RED and CN. + auto db = RentACodec::Database(); + for (size_t i = 0; i < db.size(); i++) { + if (IsCodecRED(db[i]) || IsCodecCN(db[i])) { + if (receiver_.AddCodec(static_cast<int>(i), + static_cast<uint8_t>(db[i].pltype), 1, + db[i].plfreq, nullptr, db[i].plname) < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot register master codec."); + return -1; + } + } + } + receiver_initialized_ = true; + return 0; +} + +// Get current receive frequency. +int AudioCodingModuleImpl::ReceiveFrequency() const { + const auto last_packet_sample_rate = receiver_.last_packet_sample_rate_hz(); + return last_packet_sample_rate ? *last_packet_sample_rate + : receiver_.last_output_sample_rate_hz(); +} + +// Get current playout frequency. +int AudioCodingModuleImpl::PlayoutFrequency() const { + WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, + "PlayoutFrequency()"); + return receiver_.last_output_sample_rate_hz(); +} + +// Register possible receive codecs, can be called multiple times, +// for codecs, CNG (NB, WB and SWB), DTMF, RED. +int AudioCodingModuleImpl::RegisterReceiveCodec(const CodecInst& codec) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + RTC_DCHECK(receiver_initialized_); + if (codec.channels > 2) { + LOG_F(LS_ERROR) << "Unsupported number of channels: " << codec.channels; + return -1; + } + + auto codec_id = + RentACodec::CodecIdByParams(codec.plname, codec.plfreq, codec.channels); + if (!codec_id) { + LOG_F(LS_ERROR) << "Wrong codec params to be registered as receive codec"; + return -1; + } + auto codec_index = RentACodec::CodecIndexFromId(*codec_id); + RTC_CHECK(codec_index) << "Invalid codec ID: " << static_cast<int>(*codec_id); + + // Check if the payload-type is valid. + if (!RentACodec::IsPayloadTypeValid(codec.pltype)) { + LOG_F(LS_ERROR) << "Invalid payload type " << codec.pltype << " for " + << codec.plname; + return -1; + } + + // Get |decoder| associated with |codec|. |decoder| is NULL if |codec| does + // not own its decoder. + return receiver_.AddCodec( + *codec_index, codec.pltype, codec.channels, codec.plfreq, + STR_CASE_CMP(codec.plname, "isac") == 0 ? rent_a_codec_.RentIsacDecoder() + : nullptr, + codec.plname); +} + +int AudioCodingModuleImpl::RegisterExternalReceiveCodec( + int rtp_payload_type, + AudioDecoder* external_decoder, + int sample_rate_hz, + int num_channels, + const std::string& name) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + RTC_DCHECK(receiver_initialized_); + if (num_channels > 2 || num_channels < 0) { + LOG_F(LS_ERROR) << "Unsupported number of channels: " << num_channels; + return -1; + } + + // Check if the payload-type is valid. + if (!RentACodec::IsPayloadTypeValid(rtp_payload_type)) { + LOG_F(LS_ERROR) << "Invalid payload-type " << rtp_payload_type + << " for external decoder."; + return -1; + } + + return receiver_.AddCodec(-1 /* external */, rtp_payload_type, num_channels, + sample_rate_hz, external_decoder, name); +} + +// Get current received codec. +int AudioCodingModuleImpl::ReceiveCodec(CodecInst* current_codec) const { + CriticalSectionScoped lock(acm_crit_sect_.get()); + return receiver_.LastAudioCodec(current_codec); +} + +// Incoming packet from network parsed and ready for decode. +int AudioCodingModuleImpl::IncomingPacket(const uint8_t* incoming_payload, + const size_t payload_length, + const WebRtcRTPHeader& rtp_header) { + return receiver_.InsertPacket( + rtp_header, + rtc::ArrayView<const uint8_t>(incoming_payload, payload_length)); +} + +// Minimum playout delay (Used for lip-sync). +int AudioCodingModuleImpl::SetMinimumPlayoutDelay(int time_ms) { + if ((time_ms < 0) || (time_ms > 10000)) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Delay must be in the range of 0-1000 milliseconds."); + return -1; + } + return receiver_.SetMinimumDelay(time_ms); +} + +int AudioCodingModuleImpl::SetMaximumPlayoutDelay(int time_ms) { + if ((time_ms < 0) || (time_ms > 10000)) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Delay must be in the range of 0-1000 milliseconds."); + return -1; + } + return receiver_.SetMaximumDelay(time_ms); +} + +// Get 10 milliseconds of raw audio data to play out. +// Automatic resample to the requested frequency. +int AudioCodingModuleImpl::PlayoutData10Ms(int desired_freq_hz, + AudioFrame* audio_frame) { + // GetAudio always returns 10 ms, at the requested sample rate. + if (receiver_.GetAudio(desired_freq_hz, audio_frame) != 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "PlayoutData failed, RecOut Failed"); + return -1; + } + audio_frame->id_ = id_; + return 0; +} + +///////////////////////////////////////// +// Statistics +// + +// TODO(turajs) change the return value to void. Also change the corresponding +// NetEq function. +int AudioCodingModuleImpl::GetNetworkStatistics(NetworkStatistics* statistics) { + receiver_.GetNetworkStatistics(statistics); + return 0; +} + +int AudioCodingModuleImpl::RegisterVADCallback(ACMVADCallback* vad_callback) { + WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, + "RegisterVADCallback()"); + CriticalSectionScoped lock(callback_crit_sect_.get()); + vad_callback_ = vad_callback; + return 0; +} + +// TODO(kwiberg): Remove this method, and have callers call IncomingPacket +// instead. The translation logic and state belong with them, not with +// AudioCodingModuleImpl. +int AudioCodingModuleImpl::IncomingPayload(const uint8_t* incoming_payload, + size_t payload_length, + uint8_t payload_type, + uint32_t timestamp) { + // We are not acquiring any lock when interacting with |aux_rtp_header_| no + // other method uses this member variable. + if (!aux_rtp_header_) { + // This is the first time that we are using |dummy_rtp_header_| + // so we have to create it. + aux_rtp_header_.reset(new WebRtcRTPHeader); + aux_rtp_header_->header.payloadType = payload_type; + // Don't matter in this case. + aux_rtp_header_->header.ssrc = 0; + aux_rtp_header_->header.markerBit = false; + // Start with random numbers. + aux_rtp_header_->header.sequenceNumber = 0x1234; // Arbitrary. + aux_rtp_header_->type.Audio.channel = 1; + } + + aux_rtp_header_->header.timestamp = timestamp; + IncomingPacket(incoming_payload, payload_length, *aux_rtp_header_); + // Get ready for the next payload. + aux_rtp_header_->header.sequenceNumber++; + return 0; +} + +int AudioCodingModuleImpl::SetOpusApplication(OpusApplicationMode application) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!HaveValidEncoder("SetOpusApplication")) { + return -1; + } + AudioEncoder::Application app; + switch (application) { + case kVoip: + app = AudioEncoder::Application::kSpeech; + break; + case kAudio: + app = AudioEncoder::Application::kAudio; + break; + default: + FATAL(); + return 0; + } + return rent_a_codec_.GetEncoderStack()->SetApplication(app) ? 0 : -1; +} + +// Informs Opus encoder of the maximum playback rate the receiver will render. +int AudioCodingModuleImpl::SetOpusMaxPlaybackRate(int frequency_hz) { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!HaveValidEncoder("SetOpusMaxPlaybackRate")) { + return -1; + } + rent_a_codec_.GetEncoderStack()->SetMaxPlaybackRate(frequency_hz); + return 0; +} + +int AudioCodingModuleImpl::EnableOpusDtx() { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!HaveValidEncoder("EnableOpusDtx")) { + return -1; + } + return rent_a_codec_.GetEncoderStack()->SetDtx(true) ? 0 : -1; +} + +int AudioCodingModuleImpl::DisableOpusDtx() { + CriticalSectionScoped lock(acm_crit_sect_.get()); + if (!HaveValidEncoder("DisableOpusDtx")) { + return -1; + } + return rent_a_codec_.GetEncoderStack()->SetDtx(false) ? 0 : -1; +} + +int AudioCodingModuleImpl::PlayoutTimestamp(uint32_t* timestamp) { + return receiver_.GetPlayoutTimestamp(timestamp) ? 0 : -1; +} + +bool AudioCodingModuleImpl::HaveValidEncoder(const char* caller_name) const { + if (!rent_a_codec_.GetEncoderStack()) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "%s failed: No send codec is registered.", caller_name); + return false; + } + return true; +} + +int AudioCodingModuleImpl::UnregisterReceiveCodec(uint8_t payload_type) { + return receiver_.RemoveCodec(payload_type); +} + +int AudioCodingModuleImpl::EnableNack(size_t max_nack_list_size) { + return receiver_.EnableNack(max_nack_list_size); +} + +void AudioCodingModuleImpl::DisableNack() { + receiver_.DisableNack(); +} + +std::vector<uint16_t> AudioCodingModuleImpl::GetNackList( + int64_t round_trip_time_ms) const { + return receiver_.GetNackList(round_trip_time_ms); +} + +int AudioCodingModuleImpl::LeastRequiredDelayMs() const { + return receiver_.LeastRequiredDelayMs(); +} + +void AudioCodingModuleImpl::GetDecodingCallStatistics( + AudioDecodingCallStats* call_stats) const { + receiver_.GetDecodingCallStatistics(call_stats); +} + +} // namespace acm2 +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.h b/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.h new file mode 100644 index 0000000000..926671f199 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/audio_coding_module_impl.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2012 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_AUDIO_CODING_MODULE_IMPL_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_AUDIO_CODING_MODULE_IMPL_H_ + +#include <string> +#include <vector> + +#include "webrtc/base/buffer.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/common_types.h" +#include "webrtc/engine_configurations.h" +#include "webrtc/modules/audio_coding/acm2/acm_receiver.h" +#include "webrtc/modules/audio_coding/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/acm2/codec_manager.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class AudioCodingImpl; + +namespace acm2 { + +class AudioCodingModuleImpl final : public AudioCodingModule { + public: + friend webrtc::AudioCodingImpl; + + explicit AudioCodingModuleImpl(const AudioCodingModule::Config& config); + ~AudioCodingModuleImpl() override; + + ///////////////////////////////////////// + // Sender + // + + // Can be called multiple times for Codec, CNG, RED. + int RegisterSendCodec(const CodecInst& send_codec) override; + + void RegisterExternalSendCodec( + AudioEncoder* external_speech_encoder) override; + + // Get current send codec. + rtc::Optional<CodecInst> SendCodec() const override; + + // Get current send frequency. + int SendFrequency() const override; + + // Sets the bitrate to the specified value in bits/sec. In case the codec does + // not support the requested value it will choose an appropriate value + // instead. + void SetBitRate(int bitrate_bps) override; + + // Register a transport callback which will be + // called to deliver the encoded buffers. + int RegisterTransportCallback(AudioPacketizationCallback* transport) override; + + // Add 10 ms of raw (PCM) audio data to the encoder. + int Add10MsData(const AudioFrame& audio_frame) override; + + ///////////////////////////////////////// + // (RED) Redundant Coding + // + + // Configure RED status i.e. on/off. + int SetREDStatus(bool enable_red) override; + + // Get RED status. + bool REDStatus() const override; + + ///////////////////////////////////////// + // (FEC) Forward Error Correction (codec internal) + // + + // Configure FEC status i.e. on/off. + int SetCodecFEC(bool enabled_codec_fec) override; + + // Get FEC status. + bool CodecFEC() const override; + + // Set target packet loss rate + int SetPacketLossRate(int loss_rate) override; + + ///////////////////////////////////////// + // (VAD) Voice Activity Detection + // and + // (CNG) Comfort Noise Generation + // + + int SetVAD(bool enable_dtx = true, + bool enable_vad = false, + ACMVADMode mode = VADNormal) override; + + int VAD(bool* dtx_enabled, + bool* vad_enabled, + ACMVADMode* mode) const override; + + int RegisterVADCallback(ACMVADCallback* vad_callback) override; + + ///////////////////////////////////////// + // Receiver + // + + // Initialize receiver, resets codec database etc. + int InitializeReceiver() override; + + // Get current receive frequency. + int ReceiveFrequency() const override; + + // Get current playout frequency. + int PlayoutFrequency() const override; + + // Register possible receive codecs, can be called multiple times, + // for codecs, CNG, DTMF, RED. + int RegisterReceiveCodec(const CodecInst& receive_codec) override; + + int RegisterExternalReceiveCodec(int rtp_payload_type, + AudioDecoder* external_decoder, + int sample_rate_hz, + int num_channels, + const std::string& name) override; + + // Get current received codec. + int ReceiveCodec(CodecInst* current_codec) const override; + + // Incoming packet from network parsed and ready for decode. + int IncomingPacket(const uint8_t* incoming_payload, + const size_t payload_length, + const WebRtcRTPHeader& rtp_info) override; + + // Incoming payloads, without rtp-info, the rtp-info will be created in ACM. + // One usage for this API is when pre-encoded files are pushed in ACM. + int IncomingPayload(const uint8_t* incoming_payload, + const size_t payload_length, + uint8_t payload_type, + uint32_t timestamp) override; + + // Minimum playout delay. + int SetMinimumPlayoutDelay(int time_ms) override; + + // Maximum playout delay. + int SetMaximumPlayoutDelay(int time_ms) override; + + // Smallest latency NetEq will maintain. + int LeastRequiredDelayMs() const override; + + // Get playout timestamp. + int PlayoutTimestamp(uint32_t* timestamp) override; + + // Get 10 milliseconds of raw audio data to play out, and + // automatic resample to the requested frequency if > 0. + int PlayoutData10Ms(int desired_freq_hz, AudioFrame* audio_frame) override; + + ///////////////////////////////////////// + // Statistics + // + + int GetNetworkStatistics(NetworkStatistics* statistics) override; + + int SetOpusApplication(OpusApplicationMode application) override; + + // If current send codec is Opus, informs it about the maximum playback rate + // the receiver will render. + int SetOpusMaxPlaybackRate(int frequency_hz) override; + + int EnableOpusDtx() override; + + int DisableOpusDtx() override; + + int UnregisterReceiveCodec(uint8_t payload_type) override; + + int EnableNack(size_t max_nack_list_size) override; + + void DisableNack() override; + + std::vector<uint16_t> GetNackList(int64_t round_trip_time_ms) const override; + + void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const override; + + private: + struct InputData { + uint32_t input_timestamp; + const int16_t* audio; + size_t length_per_channel; + size_t audio_channel; + // If a re-mix is required (up or down), this buffer will store a re-mixed + // version of the input. + int16_t buffer[WEBRTC_10MS_PCM_AUDIO]; + }; + + // This member class writes values to the named UMA histogram, but only if + // the value has changed since the last time (and always for the first call). + class ChangeLogger { + public: + explicit ChangeLogger(const std::string& histogram_name) + : histogram_name_(histogram_name) {} + // Logs the new value if it is different from the last logged value, or if + // this is the first call. + void MaybeLog(int value); + + private: + int last_value_ = 0; + int first_time_ = true; + const std::string histogram_name_; + }; + + int Add10MsDataInternal(const AudioFrame& audio_frame, InputData* input_data) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); + int Encode(const InputData& input_data) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); + + int InitializeReceiverSafe() EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); + + bool HaveValidEncoder(const char* caller_name) const + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); + + // Preprocessing of input audio, including resampling and down-mixing if + // required, before pushing audio into encoder's buffer. + // + // in_frame: input audio-frame + // ptr_out: pointer to output audio_frame. If no preprocessing is required + // |ptr_out| will be pointing to |in_frame|, otherwise pointing to + // |preprocess_frame_|. + // + // Return value: + // -1: if encountering an error. + // 0: otherwise. + int PreprocessToAddData(const AudioFrame& in_frame, + const AudioFrame** ptr_out) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); + + // Change required states after starting to receive the codec corresponding + // to |index|. + int UpdateUponReceivingCodec(int index); + + const rtc::scoped_ptr<CriticalSectionWrapper> acm_crit_sect_; + rtc::Buffer encode_buffer_ GUARDED_BY(acm_crit_sect_); + int id_; // TODO(henrik.lundin) Make const. + uint32_t expected_codec_ts_ GUARDED_BY(acm_crit_sect_); + uint32_t expected_in_ts_ GUARDED_BY(acm_crit_sect_); + ACMResampler resampler_ GUARDED_BY(acm_crit_sect_); + AcmReceiver receiver_; // AcmReceiver has it's own internal lock. + ChangeLogger bitrate_logger_ GUARDED_BY(acm_crit_sect_); + CodecManager codec_manager_ GUARDED_BY(acm_crit_sect_); + RentACodec rent_a_codec_ GUARDED_BY(acm_crit_sect_); + + // This is to keep track of CN instances where we can send DTMFs. + uint8_t previous_pltype_ GUARDED_BY(acm_crit_sect_); + + // Used when payloads are pushed into ACM without any RTP info + // One example is when pre-encoded bit-stream is pushed from + // a file. + // IMPORTANT: this variable is only used in IncomingPayload(), therefore, + // no lock acquired when interacting with this variable. If it is going to + // be used in other methods, locks need to be taken. + rtc::scoped_ptr<WebRtcRTPHeader> aux_rtp_header_; + + bool receiver_initialized_ GUARDED_BY(acm_crit_sect_); + + AudioFrame preprocess_frame_ GUARDED_BY(acm_crit_sect_); + bool first_10ms_data_ GUARDED_BY(acm_crit_sect_); + + bool first_frame_ GUARDED_BY(acm_crit_sect_); + uint32_t last_timestamp_ GUARDED_BY(acm_crit_sect_); + uint32_t last_rtp_timestamp_ GUARDED_BY(acm_crit_sect_); + + const rtc::scoped_ptr<CriticalSectionWrapper> callback_crit_sect_; + AudioPacketizationCallback* packetization_callback_ + GUARDED_BY(callback_crit_sect_); + ACMVADCallback* vad_callback_ GUARDED_BY(callback_crit_sect_); +}; + +} // namespace acm2 +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_AUDIO_CODING_MODULE_IMPL_H_ diff --git a/webrtc/modules/audio_coding/acm2/audio_coding_module_unittest_oldapi.cc b/webrtc/modules/audio_coding/acm2/audio_coding_module_unittest_oldapi.cc new file mode 100644 index 0000000000..6f82a96ee5 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/audio_coding_module_unittest_oldapi.cc @@ -0,0 +1,1789 @@ +/* + * Copyright (c) 2014 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 <stdio.h> +#include <string.h> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/md5digest.h" +#include "webrtc/base/platform_thread.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" +#include "webrtc/modules/audio_coding/codecs/g711/audio_decoder_pcm.h" +#include "webrtc/modules/audio_coding/codecs/g711/audio_encoder_pcm.h" +#include "webrtc/modules/audio_coding/codecs/isac/main/include/audio_encoder_isac.h" +#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" +#include "webrtc/modules/audio_coding/acm2/acm_receive_test_oldapi.h" +#include "webrtc/modules/audio_coding/acm2/acm_send_test_oldapi.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "webrtc/modules/audio_coding/neteq/audio_decoder_impl.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_checksum.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h" +#include "webrtc/modules/audio_coding/neteq/tools/constant_pcm_packet_source.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/output_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h" +#include "webrtc/modules/include/module_common_types.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/sleep.h" +#include "webrtc/test/testsupport/fileutils.h" + +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::_; + +namespace webrtc { + +namespace { +const int kSampleRateHz = 16000; +const int kNumSamples10ms = kSampleRateHz / 100; +const int kFrameSizeMs = 10; // Multiple of 10. +const int kFrameSizeSamples = kFrameSizeMs / 10 * kNumSamples10ms; +const int kPayloadSizeBytes = kFrameSizeSamples * sizeof(int16_t); +const uint8_t kPayloadType = 111; +} // namespace + +class RtpUtility { + public: + RtpUtility(int samples_per_packet, uint8_t payload_type) + : samples_per_packet_(samples_per_packet), payload_type_(payload_type) {} + + virtual ~RtpUtility() {} + + void Populate(WebRtcRTPHeader* rtp_header) { + rtp_header->header.sequenceNumber = 0xABCD; + rtp_header->header.timestamp = 0xABCDEF01; + rtp_header->header.payloadType = payload_type_; + rtp_header->header.markerBit = false; + rtp_header->header.ssrc = 0x1234; + rtp_header->header.numCSRCs = 0; + rtp_header->frameType = kAudioFrameSpeech; + + rtp_header->header.payload_type_frequency = kSampleRateHz; + rtp_header->type.Audio.channel = 1; + rtp_header->type.Audio.isCNG = false; + } + + void Forward(WebRtcRTPHeader* rtp_header) { + ++rtp_header->header.sequenceNumber; + rtp_header->header.timestamp += samples_per_packet_; + } + + private: + int samples_per_packet_; + uint8_t payload_type_; +}; + +class PacketizationCallbackStubOldApi : public AudioPacketizationCallback { + public: + PacketizationCallbackStubOldApi() + : num_calls_(0), + last_frame_type_(kEmptyFrame), + last_payload_type_(-1), + last_timestamp_(0), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()) {} + + int32_t SendData(FrameType frame_type, + uint8_t payload_type, + uint32_t timestamp, + const uint8_t* payload_data, + size_t payload_len_bytes, + const RTPFragmentationHeader* fragmentation) override { + CriticalSectionScoped lock(crit_sect_.get()); + ++num_calls_; + last_frame_type_ = frame_type; + last_payload_type_ = payload_type; + last_timestamp_ = timestamp; + last_payload_vec_.assign(payload_data, payload_data + payload_len_bytes); + return 0; + } + + int num_calls() const { + CriticalSectionScoped lock(crit_sect_.get()); + return num_calls_; + } + + int last_payload_len_bytes() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_payload_vec_.size(); + } + + FrameType last_frame_type() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_frame_type_; + } + + int last_payload_type() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_payload_type_; + } + + uint32_t last_timestamp() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_timestamp_; + } + + void SwapBuffers(std::vector<uint8_t>* payload) { + CriticalSectionScoped lock(crit_sect_.get()); + last_payload_vec_.swap(*payload); + } + + private: + int num_calls_ GUARDED_BY(crit_sect_); + FrameType last_frame_type_ GUARDED_BY(crit_sect_); + int last_payload_type_ GUARDED_BY(crit_sect_); + uint32_t last_timestamp_ GUARDED_BY(crit_sect_); + std::vector<uint8_t> last_payload_vec_ GUARDED_BY(crit_sect_); + const rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; +}; + +class AudioCodingModuleTestOldApi : public ::testing::Test { + protected: + AudioCodingModuleTestOldApi() + : id_(1), + rtp_utility_(new RtpUtility(kFrameSizeSamples, kPayloadType)), + clock_(Clock::GetRealTimeClock()) {} + + ~AudioCodingModuleTestOldApi() {} + + void TearDown() {} + + void SetUp() { + acm_.reset(AudioCodingModule::Create(id_, clock_)); + + rtp_utility_->Populate(&rtp_header_); + + input_frame_.sample_rate_hz_ = kSampleRateHz; + input_frame_.num_channels_ = 1; + input_frame_.samples_per_channel_ = kSampleRateHz * 10 / 1000; // 10 ms. + static_assert(kSampleRateHz * 10 / 1000 <= AudioFrame::kMaxDataSizeSamples, + "audio frame too small"); + memset(input_frame_.data_, + 0, + input_frame_.samples_per_channel_ * sizeof(input_frame_.data_[0])); + + ASSERT_EQ(0, acm_->RegisterTransportCallback(&packet_cb_)); + + SetUpL16Codec(); + } + + // Set up L16 codec. + virtual void SetUpL16Codec() { + ASSERT_EQ(0, AudioCodingModule::Codec("L16", &codec_, kSampleRateHz, 1)); + codec_.pltype = kPayloadType; + } + + virtual void RegisterCodec() { + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_)); + ASSERT_EQ(0, acm_->RegisterSendCodec(codec_)); + } + + virtual void InsertPacketAndPullAudio() { + InsertPacket(); + PullAudio(); + } + + virtual void InsertPacket() { + const uint8_t kPayload[kPayloadSizeBytes] = {0}; + ASSERT_EQ(0, + acm_->IncomingPacket(kPayload, kPayloadSizeBytes, rtp_header_)); + rtp_utility_->Forward(&rtp_header_); + } + + virtual void PullAudio() { + AudioFrame audio_frame; + ASSERT_EQ(0, acm_->PlayoutData10Ms(-1, &audio_frame)); + } + + virtual void InsertAudio() { + ASSERT_GE(acm_->Add10MsData(input_frame_), 0); + input_frame_.timestamp_ += kNumSamples10ms; + } + + virtual void VerifyEncoding() { + int last_length = packet_cb_.last_payload_len_bytes(); + EXPECT_TRUE(last_length == 2 * codec_.pacsize || last_length == 0) + << "Last encoded packet was " << last_length << " bytes."; + } + + virtual void InsertAudioAndVerifyEncoding() { + InsertAudio(); + VerifyEncoding(); + } + + const int id_; + rtc::scoped_ptr<RtpUtility> rtp_utility_; + rtc::scoped_ptr<AudioCodingModule> acm_; + PacketizationCallbackStubOldApi packet_cb_; + WebRtcRTPHeader rtp_header_; + AudioFrame input_frame_; + CodecInst codec_; + Clock* clock_; +}; + +// Check if the statistics are initialized correctly. Before any call to ACM +// all fields have to be zero. +#if defined(WEBRTC_ANDROID) +#define MAYBE_InitializedToZero DISABLED_InitializedToZero +#else +#define MAYBE_InitializedToZero InitializedToZero +#endif +TEST_F(AudioCodingModuleTestOldApi, MAYBE_InitializedToZero) { + RegisterCodec(); + AudioDecodingCallStats stats; + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +// Insert some packets and pull audio. Check statistics are valid. Then, +// simulate packet loss and check if PLC and PLC-to-CNG statistics are +// correctly updated. +#if defined(WEBRTC_ANDROID) +#define MAYBE_NetEqCalls DISABLED_NetEqCalls +#else +#define MAYBE_NetEqCalls NetEqCalls +#endif +TEST_F(AudioCodingModuleTestOldApi, MAYBE_NetEqCalls) { + RegisterCodec(); + AudioDecodingCallStats stats; + const int kNumNormalCalls = 10; + + for (int num_calls = 0; num_calls < kNumNormalCalls; ++num_calls) { + InsertPacketAndPullAudio(); + } + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(kNumNormalCalls, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + const int kNumPlc = 3; + const int kNumPlcCng = 5; + + // Simulate packet-loss. NetEq first performs PLC then PLC fades to CNG. + for (int n = 0; n < kNumPlc + kNumPlcCng; ++n) { + PullAudio(); + } + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(kNumNormalCalls + kNumPlc + kNumPlcCng, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(kNumPlc, stats.decoded_plc); + EXPECT_EQ(kNumPlcCng, stats.decoded_plc_cng); +} + +TEST_F(AudioCodingModuleTestOldApi, VerifyOutputFrame) { + AudioFrame audio_frame; + const int kSampleRateHz = 32000; + EXPECT_EQ(0, acm_->PlayoutData10Ms(kSampleRateHz, &audio_frame)); + EXPECT_EQ(id_, audio_frame.id_); + EXPECT_EQ(0u, audio_frame.timestamp_); + EXPECT_GT(audio_frame.num_channels_, 0u); + EXPECT_EQ(static_cast<size_t>(kSampleRateHz / 100), + audio_frame.samples_per_channel_); + EXPECT_EQ(kSampleRateHz, audio_frame.sample_rate_hz_); +} + +TEST_F(AudioCodingModuleTestOldApi, FailOnZeroDesiredFrequency) { + AudioFrame audio_frame; + EXPECT_EQ(-1, acm_->PlayoutData10Ms(0, &audio_frame)); +} + +// Checks that the transport callback is invoked once for each speech packet. +// Also checks that the frame type is kAudioFrameSpeech. +TEST_F(AudioCodingModuleTestOldApi, TransportCallbackIsInvokedForEachPacket) { + const int k10MsBlocksPerPacket = 3; + codec_.pacsize = k10MsBlocksPerPacket * kSampleRateHz / 100; + RegisterCodec(); + const int kLoops = 10; + for (int i = 0; i < kLoops; ++i) { + EXPECT_EQ(i / k10MsBlocksPerPacket, packet_cb_.num_calls()); + if (packet_cb_.num_calls() > 0) + EXPECT_EQ(kAudioFrameSpeech, packet_cb_.last_frame_type()); + InsertAudioAndVerifyEncoding(); + } + EXPECT_EQ(kLoops / k10MsBlocksPerPacket, packet_cb_.num_calls()); + EXPECT_EQ(kAudioFrameSpeech, packet_cb_.last_frame_type()); +} + +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) +// Verifies that the RTP timestamp series is not reset when the codec is +// changed. +TEST_F(AudioCodingModuleTestOldApi, TimestampSeriesContinuesWhenCodecChanges) { + RegisterCodec(); // This registers the default codec. + uint32_t expected_ts = input_frame_.timestamp_; + int blocks_per_packet = codec_.pacsize / (kSampleRateHz / 100); + // Encode 5 packets of the first codec type. + const int kNumPackets1 = 5; + for (int j = 0; j < kNumPackets1; ++j) { + for (int i = 0; i < blocks_per_packet; ++i) { + EXPECT_EQ(j, packet_cb_.num_calls()); + InsertAudio(); + } + EXPECT_EQ(j + 1, packet_cb_.num_calls()); + EXPECT_EQ(expected_ts, packet_cb_.last_timestamp()); + expected_ts += codec_.pacsize; + } + + // Change codec. + ASSERT_EQ(0, AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1)); + RegisterCodec(); + blocks_per_packet = codec_.pacsize / (kSampleRateHz / 100); + // Encode another 5 packets. + const int kNumPackets2 = 5; + for (int j = 0; j < kNumPackets2; ++j) { + for (int i = 0; i < blocks_per_packet; ++i) { + EXPECT_EQ(kNumPackets1 + j, packet_cb_.num_calls()); + InsertAudio(); + } + EXPECT_EQ(kNumPackets1 + j + 1, packet_cb_.num_calls()); + EXPECT_EQ(expected_ts, packet_cb_.last_timestamp()); + expected_ts += codec_.pacsize; + } +} +#endif + +// Introduce this class to set different expectations on the number of encoded +// bytes. This class expects all encoded packets to be 9 bytes (matching one +// CNG SID frame) or 0 bytes. This test depends on |input_frame_| containing +// (near-)zero values. It also introduces a way to register comfort noise with +// a custom payload type. +class AudioCodingModuleTestWithComfortNoiseOldApi + : public AudioCodingModuleTestOldApi { + protected: + void RegisterCngCodec(int rtp_payload_type) { + CodecInst codec; + AudioCodingModule::Codec("CN", &codec, kSampleRateHz, 1); + codec.pltype = rtp_payload_type; + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec)); + ASSERT_EQ(0, acm_->RegisterSendCodec(codec)); + } + + void VerifyEncoding() override { + int last_length = packet_cb_.last_payload_len_bytes(); + EXPECT_TRUE(last_length == 9 || last_length == 0) + << "Last encoded packet was " << last_length << " bytes."; + } + + void DoTest(int blocks_per_packet, int cng_pt) { + const int kLoops = 40; + // This array defines the expected frame types, and when they should arrive. + // We expect a frame to arrive each time the speech encoder would have + // produced a packet, and once every 100 ms the frame should be non-empty, + // that is contain comfort noise. + const struct { + int ix; + FrameType type; + } expectation[] = {{2, kAudioFrameCN}, + {5, kEmptyFrame}, + {8, kEmptyFrame}, + {11, kAudioFrameCN}, + {14, kEmptyFrame}, + {17, kEmptyFrame}, + {20, kAudioFrameCN}, + {23, kEmptyFrame}, + {26, kEmptyFrame}, + {29, kEmptyFrame}, + {32, kAudioFrameCN}, + {35, kEmptyFrame}, + {38, kEmptyFrame}}; + for (int i = 0; i < kLoops; ++i) { + int num_calls_before = packet_cb_.num_calls(); + EXPECT_EQ(i / blocks_per_packet, num_calls_before); + InsertAudioAndVerifyEncoding(); + int num_calls = packet_cb_.num_calls(); + if (num_calls == num_calls_before + 1) { + EXPECT_EQ(expectation[num_calls - 1].ix, i); + EXPECT_EQ(expectation[num_calls - 1].type, packet_cb_.last_frame_type()) + << "Wrong frame type for lap " << i; + EXPECT_EQ(cng_pt, packet_cb_.last_payload_type()); + } else { + EXPECT_EQ(num_calls, num_calls_before); + } + } + } +}; + +// Checks that the transport callback is invoked once per frame period of the +// underlying speech encoder, even when comfort noise is produced. +// Also checks that the frame type is kAudioFrameCN or kEmptyFrame. +// This test and the next check the same thing, but differ in the order of +// speech codec and CNG registration. +TEST_F(AudioCodingModuleTestWithComfortNoiseOldApi, + TransportCallbackTestForComfortNoiseRegisterCngLast) { + const int k10MsBlocksPerPacket = 3; + codec_.pacsize = k10MsBlocksPerPacket * kSampleRateHz / 100; + RegisterCodec(); + const int kCngPayloadType = 105; + RegisterCngCodec(kCngPayloadType); + ASSERT_EQ(0, acm_->SetVAD(true, true)); + DoTest(k10MsBlocksPerPacket, kCngPayloadType); +} + +TEST_F(AudioCodingModuleTestWithComfortNoiseOldApi, + TransportCallbackTestForComfortNoiseRegisterCngFirst) { + const int k10MsBlocksPerPacket = 3; + codec_.pacsize = k10MsBlocksPerPacket * kSampleRateHz / 100; + const int kCngPayloadType = 105; + RegisterCngCodec(kCngPayloadType); + RegisterCodec(); + ASSERT_EQ(0, acm_->SetVAD(true, true)); + DoTest(k10MsBlocksPerPacket, kCngPayloadType); +} + +// A multi-threaded test for ACM. This base class is using the PCM16b 16 kHz +// codec, while the derive class AcmIsacMtTest is using iSAC. +class AudioCodingModuleMtTestOldApi : public AudioCodingModuleTestOldApi { + protected: + static const int kNumPackets = 500; + static const int kNumPullCalls = 500; + + AudioCodingModuleMtTestOldApi() + : AudioCodingModuleTestOldApi(), + send_thread_(CbSendThread, this, "send"), + insert_packet_thread_(CbInsertPacketThread, this, "insert_packet"), + pull_audio_thread_(CbPullAudioThread, this, "pull_audio"), + test_complete_(EventWrapper::Create()), + send_count_(0), + insert_packet_count_(0), + pull_audio_count_(0), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + next_insert_packet_time_ms_(0), + fake_clock_(new SimulatedClock(0)) { + clock_ = fake_clock_.get(); + } + + void SetUp() { + AudioCodingModuleTestOldApi::SetUp(); + RegisterCodec(); // Must be called before the threads start below. + StartThreads(); + } + + void StartThreads() { + send_thread_.Start(); + send_thread_.SetPriority(rtc::kRealtimePriority); + insert_packet_thread_.Start(); + insert_packet_thread_.SetPriority(rtc::kRealtimePriority); + pull_audio_thread_.Start(); + pull_audio_thread_.SetPriority(rtc::kRealtimePriority); + } + + void TearDown() { + AudioCodingModuleTestOldApi::TearDown(); + pull_audio_thread_.Stop(); + send_thread_.Stop(); + insert_packet_thread_.Stop(); + } + + EventTypeWrapper RunTest() { + return test_complete_->Wait(10 * 60 * 1000); // 10 minutes' timeout. + } + + virtual bool TestDone() { + if (packet_cb_.num_calls() > kNumPackets) { + CriticalSectionScoped lock(crit_sect_.get()); + if (pull_audio_count_ > kNumPullCalls) { + // Both conditions for completion are met. End the test. + return true; + } + } + return false; + } + + static bool CbSendThread(void* context) { + return reinterpret_cast<AudioCodingModuleMtTestOldApi*>(context) + ->CbSendImpl(); + } + + // The send thread doesn't have to care about the current simulated time, + // since only the AcmReceiver is using the clock. + bool CbSendImpl() { + SleepMs(1); + if (HasFatalFailure()) { + // End the test early if a fatal failure (ASSERT_*) has occurred. + test_complete_->Set(); + } + ++send_count_; + InsertAudioAndVerifyEncoding(); + if (TestDone()) { + test_complete_->Set(); + } + return true; + } + + static bool CbInsertPacketThread(void* context) { + return reinterpret_cast<AudioCodingModuleMtTestOldApi*>(context) + ->CbInsertPacketImpl(); + } + + bool CbInsertPacketImpl() { + SleepMs(1); + { + CriticalSectionScoped lock(crit_sect_.get()); + if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) { + return true; + } + next_insert_packet_time_ms_ += 10; + } + // Now we're not holding the crit sect when calling ACM. + ++insert_packet_count_; + InsertPacket(); + return true; + } + + static bool CbPullAudioThread(void* context) { + return reinterpret_cast<AudioCodingModuleMtTestOldApi*>(context) + ->CbPullAudioImpl(); + } + + bool CbPullAudioImpl() { + SleepMs(1); + { + CriticalSectionScoped lock(crit_sect_.get()); + // Don't let the insert thread fall behind. + if (next_insert_packet_time_ms_ < clock_->TimeInMilliseconds()) { + return true; + } + ++pull_audio_count_; + } + // Now we're not holding the crit sect when calling ACM. + PullAudio(); + fake_clock_->AdvanceTimeMilliseconds(10); + return true; + } + + rtc::PlatformThread send_thread_; + rtc::PlatformThread insert_packet_thread_; + rtc::PlatformThread pull_audio_thread_; + const rtc::scoped_ptr<EventWrapper> test_complete_; + int send_count_; + int insert_packet_count_; + int pull_audio_count_ GUARDED_BY(crit_sect_); + const rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; + int64_t next_insert_packet_time_ms_ GUARDED_BY(crit_sect_); + rtc::scoped_ptr<SimulatedClock> fake_clock_; +}; + +#if defined(WEBRTC_IOS) +#define MAYBE_DoTest DISABLED_DoTest +#else +#define MAYBE_DoTest DoTest +#endif +TEST_F(AudioCodingModuleMtTestOldApi, MAYBE_DoTest) { + EXPECT_EQ(kEventSignaled, RunTest()); +} + +// This is a multi-threaded ACM test using iSAC. The test encodes audio +// from a PCM file. The most recent encoded frame is used as input to the +// receiving part. Depending on timing, it may happen that the same RTP packet +// is inserted into the receiver multiple times, but this is a valid use-case, +// and simplifies the test code a lot. +class AcmIsacMtTestOldApi : public AudioCodingModuleMtTestOldApi { + protected: + static const int kNumPackets = 500; + static const int kNumPullCalls = 500; + + AcmIsacMtTestOldApi() + : AudioCodingModuleMtTestOldApi(), last_packet_number_(0) {} + + ~AcmIsacMtTestOldApi() {} + + void SetUp() { + AudioCodingModuleTestOldApi::SetUp(); + RegisterCodec(); // Must be called before the threads start below. + + // Set up input audio source to read from specified file, loop after 5 + // seconds, and deliver blocks of 10 ms. + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/speech_mono_16kHz", "pcm"); + audio_loop_.Init(input_file_name, 5 * kSampleRateHz, kNumSamples10ms); + + // Generate one packet to have something to insert. + int loop_counter = 0; + while (packet_cb_.last_payload_len_bytes() == 0) { + InsertAudio(); + ASSERT_LT(loop_counter++, 10); + } + // Set |last_packet_number_| to one less that |num_calls| so that the packet + // will be fetched in the next InsertPacket() call. + last_packet_number_ = packet_cb_.num_calls() - 1; + + StartThreads(); + } + + void RegisterCodec() override { + static_assert(kSampleRateHz == 16000, "test designed for iSAC 16 kHz"); + AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1); + codec_.pltype = kPayloadType; + + // Register iSAC codec in ACM, effectively unregistering the PCM16B codec + // registered in AudioCodingModuleTestOldApi::SetUp(); + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_)); + ASSERT_EQ(0, acm_->RegisterSendCodec(codec_)); + } + + void InsertPacket() { + int num_calls = packet_cb_.num_calls(); // Store locally for thread safety. + if (num_calls > last_packet_number_) { + // Get the new payload out from the callback handler. + // Note that since we swap buffers here instead of directly inserting + // a pointer to the data in |packet_cb_|, we avoid locking the callback + // for the duration of the IncomingPacket() call. + packet_cb_.SwapBuffers(&last_payload_vec_); + ASSERT_GT(last_payload_vec_.size(), 0u); + rtp_utility_->Forward(&rtp_header_); + last_packet_number_ = num_calls; + } + ASSERT_GT(last_payload_vec_.size(), 0u); + ASSERT_EQ( + 0, + acm_->IncomingPacket( + &last_payload_vec_[0], last_payload_vec_.size(), rtp_header_)); + } + + void InsertAudio() { + // TODO(kwiberg): Use std::copy here. Might be complications because AFAICS + // this call confuses the number of samples with the number of bytes, and + // ends up copying only half of what it should. + memcpy(input_frame_.data_, audio_loop_.GetNextBlock().data(), + kNumSamples10ms); + AudioCodingModuleTestOldApi::InsertAudio(); + } + + // Override the verification function with no-op, since iSAC produces variable + // payload sizes. + void VerifyEncoding() override {} + + // This method is the same as AudioCodingModuleMtTestOldApi::TestDone(), but + // here it is using the constants defined in this class (i.e., shorter test + // run). + virtual bool TestDone() { + if (packet_cb_.num_calls() > kNumPackets) { + CriticalSectionScoped lock(crit_sect_.get()); + if (pull_audio_count_ > kNumPullCalls) { + // Both conditions for completion are met. End the test. + return true; + } + } + return false; + } + + int last_packet_number_; + std::vector<uint8_t> last_payload_vec_; + test::AudioLoop audio_loop_; +}; + +#if defined(WEBRTC_IOS) +#define MAYBE_DoTest DISABLED_DoTest +#else +#define MAYBE_DoTest DoTest +#endif +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) +TEST_F(AcmIsacMtTestOldApi, MAYBE_DoTest) { + EXPECT_EQ(kEventSignaled, RunTest()); +} +#endif + +class AcmReRegisterIsacMtTestOldApi : public AudioCodingModuleTestOldApi { + protected: + static const int kRegisterAfterNumPackets = 5; + static const int kNumPackets = 10; + static const int kPacketSizeMs = 30; + static const int kPacketSizeSamples = kPacketSizeMs * 16; + + AcmReRegisterIsacMtTestOldApi() + : AudioCodingModuleTestOldApi(), + receive_thread_(CbReceiveThread, this, "receive"), + codec_registration_thread_(CbCodecRegistrationThread, + this, + "codec_registration"), + test_complete_(EventWrapper::Create()), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + codec_registered_(false), + receive_packet_count_(0), + next_insert_packet_time_ms_(0), + fake_clock_(new SimulatedClock(0)) { + AudioEncoderIsac::Config config; + config.payload_type = kPayloadType; + isac_encoder_.reset(new AudioEncoderIsac(config)); + clock_ = fake_clock_.get(); + } + + void SetUp() { + AudioCodingModuleTestOldApi::SetUp(); + // Set up input audio source to read from specified file, loop after 5 + // seconds, and deliver blocks of 10 ms. + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/speech_mono_16kHz", "pcm"); + audio_loop_.Init(input_file_name, 5 * kSampleRateHz, kNumSamples10ms); + RegisterCodec(); // Must be called before the threads start below. + StartThreads(); + } + + void RegisterCodec() override { + static_assert(kSampleRateHz == 16000, "test designed for iSAC 16 kHz"); + AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1); + codec_.pltype = kPayloadType; + + // Register iSAC codec in ACM, effectively unregistering the PCM16B codec + // registered in AudioCodingModuleTestOldApi::SetUp(); + // Only register the decoder for now. The encoder is registered later. + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_)); + } + + void StartThreads() { + receive_thread_.Start(); + receive_thread_.SetPriority(rtc::kRealtimePriority); + codec_registration_thread_.Start(); + codec_registration_thread_.SetPriority(rtc::kRealtimePriority); + } + + void TearDown() { + AudioCodingModuleTestOldApi::TearDown(); + receive_thread_.Stop(); + codec_registration_thread_.Stop(); + } + + EventTypeWrapper RunTest() { + return test_complete_->Wait(10 * 60 * 1000); // 10 minutes' timeout. + } + + static bool CbReceiveThread(void* context) { + return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context) + ->CbReceiveImpl(); + } + + bool CbReceiveImpl() { + SleepMs(1); + const size_t max_encoded_bytes = isac_encoder_->MaxEncodedBytes(); + rtc::scoped_ptr<uint8_t[]> encoded(new uint8_t[max_encoded_bytes]); + AudioEncoder::EncodedInfo info; + { + CriticalSectionScoped lock(crit_sect_.get()); + if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) { + return true; + } + next_insert_packet_time_ms_ += kPacketSizeMs; + ++receive_packet_count_; + + // Encode new frame. + uint32_t input_timestamp = rtp_header_.header.timestamp; + while (info.encoded_bytes == 0) { + info = + isac_encoder_->Encode(input_timestamp, audio_loop_.GetNextBlock(), + max_encoded_bytes, encoded.get()); + input_timestamp += 160; // 10 ms at 16 kHz. + } + EXPECT_EQ(rtp_header_.header.timestamp + kPacketSizeSamples, + input_timestamp); + EXPECT_EQ(rtp_header_.header.timestamp, info.encoded_timestamp); + EXPECT_EQ(rtp_header_.header.payloadType, info.payload_type); + } + // Now we're not holding the crit sect when calling ACM. + + // Insert into ACM. + EXPECT_EQ(0, acm_->IncomingPacket(encoded.get(), info.encoded_bytes, + rtp_header_)); + + // Pull audio. + for (int i = 0; i < rtc::CheckedDivExact(kPacketSizeMs, 10); ++i) { + AudioFrame audio_frame; + EXPECT_EQ(0, acm_->PlayoutData10Ms(-1 /* default output frequency */, + &audio_frame)); + fake_clock_->AdvanceTimeMilliseconds(10); + } + rtp_utility_->Forward(&rtp_header_); + return true; + } + + static bool CbCodecRegistrationThread(void* context) { + return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context) + ->CbCodecRegistrationImpl(); + } + + bool CbCodecRegistrationImpl() { + SleepMs(1); + if (HasFatalFailure()) { + // End the test early if a fatal failure (ASSERT_*) has occurred. + test_complete_->Set(); + } + CriticalSectionScoped lock(crit_sect_.get()); + if (!codec_registered_ && + receive_packet_count_ > kRegisterAfterNumPackets) { + // Register the iSAC encoder. + EXPECT_EQ(0, acm_->RegisterSendCodec(codec_)); + codec_registered_ = true; + } + if (codec_registered_ && receive_packet_count_ > kNumPackets) { + test_complete_->Set(); + } + return true; + } + + rtc::PlatformThread receive_thread_; + rtc::PlatformThread codec_registration_thread_; + const rtc::scoped_ptr<EventWrapper> test_complete_; + const rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; + bool codec_registered_ GUARDED_BY(crit_sect_); + int receive_packet_count_ GUARDED_BY(crit_sect_); + int64_t next_insert_packet_time_ms_ GUARDED_BY(crit_sect_); + rtc::scoped_ptr<AudioEncoderIsac> isac_encoder_; + rtc::scoped_ptr<SimulatedClock> fake_clock_; + test::AudioLoop audio_loop_; +}; + +#if defined(WEBRTC_IOS) +#define MAYBE_DoTest DISABLED_DoTest +#else +#define MAYBE_DoTest DoTest +#endif +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) +TEST_F(AcmReRegisterIsacMtTestOldApi, MAYBE_DoTest) { + EXPECT_EQ(kEventSignaled, RunTest()); +} +#endif + +// Disabling all of these tests on iOS until file support has been added. +// See https://code.google.com/p/webrtc/issues/detail?id=4752 for details. +#if !defined(WEBRTC_IOS) + +class AcmReceiverBitExactnessOldApi : public ::testing::Test { + public: + static std::string PlatformChecksum(std::string others, + std::string win64, + std::string android_arm32, + std::string android_arm64) { +#if defined(_WIN32) && defined(WEBRTC_ARCH_64_BITS) + return win64; +#elif defined(WEBRTC_ANDROID) && defined(WEBRTC_ARCH_ARM) + return android_arm32; +#elif defined(WEBRTC_ANDROID) && defined(WEBRTC_ARCH_ARM64) + return android_arm64; +#else + return others; +#endif + } + + protected: + struct ExternalDecoder { + int rtp_payload_type; + AudioDecoder* external_decoder; + int sample_rate_hz; + int num_channels; + std::string name; + }; + + void Run(int output_freq_hz, + const std::string& checksum_ref, + const std::vector<ExternalDecoder>& external_decoders) { + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/neteq_universal_new", "rtp"); + rtc::scoped_ptr<test::RtpFileSource> packet_source( + test::RtpFileSource::Create(input_file_name)); +#ifdef WEBRTC_ANDROID + // Filter out iLBC and iSAC-swb since they are not supported on Android. + packet_source->FilterOutPayloadType(102); // iLBC. + packet_source->FilterOutPayloadType(104); // iSAC-swb. +#endif + + test::AudioChecksum checksum; + const std::string output_file_name = + webrtc::test::OutputPath() + + ::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name() + + "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name() + + "_output.pcm"; + test::OutputAudioFile output_file(output_file_name); + test::AudioSinkFork output(&checksum, &output_file); + + test::AcmReceiveTestOldApi test( + packet_source.get(), + &output, + output_freq_hz, + test::AcmReceiveTestOldApi::kArbitraryChannels); + ASSERT_NO_FATAL_FAILURE(test.RegisterNetEqTestCodecs()); + for (const auto& ed : external_decoders) { + ASSERT_EQ(0, test.RegisterExternalReceiveCodec( + ed.rtp_payload_type, ed.external_decoder, + ed.sample_rate_hz, ed.num_channels, ed.name)); + } + test.Run(); + + std::string checksum_string = checksum.Finish(); + EXPECT_EQ(checksum_ref, checksum_string); + + // Delete the output file. + remove(output_file_name.c_str()); + } +}; + +#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) && \ + defined(WEBRTC_CODEC_ILBC) && defined(WEBRTC_CODEC_G722) +TEST_F(AcmReceiverBitExactnessOldApi, 8kHzOutput) { + Run(8000, PlatformChecksum("908002dc01fc4eb1d2be24eb1d3f354b", + "dcee98c623b147ebe1b40dd30efa896e", + "adc92e173f908f93b96ba5844209815a", + "ba16137d3a5a1e637252289c57522bfe"), + std::vector<ExternalDecoder>()); +} + +TEST_F(AcmReceiverBitExactnessOldApi, 16kHzOutput) { + Run(16000, PlatformChecksum("a909560b5ca49fa472b17b7b277195e9", + "f790e7a8cce4e2c8b7bb5e0e4c5dac0d", + "8cffa6abcb3e18e33b9d857666dff66a", + "66ee001e23534d4dcf5d0f81f916c93b"), + std::vector<ExternalDecoder>()); +} + +TEST_F(AcmReceiverBitExactnessOldApi, 32kHzOutput) { + Run(32000, PlatformChecksum("441aab4b347fb3db4e9244337aca8d8e", + "306e0d990ee6e92de3fbecc0123ece37", + "3e126fe894720c3f85edadcc91964ba5", + "9c6ff204b14152c48fe41d5ab757943b"), + std::vector<ExternalDecoder>()); +} + +TEST_F(AcmReceiverBitExactnessOldApi, 48kHzOutput) { + Run(48000, PlatformChecksum("4ee2730fa1daae755e8a8fd3abd779ec", + "aa7c232f63a67b2a72703593bdd172e0", + "0155665e93067c4e89256b944dd11999", + "fc4f0da8844cd808d822bbddf3b9c285"), + std::vector<ExternalDecoder>()); +} + +TEST_F(AcmReceiverBitExactnessOldApi, 48kHzOutputExternalDecoder) { + // Class intended to forward a call from a mock DecodeInternal to Decode on + // the real decoder's Decode. DecodeInternal for the real decoder isn't + // public. + class DecodeForwarder { + public: + DecodeForwarder(AudioDecoder* decoder) : decoder_(decoder) {} + int Decode(const uint8_t* encoded, + size_t encoded_len, + int sample_rate_hz, + int16_t* decoded, + AudioDecoder::SpeechType* speech_type) { + return decoder_->Decode(encoded, encoded_len, sample_rate_hz, + decoder_->PacketDuration(encoded, encoded_len) * + decoder_->Channels() * sizeof(int16_t), + decoded, speech_type); + } + + private: + AudioDecoder* const decoder_; + }; + + AudioDecoderPcmU decoder(1); + DecodeForwarder decode_forwarder(&decoder); + MockAudioDecoder mock_decoder; + // Set expectations on the mock decoder and also delegate the calls to the + // real decoder. + EXPECT_CALL(mock_decoder, IncomingPacket(_, _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&decoder, &AudioDecoderPcmU::IncomingPacket)); + EXPECT_CALL(mock_decoder, Channels()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&decoder, &AudioDecoderPcmU::Channels)); + EXPECT_CALL(mock_decoder, DecodeInternal(_, _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&decode_forwarder, &DecodeForwarder::Decode)); + EXPECT_CALL(mock_decoder, HasDecodePlc()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&decoder, &AudioDecoderPcmU::HasDecodePlc)); + EXPECT_CALL(mock_decoder, PacketDuration(_, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&decoder, &AudioDecoderPcmU::PacketDuration)); + ExternalDecoder ed; + ed.rtp_payload_type = 0; + ed.external_decoder = &mock_decoder; + ed.sample_rate_hz = 8000; + ed.num_channels = 1; + ed.name = "MockPCMU"; + std::vector<ExternalDecoder> external_decoders; + external_decoders.push_back(ed); + + Run(48000, PlatformChecksum("4ee2730fa1daae755e8a8fd3abd779ec", + "aa7c232f63a67b2a72703593bdd172e0", + "0155665e93067c4e89256b944dd11999", + "fc4f0da8844cd808d822bbddf3b9c285"), + external_decoders); + + EXPECT_CALL(mock_decoder, Die()); +} +#endif + +// This test verifies bit exactness for the send-side of ACM. The test setup is +// a chain of three different test classes: +// +// test::AcmSendTest -> AcmSenderBitExactness -> test::AcmReceiveTest +// +// The receiver side is driving the test by requesting new packets from +// AcmSenderBitExactness::NextPacket(). This method, in turn, asks for the +// packet from test::AcmSendTest::NextPacket, which inserts audio from the +// input file until one packet is produced. (The input file loops indefinitely.) +// Before passing the packet to the receiver, this test class verifies the +// packet header and updates a payload checksum with the new payload. The +// decoded output from the receiver is also verified with a (separate) checksum. +class AcmSenderBitExactnessOldApi : public ::testing::Test, + public test::PacketSource { + protected: + static const int kTestDurationMs = 1000; + + AcmSenderBitExactnessOldApi() + : frame_size_rtp_timestamps_(0), + packet_count_(0), + payload_type_(0), + last_sequence_number_(0), + last_timestamp_(0) {} + + // Sets up the test::AcmSendTest object. Returns true on success, otherwise + // false. + bool SetUpSender() { + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); + // Note that |audio_source_| will loop forever. The test duration is set + // explicitly by |kTestDurationMs|. + audio_source_.reset(new test::InputAudioFile(input_file_name)); + static const int kSourceRateHz = 32000; + send_test_.reset(new test::AcmSendTestOldApi( + audio_source_.get(), kSourceRateHz, kTestDurationMs)); + return send_test_.get() != NULL; + } + + // Registers a send codec in the test::AcmSendTest object. Returns true on + // success, false on failure. + bool RegisterSendCodec(const char* payload_name, + int sampling_freq_hz, + int channels, + int payload_type, + int frame_size_samples, + int frame_size_rtp_timestamps) { + payload_type_ = payload_type; + frame_size_rtp_timestamps_ = frame_size_rtp_timestamps; + return send_test_->RegisterCodec(payload_name, + sampling_freq_hz, + channels, + payload_type, + frame_size_samples); + } + + bool RegisterExternalSendCodec(AudioEncoder* external_speech_encoder, + int payload_type) { + payload_type_ = payload_type; + frame_size_rtp_timestamps_ = + external_speech_encoder->Num10MsFramesInNextPacket() * + external_speech_encoder->RtpTimestampRateHz() / 100; + return send_test_->RegisterExternalCodec(external_speech_encoder); + } + + // Runs the test. SetUpSender() and RegisterSendCodec() must have been called + // before calling this method. + void Run(const std::string& audio_checksum_ref, + const std::string& payload_checksum_ref, + int expected_packets, + test::AcmReceiveTestOldApi::NumOutputChannels expected_channels) { + // Set up the receiver used to decode the packets and verify the decoded + // output. + test::AudioChecksum audio_checksum; + const std::string output_file_name = + webrtc::test::OutputPath() + + ::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name() + + "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name() + + "_output.pcm"; + test::OutputAudioFile output_file(output_file_name); + // Have the output audio sent both to file and to the checksum calculator. + test::AudioSinkFork output(&audio_checksum, &output_file); + const int kOutputFreqHz = 8000; + test::AcmReceiveTestOldApi receive_test( + this, &output, kOutputFreqHz, expected_channels); + ASSERT_NO_FATAL_FAILURE(receive_test.RegisterDefaultCodecs()); + + // This is where the actual test is executed. + receive_test.Run(); + + // Extract and verify the audio checksum. + std::string checksum_string = audio_checksum.Finish(); + EXPECT_EQ(audio_checksum_ref, checksum_string); + + // Extract and verify the payload checksum. + char checksum_result[rtc::Md5Digest::kSize]; + payload_checksum_.Finish(checksum_result, rtc::Md5Digest::kSize); + checksum_string = rtc::hex_encode(checksum_result, rtc::Md5Digest::kSize); + EXPECT_EQ(payload_checksum_ref, checksum_string); + + // Verify number of packets produced. + EXPECT_EQ(expected_packets, packet_count_); + + // Delete the output file. + remove(output_file_name.c_str()); + } + + // Returns a pointer to the next packet. Returns NULL if the source is + // depleted (i.e., the test duration is exceeded), or if an error occurred. + // Inherited from test::PacketSource. + test::Packet* NextPacket() override { + // Get the next packet from AcmSendTest. Ownership of |packet| is + // transferred to this method. + test::Packet* packet = send_test_->NextPacket(); + if (!packet) + return NULL; + + VerifyPacket(packet); + // TODO(henrik.lundin) Save the packet to file as well. + + // Pass it on to the caller. The caller becomes the owner of |packet|. + return packet; + } + + // Verifies the packet. + void VerifyPacket(const test::Packet* packet) { + EXPECT_TRUE(packet->valid_header()); + // (We can check the header fields even if valid_header() is false.) + EXPECT_EQ(payload_type_, packet->header().payloadType); + if (packet_count_ > 0) { + // This is not the first packet. + uint16_t sequence_number_diff = + packet->header().sequenceNumber - last_sequence_number_; + EXPECT_EQ(1, sequence_number_diff); + uint32_t timestamp_diff = packet->header().timestamp - last_timestamp_; + EXPECT_EQ(frame_size_rtp_timestamps_, timestamp_diff); + } + ++packet_count_; + last_sequence_number_ = packet->header().sequenceNumber; + last_timestamp_ = packet->header().timestamp; + // Update the checksum. + payload_checksum_.Update(packet->payload(), packet->payload_length_bytes()); + } + + void SetUpTest(const char* codec_name, + int codec_sample_rate_hz, + int channels, + int payload_type, + int codec_frame_size_samples, + int codec_frame_size_rtp_timestamps) { + ASSERT_TRUE(SetUpSender()); + ASSERT_TRUE(RegisterSendCodec(codec_name, + codec_sample_rate_hz, + channels, + payload_type, + codec_frame_size_samples, + codec_frame_size_rtp_timestamps)); + } + + void SetUpTestExternalEncoder(AudioEncoder* external_speech_encoder, + int payload_type) { + ASSERT_TRUE(SetUpSender()); + ASSERT_TRUE( + RegisterExternalSendCodec(external_speech_encoder, payload_type)); + } + + rtc::scoped_ptr<test::AcmSendTestOldApi> send_test_; + rtc::scoped_ptr<test::InputAudioFile> audio_source_; + uint32_t frame_size_rtp_timestamps_; + int packet_count_; + uint8_t payload_type_; + uint16_t last_sequence_number_; + uint32_t last_timestamp_; + rtc::Md5Digest payload_checksum_; +}; + +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) +TEST_F(AcmSenderBitExactnessOldApi, IsacWb30ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("ISAC", 16000, 1, 103, 480, 480)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "0b58f9eeee43d5891f5f6c75e77984a3", + "c7e5bdadfa2871df95639fcc297cf23d", + "0499ca260390769b3172136faad925b9", + "866abf524acd2807efbe65e133c23f95"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "3c79f16f34218271f3dca4e2b1dfe1bb", + "d42cb5195463da26c8129bbfe73a22e6", + "83de248aea9c3c2bd680b6952401b4ca", + "3c79f16f34218271f3dca4e2b1dfe1bb"), + 33, test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, IsacWb60ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("ISAC", 16000, 1, 103, 960, 960)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "1ad29139a04782a33daad8c2b9b35875", + "14d63c5f08127d280e722e3191b73bdd", + "8da003e16c5371af2dc2be79a50f9076", + "ef75e900e6f375e3061163c53fd09a63"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "9e0a0ab743ad987b55b8e14802769c56", + "ebe04a819d3a9d83a83a17f271e1139a", + "97aeef98553b5a4b5a68f8b716e8eaf0", + "9e0a0ab743ad987b55b8e14802769c56"), + 16, test::AcmReceiveTestOldApi::kMonoOutput); +} +#endif + +#if defined(WEBRTC_ANDROID) +#define MAYBE_IsacSwb30ms DISABLED_IsacSwb30ms +#else +#define MAYBE_IsacSwb30ms IsacSwb30ms +#endif +#if defined(WEBRTC_CODEC_ISAC) +TEST_F(AcmSenderBitExactnessOldApi, MAYBE_IsacSwb30ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("ISAC", 32000, 1, 104, 960, 960)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "5683b58da0fbf2063c7adc2e6bfb3fb8", + "2b3c387d06f00b7b7aad4c9be56fb83d", "android_arm32_audio", + "android_arm64_audio"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "ce86106a93419aefb063097108ec94ab", + "bcc2041e7744c7ebd9f701866856849c", "android_arm32_payload", + "android_arm64_payload"), + 33, test::AcmReceiveTestOldApi::kMonoOutput); +} +#endif + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_8000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 1, 107, 80, 80)); + Run("de4a98e1406f8b798d99cd0704e862e2", + "c1edd36339ce0326cc4550041ad719a0", + 100, + test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_16000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 16000, 1, 108, 160, 160)); + Run("ae646d7b68384a1269cc080dd4501916", + "ad786526383178b08d80d6eee06e9bad", + 100, + test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_32000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 32000, 1, 109, 320, 320)); + Run("7fe325e8fbaf755e3c5df0b11a4774fb", + "5ef82ea885e922263606c6fdbc49f651", + 100, + test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_stereo_8000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 2, 111, 80, 80)); + Run("fb263b74e7ac3de915474d77e4744ceb", + "62ce5adb0d4965d0a52ec98ae7f98974", + 100, + test::AcmReceiveTestOldApi::kStereoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_stereo_16000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 16000, 2, 112, 160, 160)); + Run("d09e9239553649d7ac93e19d304281fd", + "41ca8edac4b8c71cd54fd9f25ec14870", + 100, + test::AcmReceiveTestOldApi::kStereoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcm16_stereo_32000khz_10ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 32000, 2, 113, 320, 320)); + Run("5f025d4f390982cc26b3d92fe02e3044", + "50e58502fb04421bf5b857dda4c96879", + 100, + test::AcmReceiveTestOldApi::kStereoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcmu_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("PCMU", 8000, 1, 0, 160, 160)); + Run("81a9d4c0bb72e9becc43aef124c981e9", + "8f9b8750bd80fe26b6cbf6659b89f0f9", + 50, + test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcma_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("PCMA", 8000, 1, 8, 160, 160)); + Run("39611f798969053925a49dc06d08de29", + "6ad745e55aa48981bfc790d0eeef2dd1", + 50, + test::AcmReceiveTestOldApi::kMonoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcmu_stereo_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("PCMU", 8000, 2, 110, 160, 160)); + Run("437bec032fdc5cbaa0d5175430af7b18", + "60b6f25e8d1e74cb679cfe756dd9bca5", + 50, + test::AcmReceiveTestOldApi::kStereoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Pcma_stereo_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("PCMA", 8000, 2, 118, 160, 160)); + Run("a5c6d83c5b7cedbeff734238220a4b0c", + "92b282c83efd20e7eeef52ba40842cf7", + 50, + test::AcmReceiveTestOldApi::kStereoOutput); +} + +#if defined(WEBRTC_ANDROID) +#define MAYBE_Ilbc_30ms DISABLED_Ilbc_30ms +#else +#define MAYBE_Ilbc_30ms Ilbc_30ms +#endif +#if defined(WEBRTC_CODEC_ILBC) +TEST_F(AcmSenderBitExactnessOldApi, MAYBE_Ilbc_30ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("ILBC", 8000, 1, 102, 240, 240)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "7b6ec10910debd9af08011d3ed5249f7", + "7b6ec10910debd9af08011d3ed5249f7", "android_arm32_audio", + "android_arm64_audio"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "cfae2e9f6aba96e145f2bcdd5050ce78", + "cfae2e9f6aba96e145f2bcdd5050ce78", "android_arm32_payload", + "android_arm64_payload"), + 33, test::AcmReceiveTestOldApi::kMonoOutput); +} +#endif + +#if defined(WEBRTC_ANDROID) +#define MAYBE_G722_20ms DISABLED_G722_20ms +#else +#define MAYBE_G722_20ms G722_20ms +#endif +#if defined(WEBRTC_CODEC_G722) +TEST_F(AcmSenderBitExactnessOldApi, MAYBE_G722_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("G722", 16000, 1, 9, 320, 160)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "7d759436f2533582950d148b5161a36c", + "7d759436f2533582950d148b5161a36c", "android_arm32_audio", + "android_arm64_audio"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "fc68a87e1380614e658087cb35d5ca10", + "fc68a87e1380614e658087cb35d5ca10", "android_arm32_payload", + "android_arm64_payload"), + 50, test::AcmReceiveTestOldApi::kMonoOutput); +} +#endif + +#if defined(WEBRTC_ANDROID) +#define MAYBE_G722_stereo_20ms DISABLED_G722_stereo_20ms +#else +#define MAYBE_G722_stereo_20ms G722_stereo_20ms +#endif +#if defined(WEBRTC_CODEC_G722) +TEST_F(AcmSenderBitExactnessOldApi, MAYBE_G722_stereo_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("G722", 16000, 2, 119, 320, 160)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "7190ee718ab3d80eca181e5f7140c210", + "7190ee718ab3d80eca181e5f7140c210", "android_arm32_audio", + "android_arm64_audio"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "66516152eeaa1e650ad94ff85f668dac", + "66516152eeaa1e650ad94ff85f668dac", "android_arm32_payload", + "android_arm64_payload"), + 50, test::AcmReceiveTestOldApi::kStereoOutput); +} +#endif + +TEST_F(AcmSenderBitExactnessOldApi, Opus_stereo_20ms) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 2, 120, 960, 960)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "855041f2490b887302bce9d544731849", + "855041f2490b887302bce9d544731849", + "1e1a0fce893fef2d66886a7f09e2ebce", + "7417a66c28be42d5d9b2d64e0c191585"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "d781cce1ab986b618d0da87226cdde30", + "d781cce1ab986b618d0da87226cdde30", + "1a1fe04dd12e755949987c8d729fb3e0", + "47b0b04f1d03076b857c86c72c2c298b"), + 50, test::AcmReceiveTestOldApi::kStereoOutput); +} + +TEST_F(AcmSenderBitExactnessOldApi, Opus_stereo_20ms_voip) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 2, 120, 960, 960)); + // If not set, default will be kAudio in case of stereo. + EXPECT_EQ(0, send_test_->acm()->SetOpusApplication(kVoip)); + Run(AcmReceiverBitExactnessOldApi::PlatformChecksum( + "9b9e12bc3cc793740966e11cbfa8b35b", + "9b9e12bc3cc793740966e11cbfa8b35b", + "57412a4b5771d19ff03ec35deffe7067", + "7ad0bbefcaa87e23187bf4a56d2f3513"), + AcmReceiverBitExactnessOldApi::PlatformChecksum( + "c7340b1189652ab6b5e80dade7390cb4", + "c7340b1189652ab6b5e80dade7390cb4", + "cdfe85939c411d12b61701c566e22d26", + "7a678fbe46df5bf0c67e88264a2d9275"), + 50, test::AcmReceiveTestOldApi::kStereoOutput); +} + +// This test is for verifying the SetBitRate function. The bitrate is changed at +// the beginning, and the number of generated bytes are checked. +class AcmSetBitRateOldApi : public ::testing::Test { + protected: + static const int kTestDurationMs = 1000; + + // Sets up the test::AcmSendTest object. Returns true on success, otherwise + // false. + bool SetUpSender() { + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); + // Note that |audio_source_| will loop forever. The test duration is set + // explicitly by |kTestDurationMs|. + audio_source_.reset(new test::InputAudioFile(input_file_name)); + static const int kSourceRateHz = 32000; + send_test_.reset(new test::AcmSendTestOldApi( + audio_source_.get(), kSourceRateHz, kTestDurationMs)); + return send_test_.get(); + } + + // Registers a send codec in the test::AcmSendTest object. Returns true on + // success, false on failure. + virtual bool RegisterSendCodec(const char* payload_name, + int sampling_freq_hz, + int channels, + int payload_type, + int frame_size_samples, + int frame_size_rtp_timestamps) { + return send_test_->RegisterCodec(payload_name, sampling_freq_hz, channels, + payload_type, frame_size_samples); + } + + // Runs the test. SetUpSender() and RegisterSendCodec() must have been called + // before calling this method. + void Run(int target_bitrate_bps, int expected_total_bits) { + ASSERT_TRUE(send_test_->acm()); + send_test_->acm()->SetBitRate(target_bitrate_bps); + int nr_bytes = 0; + while (test::Packet* next_packet = send_test_->NextPacket()) { + nr_bytes += next_packet->payload_length_bytes(); + delete next_packet; + } + EXPECT_EQ(expected_total_bits, nr_bytes * 8); + } + + void SetUpTest(const char* codec_name, + int codec_sample_rate_hz, + int channels, + int payload_type, + int codec_frame_size_samples, + int codec_frame_size_rtp_timestamps) { + ASSERT_TRUE(SetUpSender()); + ASSERT_TRUE(RegisterSendCodec(codec_name, codec_sample_rate_hz, channels, + payload_type, codec_frame_size_samples, + codec_frame_size_rtp_timestamps)); + } + + rtc::scoped_ptr<test::AcmSendTestOldApi> send_test_; + rtc::scoped_ptr<test::InputAudioFile> audio_source_; +}; + +TEST_F(AcmSetBitRateOldApi, Opus_48khz_20ms_10kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); +#if defined(WEBRTC_ANDROID) + Run(10000, 9328); +#else + Run(10000, 9072); +#endif // WEBRTC_ANDROID + +} + +TEST_F(AcmSetBitRateOldApi, Opus_48khz_20ms_50kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); +#if defined(WEBRTC_ANDROID) + Run(50000, 47952); +#else + Run(50000, 49600); +#endif // WEBRTC_ANDROID +} + +// The result on the Android platforms is inconsistent for this test case. +// On android_rel the result is different from android and android arm64 rel. +#if defined(WEBRTC_ANDROID) +#define MAYBE_Opus_48khz_20ms_100kbps DISABLED_Opus_48khz_20ms_100kbps +#else +#define MAYBE_Opus_48khz_20ms_100kbps Opus_48khz_20ms_100kbps +#endif +TEST_F(AcmSetBitRateOldApi, MAYBE_Opus_48khz_20ms_100kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); + Run(100000, 100888); +} + +// These next 2 tests ensure that the SetBitRate function has no effect on PCM +TEST_F(AcmSetBitRateOldApi, Pcm16_8khz_10ms_8kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 1, 107, 80, 80)); + Run(8000, 128000); +} + +TEST_F(AcmSetBitRateOldApi, Pcm16_8khz_10ms_32kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 1, 107, 80, 80)); + Run(32000, 128000); +} + +// This test is for verifying the SetBitRate function. The bitrate is changed +// in the middle, and the number of generated bytes are before and after the +// change are checked. +class AcmChangeBitRateOldApi : public AcmSetBitRateOldApi { + protected: + AcmChangeBitRateOldApi() : sampling_freq_hz_(0), frame_size_samples_(0) {} + + // Registers a send codec in the test::AcmSendTest object. Returns true on + // success, false on failure. + bool RegisterSendCodec(const char* payload_name, + int sampling_freq_hz, + int channels, + int payload_type, + int frame_size_samples, + int frame_size_rtp_timestamps) override { + frame_size_samples_ = frame_size_samples; + sampling_freq_hz_ = sampling_freq_hz; + return AcmSetBitRateOldApi::RegisterSendCodec( + payload_name, sampling_freq_hz, channels, payload_type, + frame_size_samples, frame_size_rtp_timestamps); + } + + // Runs the test. SetUpSender() and RegisterSendCodec() must have been called + // before calling this method. + void Run(int target_bitrate_bps, + int expected_before_switch_bits, + int expected_after_switch_bits) { + ASSERT_TRUE(send_test_->acm()); + int nr_packets = + sampling_freq_hz_ * kTestDurationMs / (frame_size_samples_ * 1000); + int nr_bytes_before = 0, nr_bytes_after = 0; + int packet_counter = 0; + while (test::Packet* next_packet = send_test_->NextPacket()) { + if (packet_counter == nr_packets / 2) + send_test_->acm()->SetBitRate(target_bitrate_bps); + if (packet_counter < nr_packets / 2) + nr_bytes_before += next_packet->payload_length_bytes(); + else + nr_bytes_after += next_packet->payload_length_bytes(); + packet_counter++; + delete next_packet; + } + EXPECT_EQ(expected_before_switch_bits, nr_bytes_before * 8); + EXPECT_EQ(expected_after_switch_bits, nr_bytes_after * 8); + } + + uint32_t sampling_freq_hz_; + uint32_t frame_size_samples_; +}; + +TEST_F(AcmChangeBitRateOldApi, Opus_48khz_20ms_10kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); +#if defined(WEBRTC_ANDROID) + Run(10000, 32200, 5496); +#else + Run(10000, 32200, 5432); +#endif // WEBRTC_ANDROID +} + +TEST_F(AcmChangeBitRateOldApi, Opus_48khz_20ms_50kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); +#if defined(WEBRTC_ANDROID) + Run(50000, 32200, 24912); +#else + Run(50000, 32200, 24792); +#endif // WEBRTC_ANDROID +} + +TEST_F(AcmChangeBitRateOldApi, Opus_48khz_20ms_100kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("opus", 48000, 1, 107, 960, 960)); +#if defined(WEBRTC_ANDROID) + Run(100000, 32200, 51480); +#else + Run(100000, 32200, 50584); +#endif // WEBRTC_ANDROID +} + +// These next 2 tests ensure that the SetBitRate function has no effect on PCM +TEST_F(AcmChangeBitRateOldApi, Pcm16_8khz_10ms_8kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 1, 107, 80, 80)); + Run(8000, 64000, 64000); +} + +TEST_F(AcmChangeBitRateOldApi, Pcm16_8khz_10ms_32kbps) { + ASSERT_NO_FATAL_FAILURE(SetUpTest("L16", 8000, 1, 107, 80, 80)); + Run(32000, 64000, 64000); +} + +TEST_F(AcmSenderBitExactnessOldApi, External_Pcmu_20ms) { + CodecInst codec_inst; + codec_inst.channels = 1; + codec_inst.pacsize = 160; + codec_inst.pltype = 0; + AudioEncoderPcmU encoder(codec_inst); + MockAudioEncoder mock_encoder; + // Set expectations on the mock encoder and also delegate the calls to the + // real encoder. + EXPECT_CALL(mock_encoder, MaxEncodedBytes()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::MaxEncodedBytes)); + EXPECT_CALL(mock_encoder, SampleRateHz()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::SampleRateHz)); + EXPECT_CALL(mock_encoder, NumChannels()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::NumChannels)); + EXPECT_CALL(mock_encoder, RtpTimestampRateHz()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::RtpTimestampRateHz)); + EXPECT_CALL(mock_encoder, Num10MsFramesInNextPacket()) + .Times(AtLeast(1)) + .WillRepeatedly( + Invoke(&encoder, &AudioEncoderPcmU::Num10MsFramesInNextPacket)); + EXPECT_CALL(mock_encoder, GetTargetBitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::GetTargetBitrate)); + EXPECT_CALL(mock_encoder, EncodeInternal(_, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::EncodeInternal)); + EXPECT_CALL(mock_encoder, SetFec(_)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(&encoder, &AudioEncoderPcmU::SetFec)); + ASSERT_NO_FATAL_FAILURE( + SetUpTestExternalEncoder(&mock_encoder, codec_inst.pltype)); + Run("81a9d4c0bb72e9becc43aef124c981e9", "8f9b8750bd80fe26b6cbf6659b89f0f9", + 50, test::AcmReceiveTestOldApi::kMonoOutput); +} + +// This test fixture is implemented to run ACM and change the desired output +// frequency during the call. The input packets are simply PCM16b-wb encoded +// payloads with a constant value of |kSampleValue|. The test fixture itself +// acts as PacketSource in between the receive test class and the constant- +// payload packet source class. The output is both written to file, and analyzed +// in this test fixture. +class AcmSwitchingOutputFrequencyOldApi : public ::testing::Test, + public test::PacketSource, + public test::AudioSink { + protected: + static const size_t kTestNumPackets = 50; + static const int kEncodedSampleRateHz = 16000; + static const size_t kPayloadLenSamples = 30 * kEncodedSampleRateHz / 1000; + static const int kPayloadType = 108; // Default payload type for PCM16b-wb. + + AcmSwitchingOutputFrequencyOldApi() + : first_output_(true), + num_packets_(0), + packet_source_(kPayloadLenSamples, + kSampleValue, + kEncodedSampleRateHz, + kPayloadType), + output_freq_2_(0), + has_toggled_(false) {} + + void Run(int output_freq_1, int output_freq_2, int toggle_period_ms) { + // Set up the receiver used to decode the packets and verify the decoded + // output. + const std::string output_file_name = + webrtc::test::OutputPath() + + ::testing::UnitTest::GetInstance() + ->current_test_info() + ->test_case_name() + + "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name() + + "_output.pcm"; + test::OutputAudioFile output_file(output_file_name); + // Have the output audio sent both to file and to the WriteArray method in + // this class. + test::AudioSinkFork output(this, &output_file); + test::AcmReceiveTestToggleOutputFreqOldApi receive_test( + this, + &output, + output_freq_1, + output_freq_2, + toggle_period_ms, + test::AcmReceiveTestOldApi::kMonoOutput); + ASSERT_NO_FATAL_FAILURE(receive_test.RegisterDefaultCodecs()); + output_freq_2_ = output_freq_2; + + // This is where the actual test is executed. + receive_test.Run(); + + // Delete output file. + remove(output_file_name.c_str()); + } + + // Inherited from test::PacketSource. + test::Packet* NextPacket() override { + // Check if it is time to terminate the test. The packet source is of type + // ConstantPcmPacketSource, which is infinite, so we must end the test + // "manually". + if (num_packets_++ > kTestNumPackets) { + EXPECT_TRUE(has_toggled_); + return NULL; // Test ended. + } + + // Get the next packet from the source. + return packet_source_.NextPacket(); + } + + // Inherited from test::AudioSink. + bool WriteArray(const int16_t* audio, size_t num_samples) { + // Skip checking the first output frame, since it has a number of zeros + // due to how NetEq is initialized. + if (first_output_) { + first_output_ = false; + return true; + } + for (size_t i = 0; i < num_samples; ++i) { + EXPECT_EQ(kSampleValue, audio[i]); + } + if (num_samples == + static_cast<size_t>(output_freq_2_ / 100)) // Size of 10 ms frame. + has_toggled_ = true; + // The return value does not say if the values match the expectation, just + // that the method could process the samples. + return true; + } + + const int16_t kSampleValue = 1000; + bool first_output_; + size_t num_packets_; + test::ConstantPcmPacketSource packet_source_; + int output_freq_2_; + bool has_toggled_; +}; + +TEST_F(AcmSwitchingOutputFrequencyOldApi, TestWithoutToggling) { + Run(16000, 16000, 1000); +} + +TEST_F(AcmSwitchingOutputFrequencyOldApi, Toggle16KhzTo32Khz) { + Run(16000, 32000, 1000); +} + +TEST_F(AcmSwitchingOutputFrequencyOldApi, Toggle32KhzTo16Khz) { + Run(32000, 16000, 1000); +} + +TEST_F(AcmSwitchingOutputFrequencyOldApi, Toggle16KhzTo8Khz) { + Run(16000, 8000, 1000); +} + +TEST_F(AcmSwitchingOutputFrequencyOldApi, Toggle8KhzTo16Khz) { + Run(8000, 16000, 1000); +} + +#endif + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/call_statistics.cc b/webrtc/modules/audio_coding/acm2/call_statistics.cc new file mode 100644 index 0000000000..4441932c8c --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/call_statistics.cc @@ -0,0 +1,55 @@ +/* + * 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/acm2/call_statistics.h" + +#include <assert.h> + +namespace webrtc { + +namespace acm2 { + +void CallStatistics::DecodedByNetEq(AudioFrame::SpeechType speech_type) { + ++decoding_stat_.calls_to_neteq; + switch (speech_type) { + case AudioFrame::kNormalSpeech: { + ++decoding_stat_.decoded_normal; + break; + } + case AudioFrame::kPLC: { + ++decoding_stat_.decoded_plc; + break; + } + case AudioFrame::kCNG: { + ++decoding_stat_.decoded_cng; + break; + } + case AudioFrame::kPLCCNG: { + ++decoding_stat_.decoded_plc_cng; + break; + } + case AudioFrame::kUndefined: { + // If the audio is decoded by NetEq, |kUndefined| is not an option. + assert(false); + } + } +} + +void CallStatistics::DecodedBySilenceGenerator() { + ++decoding_stat_.calls_to_silence_generator; +} + +const AudioDecodingCallStats& CallStatistics::GetDecodingStatistics() const { + return decoding_stat_; +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/call_statistics.h b/webrtc/modules/audio_coding/acm2/call_statistics.h new file mode 100644 index 0000000000..888afea0a7 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/call_statistics.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_ + +#include "webrtc/common_types.h" +#include "webrtc/modules/include/module_common_types.h" + +// +// This class is for book keeping of calls to ACM. It is not useful to log API +// calls which are supposed to be called every 10ms, e.g. PlayoutData10Ms(), +// however, it is useful to know the number of such calls in a given time +// interval. The current implementation covers calls to PlayoutData10Ms() with +// detailed accounting of the decoded speech type. +// +// Thread Safety +// ============= +// Please note that this class in not thread safe. The class must be protected +// if different APIs are called from different threads. +// + +namespace webrtc { + +namespace acm2 { + +class CallStatistics { + public: + CallStatistics() {} + ~CallStatistics() {} + + // Call this method to indicate that NetEq engaged in decoding. |speech_type| + // is the audio-type according to NetEq. + void DecodedByNetEq(AudioFrame::SpeechType speech_type); + + // Call this method to indicate that a decoding call resulted in generating + // silence, i.e. call to NetEq is bypassed and the output audio is zero. + void DecodedBySilenceGenerator(); + + // Get statistics for decoding. The statistics include the number of calls to + // NetEq and silence generator, as well as the type of speech pulled of off + // NetEq, c.f. declaration of AudioDecodingCallStats for detailed description. + const AudioDecodingCallStats& GetDecodingStatistics() const; + + private: + // Reset the decoding statistics. + void ResetDecodingStatistics(); + + AudioDecodingCallStats decoding_stat_; +}; + +} // namespace acm2 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_CALL_STATISTICS_H_ diff --git a/webrtc/modules/audio_coding/acm2/call_statistics_unittest.cc b/webrtc/modules/audio_coding/acm2/call_statistics_unittest.cc new file mode 100644 index 0000000000..9ba0774ce1 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/call_statistics_unittest.cc @@ -0,0 +1,55 @@ +/* + * 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 "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/acm2/call_statistics.h" + +namespace webrtc { + +namespace acm2 { + +TEST(CallStatisticsTest, InitializedZero) { + CallStatistics call_stats; + AudioDecodingCallStats stats; + + stats = call_stats.GetDecodingStatistics(); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +TEST(CallStatisticsTest, AllCalls) { + CallStatistics call_stats; + AudioDecodingCallStats stats; + + call_stats.DecodedBySilenceGenerator(); + call_stats.DecodedByNetEq(AudioFrame::kNormalSpeech); + call_stats.DecodedByNetEq(AudioFrame::kPLC); + call_stats.DecodedByNetEq(AudioFrame::kPLCCNG); + call_stats.DecodedByNetEq(AudioFrame::kCNG); + + stats = call_stats.GetDecodingStatistics(); + EXPECT_EQ(4, stats.calls_to_neteq); + EXPECT_EQ(1, stats.calls_to_silence_generator); + EXPECT_EQ(1, stats.decoded_normal); + EXPECT_EQ(1, stats.decoded_cng); + EXPECT_EQ(1, stats.decoded_plc); + EXPECT_EQ(1, stats.decoded_plc_cng); +} + +} // namespace acm2 + +} // namespace webrtc + + + diff --git a/webrtc/modules/audio_coding/acm2/codec_manager.cc b/webrtc/modules/audio_coding/acm2/codec_manager.cc new file mode 100644 index 0000000000..ad67377d42 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/codec_manager.cc @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2015 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/acm2/codec_manager.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/format_macros.h" +#include "webrtc/engine_configurations.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { +namespace acm2 { + +namespace { + +// Check if the given codec is a valid to be registered as send codec. +int IsValidSendCodec(const CodecInst& send_codec) { + int dummy_id = 0; + if ((send_codec.channels != 1) && (send_codec.channels != 2)) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "Wrong number of channels (%" PRIuS ", only mono and stereo " + "are supported)", + send_codec.channels); + return -1; + } + + auto maybe_codec_id = RentACodec::CodecIdByInst(send_codec); + if (!maybe_codec_id) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "Invalid codec setting for the send codec."); + return -1; + } + + // Telephone-event cannot be a send codec. + if (!STR_CASE_CMP(send_codec.plname, "telephone-event")) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "telephone-event cannot be a send codec"); + return -1; + } + + if (!RentACodec::IsSupportedNumChannels(*maybe_codec_id, send_codec.channels) + .value_or(false)) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "%" PRIuS " number of channels not supportedn for %s.", + send_codec.channels, send_codec.plname); + return -1; + } + return RentACodec::CodecIndexFromId(*maybe_codec_id).value_or(-1); +} + +bool IsOpus(const CodecInst& codec) { + return +#ifdef WEBRTC_CODEC_OPUS + !STR_CASE_CMP(codec.plname, "opus") || +#endif + false; +} + +} // namespace + +CodecManager::CodecManager() { + thread_checker_.DetachFromThread(); +} + +CodecManager::~CodecManager() = default; + +bool CodecManager::RegisterEncoder(const CodecInst& send_codec) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + int codec_id = IsValidSendCodec(send_codec); + + // Check for reported errors from function IsValidSendCodec(). + if (codec_id < 0) { + return false; + } + + int dummy_id = 0; + switch (RentACodec::RegisterRedPayloadType( + &codec_stack_params_.red_payload_types, send_codec)) { + case RentACodec::RegistrationResult::kOk: + return true; + case RentACodec::RegistrationResult::kBadFreq: + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "RegisterSendCodec() failed, invalid frequency for RED" + " registration"); + return false; + case RentACodec::RegistrationResult::kSkip: + break; + } + switch (RentACodec::RegisterCngPayloadType( + &codec_stack_params_.cng_payload_types, send_codec)) { + case RentACodec::RegistrationResult::kOk: + return true; + case RentACodec::RegistrationResult::kBadFreq: + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, dummy_id, + "RegisterSendCodec() failed, invalid frequency for CNG" + " registration"); + return false; + case RentACodec::RegistrationResult::kSkip: + break; + } + + if (IsOpus(send_codec)) { + // VAD/DTX not supported. + codec_stack_params_.use_cng = false; + } + + send_codec_inst_ = rtc::Optional<CodecInst>(send_codec); + codec_stack_params_.speech_encoder = nullptr; // Caller must recreate it. + return true; +} + +CodecInst CodecManager::ForgeCodecInst( + const AudioEncoder* external_speech_encoder) { + CodecInst ci; + ci.channels = external_speech_encoder->NumChannels(); + ci.plfreq = external_speech_encoder->SampleRateHz(); + ci.pacsize = rtc::CheckedDivExact( + static_cast<int>(external_speech_encoder->Max10MsFramesInAPacket() * + ci.plfreq), + 100); + ci.pltype = -1; // Not valid. + ci.rate = -1; // Not valid. + static const char kName[] = "external"; + memcpy(ci.plname, kName, sizeof(kName)); + return ci; +} + +bool CodecManager::SetCopyRed(bool enable) { + if (enable && codec_stack_params_.use_codec_fec) { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, 0, + "Codec internal FEC and RED cannot be co-enabled."); + return false; + } + if (enable && send_codec_inst_ && + codec_stack_params_.red_payload_types.count(send_codec_inst_->plfreq) < + 1) { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, 0, + "Cannot enable RED at %i Hz.", send_codec_inst_->plfreq); + return false; + } + codec_stack_params_.use_red = enable; + return true; +} + +bool CodecManager::SetVAD(bool enable, ACMVADMode mode) { + // Sanity check of the mode. + RTC_DCHECK(mode == VADNormal || mode == VADLowBitrate || mode == VADAggr || + mode == VADVeryAggr); + + // Check that the send codec is mono. We don't support VAD/DTX for stereo + // sending. + const bool stereo_send = + codec_stack_params_.speech_encoder + ? (codec_stack_params_.speech_encoder->NumChannels() != 1) + : false; + if (enable && stereo_send) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, 0, + "VAD/DTX not supported for stereo sending"); + return false; + } + + // TODO(kwiberg): This doesn't protect Opus when injected as an external + // encoder. + if (send_codec_inst_ && IsOpus(*send_codec_inst_)) { + // VAD/DTX not supported, but don't fail. + enable = false; + } + + codec_stack_params_.use_cng = enable; + codec_stack_params_.vad_mode = mode; + return true; +} + +bool CodecManager::SetCodecFEC(bool enable_codec_fec) { + if (enable_codec_fec && codec_stack_params_.use_red) { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, 0, + "Codec internal FEC and RED cannot be co-enabled."); + return false; + } + + codec_stack_params_.use_codec_fec = enable_codec_fec; + return true; +} + +} // namespace acm2 +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/codec_manager.h b/webrtc/modules/audio_coding/acm2/codec_manager.h new file mode 100644 index 0000000000..9227e13f09 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/codec_manager.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_CODEC_MANAGER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_CODEC_MANAGER_H_ + +#include <map> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/optional.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_checker.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "webrtc/common_types.h" + +namespace webrtc { + +class AudioDecoder; +class AudioEncoder; + +namespace acm2 { + +class CodecManager final { + public: + CodecManager(); + ~CodecManager(); + + // Parses the given specification. On success, returns true and updates the + // stored CodecInst and stack parameters; on error, returns false. + bool RegisterEncoder(const CodecInst& send_codec); + + static CodecInst ForgeCodecInst(const AudioEncoder* external_speech_encoder); + + const CodecInst* GetCodecInst() const { + return send_codec_inst_ ? &*send_codec_inst_ : nullptr; + } + const RentACodec::StackParameters* GetStackParams() const { + return &codec_stack_params_; + } + RentACodec::StackParameters* GetStackParams() { return &codec_stack_params_; } + + bool SetCopyRed(bool enable); + + bool SetVAD(bool enable, ACMVADMode mode); + + bool SetCodecFEC(bool enable_codec_fec); + + private: + rtc::ThreadChecker thread_checker_; + rtc::Optional<CodecInst> send_codec_inst_; + RentACodec::StackParameters codec_stack_params_; + + RTC_DISALLOW_COPY_AND_ASSIGN(CodecManager); +}; + +} // namespace acm2 +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_CODEC_MANAGER_H_ diff --git a/webrtc/modules/audio_coding/acm2/codec_manager_unittest.cc b/webrtc/modules/audio_coding/acm2/codec_manager_unittest.cc new file mode 100644 index 0000000000..dce8f38842 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/codec_manager_unittest.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 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 "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" +#include "webrtc/modules/audio_coding/acm2/codec_manager.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" + +namespace webrtc { +namespace acm2 { + +using ::testing::Return; + +namespace { + +// Create a MockAudioEncoder with some reasonable default behavior. +rtc::scoped_ptr<MockAudioEncoder> CreateMockEncoder() { + auto enc = rtc_make_scoped_ptr(new MockAudioEncoder); + EXPECT_CALL(*enc, SampleRateHz()).WillRepeatedly(Return(8000)); + EXPECT_CALL(*enc, NumChannels()).WillRepeatedly(Return(1)); + EXPECT_CALL(*enc, Max10MsFramesInAPacket()).WillRepeatedly(Return(1)); + EXPECT_CALL(*enc, Die()); + return enc; +} + +} // namespace + +TEST(CodecManagerTest, ExternalEncoderFec) { + auto enc0 = CreateMockEncoder(); + auto enc1 = CreateMockEncoder(); + { + ::testing::InSequence s; + EXPECT_CALL(*enc0, SetFec(false)).WillOnce(Return(true)); + EXPECT_CALL(*enc0, Mark("A")); + EXPECT_CALL(*enc0, SetFec(true)).WillOnce(Return(true)); + EXPECT_CALL(*enc1, SetFec(true)).WillOnce(Return(true)); + EXPECT_CALL(*enc1, SetFec(false)).WillOnce(Return(true)); + EXPECT_CALL(*enc0, Mark("B")); + EXPECT_CALL(*enc0, SetFec(false)).WillOnce(Return(true)); + } + + CodecManager cm; + RentACodec rac; + EXPECT_FALSE(cm.GetStackParams()->use_codec_fec); + cm.GetStackParams()->speech_encoder = enc0.get(); + EXPECT_TRUE(rac.RentEncoderStack(cm.GetStackParams())); + EXPECT_FALSE(cm.GetStackParams()->use_codec_fec); + enc0->Mark("A"); + EXPECT_EQ(true, cm.SetCodecFEC(true)); + EXPECT_TRUE(rac.RentEncoderStack(cm.GetStackParams())); + EXPECT_TRUE(cm.GetStackParams()->use_codec_fec); + cm.GetStackParams()->speech_encoder = enc1.get(); + EXPECT_TRUE(rac.RentEncoderStack(cm.GetStackParams())); + EXPECT_TRUE(cm.GetStackParams()->use_codec_fec); + + EXPECT_EQ(true, cm.SetCodecFEC(false)); + EXPECT_TRUE(rac.RentEncoderStack(cm.GetStackParams())); + enc0->Mark("B"); + EXPECT_FALSE(cm.GetStackParams()->use_codec_fec); + cm.GetStackParams()->speech_encoder = enc0.get(); + EXPECT_TRUE(rac.RentEncoderStack(cm.GetStackParams())); + EXPECT_FALSE(cm.GetStackParams()->use_codec_fec); +} + +} // namespace acm2 +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/initial_delay_manager.cc b/webrtc/modules/audio_coding/acm2/initial_delay_manager.cc new file mode 100644 index 0000000000..0c31b83eb3 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/initial_delay_manager.cc @@ -0,0 +1,242 @@ +/* + * 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/acm2/initial_delay_manager.h" + +namespace webrtc { + +namespace acm2 { + +InitialDelayManager::InitialDelayManager(int initial_delay_ms, + int late_packet_threshold) + : last_packet_type_(kUndefinedPacket), + last_receive_timestamp_(0), + timestamp_step_(0), + audio_payload_type_(kInvalidPayloadType), + initial_delay_ms_(initial_delay_ms), + buffered_audio_ms_(0), + buffering_(true), + playout_timestamp_(0), + late_packet_threshold_(late_packet_threshold) { + last_packet_rtp_info_.header.payloadType = kInvalidPayloadType; + last_packet_rtp_info_.header.ssrc = 0; + last_packet_rtp_info_.header.sequenceNumber = 0; + last_packet_rtp_info_.header.timestamp = 0; +} + +void InitialDelayManager::UpdateLastReceivedPacket( + const WebRtcRTPHeader& rtp_info, + uint32_t receive_timestamp, + PacketType type, + bool new_codec, + int sample_rate_hz, + SyncStream* sync_stream) { + assert(sync_stream); + + // If payload of audio packets is changing |new_codec| has to be true. + assert(!(!new_codec && type == kAudioPacket && + rtp_info.header.payloadType != audio_payload_type_)); + + // Just shorthands. + const RTPHeader* current_header = &rtp_info.header; + RTPHeader* last_header = &last_packet_rtp_info_.header; + + // Don't do anything if getting DTMF. The chance of DTMF in applications where + // initial delay is required is very low (we don't know of any). This avoids a + // lot of corner cases. The effect of ignoring DTMF packet is minimal. Note + // that DTMFs are inserted into NetEq just not accounted here. + if (type == kAvtPacket || + (last_packet_type_ != kUndefinedPacket && + !IsNewerSequenceNumber(current_header->sequenceNumber, + last_header->sequenceNumber))) { + sync_stream->num_sync_packets = 0; + return; + } + + // Either if it is a new packet or the first packet record and set variables. + if (new_codec || + last_packet_rtp_info_.header.payloadType == kInvalidPayloadType) { + timestamp_step_ = 0; + if (type == kAudioPacket) + audio_payload_type_ = rtp_info.header.payloadType; + else + audio_payload_type_ = kInvalidPayloadType; // Invalid. + + RecordLastPacket(rtp_info, receive_timestamp, type); + sync_stream->num_sync_packets = 0; + buffered_audio_ms_ = 0; + buffering_ = true; + + // If |buffering_| is set then |playout_timestamp_| should have correct + // value. + UpdatePlayoutTimestamp(*current_header, sample_rate_hz); + return; + } + + uint32_t timestamp_increase = current_header->timestamp - + last_header->timestamp; + + // |timestamp_increase| is invalid if this is the first packet. The effect is + // that |buffered_audio_ms_| is not increased. + if (last_packet_type_ == kUndefinedPacket) { + timestamp_increase = 0; + } + + if (buffering_) { + buffered_audio_ms_ += timestamp_increase * 1000 / sample_rate_hz; + + // A timestamp that reflects the initial delay, while buffering. + UpdatePlayoutTimestamp(*current_header, sample_rate_hz); + + if (buffered_audio_ms_ >= initial_delay_ms_) + buffering_ = false; + } + + if (current_header->sequenceNumber == last_header->sequenceNumber + 1) { + // Two consecutive audio packets, the previous packet-type is audio, so we + // can update |timestamp_step_|. + if (last_packet_type_ == kAudioPacket) + timestamp_step_ = timestamp_increase; + RecordLastPacket(rtp_info, receive_timestamp, type); + sync_stream->num_sync_packets = 0; + return; + } + + uint16_t packet_gap = current_header->sequenceNumber - + last_header->sequenceNumber - 1; + + // For smooth transitions leave a gap between audio and sync packets. + sync_stream->num_sync_packets = last_packet_type_ == kSyncPacket ? + packet_gap - 1 : packet_gap - 2; + + // Do nothing if we haven't received any audio packet. + if (sync_stream->num_sync_packets > 0 && + audio_payload_type_ != kInvalidPayloadType) { + if (timestamp_step_ == 0) { + // Make an estimate for |timestamp_step_| if it is not updated, yet. + assert(packet_gap > 0); + timestamp_step_ = timestamp_increase / (packet_gap + 1); + } + sync_stream->timestamp_step = timestamp_step_; + + // Build the first sync-packet based on the current received packet. + memcpy(&sync_stream->rtp_info, &rtp_info, sizeof(rtp_info)); + sync_stream->rtp_info.header.payloadType = audio_payload_type_; + + uint16_t sequence_number_update = sync_stream->num_sync_packets + 1; + uint32_t timestamp_update = timestamp_step_ * sequence_number_update; + + // Rewind sequence number and timestamps. This will give a more accurate + // description of the missing packets. + // + // Note that we leave a gap between the last packet in sync-stream and the + // current received packet, so it should be compensated for in the following + // computation of timestamps and sequence number. + sync_stream->rtp_info.header.sequenceNumber -= sequence_number_update; + sync_stream->receive_timestamp = receive_timestamp - timestamp_update; + sync_stream->rtp_info.header.timestamp -= timestamp_update; + sync_stream->rtp_info.header.payloadType = audio_payload_type_; + } else { + sync_stream->num_sync_packets = 0; + } + + RecordLastPacket(rtp_info, receive_timestamp, type); + return; +} + +void InitialDelayManager::RecordLastPacket(const WebRtcRTPHeader& rtp_info, + uint32_t receive_timestamp, + PacketType type) { + last_packet_type_ = type; + last_receive_timestamp_ = receive_timestamp; + memcpy(&last_packet_rtp_info_, &rtp_info, sizeof(rtp_info)); +} + +void InitialDelayManager::LatePackets( + uint32_t timestamp_now, SyncStream* sync_stream) { + assert(sync_stream); + sync_stream->num_sync_packets = 0; + + // If there is no estimate of timestamp increment, |timestamp_step_|, then + // we cannot estimate the number of late packets. + // If the last packet has been CNG, estimating late packets is not meaningful, + // as a CNG packet is on unknown length. + // We can set a higher threshold if the last packet is CNG and continue + // execution, but this is how ACM1 code was written. + if (timestamp_step_ <= 0 || + last_packet_type_ == kCngPacket || + last_packet_type_ == kUndefinedPacket || + audio_payload_type_ == kInvalidPayloadType) // No audio packet received. + return; + + int num_late_packets = (timestamp_now - last_receive_timestamp_) / + timestamp_step_; + + if (num_late_packets < late_packet_threshold_) + return; + + int sync_offset = 1; // One gap at the end of the sync-stream. + if (last_packet_type_ != kSyncPacket) { + ++sync_offset; // One more gap at the beginning of the sync-stream. + --num_late_packets; + } + uint32_t timestamp_update = sync_offset * timestamp_step_; + + sync_stream->num_sync_packets = num_late_packets; + if (num_late_packets == 0) + return; + + // Build the first sync-packet in the sync-stream. + memcpy(&sync_stream->rtp_info, &last_packet_rtp_info_, + sizeof(last_packet_rtp_info_)); + + // Increase sequence number and timestamps. + sync_stream->rtp_info.header.sequenceNumber += sync_offset; + sync_stream->rtp_info.header.timestamp += timestamp_update; + sync_stream->receive_timestamp = last_receive_timestamp_ + timestamp_update; + sync_stream->timestamp_step = timestamp_step_; + + // Sync-packets have audio payload-type. + sync_stream->rtp_info.header.payloadType = audio_payload_type_; + + uint16_t sequence_number_update = num_late_packets + sync_offset - 1; + timestamp_update = sequence_number_update * timestamp_step_; + + // Fake the last RTP, assuming the caller will inject the whole sync-stream. + last_packet_rtp_info_.header.timestamp += timestamp_update; + last_packet_rtp_info_.header.sequenceNumber += sequence_number_update; + last_packet_rtp_info_.header.payloadType = audio_payload_type_; + last_receive_timestamp_ += timestamp_update; + + last_packet_type_ = kSyncPacket; + return; +} + +bool InitialDelayManager::GetPlayoutTimestamp(uint32_t* playout_timestamp) { + if (!buffering_) { + return false; + } + *playout_timestamp = playout_timestamp_; + return true; +} + +void InitialDelayManager::DisableBuffering() { + buffering_ = false; +} + +void InitialDelayManager::UpdatePlayoutTimestamp( + const RTPHeader& current_header, int sample_rate_hz) { + playout_timestamp_ = current_header.timestamp - static_cast<uint32_t>( + initial_delay_ms_ * sample_rate_hz / 1000); +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/initial_delay_manager.h b/webrtc/modules/audio_coding/acm2/initial_delay_manager.h new file mode 100644 index 0000000000..32dd1260f1 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/initial_delay_manager.h @@ -0,0 +1,120 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_INITIAL_DELAY_MANAGER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_INITIAL_DELAY_MANAGER_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/include/module_common_types.h" + +namespace webrtc { + +namespace acm2 { + +class InitialDelayManager { + public: + enum PacketType { + kUndefinedPacket, kCngPacket, kAvtPacket, kAudioPacket, kSyncPacket }; + + // Specifies a stream of sync-packets. + struct SyncStream { + SyncStream() + : num_sync_packets(0), + receive_timestamp(0), + timestamp_step(0) { + memset(&rtp_info, 0, sizeof(rtp_info)); + } + + int num_sync_packets; + + // RTP header of the first sync-packet in the sequence. + WebRtcRTPHeader rtp_info; + + // Received timestamp of the first sync-packet in the sequence. + uint32_t receive_timestamp; + + // Samples per packet. + uint32_t timestamp_step; + }; + + InitialDelayManager(int initial_delay_ms, int late_packet_threshold); + + // Update with the last received RTP header, |header|, and received timestamp, + // |received_timestamp|. |type| indicates the packet type. If codec is changed + // since the last time |new_codec| should be true. |sample_rate_hz| is the + // decoder's sampling rate in Hz. |header| has a field to store sampling rate + // but we are not sure if that is properly set at the send side, and |header| + // is declared constant in the caller of this function + // (AcmReceiver::InsertPacket()). |sync_stream| contains information required + // to generate a stream of sync packets. + void UpdateLastReceivedPacket(const WebRtcRTPHeader& header, + uint32_t receive_timestamp, + PacketType type, + bool new_codec, + int sample_rate_hz, + SyncStream* sync_stream); + + // Based on the last received timestamp and given the current timestamp, + // sequence of late (or perhaps missing) packets is computed. + void LatePackets(uint32_t timestamp_now, SyncStream* sync_stream); + + // Get playout timestamp. + // Returns true if the timestamp is valid (when buffering), otherwise false. + bool GetPlayoutTimestamp(uint32_t* playout_timestamp); + + // True if buffered audio is less than the given initial delay (specified at + // the constructor). Buffering might be disabled by the client of this class. + bool buffering() { return buffering_; } + + // Disable buffering in the class. + void DisableBuffering(); + + // True if any packet received for buffering. + bool PacketBuffered() { return last_packet_type_ != kUndefinedPacket; } + + private: + static const uint8_t kInvalidPayloadType = 0xFF; + + // Update playout timestamps. While buffering, this is about + // |initial_delay_ms| millisecond behind the latest received timestamp. + void UpdatePlayoutTimestamp(const RTPHeader& current_header, + int sample_rate_hz); + + // Record an RTP headr and related parameter + void RecordLastPacket(const WebRtcRTPHeader& rtp_info, + uint32_t receive_timestamp, + PacketType type); + + PacketType last_packet_type_; + WebRtcRTPHeader last_packet_rtp_info_; + uint32_t last_receive_timestamp_; + uint32_t timestamp_step_; + uint8_t audio_payload_type_; + const int initial_delay_ms_; + int buffered_audio_ms_; + bool buffering_; + + // During the initial phase where packets are being accumulated and silence + // is played out, |playout_ts| is a timestamp which is equal to + // |initial_delay_ms_| milliseconds earlier than the most recently received + // RTP timestamp. + uint32_t playout_timestamp_; + + // If the number of late packets exceed this value (computed based on current + // timestamp and last received timestamp), sequence of sync-packets is + // specified. + const int late_packet_threshold_; +}; + +} // namespace acm2 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_INITIAL_DELAY_MANAGER_H_ diff --git a/webrtc/modules/audio_coding/acm2/initial_delay_manager_unittest.cc b/webrtc/modules/audio_coding/acm2/initial_delay_manager_unittest.cc new file mode 100644 index 0000000000..d86d221851 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/initial_delay_manager_unittest.cc @@ -0,0 +1,376 @@ +/* + * 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 <string.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/acm2/initial_delay_manager.h" + +namespace webrtc { + +namespace acm2 { + +namespace { + +const uint8_t kAudioPayloadType = 0; +const uint8_t kCngPayloadType = 1; +const uint8_t kAvtPayloadType = 2; + +const int kSamplingRateHz = 16000; +const int kInitDelayMs = 200; +const int kFrameSizeMs = 20; +const uint32_t kTimestampStep = kFrameSizeMs * kSamplingRateHz / 1000; +const int kLatePacketThreshold = 5; + +void InitRtpInfo(WebRtcRTPHeader* rtp_info) { + memset(rtp_info, 0, sizeof(*rtp_info)); + rtp_info->header.markerBit = false; + rtp_info->header.payloadType = kAudioPayloadType; + rtp_info->header.sequenceNumber = 1234; + rtp_info->header.timestamp = 0xFFFFFFFD; // Close to wrap around. + rtp_info->header.ssrc = 0x87654321; // Arbitrary. + rtp_info->header.numCSRCs = 0; // Arbitrary. + rtp_info->header.paddingLength = 0; + rtp_info->header.headerLength = sizeof(RTPHeader); + rtp_info->header.payload_type_frequency = kSamplingRateHz; + rtp_info->header.extension.absoluteSendTime = 0; + rtp_info->header.extension.transmissionTimeOffset = 0; + rtp_info->frameType = kAudioFrameSpeech; +} + +void ForwardRtpHeader(int n, + WebRtcRTPHeader* rtp_info, + uint32_t* rtp_receive_timestamp) { + rtp_info->header.sequenceNumber += n; + rtp_info->header.timestamp += n * kTimestampStep; + *rtp_receive_timestamp += n * kTimestampStep; +} + +void NextRtpHeader(WebRtcRTPHeader* rtp_info, + uint32_t* rtp_receive_timestamp) { + ForwardRtpHeader(1, rtp_info, rtp_receive_timestamp); +} + +} // namespace + +class InitialDelayManagerTest : public ::testing::Test { + protected: + InitialDelayManagerTest() + : manager_(new InitialDelayManager(kInitDelayMs, kLatePacketThreshold)), + rtp_receive_timestamp_(1111) { } // Arbitrary starting point. + + virtual void SetUp() { + ASSERT_TRUE(manager_.get() != NULL); + InitRtpInfo(&rtp_info_); + } + + void GetNextRtpHeader(WebRtcRTPHeader* rtp_info, + uint32_t* rtp_receive_timestamp) const { + memcpy(rtp_info, &rtp_info_, sizeof(*rtp_info)); + *rtp_receive_timestamp = rtp_receive_timestamp_; + NextRtpHeader(rtp_info, rtp_receive_timestamp); + } + + rtc::scoped_ptr<InitialDelayManager> manager_; + WebRtcRTPHeader rtp_info_; + uint32_t rtp_receive_timestamp_; +}; + +TEST_F(InitialDelayManagerTest, Init) { + EXPECT_TRUE(manager_->buffering()); + EXPECT_FALSE(manager_->PacketBuffered()); + manager_->DisableBuffering(); + EXPECT_FALSE(manager_->buffering()); + InitialDelayManager::SyncStream sync_stream; + + // Call before any packet inserted. + manager_->LatePackets(0x6789ABCD, &sync_stream); // Arbitrary but large + // receive timestamp. + EXPECT_EQ(0, sync_stream.num_sync_packets); + + // Insert non-audio packets, a CNG and DTMF. + rtp_info_.header.payloadType = kCngPayloadType; + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kCngPacket, false, + kSamplingRateHz, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets); + ForwardRtpHeader(5, &rtp_info_, &rtp_receive_timestamp_); + rtp_info_.header.payloadType = kAvtPayloadType; + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAvtPacket, false, + kSamplingRateHz, &sync_stream); + // Gap in sequence numbers but no audio received, sync-stream should be empty. + EXPECT_EQ(0, sync_stream.num_sync_packets); + manager_->LatePackets(0x45678987, &sync_stream); // Large arbitrary receive + // timestamp. + // |manager_| has no estimate of timestamp-step and has not received any + // audio packet. + EXPECT_EQ(0, sync_stream.num_sync_packets); + + + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + rtp_info_.header.payloadType = kAudioPayloadType; + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets); + + // Call LatePAcket() after only one packet inserted. + manager_->LatePackets(0x6789ABCD, &sync_stream); // Arbitrary but large + // receive timestamp. + EXPECT_EQ(0, sync_stream.num_sync_packets); + + // Gap in timestamp, but this packet is also flagged as "new," therefore, + // expecting empty sync-stream. + ForwardRtpHeader(5, &rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); +} + +TEST_F(InitialDelayManagerTest, MissingPacket) { + InitialDelayManager::SyncStream sync_stream; + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Second packet. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Third packet, missing packets start from here. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + + // First sync-packet in sync-stream is one after the above packet. + WebRtcRTPHeader expected_rtp_info; + uint32_t expected_receive_timestamp; + GetNextRtpHeader(&expected_rtp_info, &expected_receive_timestamp); + + const int kNumMissingPackets = 10; + ForwardRtpHeader(kNumMissingPackets, &rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + EXPECT_EQ(kNumMissingPackets - 2, sync_stream.num_sync_packets); + EXPECT_EQ(0, memcmp(&expected_rtp_info, &sync_stream.rtp_info, + sizeof(expected_rtp_info))); + EXPECT_EQ(kTimestampStep, sync_stream.timestamp_step); + EXPECT_EQ(expected_receive_timestamp, sync_stream.receive_timestamp); +} + +// There hasn't been any consecutive packets to estimate timestamp-step. +TEST_F(InitialDelayManagerTest, MissingPacketEstimateTimestamp) { + InitialDelayManager::SyncStream sync_stream; + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Second packet, missing packets start here. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + + // First sync-packet in sync-stream is one after the above. + WebRtcRTPHeader expected_rtp_info; + uint32_t expected_receive_timestamp; + GetNextRtpHeader(&expected_rtp_info, &expected_receive_timestamp); + + const int kNumMissingPackets = 10; + ForwardRtpHeader(kNumMissingPackets, &rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + EXPECT_EQ(kNumMissingPackets - 2, sync_stream.num_sync_packets); + EXPECT_EQ(0, memcmp(&expected_rtp_info, &sync_stream.rtp_info, + sizeof(expected_rtp_info))); +} + +TEST_F(InitialDelayManagerTest, MissingPacketWithCng) { + InitialDelayManager::SyncStream sync_stream; + + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Second packet as CNG. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + rtp_info_.header.payloadType = kCngPayloadType; + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kCngPacket, false, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Audio packet after CNG. Missing packets start from this packet. + rtp_info_.header.payloadType = kAudioPayloadType; + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + + // Timestamps are increased higher than regular packet. + const uint32_t kCngTimestampStep = 5 * kTimestampStep; + rtp_info_.header.timestamp += kCngTimestampStep; + rtp_receive_timestamp_ += kCngTimestampStep; + + // First sync-packet in sync-stream is the one after the above packet. + WebRtcRTPHeader expected_rtp_info; + uint32_t expected_receive_timestamp; + GetNextRtpHeader(&expected_rtp_info, &expected_receive_timestamp); + + const int kNumMissingPackets = 10; + ForwardRtpHeader(kNumMissingPackets, &rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + EXPECT_EQ(kNumMissingPackets - 2, sync_stream.num_sync_packets); + EXPECT_EQ(0, memcmp(&expected_rtp_info, &sync_stream.rtp_info, + sizeof(expected_rtp_info))); + EXPECT_EQ(kTimestampStep, sync_stream.timestamp_step); + EXPECT_EQ(expected_receive_timestamp, sync_stream.receive_timestamp); +} + +TEST_F(InitialDelayManagerTest, LatePacket) { + InitialDelayManager::SyncStream sync_stream; + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Second packet. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Timestamp increment for 10ms; + const uint32_t kTimestampStep10Ms = kSamplingRateHz / 100; + + // 10 ms after the second packet is inserted. + uint32_t timestamp_now = rtp_receive_timestamp_ + kTimestampStep10Ms; + + // Third packet, late packets start from this packet. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + + // First sync-packet in sync-stream, which is one after the above packet. + WebRtcRTPHeader expected_rtp_info; + uint32_t expected_receive_timestamp; + GetNextRtpHeader(&expected_rtp_info, &expected_receive_timestamp); + + const int kLatePacketThreshold = 5; + + int expected_num_late_packets = kLatePacketThreshold - 1; + for (int k = 0; k < 2; ++k) { + for (int n = 1; n < kLatePacketThreshold * kFrameSizeMs / 10; ++n) { + manager_->LatePackets(timestamp_now, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets) << + "try " << k << " loop number " << n; + timestamp_now += kTimestampStep10Ms; + } + manager_->LatePackets(timestamp_now, &sync_stream); + + EXPECT_EQ(expected_num_late_packets, sync_stream.num_sync_packets) << + "try " << k; + EXPECT_EQ(kTimestampStep, sync_stream.timestamp_step) << + "try " << k; + EXPECT_EQ(expected_receive_timestamp, sync_stream.receive_timestamp) << + "try " << k; + EXPECT_EQ(0, memcmp(&expected_rtp_info, &sync_stream.rtp_info, + sizeof(expected_rtp_info))); + + timestamp_now += kTimestampStep10Ms; + + // |manger_| assumes the |sync_stream| obtained by LatePacket() is fully + // injected. The last injected packet is sync-packet, therefore, there will + // not be any gap between sync stream of this and the next iteration. + ForwardRtpHeader(sync_stream.num_sync_packets, &expected_rtp_info, + &expected_receive_timestamp); + expected_num_late_packets = kLatePacketThreshold; + } + + // Test "no-gap" for missing packet after late packet. + // |expected_rtp_info| is the expected sync-packet if any packet is missing. + memcpy(&rtp_info_, &expected_rtp_info, sizeof(rtp_info_)); + rtp_receive_timestamp_ = expected_receive_timestamp; + + int kNumMissingPackets = 3; // Arbitrary. + ForwardRtpHeader(kNumMissingPackets, &rtp_info_, &rtp_receive_timestamp_); + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, false, + kSamplingRateHz, &sync_stream); + + // Note that there is one packet gap between the last sync-packet and the + // latest inserted packet. + EXPECT_EQ(kNumMissingPackets - 1, sync_stream.num_sync_packets); + EXPECT_EQ(kTimestampStep, sync_stream.timestamp_step); + EXPECT_EQ(expected_receive_timestamp, sync_stream.receive_timestamp); + EXPECT_EQ(0, memcmp(&expected_rtp_info, &sync_stream.rtp_info, + sizeof(expected_rtp_info))); +} + +TEST_F(InitialDelayManagerTest, NoLatePacketAfterCng) { + InitialDelayManager::SyncStream sync_stream; + + // First packet. + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, true, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Second packet as CNG. + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + rtp_info_.header.payloadType = kCngPayloadType; + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kCngPacket, false, + kSamplingRateHz, &sync_stream); + ASSERT_EQ(0, sync_stream.num_sync_packets); + + // Forward the time more then |kLatePacketThreshold| packets. + uint32_t timestamp_now = rtp_receive_timestamp_ + kTimestampStep * (3 + + kLatePacketThreshold); + + manager_->LatePackets(timestamp_now, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets); +} + +TEST_F(InitialDelayManagerTest, BufferingAudio) { + InitialDelayManager::SyncStream sync_stream; + + // Very first packet is not counted in calculation of buffered audio. + for (int n = 0; n < kInitDelayMs / kFrameSizeMs; ++n) { + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, + n == 0, kSamplingRateHz, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets); + EXPECT_TRUE(manager_->buffering()); + const uint32_t expected_playout_timestamp = rtp_info_.header.timestamp - + kInitDelayMs * kSamplingRateHz / 1000; + uint32_t actual_playout_timestamp = 0; + EXPECT_TRUE(manager_->GetPlayoutTimestamp(&actual_playout_timestamp)); + EXPECT_EQ(expected_playout_timestamp, actual_playout_timestamp); + NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); + } + + manager_->UpdateLastReceivedPacket(rtp_info_, rtp_receive_timestamp_, + InitialDelayManager::kAudioPacket, + false, kSamplingRateHz, &sync_stream); + EXPECT_EQ(0, sync_stream.num_sync_packets); + EXPECT_FALSE(manager_->buffering()); +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/rent_a_codec.cc b/webrtc/modules/audio_coding/acm2/rent_a_codec.cc new file mode 100644 index 0000000000..5695fd6e08 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/rent_a_codec.cc @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2015 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/acm2/rent_a_codec.h" + +#include <utility> + +#include "webrtc/base/logging.h" +#include "webrtc/modules/audio_coding/codecs/cng/audio_encoder_cng.h" +#include "webrtc/modules/audio_coding/codecs/g711/audio_encoder_pcm.h" +#ifdef WEBRTC_CODEC_G722 +#include "webrtc/modules/audio_coding/codecs/g722/audio_encoder_g722.h" +#endif +#ifdef WEBRTC_CODEC_ILBC +#include "webrtc/modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.h" +#endif +#ifdef WEBRTC_CODEC_ISACFX +#include "webrtc/modules/audio_coding/codecs/isac/fix/include/audio_decoder_isacfix.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/include/audio_encoder_isacfix.h" +#endif +#ifdef WEBRTC_CODEC_ISAC +#include "webrtc/modules/audio_coding/codecs/isac/main/include/audio_decoder_isac.h" +#include "webrtc/modules/audio_coding/codecs/isac/main/include/audio_encoder_isac.h" +#endif +#ifdef WEBRTC_CODEC_OPUS +#include "webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h" +#endif +#include "webrtc/modules/audio_coding/codecs/pcm16b/audio_encoder_pcm16b.h" +#ifdef WEBRTC_CODEC_RED +#include "webrtc/modules/audio_coding/codecs/red/audio_encoder_copy_red.h" +#endif +#include "webrtc/modules/audio_coding/acm2/acm_codec_database.h" +#include "webrtc/modules/audio_coding/acm2/acm_common_defs.h" + +namespace webrtc { +namespace acm2 { + +rtc::Optional<RentACodec::CodecId> RentACodec::CodecIdByParams( + const char* payload_name, + int sampling_freq_hz, + size_t channels) { + return CodecIdFromIndex( + ACMCodecDB::CodecId(payload_name, sampling_freq_hz, channels)); +} + +rtc::Optional<CodecInst> RentACodec::CodecInstById(CodecId codec_id) { + rtc::Optional<int> mi = CodecIndexFromId(codec_id); + return mi ? rtc::Optional<CodecInst>(Database()[*mi]) + : rtc::Optional<CodecInst>(); +} + +rtc::Optional<RentACodec::CodecId> RentACodec::CodecIdByInst( + const CodecInst& codec_inst) { + return CodecIdFromIndex(ACMCodecDB::CodecNumber(codec_inst)); +} + +rtc::Optional<CodecInst> RentACodec::CodecInstByParams(const char* payload_name, + int sampling_freq_hz, + size_t channels) { + rtc::Optional<CodecId> codec_id = + CodecIdByParams(payload_name, sampling_freq_hz, channels); + if (!codec_id) + return rtc::Optional<CodecInst>(); + rtc::Optional<CodecInst> ci = CodecInstById(*codec_id); + RTC_DCHECK(ci); + + // Keep the number of channels from the function call. For most codecs it + // will be the same value as in default codec settings, but not for all. + ci->channels = channels; + + return ci; +} + +bool RentACodec::IsCodecValid(const CodecInst& codec_inst) { + return ACMCodecDB::CodecNumber(codec_inst) >= 0; +} + +rtc::Optional<bool> RentACodec::IsSupportedNumChannels(CodecId codec_id, + size_t num_channels) { + auto i = CodecIndexFromId(codec_id); + return i ? rtc::Optional<bool>( + ACMCodecDB::codec_settings_[*i].channel_support >= + num_channels) + : rtc::Optional<bool>(); +} + +rtc::ArrayView<const CodecInst> RentACodec::Database() { + return rtc::ArrayView<const CodecInst>(ACMCodecDB::database_, + NumberOfCodecs()); +} + +rtc::Optional<NetEqDecoder> RentACodec::NetEqDecoderFromCodecId( + CodecId codec_id, + size_t num_channels) { + rtc::Optional<int> i = CodecIndexFromId(codec_id); + if (!i) + return rtc::Optional<NetEqDecoder>(); + const NetEqDecoder ned = ACMCodecDB::neteq_decoders_[*i]; + return rtc::Optional<NetEqDecoder>( + (ned == NetEqDecoder::kDecoderOpus && num_channels == 2) + ? NetEqDecoder::kDecoderOpus_2ch + : ned); +} + +RentACodec::RegistrationResult RentACodec::RegisterCngPayloadType( + std::map<int, int>* pt_map, + const CodecInst& codec_inst) { + if (STR_CASE_CMP(codec_inst.plname, "CN") != 0) + return RegistrationResult::kSkip; + switch (codec_inst.plfreq) { + case 8000: + case 16000: + case 32000: + case 48000: + (*pt_map)[codec_inst.plfreq] = codec_inst.pltype; + return RegistrationResult::kOk; + default: + return RegistrationResult::kBadFreq; + } +} + +RentACodec::RegistrationResult RentACodec::RegisterRedPayloadType( + std::map<int, int>* pt_map, + const CodecInst& codec_inst) { + if (STR_CASE_CMP(codec_inst.plname, "RED") != 0) + return RegistrationResult::kSkip; + switch (codec_inst.plfreq) { + case 8000: + (*pt_map)[codec_inst.plfreq] = codec_inst.pltype; + return RegistrationResult::kOk; + default: + return RegistrationResult::kBadFreq; + } +} + +namespace { + +// Returns a new speech encoder, or null on error. +// TODO(kwiberg): Don't handle errors here (bug 5033) +rtc::scoped_ptr<AudioEncoder> CreateEncoder( + const CodecInst& speech_inst, + LockedIsacBandwidthInfo* bwinfo) { +#if defined(WEBRTC_CODEC_ISACFX) + if (STR_CASE_CMP(speech_inst.plname, "isac") == 0) + return rtc_make_scoped_ptr(new AudioEncoderIsacFix(speech_inst, bwinfo)); +#endif +#if defined(WEBRTC_CODEC_ISAC) + if (STR_CASE_CMP(speech_inst.plname, "isac") == 0) + return rtc_make_scoped_ptr(new AudioEncoderIsac(speech_inst, bwinfo)); +#endif +#ifdef WEBRTC_CODEC_OPUS + if (STR_CASE_CMP(speech_inst.plname, "opus") == 0) + return rtc_make_scoped_ptr(new AudioEncoderOpus(speech_inst)); +#endif + if (STR_CASE_CMP(speech_inst.plname, "pcmu") == 0) + return rtc_make_scoped_ptr(new AudioEncoderPcmU(speech_inst)); + if (STR_CASE_CMP(speech_inst.plname, "pcma") == 0) + return rtc_make_scoped_ptr(new AudioEncoderPcmA(speech_inst)); + if (STR_CASE_CMP(speech_inst.plname, "l16") == 0) + return rtc_make_scoped_ptr(new AudioEncoderPcm16B(speech_inst)); +#ifdef WEBRTC_CODEC_ILBC + if (STR_CASE_CMP(speech_inst.plname, "ilbc") == 0) + return rtc_make_scoped_ptr(new AudioEncoderIlbc(speech_inst)); +#endif +#ifdef WEBRTC_CODEC_G722 + if (STR_CASE_CMP(speech_inst.plname, "g722") == 0) + return rtc_make_scoped_ptr(new AudioEncoderG722(speech_inst)); +#endif + LOG_F(LS_ERROR) << "Could not create encoder of type " << speech_inst.plname; + return rtc::scoped_ptr<AudioEncoder>(); +} + +rtc::scoped_ptr<AudioEncoder> CreateRedEncoder(AudioEncoder* encoder, + int red_payload_type) { +#ifdef WEBRTC_CODEC_RED + AudioEncoderCopyRed::Config config; + config.payload_type = red_payload_type; + config.speech_encoder = encoder; + return rtc::scoped_ptr<AudioEncoder>(new AudioEncoderCopyRed(config)); +#else + return rtc::scoped_ptr<AudioEncoder>(); +#endif +} + +rtc::scoped_ptr<AudioEncoder> CreateCngEncoder(AudioEncoder* encoder, + int payload_type, + ACMVADMode vad_mode) { + AudioEncoderCng::Config config; + config.num_channels = encoder->NumChannels(); + config.payload_type = payload_type; + config.speech_encoder = encoder; + switch (vad_mode) { + case VADNormal: + config.vad_mode = Vad::kVadNormal; + break; + case VADLowBitrate: + config.vad_mode = Vad::kVadLowBitrate; + break; + case VADAggr: + config.vad_mode = Vad::kVadAggressive; + break; + case VADVeryAggr: + config.vad_mode = Vad::kVadVeryAggressive; + break; + default: + FATAL(); + } + return rtc::scoped_ptr<AudioEncoder>(new AudioEncoderCng(config)); +} + +rtc::scoped_ptr<AudioDecoder> CreateIsacDecoder( + LockedIsacBandwidthInfo* bwinfo) { +#if defined(WEBRTC_CODEC_ISACFX) + return rtc_make_scoped_ptr(new AudioDecoderIsacFix(bwinfo)); +#elif defined(WEBRTC_CODEC_ISAC) + return rtc_make_scoped_ptr(new AudioDecoderIsac(bwinfo)); +#else + FATAL() << "iSAC is not supported."; + return rtc::scoped_ptr<AudioDecoder>(); +#endif +} + +} // namespace + +RentACodec::RentACodec() = default; +RentACodec::~RentACodec() = default; + +AudioEncoder* RentACodec::RentEncoder(const CodecInst& codec_inst) { + rtc::scoped_ptr<AudioEncoder> enc = + CreateEncoder(codec_inst, &isac_bandwidth_info_); + if (!enc) + return nullptr; + speech_encoder_ = std::move(enc); + return speech_encoder_.get(); +} + +RentACodec::StackParameters::StackParameters() { + // Register the default payload types for RED and CNG. + for (const CodecInst& ci : RentACodec::Database()) { + RentACodec::RegisterCngPayloadType(&cng_payload_types, ci); + RentACodec::RegisterRedPayloadType(&red_payload_types, ci); + } +} + +RentACodec::StackParameters::~StackParameters() = default; + +AudioEncoder* RentACodec::RentEncoderStack(StackParameters* param) { + RTC_DCHECK(param->speech_encoder); + + if (param->use_codec_fec) { + // Switch FEC on. On failure, remember that FEC is off. + if (!param->speech_encoder->SetFec(true)) + param->use_codec_fec = false; + } else { + // Switch FEC off. This shouldn't fail. + const bool success = param->speech_encoder->SetFec(false); + RTC_DCHECK(success); + } + + auto pt = [¶m](const std::map<int, int>& m) { + auto it = m.find(param->speech_encoder->SampleRateHz()); + return it == m.end() ? rtc::Optional<int>() + : rtc::Optional<int>(it->second); + }; + auto cng_pt = pt(param->cng_payload_types); + param->use_cng = + param->use_cng && cng_pt && param->speech_encoder->NumChannels() == 1; + auto red_pt = pt(param->red_payload_types); + param->use_red = param->use_red && red_pt; + + if (param->use_cng || param->use_red) { + // The RED and CNG encoders need to be in sync with the speech encoder, so + // reset the latter to ensure its buffer is empty. + param->speech_encoder->Reset(); + } + encoder_stack_ = param->speech_encoder; + if (param->use_red) { + red_encoder_ = CreateRedEncoder(encoder_stack_, *red_pt); + if (red_encoder_) + encoder_stack_ = red_encoder_.get(); + } else { + red_encoder_.reset(); + } + if (param->use_cng) { + cng_encoder_ = CreateCngEncoder(encoder_stack_, *cng_pt, param->vad_mode); + encoder_stack_ = cng_encoder_.get(); + } else { + cng_encoder_.reset(); + } + return encoder_stack_; +} + +AudioDecoder* RentACodec::RentIsacDecoder() { + if (!isac_decoder_) + isac_decoder_ = CreateIsacDecoder(&isac_bandwidth_info_); + return isac_decoder_.get(); +} + +} // namespace acm2 +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/acm2/rent_a_codec.h b/webrtc/modules/audio_coding/acm2/rent_a_codec.h new file mode 100644 index 0000000000..b1dcc9196c --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/rent_a_codec.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2015 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_ACM2_RENT_A_CODEC_H_ +#define WEBRTC_MODULES_AUDIO_CODING_ACM2_RENT_A_CODEC_H_ + +#include <stddef.h> +#include <map> + +#include "webrtc/base/array_view.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/optional.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/audio_coding/codecs/audio_decoder.h" +#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" +#include "webrtc/modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "webrtc/typedefs.h" + +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) +#include "webrtc/modules/audio_coding/codecs/isac/locked_bandwidth_info.h" +#else +// Dummy implementation, for when we don't have iSAC. +namespace webrtc { +class LockedIsacBandwidthInfo {}; +} +#endif + +namespace webrtc { + +struct CodecInst; + +namespace acm2 { + +class RentACodec { + public: + enum class CodecId { +#if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) + kISAC, +#endif +#ifdef WEBRTC_CODEC_ISAC + kISACSWB, +#endif + // Mono + kPCM16B, + kPCM16Bwb, + kPCM16Bswb32kHz, + // Stereo + kPCM16B_2ch, + kPCM16Bwb_2ch, + kPCM16Bswb32kHz_2ch, + // Mono + kPCMU, + kPCMA, + // Stereo + kPCMU_2ch, + kPCMA_2ch, +#ifdef WEBRTC_CODEC_ILBC + kILBC, +#endif +#ifdef WEBRTC_CODEC_G722 + kG722, // Mono + kG722_2ch, // Stereo +#endif +#ifdef WEBRTC_CODEC_OPUS + kOpus, // Mono and stereo +#endif + kCNNB, + kCNWB, + kCNSWB, +#ifdef ENABLE_48000_HZ + kCNFB, +#endif + kAVT, +#ifdef WEBRTC_CODEC_RED + kRED, +#endif + kNumCodecs, // Implementation detail. Don't use. + +// Set unsupported codecs to -1. +#if !defined(WEBRTC_CODEC_ISAC) && !defined(WEBRTC_CODEC_ISACFX) + kISAC = -1, +#endif +#ifndef WEBRTC_CODEC_ISAC + kISACSWB = -1, +#endif + // 48 kHz not supported, always set to -1. + kPCM16Bswb48kHz = -1, +#ifndef WEBRTC_CODEC_ILBC + kILBC = -1, +#endif +#ifndef WEBRTC_CODEC_G722 + kG722 = -1, // Mono + kG722_2ch = -1, // Stereo +#endif +#ifndef WEBRTC_CODEC_OPUS + kOpus = -1, // Mono and stereo +#endif +#ifndef WEBRTC_CODEC_RED + kRED = -1, +#endif +#ifndef ENABLE_48000_HZ + kCNFB = -1, +#endif + + kNone = -1 + }; + + enum class NetEqDecoder { + kDecoderPCMu, + kDecoderPCMa, + kDecoderPCMu_2ch, + kDecoderPCMa_2ch, + kDecoderILBC, + kDecoderISAC, + kDecoderISACswb, + kDecoderPCM16B, + kDecoderPCM16Bwb, + kDecoderPCM16Bswb32kHz, + kDecoderPCM16Bswb48kHz, + kDecoderPCM16B_2ch, + kDecoderPCM16Bwb_2ch, + kDecoderPCM16Bswb32kHz_2ch, + kDecoderPCM16Bswb48kHz_2ch, + kDecoderPCM16B_5ch, + kDecoderG722, + kDecoderG722_2ch, + kDecoderRED, + kDecoderAVT, + kDecoderCNGnb, + kDecoderCNGwb, + kDecoderCNGswb32kHz, + kDecoderCNGswb48kHz, + kDecoderArbitrary, + kDecoderOpus, + kDecoderOpus_2ch, + }; + + static inline size_t NumberOfCodecs() { + return static_cast<size_t>(CodecId::kNumCodecs); + } + + static inline rtc::Optional<int> CodecIndexFromId(CodecId codec_id) { + const int i = static_cast<int>(codec_id); + return i >= 0 && i < static_cast<int>(NumberOfCodecs()) + ? rtc::Optional<int>(i) + : rtc::Optional<int>(); + } + + static inline rtc::Optional<CodecId> CodecIdFromIndex(int codec_index) { + return static_cast<size_t>(codec_index) < NumberOfCodecs() + ? rtc::Optional<RentACodec::CodecId>( + static_cast<RentACodec::CodecId>(codec_index)) + : rtc::Optional<RentACodec::CodecId>(); + } + + static rtc::Optional<CodecId> CodecIdByParams(const char* payload_name, + int sampling_freq_hz, + size_t channels); + static rtc::Optional<CodecInst> CodecInstById(CodecId codec_id); + static rtc::Optional<CodecId> CodecIdByInst(const CodecInst& codec_inst); + static rtc::Optional<CodecInst> CodecInstByParams(const char* payload_name, + int sampling_freq_hz, + size_t channels); + static bool IsCodecValid(const CodecInst& codec_inst); + + static inline bool IsPayloadTypeValid(int payload_type) { + return payload_type >= 0 && payload_type <= 127; + } + + static rtc::ArrayView<const CodecInst> Database(); + + static rtc::Optional<bool> IsSupportedNumChannels(CodecId codec_id, + size_t num_channels); + + static rtc::Optional<NetEqDecoder> NetEqDecoderFromCodecId( + CodecId codec_id, + size_t num_channels); + + // Parse codec_inst and extract payload types. If the given CodecInst was for + // the wrong sort of codec, return kSkip; otherwise, if the rate was illegal, + // return kBadFreq; otherwise, update the given RTP timestamp rate (Hz) -> + // payload type map and return kOk. + enum class RegistrationResult { kOk, kSkip, kBadFreq }; + static RegistrationResult RegisterCngPayloadType(std::map<int, int>* pt_map, + const CodecInst& codec_inst); + static RegistrationResult RegisterRedPayloadType(std::map<int, int>* pt_map, + const CodecInst& codec_inst); + + RentACodec(); + ~RentACodec(); + + // Creates and returns an audio encoder built to the given specification. + // Returns null in case of error. The returned encoder is live until the next + // successful call to this function, or until the Rent-A-Codec is destroyed. + AudioEncoder* RentEncoder(const CodecInst& codec_inst); + + struct StackParameters { + StackParameters(); + ~StackParameters(); + + AudioEncoder* speech_encoder = nullptr; + bool use_codec_fec = false; + bool use_red = false; + bool use_cng = false; + ACMVADMode vad_mode = VADNormal; + + // Maps from RTP timestamp rate (in Hz) to payload type. + std::map<int, int> cng_payload_types; + std::map<int, int> red_payload_types; + }; + + // Creates and returns an audio encoder stack constructed to the given + // specification. If the specification isn't compatible with the encoder, it + // will be changed to match (things will be switched off). The returned + // encoder is live until the next successful call to this function, or until + // the Rent-A-Codec is destroyed. + AudioEncoder* RentEncoderStack(StackParameters* param); + + // The last return value of RentEncoderStack, or null if it hasn't been + // called. + AudioEncoder* GetEncoderStack() const { return encoder_stack_; } + + // Creates and returns an iSAC decoder, which will remain live until the + // Rent-A-Codec is destroyed. Subsequent calls will simply return the same + // object. + AudioDecoder* RentIsacDecoder(); + + private: + rtc::scoped_ptr<AudioEncoder> speech_encoder_; + rtc::scoped_ptr<AudioEncoder> cng_encoder_; + rtc::scoped_ptr<AudioEncoder> red_encoder_; + rtc::scoped_ptr<AudioDecoder> isac_decoder_; + AudioEncoder* encoder_stack_ = nullptr; + LockedIsacBandwidthInfo isac_bandwidth_info_; + + RTC_DISALLOW_COPY_AND_ASSIGN(RentACodec); +}; + +} // namespace acm2 +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_ACM2_RENT_A_CODEC_H_ diff --git a/webrtc/modules/audio_coding/acm2/rent_a_codec_unittest.cc b/webrtc/modules/audio_coding/acm2/rent_a_codec_unittest.cc new file mode 100644 index 0000000000..e838488e53 --- /dev/null +++ b/webrtc/modules/audio_coding/acm2/rent_a_codec_unittest.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2015 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 "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/arraysize.h" +#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" +#include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" + +namespace webrtc { +namespace acm2 { + +using ::testing::Return; + +namespace { +const int kDataLengthSamples = 80; +const int kPacketSizeSamples = 2 * kDataLengthSamples; +const int16_t kZeroData[kDataLengthSamples] = {0}; +const CodecInst kDefaultCodecInst = {0, "pcmu", 8000, kPacketSizeSamples, + 1, 64000}; +const int kCngPt = 13; +} // namespace + +class RentACodecTestF : public ::testing::Test { + protected: + void CreateCodec() { + speech_encoder_ = rent_a_codec_.RentEncoder(kDefaultCodecInst); + ASSERT_TRUE(speech_encoder_); + RentACodec::StackParameters param; + param.use_cng = true; + param.speech_encoder = speech_encoder_; + encoder_ = rent_a_codec_.RentEncoderStack(¶m); + } + + void EncodeAndVerify(size_t expected_out_length, + uint32_t expected_timestamp, + int expected_payload_type, + int expected_send_even_if_empty) { + uint8_t out[kPacketSizeSamples]; + AudioEncoder::EncodedInfo encoded_info; + encoded_info = + encoder_->Encode(timestamp_, kZeroData, kPacketSizeSamples, out); + timestamp_ += kDataLengthSamples; + EXPECT_TRUE(encoded_info.redundant.empty()); + EXPECT_EQ(expected_out_length, encoded_info.encoded_bytes); + EXPECT_EQ(expected_timestamp, encoded_info.encoded_timestamp); + if (expected_payload_type >= 0) + EXPECT_EQ(expected_payload_type, encoded_info.payload_type); + if (expected_send_even_if_empty >= 0) + EXPECT_EQ(static_cast<bool>(expected_send_even_if_empty), + encoded_info.send_even_if_empty); + } + + RentACodec rent_a_codec_; + AudioEncoder* speech_encoder_ = nullptr; + AudioEncoder* encoder_ = nullptr; + uint32_t timestamp_ = 0; +}; + +// This test verifies that CNG frames are delivered as expected. Since the frame +// size is set to 20 ms, we expect the first encode call to produce no output +// (which is signaled as 0 bytes output of type kNoEncoding). The next encode +// call should produce one SID frame of 9 bytes. The third call should not +// result in any output (just like the first one). The fourth and final encode +// call should produce an "empty frame", which is like no output, but with +// AudioEncoder::EncodedInfo::send_even_if_empty set to true. (The reason to +// produce an empty frame is to drive sending of DTMF packets in the RTP/RTCP +// module.) +TEST_F(RentACodecTestF, VerifyCngFrames) { + CreateCodec(); + uint32_t expected_timestamp = timestamp_; + // Verify no frame. + { + SCOPED_TRACE("First encoding"); + EncodeAndVerify(0, expected_timestamp, -1, -1); + } + + // Verify SID frame delivered. + { + SCOPED_TRACE("Second encoding"); + EncodeAndVerify(9, expected_timestamp, kCngPt, 1); + } + + // Verify no frame. + { + SCOPED_TRACE("Third encoding"); + EncodeAndVerify(0, expected_timestamp, -1, -1); + } + + // Verify NoEncoding. + expected_timestamp += 2 * kDataLengthSamples; + { + SCOPED_TRACE("Fourth encoding"); + EncodeAndVerify(0, expected_timestamp, kCngPt, 1); + } +} + +TEST(RentACodecTest, ExternalEncoder) { + const int kSampleRateHz = 8000; + MockAudioEncoder external_encoder; + EXPECT_CALL(external_encoder, SampleRateHz()) + .WillRepeatedly(Return(kSampleRateHz)); + EXPECT_CALL(external_encoder, NumChannels()).WillRepeatedly(Return(1)); + EXPECT_CALL(external_encoder, SetFec(false)).WillRepeatedly(Return(true)); + + RentACodec rac; + RentACodec::StackParameters param; + param.speech_encoder = &external_encoder; + EXPECT_EQ(&external_encoder, rac.RentEncoderStack(¶m)); + const int kPacketSizeSamples = kSampleRateHz / 100; + int16_t audio[kPacketSizeSamples] = {0}; + uint8_t encoded[kPacketSizeSamples]; + AudioEncoder::EncodedInfo info; + + { + ::testing::InSequence s; + info.encoded_timestamp = 0; + EXPECT_CALL(external_encoder, + EncodeInternal(0, rtc::ArrayView<const int16_t>(audio), + arraysize(encoded), encoded)) + .WillOnce(Return(info)); + EXPECT_CALL(external_encoder, Mark("A")); + EXPECT_CALL(external_encoder, Mark("B")); + info.encoded_timestamp = 2; + EXPECT_CALL(external_encoder, + EncodeInternal(2, rtc::ArrayView<const int16_t>(audio), + arraysize(encoded), encoded)) + .WillOnce(Return(info)); + EXPECT_CALL(external_encoder, Die()); + } + + info = rac.GetEncoderStack()->Encode(0, audio, arraysize(encoded), encoded); + EXPECT_EQ(0u, info.encoded_timestamp); + external_encoder.Mark("A"); + + // Change to internal encoder. + CodecInst codec_inst = kDefaultCodecInst; + codec_inst.pacsize = kPacketSizeSamples; + param.speech_encoder = rac.RentEncoder(codec_inst); + ASSERT_TRUE(param.speech_encoder); + EXPECT_EQ(param.speech_encoder, rac.RentEncoderStack(¶m)); + + // Don't expect any more calls to the external encoder. + info = rac.GetEncoderStack()->Encode(1, audio, arraysize(encoded), encoded); + external_encoder.Mark("B"); + + // Change back to external encoder again. + param.speech_encoder = &external_encoder; + EXPECT_EQ(&external_encoder, rac.RentEncoderStack(¶m)); + info = rac.GetEncoderStack()->Encode(2, audio, arraysize(encoded), encoded); + EXPECT_EQ(2u, info.encoded_timestamp); +} + +// Verify that the speech encoder's Reset method is called when CNG or RED +// (or both) are switched on, but not when they're switched off. +void TestCngAndRedResetSpeechEncoder(bool use_cng, bool use_red) { + MockAudioEncoder speech_encoder; + EXPECT_CALL(speech_encoder, NumChannels()).WillRepeatedly(Return(1)); + EXPECT_CALL(speech_encoder, Max10MsFramesInAPacket()) + .WillRepeatedly(Return(2)); + EXPECT_CALL(speech_encoder, SampleRateHz()).WillRepeatedly(Return(8000)); + EXPECT_CALL(speech_encoder, SetFec(false)).WillRepeatedly(Return(true)); + { + ::testing::InSequence s; + EXPECT_CALL(speech_encoder, Mark("disabled")); + EXPECT_CALL(speech_encoder, Mark("enabled")); + if (use_cng || use_red) + EXPECT_CALL(speech_encoder, Reset()); + EXPECT_CALL(speech_encoder, Die()); + } + + RentACodec::StackParameters param1, param2; + param1.speech_encoder = &speech_encoder; + param2.speech_encoder = &speech_encoder; + param2.use_cng = use_cng; + param2.use_red = use_red; + speech_encoder.Mark("disabled"); + RentACodec rac; + rac.RentEncoderStack(¶m1); + speech_encoder.Mark("enabled"); + rac.RentEncoderStack(¶m2); +} + +TEST(RentACodecTest, CngResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(true, false); +} + +TEST(RentACodecTest, RedResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(false, true); +} + +TEST(RentACodecTest, CngAndRedResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(true, true); +} + +TEST(RentACodecTest, NoCngAndRedNoSpeechEncoderReset) { + TestCngAndRedResetSpeechEncoder(false, false); +} + +TEST(RentACodecTest, RentEncoderError) { + const CodecInst codec_inst = { + 0, "Robert'); DROP TABLE Students;", 8000, 160, 1, 64000}; + RentACodec rent_a_codec; + EXPECT_FALSE(rent_a_codec.RentEncoder(codec_inst)); +} + +#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(RentACodecTest, RentEncoderStackWithoutSpeechEncoder) { + RentACodec::StackParameters sp; + EXPECT_EQ(nullptr, sp.speech_encoder); + EXPECT_DEATH(RentACodec().RentEncoderStack(&sp), ""); +} +#endif + +} // namespace acm2 +} // namespace webrtc |