From b28bfa7efcffa3800f64ebd6aeb54face45c180d Mon Sep 17 00:00:00 2001 From: "minyue@webrtc.org" Date: Fri, 21 Mar 2014 12:07:40 +0000 Subject: [PATCH] Adding FEC support in NetEq 4. R=henrik.lundin@webrtc.org, turaj@webrtc.org TEST=passes all trybots BUG= Review URL: https://webrtc-codereview.appspot.com/9999004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5748 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../audio_coding/neteq4/audio_decoder.cc | 10 + .../audio_coding/neteq4/audio_decoder_impl.cc | 25 +++ .../audio_coding/neteq4/audio_decoder_impl.h | 5 + .../neteq4/interface/audio_decoder.h | 11 ++ .../neteq4/mock/mock_payload_splitter.h | 2 + webrtc/modules/audio_coding/neteq4/neteq.gypi | 2 + .../modules/audio_coding/neteq4/neteq_impl.cc | 25 ++- .../audio_coding/neteq4/neteq_tests.gypi | 16 ++ .../audio_coding/neteq4/packet_buffer.cc | 11 +- .../audio_coding/neteq4/payload_splitter.cc | 58 +++++- .../audio_coding/neteq4/payload_splitter.h | 9 +- .../neteq4/payload_splitter_unittest.cc | 83 ++++++++ .../test/neteq_opus_fec_quality_test.cc | 178 ++++++++++++++++++ .../audio_coding/neteq4/timestamp_scaler.cc | 3 +- .../neteq4/tools/neteq_quality_test.cc | 113 +++++++++++ .../neteq4/tools/neteq_quality_test.h | 100 ++++++++++ 16 files changed, 643 insertions(+), 8 deletions(-) create mode 100644 webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc create mode 100644 webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc create mode 100644 webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc b/webrtc/modules/audio_coding/neteq4/audio_decoder.cc index 35422e3f9f..2a252e6f11 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc +++ b/webrtc/modules/audio_coding/neteq4/audio_decoder.cc @@ -41,6 +41,16 @@ int AudioDecoder::PacketDuration(const uint8_t* encoded, size_t encoded_len) { return kNotImplemented; } +int AudioDecoder::PacketDurationRedundant(const uint8_t* encoded, + size_t encoded_len) const { + return kNotImplemented; +} + +bool AudioDecoder::PacketHasFec(const uint8_t* encoded, + size_t encoded_len) const { + return false; +} + NetEqDecoder AudioDecoder::codec_type() const { return codec_type_; } bool AudioDecoder::CodecSupported(NetEqDecoder codec_type) { diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc index 5296a1bd0f..94e507e029 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc +++ b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc @@ -458,6 +458,19 @@ int AudioDecoderOpus::Decode(const uint8_t* encoded, size_t encoded_len, return ret; } +int AudioDecoderOpus::DecodeRedundant(const uint8_t* encoded, + size_t encoded_len, int16_t* decoded, + SpeechType* speech_type) { + int16_t temp_type = 1; // Default is speech. + int16_t ret = WebRtcOpus_DecodeFec(static_cast(state_), encoded, + static_cast(encoded_len), decoded, + &temp_type); + if (ret > 0) + ret *= static_cast(channels_); // Return total number of samples. + *speech_type = ConvertSpeechType(temp_type); + return ret; +} + int AudioDecoderOpus::Init() { return WebRtcOpus_DecoderInitNew(static_cast(state_)); } @@ -467,6 +480,18 @@ int AudioDecoderOpus::PacketDuration(const uint8_t* encoded, return WebRtcOpus_DurationEst(static_cast(state_), encoded, static_cast(encoded_len)); } + +int AudioDecoderOpus::PacketDurationRedundant(const uint8_t* encoded, + size_t encoded_len) const { + return WebRtcOpus_FecDurationEst(encoded, static_cast(encoded_len)); +} + +bool AudioDecoderOpus::PacketHasFec(const uint8_t* encoded, + size_t encoded_len) const { + int fec; + fec = WebRtcOpus_PacketHasFec(encoded, static_cast(encoded_len)); + return (fec == 1); +} #endif AudioDecoderCng::AudioDecoderCng(enum NetEqDecoder type) diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h index aa35db7808..5df649a2fc 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h +++ b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h @@ -236,8 +236,13 @@ class AudioDecoderOpus : public AudioDecoder { virtual ~AudioDecoderOpus(); virtual int Decode(const uint8_t* encoded, size_t encoded_len, int16_t* decoded, SpeechType* speech_type); + virtual int DecodeRedundant(const uint8_t* encoded, size_t encoded_len, + int16_t* decoded, SpeechType* speech_type); virtual int Init(); virtual int PacketDuration(const uint8_t* encoded, size_t encoded_len); + virtual int PacketDurationRedundant(const uint8_t* encoded, + size_t encoded_len) const; + virtual bool PacketHasFec(const uint8_t* encoded, size_t encoded_len) const; private: DISALLOW_COPY_AND_ASSIGN(AudioDecoderOpus); diff --git a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h b/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h index f3bcc711f3..6b4b19127c 100644 --- a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h +++ b/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h @@ -108,6 +108,17 @@ class AudioDecoder { // is available, or -1 in case of an error. virtual int PacketDuration(const uint8_t* encoded, size_t encoded_len); + // Returns the duration in samples of the redandant payload in |encoded| which + // is |encoded_len| bytes long. Returns kNotImplemented if no duration + // estimate is available, or -1 in case of an error. + virtual int PacketDurationRedundant(const uint8_t* encoded, + size_t encoded_len) const; + + // Detects whether a packet has forward error correction. The packet is + // comprised of the samples in |encoded| which is |encoded_len| bytes long. + // Returns true if the packet has FEC and false otherwise. + virtual bool PacketHasFec(const uint8_t* encoded, size_t encoded_len) const; + virtual NetEqDecoder codec_type() const; // Returns the underlying decoder state. diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h b/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h index f3d8c9b048..369dfc43f4 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h +++ b/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h @@ -21,6 +21,8 @@ class MockPayloadSplitter : public PayloadSplitter { public: MOCK_METHOD1(SplitRed, int(PacketList* packet_list)); + MOCK_METHOD2(SplitFec, + int(PacketList* packet_list, DecoderDatabase* decoder_database)); MOCK_METHOD2(CheckRedPayloads, int(PacketList* packet_list, const DecoderDatabase& decoder_database)); MOCK_METHOD2(SplitAudio, diff --git a/webrtc/modules/audio_coding/neteq4/neteq.gypi b/webrtc/modules/audio_coding/neteq4/neteq.gypi index 6580d6832f..3e7ede4e66 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq.gypi +++ b/webrtc/modules/audio_coding/neteq4/neteq.gypi @@ -189,6 +189,8 @@ 'tools/neteq_performance_test.h', 'tools/rtp_generator.cc', 'tools/rtp_generator.h', + 'tools/neteq_quality_test.cc', + 'tools/neteq_quality_test.h', ], }, # neteq_unittest_tools ], # targets diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc index 2b98d05638..8b7e5173a1 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc +++ b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc @@ -512,6 +512,19 @@ int NetEqImpl::InsertPacketInternal(const WebRtcRTPHeader& rtp_header, memcpy(&main_header, &packet_list.front()->header, sizeof(main_header)); } + // Check for FEC in packets, and separate payloads into several packets. + int ret = payload_splitter_->SplitFec(&packet_list, decoder_database_.get()); + if (ret != PayloadSplitter::kOK) { + LOG_FERR1(LS_WARNING, SplitFec, packet_list.size()); + PacketBuffer::DeleteAllPackets(&packet_list); + switch (ret) { + case PayloadSplitter::kUnknownPayloadType: + return kUnknownRtpPayloadType; + default: + return kOtherError; + } + } + // Check payload types. if (decoder_database_->CheckPayloadTypes(packet_list) == DecoderDatabase::kDecoderNotFound) { @@ -561,7 +574,7 @@ int NetEqImpl::InsertPacketInternal(const WebRtcRTPHeader& rtp_header, // Split payloads into smaller chunks. This also verifies that all payloads // are of a known payload type. SplitAudio() method is protected against // sync-packets. - int ret = payload_splitter_->SplitAudio(&packet_list, *decoder_database_); + ret = payload_splitter_->SplitAudio(&packet_list, *decoder_database_); if (ret != PayloadSplitter::kOK) { LOG_FERR1(LS_WARNING, SplitAudio, packet_list.size()); PacketBuffer::DeleteAllPackets(&packet_list); @@ -1777,8 +1790,14 @@ int NetEqImpl::ExtractPackets(int required_samples, PacketList* packet_list) { AudioDecoder* decoder = decoder_database_->GetDecoder( packet->header.payloadType); if (decoder) { - packet_duration = packet->sync_packet ? decoder_frame_length_ : - decoder->PacketDuration(packet->payload, packet->payload_length); + if (packet->sync_packet) { + packet_duration = decoder_frame_length_; + } else { + packet_duration = packet->primary ? + decoder->PacketDuration(packet->payload, packet->payload_length) : + decoder->PacketDurationRedundant(packet->payload, + packet->payload_length); + } } else { LOG_FERR1(LS_WARNING, GetDecoder, packet->header.payloadType) << "Could not find a decoder for a packet about to be extracted."; diff --git a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi index e1fcae7aad..a73c8a2790 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi @@ -166,6 +166,22 @@ ], }, + { + 'target_name': 'neteq4_opus_fec_quality_test', + 'type': 'executable', + 'dependencies': [ + 'NetEq4', + 'neteq_unittest_tools', + 'webrtc_opus', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/test/test.gyp:test_support_main', + ], + 'sources': [ + 'test/neteq_opus_fec_quality_test.cc', + ], + }, + { 'target_name': 'NetEq4TestTools', # Collection of useful functions used in other tests. diff --git a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc b/webrtc/modules/audio_coding/neteq4/packet_buffer.cc index c461463d9c..0cc0854fc5 100644 --- a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc +++ b/webrtc/modules/audio_coding/neteq4/packet_buffer.cc @@ -238,8 +238,15 @@ int PacketBuffer::NumSamplesInBuffer(DecoderDatabase* decoder_database, AudioDecoder* decoder = decoder_database->GetDecoder(packet->header.payloadType); if (decoder) { - int duration = packet->sync_packet ? last_duration : - decoder->PacketDuration(packet->payload, packet->payload_length); + int duration; + if (packet->sync_packet) { + duration = last_duration; + } else { + duration = packet->primary ? + decoder->PacketDuration(packet->payload, packet->payload_length) : + decoder->PacketDurationRedundant(packet->payload, + packet->payload_length); + } if (duration >= 0) { last_duration = duration; // Save the most up-to-date (valid) duration. } diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc b/webrtc/modules/audio_coding/neteq4/payload_splitter.cc index 56039a57ec..0209ad928f 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc +++ b/webrtc/modules/audio_coding/neteq4/payload_splitter.cc @@ -119,6 +119,62 @@ int PayloadSplitter::SplitRed(PacketList* packet_list) { return ret; } +int PayloadSplitter::SplitFec(PacketList* packet_list, + DecoderDatabase* decoder_database) { + PacketList::iterator it = packet_list->begin(); + // Iterate through all packets in |packet_list|. + while (it != packet_list->end()) { + Packet* packet = (*it); // Just to make the notation more intuitive. + // Get codec type for this payload. + uint8_t payload_type = packet->header.payloadType; + const DecoderDatabase::DecoderInfo* info = + decoder_database->GetDecoderInfo(payload_type); + if (!info) { + return kUnknownPayloadType; + } + // No splitting for a sync-packet. + if (packet->sync_packet) { + ++it; + continue; + } + + // Not an FEC packet. + AudioDecoder* decoder = decoder_database->GetDecoder(payload_type); + if (!decoder->PacketHasFec(packet->payload, packet->payload_length)) { + ++it; + continue; + } + + switch (info->codec_type) { + case kDecoderOpus: + case kDecoderOpus_2ch: { + Packet* new_packet = new Packet; + + new_packet->header = packet->header; + int duration = decoder-> + PacketDurationRedundant(packet->payload, + packet->payload_length) * 3 / 2; + new_packet->header.timestamp -= duration; + new_packet->payload = new uint8_t[packet->payload_length]; + memcpy(new_packet->payload, packet->payload, packet->payload_length); + new_packet->payload_length = packet->payload_length; + new_packet->primary = false; + new_packet->waiting_time = packet->waiting_time; + new_packet->sync_packet = packet->sync_packet; + + packet_list->insert(it, new_packet); + break; + } + default: { + return kFecSplitError; + } + } + + ++it; + } + return kOK; +} + int PayloadSplitter::CheckRedPayloads(PacketList* packet_list, const DecoderDatabase& decoder_database) { PacketList::iterator it = packet_list->begin(); @@ -283,7 +339,7 @@ int PayloadSplitter::SplitAudio(PacketList* packet_list, // increment it manually. it = packet_list->erase(it); } - return 0; + return kOK; } void PayloadSplitter::SplitBySamples(const Packet* packet, diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.h b/webrtc/modules/audio_coding/neteq4/payload_splitter.h index 3768c2f2b1..0f6caed8a8 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter.h +++ b/webrtc/modules/audio_coding/neteq4/payload_splitter.h @@ -32,7 +32,8 @@ class PayloadSplitter { kTooLargePayload = -1, kFrameSplitError = -2, kUnknownPayloadType = -3, - kRedLengthMismatch = -4 + kRedLengthMismatch = -4, + kFecSplitError = -5, }; PayloadSplitter() {} @@ -47,6 +48,12 @@ class PayloadSplitter { // Returns kOK or an error. virtual int SplitRed(PacketList* packet_list); + // Iterates through |packet_list| and, duplicate each audio payload that has + // FEC as new packet for redundant decoding. The decoder database is needed to + // get information about which payload type each packet contains. + virtual int SplitFec(PacketList* packet_list, + DecoderDatabase* decoder_database); + // Checks all packets in |packet_list|. Packets that are DTMF events or // comfort noise payloads are kept. Except that, only one single payload type // is accepted. Any packet with another payload type is discarded. diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc b/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc index 5a7a6ca3e4..97bdc5c22d 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc +++ b/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc @@ -91,6 +91,34 @@ Packet* CreateRedPayload(int num_payloads, return packet; } + +// A possible Opus packet that contains FEC is the following. +// The frame is 20 ms in duration. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0|0|0|0|1|0|0|0|x|1|x|x|x|x|x|x|x| | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +// | Compressed frame 1 (N-2 bytes)... : +// : | +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Packet* CreateOpusFecPacket(uint8_t payload_type, int payload_length, + uint8_t payload_value) { + Packet* packet = new Packet; + packet->header.payloadType = payload_type; + packet->header.timestamp = kBaseTimestamp; + packet->header.sequenceNumber = kSequenceNumber; + packet->payload_length = payload_length; + uint8_t* payload = new uint8_t[packet->payload_length]; + payload[0] = 0x08; + payload[1] = 0x40; + memset(&payload[2], payload_value, payload_length - 2); + packet->payload = payload; + return packet; +} + // Create a packet with all payload bytes set to |payload_value|. Packet* CreatePacket(uint8_t payload_type, int payload_length, uint8_t payload_value) { @@ -691,4 +719,59 @@ TEST(IlbcPayloadSplitter, UnevenPayload) { EXPECT_CALL(decoder_database, Die()); } +TEST(FecPayloadSplitter, MixedPayload) { + PacketList packet_list; + DecoderDatabase decoder_database; + + decoder_database.RegisterPayload(0, kDecoderOpus); + decoder_database.RegisterPayload(1, kDecoderPCMu); + + Packet* packet = CreateOpusFecPacket(0, 10, 0xFF); + packet_list.push_back(packet); + + packet = CreatePacket(0, 10, 0); // Non-FEC Opus payload. + packet_list.push_back(packet); + + packet = CreatePacket(1, 10, 0); // Non-Opus payload. + packet_list.push_back(packet); + + PayloadSplitter splitter; + EXPECT_EQ(PayloadSplitter::kOK, + splitter.SplitFec(&packet_list, &decoder_database)); + EXPECT_EQ(4u, packet_list.size()); + + // Check first packet. + packet = packet_list.front(); + EXPECT_EQ(0, packet->header.payloadType); + EXPECT_EQ(kBaseTimestamp - 20 * 48, packet->header.timestamp); + EXPECT_EQ(10, packet->payload_length); + EXPECT_FALSE(packet->primary); + delete [] packet->payload; + delete packet; + packet_list.pop_front(); + + // Check second packet. + packet = packet_list.front(); + EXPECT_EQ(0, packet->header.payloadType); + EXPECT_EQ(kBaseTimestamp, packet->header.timestamp); + EXPECT_EQ(10, packet->payload_length); + EXPECT_TRUE(packet->primary); + delete [] packet->payload; + delete packet; + packet_list.pop_front(); + + // Check third packet. + packet = packet_list.front(); + VerifyPacket(packet, 10, 0, kSequenceNumber, kBaseTimestamp, 0, true); + delete [] packet->payload; + delete packet; + packet_list.pop_front(); + + // Check fourth packet. + packet = packet_list.front(); + VerifyPacket(packet, 10, 1, kSequenceNumber, kBaseTimestamp, 0, true); + delete [] packet->payload; + delete packet; +} + } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc b/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc new file mode 100644 index 0000000000..aa4522b1fa --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc @@ -0,0 +1,178 @@ +/* + * 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 +#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" +#include "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h" +#include "webrtc/test/testsupport/fileutils.h" + +using google::RegisterFlagValidator; +using google::ParseCommandLineFlags; +using std::string; +using testing::InitGoogleTest; + +namespace webrtc { +namespace test { + +static const int kOpusBlockDurationMs = 20; +static const int kOpusInputSamplingKhz = 48; +static const int kOpusOutputSamplingKhz = 32; + +static bool ValidateInFilename(const char* flagname, const string& value) { + FILE* fid = fopen(value.c_str(), "rb"); + if (fid != NULL) { + fclose(fid); + return true; + } + printf("Invalid input filename."); + return false; +} +DEFINE_string(in_filename, + ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm"), + "Filename for input audio (should be 48 kHz sampled raw data)."); +static const bool in_filename_dummy = + RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename); + +static bool ValidateOutFilename(const char* flagname, const string& value) { + FILE* fid = fopen(value.c_str(), "wb"); + if (fid != NULL) { + fclose(fid); + return true; + } + printf("Invalid output filename."); + return false; +} +DEFINE_string(out_filename, OutputPath() + "neteq4_opus_fec_quality_test.pcm", + "Name of output audio file."); +static const bool out_filename_dummy = + RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename); + +static bool ValidateChannels(const char* flagname, int32_t value) { + if (value == 1 || value == 2) + return true; + printf("Invalid number of channels, should be either 1 or 2."); + return false; +} +DEFINE_int32(channels, 1, "Number of channels in input audio."); +static const bool channels_dummy = + RegisterFlagValidator(&FLAGS_channels, &ValidateChannels); + +static bool ValidateBitRate(const char* flagname, int32_t value) { + if (value >= 6 && value <= 510) + return true; + printf("Invalid bit rate, should be between 6 and 510 kbps."); + return false; +} +DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps)."); +static const bool bit_rate_dummy = + RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate); + +static bool ValidatePacketLossRate(const char* flagname, int32_t value) { + if (value >= 0 && value <= 100) + return true; + printf("Invalid packet loss percentile, should be between 0 and 100."); + return false; +} +DEFINE_int32(reported_loss_rate, 10, "Reported percentile of packet loss."); +static const bool reported_loss_rate_dummy = + RegisterFlagValidator(&FLAGS_reported_loss_rate, &ValidatePacketLossRate); +DEFINE_int32(actual_loss_rate, 0, "Actual percentile of packet loss."); +static const bool actual_loss_rate_dummy = + RegisterFlagValidator(&FLAGS_actual_loss_rate, &ValidatePacketLossRate); + +static bool ValidateRuntime(const char* flagname, int32_t value) { + if (value > 0) + return true; + printf("Invalid runtime, should be greater than 0."); + return false; +} +DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds)."); +static const bool runtime_dummy = + RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime); + +DEFINE_bool(fec, true, "Whether to enable FEC for encoding."); + +class NetEqOpusFecQualityTest : public NetEqQualityTest { + protected: + NetEqOpusFecQualityTest(); + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + virtual int EncodeBlock(int16_t* in_data, int block_size_samples, + uint8_t* payload, int max_bytes); + virtual bool PacketLost(int packet_input_time_ms); + private: + WebRtcOpusEncInst* opus_encoder_; + int channels_; + int bit_rate_kbps_; + bool fec_; + int target_loss_rate_; + int actual_loss_rate_; +}; + +NetEqOpusFecQualityTest::NetEqOpusFecQualityTest() + : NetEqQualityTest(kOpusBlockDurationMs, kOpusInputSamplingKhz, + kOpusOutputSamplingKhz, + (FLAGS_channels == 1) ? kDecoderOpus : kDecoderOpus_2ch, + FLAGS_channels, 0.0f, FLAGS_in_filename, + FLAGS_out_filename), + opus_encoder_(NULL), + channels_(FLAGS_channels), + bit_rate_kbps_(FLAGS_bit_rate_kbps), + fec_(FLAGS_fec), + target_loss_rate_(FLAGS_reported_loss_rate), + actual_loss_rate_(FLAGS_actual_loss_rate) { +} + +void NetEqOpusFecQualityTest::SetUp() { + // Create encoder memory. + WebRtcOpus_EncoderCreate(&opus_encoder_, channels_); + ASSERT_TRUE(opus_encoder_ != NULL); + // Set bitrate. + EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_kbps_ * 1000)); + if (fec_) { + EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_)); + EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_, + target_loss_rate_)); + } + NetEqQualityTest::SetUp(); +} + +void NetEqOpusFecQualityTest::TearDown() { + // Free memory. + EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_)); + NetEqQualityTest::TearDown(); +} + +int NetEqOpusFecQualityTest::EncodeBlock(int16_t* in_data, + int block_size_samples, + uint8_t* payload, int max_bytes) { + int value = WebRtcOpus_Encode(opus_encoder_, in_data, + block_size_samples, max_bytes, + payload); + EXPECT_GT(value, 0); + return value; +} + +bool NetEqOpusFecQualityTest::PacketLost(int packet_input_time_ms) { + static int packets = 0, lost_packets = 0; + packets++; + if (lost_packets * 100 < actual_loss_rate_ * packets) { + lost_packets++; + return true; + } + return false; +} + +TEST_F(NetEqOpusFecQualityTest, Test) { + Simulate(FLAGS_runtime_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc b/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc index b2b5b40a3a..fb47616cfe 100644 --- a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc +++ b/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc @@ -55,7 +55,8 @@ uint32_t TimestampScaler::ToInternal(uint32_t external_timestamp, // Use timestamp scaling with factor 2/3 (32 kHz sample rate, but RTP // timestamps run on 48 kHz). // TODO(tlegrand): Remove scaling for kDecoderCNGswb48kHz once ACM has - // full 48 kHz support. + // full 48 kHz support. Change also ought to be made in + // PayloadSplitter::SplitFec(). numerator_ = 2; denominator_ = 3; } diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc new file mode 100644 index 0000000000..c56e5b98e1 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc @@ -0,0 +1,113 @@ +/* + * 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 +#include "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h" + +namespace webrtc { +namespace test { + +const uint8_t kPayloadType = 95; +const int kOutputSizeMs = 10; + +NetEqQualityTest::NetEqQualityTest(int block_duration_ms, + int in_sampling_khz, + int out_sampling_khz, + enum NetEqDecoder decoder_type, + int channels, + double drift_factor, + std::string in_filename, + std::string out_filename) + : decoded_time_ms_(0), + decodable_time_ms_(0), + drift_factor_(drift_factor), + block_duration_ms_(block_duration_ms), + in_sampling_khz_(in_sampling_khz), + out_sampling_khz_(out_sampling_khz), + decoder_type_(decoder_type), + channels_(channels), + in_filename_(in_filename), + out_filename_(out_filename), + in_size_samples_(in_sampling_khz_ * block_duration_ms_), + out_size_samples_(out_sampling_khz_ * kOutputSizeMs), + payload_size_bytes_(0), + max_payload_bytes_(0), + in_file_(new InputAudioFile(in_filename_)), + out_file_(NULL), + rtp_generator_(new RtpGenerator(in_sampling_khz_, 0, 0, + decodable_time_ms_)), + neteq_(NetEq::Create(out_sampling_khz_ * 1000)) { + max_payload_bytes_ = in_size_samples_ * channels_ * sizeof(int16_t); + in_data_.reset(new int16_t[in_size_samples_ * channels_]); + payload_.reset(new uint8_t[max_payload_bytes_]); + out_data_.reset(new int16_t[out_size_samples_ * channels_]); +} + +void NetEqQualityTest::SetUp() { + out_file_ = fopen(out_filename_.c_str(), "wb"); + ASSERT_TRUE(out_file_ != NULL); + ASSERT_EQ(0, neteq_->RegisterPayloadType(decoder_type_, kPayloadType)); + rtp_generator_->set_drift_factor(drift_factor_); +} + +void NetEqQualityTest::TearDown() { + fclose(out_file_); +} + +int NetEqQualityTest::Transmit() { + int packet_input_time_ms = + rtp_generator_->GetRtpHeader(kPayloadType, in_size_samples_, + &rtp_header_); + if (!PacketLost(packet_input_time_ms) && payload_size_bytes_ > 0) { + int ret = neteq_->InsertPacket(rtp_header_, &payload_[0], + payload_size_bytes_, + packet_input_time_ms * in_sampling_khz_); + if (ret != NetEq::kOK) + return -1; + } + return packet_input_time_ms; +} + +int NetEqQualityTest::DecodeBlock() { + int channels; + int samples; + int ret = neteq_->GetAudio(out_size_samples_ * channels_, &out_data_[0], + &samples, &channels, NULL); + + if (ret != NetEq::kOK) { + return -1; + } else { + assert(channels == channels_); + assert(samples == kOutputSizeMs * out_sampling_khz_); + fwrite(&out_data_[0], sizeof(int16_t), samples * channels, out_file_); + return samples; + } +} + +void NetEqQualityTest::Simulate(int end_time_ms) { + int audio_size_samples; + + while (decoded_time_ms_ < end_time_ms) { + while (decodable_time_ms_ - kOutputSizeMs < decoded_time_ms_) { + ASSERT_TRUE(in_file_->Read(in_size_samples_ * channels_, &in_data_[0])); + payload_size_bytes_ = EncodeBlock(&in_data_[0], + in_size_samples_, &payload_[0], + max_payload_bytes_); + decodable_time_ms_ = Transmit() + block_duration_ms_; + } + audio_size_samples = DecodeBlock(); + if (audio_size_samples > 0) { + decoded_time_ms_ += audio_size_samples / out_sampling_khz_; + } + } +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h new file mode 100644 index 0000000000..03aabc8842 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h @@ -0,0 +1,100 @@ +/* + * 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_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_ + +#include +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class NetEqQualityTest : public ::testing::Test { + protected: + NetEqQualityTest(int block_duration_ms, + int in_sampling_khz, + int out_sampling_khz, + enum NetEqDecoder decoder_type, + int channels, + double drift_factor, + std::string in_filename, + std::string out_filename); + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + // EncodeBlock(...) does the following: + // 1. encodes a block of audio, saved in |in_data| and has a length of + // |block_size_samples| (samples per channel), + // 2. save the bit stream to |payload| of |max_bytes| bytes in size, + // 3. returns the length of the payload (in bytes), + virtual int EncodeBlock(int16_t* in_data, int block_size_samples, + uint8_t* payload, int max_bytes) = 0; + + // PacketLoss(...) determines weather a packet sent at an indicated time gets + // lost or not. + virtual bool PacketLost(int packet_input_time_ms) { return false; } + + // DecodeBlock() decodes a block of audio using the payload stored in + // |payload_| with the length of |payload_size_bytes_| (bytes). The decoded + // audio is to be stored in |out_data_|. + int DecodeBlock(); + + // Transmit() uses |rtp_generator_| to generate a packet and passes it to + // |neteq_|. + int Transmit(); + + // Simulate(...) runs encoding / transmitting / decoding up to |end_time_ms| + // (miliseconds), the resulted audio is stored in the file with the name of + // |out_filename_|. + void Simulate(int end_time_ms); + + private: + int decoded_time_ms_; + int decodable_time_ms_; + double drift_factor_; + const int block_duration_ms_; + const int in_sampling_khz_; + const int out_sampling_khz_; + const enum NetEqDecoder decoder_type_; + const int channels_; + const std::string in_filename_; + const std::string out_filename_; + + // Number of samples per channel in a frame. + const int in_size_samples_; + + // Expected output number of samples per channel in a frame. + const int out_size_samples_; + + int payload_size_bytes_; + int max_payload_bytes_; + + scoped_ptr in_file_; + FILE* out_file_; + + scoped_ptr rtp_generator_; + scoped_ptr neteq_; + + scoped_ptr in_data_; + scoped_ptr payload_; + scoped_ptr out_data_; + WebRtcRTPHeader rtp_header_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_