diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn index aae9e58776..1b0ceb0b4a 100644 --- a/webrtc/modules/audio_coding/BUILD.gn +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -991,7 +991,6 @@ if (rtc_include_tests) { deps += [ ":neteq", ":neteq_unittest_tools", - ":rtc_event_log_source", "../../system_wrappers:system_wrappers_default", "../../test:test_support", "//third_party/gflags", @@ -1040,8 +1039,17 @@ if (rtc_include_tests) { "neteq/tools/audio_sink.h", "neteq/tools/constant_pcm_packet_source.cc", "neteq/tools/constant_pcm_packet_source.h", + "neteq/tools/fake_decode_from_file.cc", + "neteq/tools/fake_decode_from_file.h", "neteq/tools/input_audio_file.cc", "neteq/tools/input_audio_file.h", + "neteq/tools/neteq_input.h", + "neteq/tools/neteq_packet_source_input.cc", + "neteq/tools/neteq_packet_source_input.h", + "neteq/tools/neteq_replacement_input.cc", + "neteq/tools/neteq_replacement_input.h", + "neteq/tools/neteq_test.cc", + "neteq/tools/neteq_test.h", "neteq/tools/output_audio_file.h", "neteq/tools/output_wav_file.h", "neteq/tools/packet.cc", @@ -1071,5 +1079,9 @@ if (rtc_include_tests) { "../../test:rtp_test_utils", "../rtp_rtcp", ] + + if (rtc_enable_protobuf) { + deps += [ ":rtc_event_log_source" ] + } } } diff --git a/webrtc/modules/audio_coding/neteq/neteq.gypi b/webrtc/modules/audio_coding/neteq/neteq.gypi index 4eed4e3283..a06b613938 100644 --- a/webrtc/modules/audio_coding/neteq/neteq.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq.gypi @@ -157,8 +157,6 @@ '<@(neteq_defines)', ], 'sources': [ - 'audio_decoder_impl.cc', - 'audio_decoder_impl.h', 'audio_decoder_unittest.cc', ], 'conditions': [ @@ -175,11 +173,45 @@ ], }, # audio_decoder_unittests + { + 'target_name': 'rtc_event_log_source', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', + '<(webrtc_root)/webrtc.gyp:rtc_event_log_proto', + ], + 'export_dependent_settings': [ + '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', + ], + 'sources': [ + 'tools/rtc_event_log_source.h', + 'tools/rtc_event_log_source.cc', + ], + }, + + { + 'target_name': 'neteq_unittest_proto', + 'type': 'static_library', + 'sources': [ + 'neteq_unittest.proto', + ], + 'variables': { + 'proto_in_dir': '.', + # Workaround to protect against gyp's pathname relativization when + # this file is included by modules.gyp. + 'proto_out_protected': 'webrtc/audio_coding/neteq', + 'proto_out_dir': '<(proto_out_protected)', + }, + 'includes': ['../../../build/protoc.gypi',], + }, + { 'target_name': 'neteq_unittest_tools', 'type': 'static_library', 'dependencies': [ + 'neteq', 'rtp_rtcp', + 'rtc_event_log_source', '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', '<(webrtc_root)/test/test.gyp:rtp_test_utils', ], @@ -198,8 +230,17 @@ 'tools/audio_sink.h', 'tools/constant_pcm_packet_source.cc', 'tools/constant_pcm_packet_source.h', + 'tools/fake_decode_from_file.cc', + 'tools/fake_decode_from_file.h', 'tools/input_audio_file.cc', 'tools/input_audio_file.h', + 'tools/neteq_input.h', + 'tools/neteq_packet_source_input.cc', + 'tools/neteq_packet_source_input.h', + 'tools/neteq_replacement_input.cc', + 'tools/neteq_replacement_input.h', + 'tools/neteq_test.cc', + 'tools/neteq_test.h', 'tools/output_audio_file.h', 'tools/output_wav_file.h', 'tools/packet.cc', diff --git a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi index 004556941b..203e96e867 100644 --- a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi @@ -10,21 +10,6 @@ 'conditions': [ ['enable_protobuf==1', { 'targets': [ - { - 'target_name': 'rtc_event_log_source', - 'type': 'static_library', - 'dependencies': [ - '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', - '<(webrtc_root)/webrtc.gyp:rtc_event_log_proto', - ], - 'export_dependent_settings': [ - '<(webrtc_root)/webrtc.gyp:rtc_event_log_parser', - ], - 'sources': [ - 'tools/rtc_event_log_source.h', - 'tools/rtc_event_log_source.cc', - ], - }, { 'target_name': 'neteq_rtpplay', 'type': 'executable', @@ -32,10 +17,8 @@ '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', '<(webrtc_root)/test/test.gyp:test_support', '<(webrtc_root)/system_wrappers/system_wrappers.gyp:metrics_default', - 'rtc_event_log_source', 'neteq', 'neteq_unittest_tools', - 'pcm16b', ], 'sources': [ 'tools/neteq_rtpplay.cc', @@ -43,21 +26,6 @@ 'defines': [ ], }, # neteq_rtpplay - { - 'target_name': 'neteq_unittest_proto', - 'type': 'static_library', - 'sources': [ - 'neteq_unittest.proto', - ], - 'variables': { - 'proto_in_dir': '.', - # Workaround to protect against gyp's pathname relativization when - # this file is included by modules.gyp. - 'proto_out_protected': 'webrtc/audio_coding/neteq', - 'proto_out_dir': '<(proto_out_protected)', - }, - 'includes': ['../../../build/protoc.gypi',], - }, ], }], ], diff --git a/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.cc b/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.cc new file mode 100644 index 0000000000..9ca5fb810b --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016 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/neteq/tools/fake_decode_from_file.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace test { + +int FakeDecodeFromFile::DecodeInternal(const uint8_t* encoded, + size_t encoded_len, + int /*sample_rate_hz*/, + int16_t* decoded, + SpeechType* speech_type) { + RTC_CHECK_GE(encoded_len, 8u); + uint32_t timestamp_to_decode = + ByteReader::ReadLittleEndian(encoded); + uint32_t samples_to_decode = + ByteReader::ReadLittleEndian(&encoded[4]); + + if (next_timestamp_from_input_ && + timestamp_to_decode != *next_timestamp_from_input_) { + // A gap in the timestamp sequence is detected. Skip the same number of + // samples from the file. + uint32_t jump = timestamp_to_decode - *next_timestamp_from_input_; + RTC_CHECK(input_->Seek(jump)); + } + + RTC_CHECK(input_->Read(static_cast(samples_to_decode), decoded)); + next_timestamp_from_input_ = + rtc::Optional(timestamp_to_decode + samples_to_decode); + + if (stereo_) { + InputAudioFile::DuplicateInterleaved(decoded, samples_to_decode, 2, + decoded); + samples_to_decode *= 2; + } + + *speech_type = kSpeech; + return samples_to_decode; +} + +void FakeDecodeFromFile::PrepareEncoded(uint32_t timestamp, + size_t samples, + rtc::ArrayView encoded) { + RTC_CHECK_GE(encoded.size(), 8u); + ByteWriter::WriteLittleEndian(&encoded[0], timestamp); + ByteWriter::WriteLittleEndian(&encoded[4], + rtc::checked_cast(samples)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h b/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h new file mode 100644 index 0000000000..2c51998261 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016 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_NETEQ_TOOLS_FAKE_DECODE_FROM_FILE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_FAKE_DECODE_FROM_FILE_H_ + +#include + +#include "webrtc/base/array_view.h" +#include "webrtc/base/optional.h" +#include "webrtc/modules/audio_coding/codecs/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" + +namespace webrtc { +namespace test { + +// Provides an AudioDecoder implementation that delivers audio data from a file. +// The "encoded" input should contain information about what RTP timestamp the +// encoding represents, and how many samples the decoder should produce for that +// encoding. A helper method PrepareEncoded is provided to prepare such +// encodings. If packets are missing, as determined from the timestamps, the +// file reading will skip forward to match the loss. +class FakeDecodeFromFile : public AudioDecoder { + public: + FakeDecodeFromFile(std::unique_ptr input, + int sample_rate_hz, + bool stereo) + : input_(std::move(input)), + sample_rate_hz_(sample_rate_hz), + stereo_(stereo) {} + + ~FakeDecodeFromFile() = default; + + void Reset() override {} + + int SampleRateHz() const override { return sample_rate_hz_; } + + size_t Channels() const override { return stereo_ ? 2 : 1; } + + int DecodeInternal(const uint8_t* encoded, + size_t encoded_len, + int sample_rate_hz, + int16_t* decoded, + SpeechType* speech_type) override; + + // Helper method. Writes |timestamp| and |samples| to |encoded| in a format + // that the FakeDecpdeFromFile decoder will understand. |encoded| must be at + // least 8 bytes long. + static void PrepareEncoded(uint32_t timestamp, + size_t samples, + rtc::ArrayView encoded); + + private: + std::unique_ptr input_; + rtc::Optional next_timestamp_from_input_; + const int sample_rate_hz_; + const bool stereo_; +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_FAKE_DECODE_FROM_FILE_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_input.h b/webrtc/modules/audio_coding/neteq/tools/neteq_input.h new file mode 100644 index 0000000000..8abec6303f --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_input.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 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_NETEQ_TOOLS_NETEQ_INPUT_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_INPUT_H_ + +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/optional.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" +#include "webrtc/modules/include/module_common_types.h" + +namespace webrtc { +namespace test { + +// Interface class for input to the NetEqTest class. +class NetEqInput { + public: + struct PacketData { + WebRtcRTPHeader header; + rtc::Buffer payload; + double time_ms; + }; + + virtual ~NetEqInput() = default; + + // Returns at what time (in ms) NetEq::InsertPacket should be called next, or + // empty if the source is out of packets. + virtual rtc::Optional NextPacketTime() const = 0; + + // Returns at what time (in ms) NetEq::GetAudio should be called next, or + // empty if no more output events are available. + virtual rtc::Optional NextOutputEventTime() const = 0; + + // Returns the time (in ms) for the next event from either NextPacketTime() + // or NextOutputEventTime(), or empty if both are out of events. + rtc::Optional NextEventTime() const { + const auto a = NextPacketTime(); + const auto b = NextOutputEventTime(); + // Return the minimum of non-empty |a| and |b|, or empty if both are empty. + if (a) { + return b ? rtc::Optional(std::min(*a, *b)) : a; + } + return b ? b : rtc::Optional(); + } + + // Returns the next packet to be inserted into NetEq. The packet following the + // returned one is pre-fetched in the NetEqInput object, such that future + // calls to NextPacketTime() or NextHeader() will return information from that + // packet. + virtual std::unique_ptr PopPacket() = 0; + + // Move to the next output event. This will make NextOutputEventTime() return + // a new value (potentially the same if several output events share the same + // time). + virtual void AdvanceOutputEvent() = 0; + + // Returns true if the source has come to an end. + virtual bool ended() const = 0; + + // Returns the RTP header for the next packet, i.e., the packet that will be + // delivered next by PopPacket(). + virtual rtc::Optional NextHeader() const = 0; +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_INPUT_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.cc new file mode 100644 index 0000000000..5c7c1edd5f --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 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/neteq/tools/neteq_packet_source_input.h" + +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h" + +namespace webrtc { +namespace test { + +NetEqPacketSourceInput::NetEqPacketSourceInput() : next_output_event_ms_(0) {} + +rtc::Optional NetEqPacketSourceInput::NextPacketTime() const { + return packet_ + ? rtc::Optional(static_cast(packet_->time_ms())) + : rtc::Optional(); +} + +rtc::Optional NetEqPacketSourceInput::NextHeader() const { + return packet_ ? rtc::Optional(packet_->header()) + : rtc::Optional(); +} + +void NetEqPacketSourceInput::LoadNextPacket() { + packet_ = source()->NextPacket(); +} + +std::unique_ptr NetEqPacketSourceInput::PopPacket() { + if (!packet_) { + return std::unique_ptr(); + } + std::unique_ptr packet_data(new PacketData); + packet_->ConvertHeader(&packet_data->header); + packet_data->payload.SetData(packet_->payload(), + packet_->payload_length_bytes()); + packet_data->time_ms = packet_->time_ms(); + + LoadNextPacket(); + + return packet_data; +} + +NetEqRtpDumpInput::NetEqRtpDumpInput(const std::string& file_name) + : source_(RtpFileSource::Create(file_name)) { + LoadNextPacket(); +} + +rtc::Optional NetEqRtpDumpInput::NextOutputEventTime() const { + return next_output_event_ms_; +} + +void NetEqRtpDumpInput::AdvanceOutputEvent() { + if (next_output_event_ms_) { + *next_output_event_ms_ += kOutputPeriodMs; + } + if (!NextPacketTime()) { + next_output_event_ms_ = rtc::Optional(); + } +} + +PacketSource* NetEqRtpDumpInput::source() { + return source_.get(); +} + +NetEqEventLogInput::NetEqEventLogInput(const std::string& file_name) + : source_(RtcEventLogSource::Create(file_name)) { + LoadNextPacket(); + AdvanceOutputEvent(); +} + +rtc::Optional NetEqEventLogInput::NextOutputEventTime() const { + return rtc::Optional(next_output_event_ms_); +} + +void NetEqEventLogInput::AdvanceOutputEvent() { + next_output_event_ms_ = + rtc::Optional(source_->NextAudioOutputEventMs()); + if (*next_output_event_ms_ == std::numeric_limits::max()) { + next_output_event_ms_ = rtc::Optional(); + } +} + +PacketSource* NetEqEventLogInput::source() { + return source_.get(); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.h b/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.h new file mode 100644 index 0000000000..4e350a6799 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 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_NETEQ_TOOLS_NETEQ_PACKET_SOURCE_INPUT_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PACKET_SOURCE_INPUT_H_ + +#include + +#include "webrtc/modules/audio_coding/neteq/tools/neteq_input.h" + +namespace webrtc { +namespace test { + +class RtpFileSource; +class RtcEventLogSource; + +// An adapter class to dress up a PacketSource object as a NetEqInput. +class NetEqPacketSourceInput : public NetEqInput { + public: + NetEqPacketSourceInput(); + rtc::Optional NextPacketTime() const override; + std::unique_ptr PopPacket() override; + rtc::Optional NextHeader() const override; + bool ended() const override { return !next_output_event_ms_; } + + protected: + virtual PacketSource* source() = 0; + void LoadNextPacket(); + + rtc::Optional next_output_event_ms_; + + private: + std::unique_ptr packet_; +}; + +// Implementation of NetEqPacketSourceInput to be used with an RtpFileSource. +class NetEqRtpDumpInput final : public NetEqPacketSourceInput { + public: + explicit NetEqRtpDumpInput(const std::string& file_name); + + rtc::Optional NextOutputEventTime() const override; + void AdvanceOutputEvent() override; + + protected: + PacketSource* source() override; + + private: + static constexpr int64_t kOutputPeriodMs = 10; + + std::unique_ptr source_; +}; + +// Implementation of NetEqPacketSourceInput to be used with an +// RtcEventLogSource. +class NetEqEventLogInput final : public NetEqPacketSourceInput { + public: + explicit NetEqEventLogInput(const std::string& file_name); + + rtc::Optional NextOutputEventTime() const override; + void AdvanceOutputEvent() override; + + protected: + PacketSource* source() override; + + private: + std::unique_ptr source_; +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PACKET_SOURCE_INPUT_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.cc new file mode 100644 index 0000000000..2a940fedc4 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.cc @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016 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/neteq/tools/neteq_replacement_input.h" + +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h" + +namespace webrtc { +namespace test { + +NetEqReplacementInput::NetEqReplacementInput( + std::unique_ptr source, + uint8_t replacement_payload_type, + const std::set& comfort_noise_types, + const std::set& forbidden_types) + : source_(std::move(source)), + replacement_payload_type_(replacement_payload_type), + comfort_noise_types_(comfort_noise_types), + forbidden_types_(forbidden_types) { + RTC_CHECK(source_); + packet_ = source_->PopPacket(); + ReplacePacket(); + RTC_CHECK(packet_); +} + +rtc::Optional NetEqReplacementInput::NextPacketTime() const { + return packet_ + ? rtc::Optional(static_cast(packet_->time_ms)) + : rtc::Optional(); +} + +rtc::Optional NetEqReplacementInput::NextOutputEventTime() const { + return source_->NextOutputEventTime(); +} + +std::unique_ptr NetEqReplacementInput::PopPacket() { + std::unique_ptr to_return = std::move(packet_); + packet_ = source_->PopPacket(); + ReplacePacket(); + return to_return; +} + +void NetEqReplacementInput::AdvanceOutputEvent() { + source_->AdvanceOutputEvent(); +} + +bool NetEqReplacementInput::ended() const { + return source_->ended(); +} + +rtc::Optional NetEqReplacementInput::NextHeader() const { + return source_->NextHeader(); +} + +void NetEqReplacementInput::ReplacePacket() { + if (!source_->NextPacketTime()) { + // End of input. Cannot do proper replacement on the very last packet, so we + // delete it instead. + packet_.reset(); + return; + } + + RTC_DCHECK(packet_); + + RTC_CHECK_EQ(forbidden_types_.count(packet_->header.header.payloadType), 0u) + << "Payload type " << static_cast(packet_->header.header.payloadType) + << " is forbidden."; + + // Check if this packet is comfort noise. + if (comfort_noise_types_.count(packet_->header.header.payloadType) != 0) { + // If CNG, simply insert a zero-energy one-byte payload. + uint8_t cng_payload[1] = {127}; // Max attenuation of CNG. + packet_->payload.SetData(cng_payload); + return; + } + + rtc::Optional next_hdr = source_->NextHeader(); + RTC_DCHECK(next_hdr); + uint8_t payload[8]; + uint32_t input_frame_size_timestamps = + next_hdr->timestamp - packet_->header.header.timestamp; + FakeDecodeFromFile::PrepareEncoded(packet_->header.header.timestamp, + input_frame_size_timestamps, payload); + packet_->payload.SetData(payload); + packet_->header.header.payloadType = replacement_payload_type_; + return; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.h b/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.h new file mode 100644 index 0000000000..07153dbb76 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 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_NETEQ_TOOLS_NETEQ_REPLACEMENT_INPUT_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_REPLACEMENT_INPUT_H_ + +#include + +#include "webrtc/modules/audio_coding/neteq/tools/neteq_input.h" + +namespace webrtc { +namespace test { + +// This class converts the packets from a NetEqInput to fake encodings to be +// decoded by a FakeDecodeFromFile decoder. +class NetEqReplacementInput : public NetEqInput { + public: + NetEqReplacementInput(std::unique_ptr source, + uint8_t replacement_payload_type, + const std::set& comfort_noise_types, + const std::set& forbidden_types); + + rtc::Optional NextPacketTime() const override; + rtc::Optional NextOutputEventTime() const override; + std::unique_ptr PopPacket() override; + void AdvanceOutputEvent() override; + bool ended() const override; + rtc::Optional NextHeader() const override; + + private: + void ReplacePacket(); + + std::unique_ptr source_; + const uint8_t replacement_payload_type_; + const std::set comfort_noise_types_; + const std::set forbidden_types_; + std::unique_ptr packet_; // The next packet to deliver. +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_REPLACEMENT_INPUT_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc index 38ab2e9ba4..9e3119b246 100644 --- a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc @@ -8,11 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -// TODO(hlundin): The functionality in this file should be moved into one or -// several classes. - -#include #include +#include #include // For ULONG_MAX returned by strtoul. #include #include // For strtoul. @@ -20,24 +17,20 @@ #include #include #include -#include #include #include "gflags/gflags.h" #include "webrtc/base/checks.h" -#include "webrtc/base/safe_conversions.h" -#include "webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.h" -#include "webrtc/modules/audio_coding/codecs/pcm16b/pcm16b.h" #include "webrtc/modules/audio_coding/neteq/include/neteq.h" +#include "webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h" #include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_test.h" #include "webrtc/modules/audio_coding/neteq/tools/output_audio_file.h" #include "webrtc/modules/audio_coding/neteq/tools/output_wav_file.h" -#include "webrtc/modules/audio_coding/neteq/tools/packet.h" -#include "webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.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/trace.h" -#include "webrtc/test/rtp_file_reader.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/typedefs.h" @@ -182,53 +175,11 @@ std::string CodecName(NetEqDecoder codec) { case NetEqDecoder::kDecoderCNGswb48kHz: return "comfort noise (48 kHz)"; default: - assert(false); + FATAL(); return "undefined"; } } -void RegisterPayloadType(NetEq* neteq, - NetEqDecoder codec, - const std::string& name, - google::int32 flag) { - if (neteq->RegisterPayloadType(codec, name, static_cast(flag))) { - std::cerr << "Cannot register payload type " << flag << " as " - << CodecName(codec) << std::endl; - exit(1); - } -} - -// Registers all decoders in |neteq|. -void RegisterPayloadTypes(NetEq* neteq) { - assert(neteq); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCMu, "pcmu", FLAGS_pcmu); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCMa, "pcma", FLAGS_pcma); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderILBC, "ilbc", FLAGS_ilbc); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderISAC, "isac", FLAGS_isac); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderISACswb, "isac-swb", - FLAGS_isac_swb); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderOpus, "opus", FLAGS_opus); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCM16B, "pcm16-nb", - FLAGS_pcm16b); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCM16Bwb, "pcm16-wb", - FLAGS_pcm16b_wb); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCM16Bswb32kHz, - "pcm16-swb32", FLAGS_pcm16b_swb32); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderPCM16Bswb48kHz, - "pcm16-swb48", FLAGS_pcm16b_swb48); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderG722, "g722", FLAGS_g722); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderAVT, "avt", FLAGS_avt); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderRED, "red", FLAGS_red); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderCNGnb, "cng-nb", - FLAGS_cn_nb); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderCNGwb, "cng-wb", - FLAGS_cn_wb); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderCNGswb32kHz, "cng-swb32", - FLAGS_cn_swb32); - RegisterPayloadType(neteq, NetEqDecoder::kDecoderCNGswb48kHz, "cng-swb48", - FLAGS_cn_swb48); -} - void PrintCodecMappingEntry(NetEqDecoder codec, google::int32 flag) { std::cout << CodecName(codec) << ": " << flag << std::endl; } @@ -255,11 +206,6 @@ void PrintCodecMapping() { PrintCodecMappingEntry(NetEqDecoder::kDecoderCNGswb48kHz, FLAGS_cn_swb48); } -bool IsComfortNoise(uint8_t payload_type) { - return payload_type == FLAGS_cn_nb || payload_type == FLAGS_cn_wb || - payload_type == FLAGS_cn_swb32 || payload_type == FLAGS_cn_swb48; -} - int CodecSampleRate(uint8_t payload_type) { if (payload_type == FLAGS_pcmu || payload_type == FLAGS_pcma || payload_type == FLAGS_ilbc || payload_type == FLAGS_pcm16b || @@ -279,98 +225,56 @@ int CodecSampleRate(uint8_t payload_type) { return -1; } -int CodecTimestampRate(uint8_t payload_type) { - return (payload_type == FLAGS_g722) ? 8000 : CodecSampleRate(payload_type); -} +// Class to let through only the packets with a given SSRC. Should be used as an +// outer layer on another NetEqInput object. +class FilterSsrcInput : public NetEqInput { + public: + FilterSsrcInput(std::unique_ptr source, uint32_t ssrc) + : source_(std::move(source)), ssrc_(ssrc) { + FindNextWithCorrectSsrc(); + } -size_t ReplacePayload(InputAudioFile* replacement_audio_file, - std::unique_ptr* replacement_audio, - std::unique_ptr* payload, - size_t* payload_mem_size_bytes, - size_t* frame_size_samples, - WebRtcRTPHeader* rtp_header, - const Packet* next_packet) { - size_t payload_len = 0; - // Check for CNG. - if (IsComfortNoise(rtp_header->header.payloadType)) { - // If CNG, simply insert a zero-energy one-byte payload. - if (*payload_mem_size_bytes < 1) { - (*payload).reset(new uint8_t[1]); - *payload_mem_size_bytes = 1; - } - (*payload)[0] = 127; // Max attenuation of CNG. - payload_len = 1; - } else { - assert(next_packet->virtual_payload_length_bytes() > 0); - // Check if payload length has changed. - if (next_packet->header().sequenceNumber == - rtp_header->header.sequenceNumber + 1) { - if (*frame_size_samples != - next_packet->header().timestamp - rtp_header->header.timestamp) { - *frame_size_samples = - next_packet->header().timestamp - rtp_header->header.timestamp; - (*replacement_audio).reset( - new int16_t[*frame_size_samples]); - *payload_mem_size_bytes = 2 * *frame_size_samples; - (*payload).reset(new uint8_t[*payload_mem_size_bytes]); - } - } - // Get new speech. - assert((*replacement_audio).get()); - if (CodecTimestampRate(rtp_header->header.payloadType) != - CodecSampleRate(rtp_header->header.payloadType) || - rtp_header->header.payloadType == FLAGS_red || - rtp_header->header.payloadType == FLAGS_avt) { - // Some codecs have different sample and timestamp rates. And neither - // RED nor DTMF is supported for replacement. - std::cerr << "Codec not supported for audio replacement." << - std::endl; - Trace::ReturnTrace(); - exit(1); - } - assert(*frame_size_samples > 0); - if (!replacement_audio_file->Read(*frame_size_samples, - (*replacement_audio).get())) { - std::cerr << "Could not read replacement audio file." << std::endl; - Trace::ReturnTrace(); - exit(1); - } - // Encode it as PCM16. - assert((*payload).get()); - payload_len = WebRtcPcm16b_Encode((*replacement_audio).get(), - *frame_size_samples, - (*payload).get()); - assert(payload_len == 2 * *frame_size_samples); - // Change payload type to PCM16. - switch (CodecSampleRate(rtp_header->header.payloadType)) { - case 8000: - rtp_header->header.payloadType = static_cast(FLAGS_pcm16b); - break; - case 16000: - rtp_header->header.payloadType = static_cast(FLAGS_pcm16b_wb); - break; - case 32000: - rtp_header->header.payloadType = - static_cast(FLAGS_pcm16b_swb32); - break; - case 48000: - rtp_header->header.payloadType = - static_cast(FLAGS_pcm16b_swb48); - break; - default: - std::cerr << "Payload type " << - static_cast(rtp_header->header.payloadType) << - " not supported or unknown." << std::endl; - Trace::ReturnTrace(); - exit(1); + // All methods but PopPacket() simply relay to the |source_| object. + rtc::Optional NextPacketTime() const override { + return source_->NextPacketTime(); + } + rtc::Optional NextOutputEventTime() const override { + return source_->NextOutputEventTime(); + } + + // Returns the next packet, and throws away upcoming packets that do not match + // the desired SSRC. + std::unique_ptr PopPacket() override { + std::unique_ptr packet_to_return = source_->PopPacket(); + RTC_DCHECK(!packet_to_return || + packet_to_return->header.header.ssrc == ssrc_); + // Pre-fetch the next packet with correct SSRC. Hence, |source_| will always + // be have a valid packet (or empty if no more packets are available) when + // this method returns. + FindNextWithCorrectSsrc(); + return packet_to_return; + } + + void AdvanceOutputEvent() override { source_->AdvanceOutputEvent(); } + + bool ended() const override { return source_->ended(); } + + rtc::Optional NextHeader() const override { + return source_->NextHeader(); + } + + private: + void FindNextWithCorrectSsrc() { + while (source_->NextHeader() && source_->NextHeader()->ssrc != ssrc_) { + source_->PopPacket(); } } - return payload_len; -} + + std::unique_ptr source_; + uint32_t ssrc_; +}; int RunTest(int argc, char* argv[]) { - static const int kOutputBlockSizeMs = 10; - std::string program_name = argv[0]; std::string usage = "Tool for decoding an RTP dump file using NetEq.\n" "Run " + program_name + " --helpshort for usage.\n" @@ -393,66 +297,35 @@ int RunTest(int argc, char* argv[]) { return 0; } - printf("Input file: %s\n", argv[1]); - - bool is_rtp_dump = false; - std::unique_ptr file_source; - RtcEventLogSource* event_log_source = nullptr; - if (RtpFileSource::ValidRtpDump(argv[1]) || - RtpFileSource::ValidPcap(argv[1])) { - is_rtp_dump = true; - file_source.reset(RtpFileSource::Create(argv[1])); + const std::string input_file_name = argv[1]; + std::unique_ptr input; + if (RtpFileSource::ValidRtpDump(input_file_name) || + RtpFileSource::ValidPcap(input_file_name)) { + input.reset(new NetEqRtpDumpInput(input_file_name)); } else { - event_log_source = RtcEventLogSource::Create(argv[1]); - file_source.reset(event_log_source); + input.reset(new NetEqEventLogInput(input_file_name)); } - assert(file_source.get()); + std::cout << "Input file: " << input_file_name << std::endl; + RTC_CHECK(input) << "Cannot open input file"; + RTC_CHECK(!input->ended()) << "Input file is empty"; // Check if an SSRC value was provided. if (!FLAGS_ssrc.empty()) { uint32_t ssrc; RTC_CHECK(ParseSsrc(FLAGS_ssrc, &ssrc)) << "Flag verification has failed."; - file_source->SelectSsrc(ssrc); - } - - // Check if a replacement audio file was provided, and if so, open it. - bool replace_payload = false; - std::unique_ptr replacement_audio_file; - if (!FLAGS_replacement_audio_file.empty()) { - replacement_audio_file.reset( - new InputAudioFile(FLAGS_replacement_audio_file)); - replace_payload = true; - } - - // Read first packet. - std::unique_ptr packet(file_source->NextPacket()); - if (!packet) { - printf( - "Warning: input file is empty, or the filters did not match any " - "packets\n"); - Trace::ReturnTrace(); - return 0; - } - if (packet->payload_length_bytes() == 0 && !replace_payload) { - std::cerr << "Warning: input file contains header-only packets, but no " - << "replacement file is specified." << std::endl; - Trace::ReturnTrace(); - return -1; + input.reset(new FilterSsrcInput(std::move(input), ssrc)); } // Check the sample rate. - int sample_rate_hz = CodecSampleRate(packet->header().payloadType); - if (sample_rate_hz <= 0) { - printf("Warning: Invalid sample rate from RTP packet.\n"); - Trace::ReturnTrace(); - return 0; - } + rtc::Optional first_rtp_header = input->NextHeader(); + RTC_CHECK(first_rtp_header); + const int sample_rate_hz = CodecSampleRate(first_rtp_header->payloadType); + RTC_CHECK_GT(sample_rate_hz, 0); // Open the output file now that we know the sample rate. (Rate is only needed // for wav files.) - // Check output file type. - std::string output_file_name = argv[2]; + const std::string output_file_name = argv[2]; std::unique_ptr output; if (output_file_name.size() >= 4 && output_file_name.substr(output_file_name.size() - 4) == ".wav") { @@ -463,170 +336,98 @@ int RunTest(int argc, char* argv[]) { output.reset(new OutputAudioFile(output_file_name)); } - std::cout << "Output file: " << argv[2] << std::endl; + std::cout << "Output file: " << output_file_name << std::endl; - // Enable tracing. - Trace::CreateTrace(); - Trace::SetTraceFile((OutputPath() + "neteq_trace.txt").c_str()); - Trace::set_level_filter(kTraceAll); + NetEqTest::DecoderMap codecs = { + {FLAGS_pcmu, std::make_pair(NetEqDecoder::kDecoderPCMu, "pcmu")}, + {FLAGS_pcma, std::make_pair(NetEqDecoder::kDecoderPCMa, "pcma")}, + {FLAGS_ilbc, std::make_pair(NetEqDecoder::kDecoderILBC, "ilbc")}, + {FLAGS_isac, std::make_pair(NetEqDecoder::kDecoderISAC, "isac")}, + {FLAGS_isac_swb, + std::make_pair(NetEqDecoder::kDecoderISACswb, "isac-swb")}, + {FLAGS_opus, std::make_pair(NetEqDecoder::kDecoderOpus, "opus")}, + {FLAGS_pcm16b, std::make_pair(NetEqDecoder::kDecoderPCM16B, "pcm16-nb")}, + {FLAGS_pcm16b_wb, + std::make_pair(NetEqDecoder::kDecoderPCM16Bwb, "pcm16-wb")}, + {FLAGS_pcm16b_swb32, + std::make_pair(NetEqDecoder::kDecoderPCM16Bswb32kHz, "pcm16-swb32")}, + {FLAGS_pcm16b_swb48, + std::make_pair(NetEqDecoder::kDecoderPCM16Bswb48kHz, "pcm16-swb48")}, + {FLAGS_g722, std::make_pair(NetEqDecoder::kDecoderG722, "g722")}, + {FLAGS_avt, std::make_pair(NetEqDecoder::kDecoderAVT, "avt")}, + {FLAGS_red, std::make_pair(NetEqDecoder::kDecoderRED, "red")}, + {FLAGS_cn_nb, std::make_pair(NetEqDecoder::kDecoderCNGnb, "cng-nb")}, + {FLAGS_cn_wb, std::make_pair(NetEqDecoder::kDecoderCNGwb, "cng-wb")}, + {FLAGS_cn_swb32, + std::make_pair(NetEqDecoder::kDecoderCNGswb32kHz, "cng-swb32")}, + {FLAGS_cn_swb48, + std::make_pair(NetEqDecoder::kDecoderCNGswb48kHz, "cng-swb48")}}; - // Initialize NetEq instance. + // Check if a replacement audio file was provided. + std::unique_ptr replacement_decoder; + NetEqTest::ExtDecoderMap ext_codecs; + if (!FLAGS_replacement_audio_file.empty()) { + // Find largest unused payload type. + int replacement_pt = 127; + while (!(codecs.find(replacement_pt) == codecs.end() && + ext_codecs.find(replacement_pt) == ext_codecs.end())) { + --replacement_pt; + RTC_CHECK_GE(replacement_pt, 0); + } + + auto std_set_int32_to_uint8 = [](const std::set& a) { + std::set b; + for (auto& x : a) { + b.insert(static_cast(x)); + } + return b; + }; + + std::set cn_types = std_set_int32_to_uint8( + {FLAGS_cn_nb, FLAGS_cn_wb, FLAGS_cn_swb32, FLAGS_cn_swb48}); + std::set forbidden_types = + std_set_int32_to_uint8({FLAGS_g722, FLAGS_red, FLAGS_avt}); + input.reset(new NetEqReplacementInput(std::move(input), replacement_pt, + cn_types, forbidden_types)); + + replacement_decoder.reset(new FakeDecodeFromFile( + std::unique_ptr( + new InputAudioFile(FLAGS_replacement_audio_file)), + 48000, false)); + NetEqTest::ExternalDecoderInfo ext_dec_info = { + replacement_decoder.get(), NetEqDecoder::kDecoderArbitrary, + "replacement codec"}; + ext_codecs[replacement_pt] = ext_dec_info; + } + + DefaultNetEqTestErrorCallback error_cb; NetEq::Config config; config.sample_rate_hz = sample_rate_hz; - NetEq* neteq = - NetEq::Create(config, CreateBuiltinAudioDecoderFactory()); - RegisterPayloadTypes(neteq); + NetEqTest test(config, codecs, ext_codecs, std::move(input), + std::move(output), &error_cb); + int64_t test_duration_ms = test.Run(); + NetEqNetworkStatistics stats = test.SimulationStats(); - // Set up variables for audio replacement if needed. - std::unique_ptr next_packet; - bool next_packet_available = false; - size_t input_frame_size_timestamps = 0; - std::unique_ptr replacement_audio; - std::unique_ptr payload; - size_t payload_mem_size_bytes = 0; - if (replace_payload) { - // Initially assume that the frame size is 30 ms at the initial sample rate. - // This value will be replaced with the correct one as soon as two - // consecutive packets are found. - input_frame_size_timestamps = 30 * sample_rate_hz / 1000; - replacement_audio.reset(new int16_t[input_frame_size_timestamps]); - payload_mem_size_bytes = 2 * input_frame_size_timestamps; - payload.reset(new uint8_t[payload_mem_size_bytes]); - next_packet = file_source->NextPacket(); - assert(next_packet); - next_packet_available = true; - } + printf("Simulation statistics:\n"); + printf(" output duration: %" PRId64 " ms\n", test_duration_ms); + printf(" packet_loss_rate: %f %%\n", + 100.0 * stats.packet_loss_rate / 16384.0); + printf(" packet_discard_rate: %f %%\n", + 100.0 * stats.packet_discard_rate / 16384.0); + printf(" expand_rate: %f %%\n", 100.0 * stats.expand_rate / 16384.0); + printf(" speech_expand_rate: %f %%\n", + 100.0 * stats.speech_expand_rate / 16384.0); + printf(" preemptive_rate: %f %%\n", 100.0 * stats.preemptive_rate / 16384.0); + printf(" accelerate_rate: %f %%\n", 100.0 * stats.accelerate_rate / 16384.0); + printf(" secondary_decoded_rate: %f %%\n", + 100.0 * stats.secondary_decoded_rate / 16384.0); + printf(" clockdrift_ppm: %d ppm\n", stats.clockdrift_ppm); + printf(" mean_waiting_time_ms: %d ms\n", stats.mean_waiting_time_ms); + printf(" median_waiting_time_ms: %d ms\n", stats.median_waiting_time_ms); + printf(" min_waiting_time_ms: %d ms\n", stats.min_waiting_time_ms); + printf(" max_waiting_time_ms: %d ms\n", stats.max_waiting_time_ms); - // This is the main simulation loop. - // Set the simulation clock to start immediately with the first packet. - int64_t start_time_ms = rtc::checked_cast(packet->time_ms()); - int64_t time_now_ms = start_time_ms; - int64_t next_input_time_ms = time_now_ms; - int64_t next_output_time_ms = time_now_ms; - if (time_now_ms % kOutputBlockSizeMs != 0) { - // Make sure that next_output_time_ms is rounded up to the next multiple - // of kOutputBlockSizeMs. (Legacy bit-exactness.) - next_output_time_ms += - kOutputBlockSizeMs - time_now_ms % kOutputBlockSizeMs; - } - - bool packet_available = true; - bool output_event_available = true; - if (!is_rtp_dump) { - next_output_time_ms = event_log_source->NextAudioOutputEventMs(); - if (next_output_time_ms == std::numeric_limits::max()) - output_event_available = false; - start_time_ms = time_now_ms = - std::min(next_input_time_ms, next_output_time_ms); - } - while (packet_available || output_event_available) { - // Advance time to next event. - time_now_ms = std::min(next_input_time_ms, next_output_time_ms); - // Check if it is time to insert packet. - while (time_now_ms >= next_input_time_ms && packet_available) { - assert(packet->virtual_payload_length_bytes() > 0); - // Parse RTP header. - WebRtcRTPHeader rtp_header; - packet->ConvertHeader(&rtp_header); - const uint8_t* payload_ptr = packet->payload(); - size_t payload_len = packet->payload_length_bytes(); - if (replace_payload) { - payload_len = ReplacePayload(replacement_audio_file.get(), - &replacement_audio, - &payload, - &payload_mem_size_bytes, - &input_frame_size_timestamps, - &rtp_header, - next_packet.get()); - payload_ptr = payload.get(); - } - int error = neteq->InsertPacket( - rtp_header, rtc::ArrayView(payload_ptr, payload_len), - static_cast(packet->time_ms() * sample_rate_hz / 1000)); - if (error != NetEq::kOK) { - if (neteq->LastError() == NetEq::kUnknownRtpPayloadType) { - std::cerr << "RTP Payload type " - << static_cast(rtp_header.header.payloadType) - << " is unknown." << std::endl; - std::cerr << "Use --codec_map to view default mapping." << std::endl; - std::cerr << "Use --helpshort for information on how to make custom " - "mappings." << std::endl; - } else { - std::cerr << "InsertPacket returned error code " << neteq->LastError() - << std::endl; - std::cerr << "Header data:" << std::endl; - std::cerr << " PT = " - << static_cast(rtp_header.header.payloadType) - << std::endl; - std::cerr << " SN = " << rtp_header.header.sequenceNumber - << std::endl; - std::cerr << " TS = " << rtp_header.header.timestamp << std::endl; - } - } - - // Get next packet from file. - std::unique_ptr temp_packet = file_source->NextPacket(); - if (temp_packet) { - packet = std::move(temp_packet); - if (replace_payload) { - // At this point |packet| contains the packet *after* |next_packet|. - // Swap Packet objects between |packet| and |next_packet|. - packet.swap(next_packet); - // Swap the status indicators unless they're already the same. - if (packet_available != next_packet_available) { - packet_available = !packet_available; - next_packet_available = !next_packet_available; - } - } - next_input_time_ms = rtc::checked_cast(packet->time_ms()); - } else { - // Set next input time to the maximum value of int64_t to prevent the - // time_now_ms from becoming stuck at the final value. - next_input_time_ms = std::numeric_limits::max(); - packet_available = false; - } - RTC_DCHECK(!temp_packet); // Must have transferred to another variable. - } - - // Check if it is time to get output audio. - while (time_now_ms >= next_output_time_ms && output_event_available) { - AudioFrame out_frame; - bool muted; - int error = neteq->GetAudio(&out_frame, &muted); - RTC_CHECK(!muted); - if (error != NetEq::kOK) { - std::cerr << "GetAudio returned error code " << - neteq->LastError() << std::endl; - } else { - sample_rate_hz = out_frame.sample_rate_hz_; - } - - // Write to file. - // TODO(hlundin): Make writing to file optional. - if (!output->WriteArray(out_frame.data_, out_frame.samples_per_channel_ * - out_frame.num_channels_)) { - std::cerr << "Error while writing to file" << std::endl; - Trace::ReturnTrace(); - exit(1); - } - if (is_rtp_dump) { - next_output_time_ms += kOutputBlockSizeMs; - if (!packet_available) - output_event_available = false; - } else { - next_output_time_ms = event_log_source->NextAudioOutputEventMs(); - if (next_output_time_ms == std::numeric_limits::max()) - output_event_available = false; - } - } - } - printf("Simulation done\n"); - printf("Produced %i ms of audio\n", - static_cast(time_now_ms - start_time_ms)); - - delete neteq; - Trace::ReturnTrace(); return 0; } diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_test.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_test.cc new file mode 100644 index 0000000000..1ea2cb8412 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_test.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2016 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/neteq/tools/neteq_test.h" + +#include + +#include "webrtc/modules/audio_coding/codecs/builtin_audio_decoder_factory.h" + +namespace webrtc { +namespace test { + +void DefaultNetEqTestErrorCallback::OnInsertPacketError( + int error_code, + const NetEqInput::PacketData& packet) { + if (error_code == NetEq::kUnknownRtpPayloadType) { + std::cerr << "RTP Payload type " + << static_cast(packet.header.header.payloadType) + << " is unknown." << std::endl; + } else { + std::cerr << "InsertPacket returned error code " << error_code << std::endl; + std::cerr << "Header data:" << std::endl; + std::cerr << " PT = " << static_cast(packet.header.header.payloadType) + << std::endl; + std::cerr << " SN = " << packet.header.header.sequenceNumber << std::endl; + std::cerr << " TS = " << packet.header.header.timestamp << std::endl; + } + FATAL(); +} + +void DefaultNetEqTestErrorCallback::OnGetAudioError(int error_code) { + std::cerr << "GetAudio returned error code " << error_code << std::endl; + FATAL(); +} + +NetEqTest::NetEqTest(const NetEq::Config& config, + const DecoderMap& codecs, + const ExtDecoderMap& ext_codecs, + std::unique_ptr input, + std::unique_ptr output, + NetEqTestErrorCallback* error_callback) + : neteq_(NetEq::Create(config, CreateBuiltinAudioDecoderFactory())), + input_(std::move(input)), + output_(std::move(output)), + error_callback_(error_callback), + sample_rate_hz_(config.sample_rate_hz) { + RTC_CHECK(!config.enable_muted_state) + << "The code does not handle enable_muted_state"; + RegisterDecoders(codecs); + RegisterExternalDecoders(ext_codecs); +} + +int64_t NetEqTest::Run() { + const int64_t start_time_ms = *input_->NextEventTime(); + int64_t time_now_ms = start_time_ms; + + while (!input_->ended()) { + // Advance time to next event. + RTC_DCHECK(input_->NextEventTime()); + time_now_ms = *input_->NextEventTime(); + // Check if it is time to insert packet. + if (input_->NextPacketTime() && time_now_ms >= *input_->NextPacketTime()) { + std::unique_ptr packet_data = input_->PopPacket(); + RTC_CHECK(packet_data); + int error = neteq_->InsertPacket( + packet_data->header, + rtc::ArrayView(packet_data->payload), + static_cast(packet_data->time_ms * sample_rate_hz_ / 1000)); + if (error != NetEq::kOK && error_callback_) { + error_callback_->OnInsertPacketError(neteq_->LastError(), *packet_data); + } + } + + // Check if it is time to get output audio. + if (input_->NextOutputEventTime() && + time_now_ms >= *input_->NextOutputEventTime()) { + AudioFrame out_frame; + bool muted; + int error = neteq_->GetAudio(&out_frame, &muted); + RTC_CHECK(!muted) << "The code does not handle enable_muted_state"; + if (error != NetEq::kOK) { + if (error_callback_) { + error_callback_->OnGetAudioError(neteq_->LastError()); + } + } else { + sample_rate_hz_ = out_frame.sample_rate_hz_; + } + + if (output_) { + RTC_CHECK(output_->WriteArray( + out_frame.data_, + out_frame.samples_per_channel_ * out_frame.num_channels_)); + } + + input_->AdvanceOutputEvent(); + } + } + return time_now_ms - start_time_ms; +} + +NetEqNetworkStatistics NetEqTest::SimulationStats() { + NetEqNetworkStatistics stats; + RTC_CHECK_EQ(neteq_->NetworkStatistics(&stats), 0); + return stats; +} + +void NetEqTest::RegisterDecoders(const DecoderMap& codecs) { + for (const auto& c : codecs) { + RTC_CHECK_EQ( + neteq_->RegisterPayloadType(c.second.first, c.second.second, c.first), + NetEq::kOK) + << "Cannot register " << c.second.second << " to payload type " + << c.first; + } +} + +void NetEqTest::RegisterExternalDecoders(const ExtDecoderMap& codecs) { + for (const auto& c : codecs) { + RTC_CHECK_EQ( + neteq_->RegisterExternalDecoder(c.second.decoder, c.second.codec, + c.second.codec_name, c.first), + NetEq::kOK) + << "Cannot register " << c.second.codec_name << " to payload type " + << c.first; + } +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_test.h b/webrtc/modules/audio_coding/neteq/tools/neteq_test.h new file mode 100644 index 0000000000..619626cf14 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_test.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 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_NETEQ_TOOLS_NETEQ_TEST_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_TEST_H_ + +#include +#include +#include +#include + +#include "webrtc/modules/audio_coding/neteq/include/neteq.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_sink.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_input.h" + +namespace webrtc { +namespace test { + +class NetEqTestErrorCallback { + public: + virtual ~NetEqTestErrorCallback() = default; + virtual void OnInsertPacketError(int error_code, + const NetEqInput::PacketData& packet) {} + virtual void OnGetAudioError(int error_code) {} +}; + +class DefaultNetEqTestErrorCallback : public NetEqTestErrorCallback { + void OnInsertPacketError(int error_code, + const NetEqInput::PacketData& packet) override; + void OnGetAudioError(int error_code) override; +}; + +// Class that provides an input--output test for NetEq. The input (both packets +// and output events) is provided by a NetEqInput object, while the output is +// directed to an AudioSink object. +class NetEqTest { + public: + using DecoderMap = std::map >; + + struct ExternalDecoderInfo { + AudioDecoder* decoder; + NetEqDecoder codec; + std::string codec_name; + }; + + using ExtDecoderMap = std::map; + + // Sets up the test with given configuration, codec mappings, input, ouput, + // and callback objects for error reporting. + NetEqTest(const NetEq::Config& config, + const DecoderMap& codecs, + const ExtDecoderMap& ext_codecs, + std::unique_ptr input, + std::unique_ptr output, + NetEqTestErrorCallback* error_callback); + + ~NetEqTest() = default; + + // Runs the test. Returns the duration of the produced audio in ms. + int64_t Run(); + + // Returns the statistics from NetEq. + NetEqNetworkStatistics SimulationStats(); + + private: + void RegisterDecoders(const DecoderMap& codecs); + void RegisterExternalDecoders(const ExtDecoderMap& codecs); + + std::unique_ptr neteq_; + std::unique_ptr input_; + std::unique_ptr output_; + NetEqTestErrorCallback* error_callback_ = nullptr; + int sample_rate_hz_; +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_TEST_H_