Refactor neteq_rtpplay
This change is a major refactoring of the neteq_rtpplay tool. It consists of the following parts: - NetEqTest class: Breaks out the main simulation loop from neteq_rtpplay into a separate class with well defined inputs and outputs. - NetEqInput: Interface class for the input to NetEqTest. - NetEqPacketSourceInput: Implementation of NetEqInput that provides a PacketSource objects with a NetEqInput interface. This has two subclasses; one for RtpFileSource and one for RtcEventLogSource. - NetEqReplacementInput: An object that modifies the packets provided by another NetEqInput object, and replaces the packet payloads with meta data readable by a FakeDecodeFromFile decoder. - FakeDecodeFromFile: An AudioDecoder implementation that produces "decoded" data by reading from an audio file. BUG=webrtc:2692, webrtc:5447 Review-Url: https://codereview.webrtc.org/2020363003 Cr-Commit-Position: refs/heads/master@{#13252}
This commit is contained in:
committed by
Commit bot
parent
dfb63ec353
commit
e8a77e3309
@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
||||
@ -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<uint32_t>::ReadLittleEndian(encoded);
|
||||
uint32_t samples_to_decode =
|
||||
ByteReader<uint32_t>::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<size_t>(samples_to_decode), decoded));
|
||||
next_timestamp_from_input_ =
|
||||
rtc::Optional<uint32_t>(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<uint8_t> encoded) {
|
||||
RTC_CHECK_GE(encoded.size(), 8u);
|
||||
ByteWriter<uint32_t>::WriteLittleEndian(&encoded[0], timestamp);
|
||||
ByteWriter<uint32_t>::WriteLittleEndian(&encoded[4],
|
||||
rtc::checked_cast<uint32_t>(samples));
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -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 <memory>
|
||||
|
||||
#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<InputAudioFile> 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<uint8_t> encoded);
|
||||
|
||||
private:
|
||||
std::unique_ptr<InputAudioFile> input_;
|
||||
rtc::Optional<uint32_t> 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_
|
||||
78
webrtc/modules/audio_coding/neteq/tools/neteq_input.h
Normal file
78
webrtc/modules/audio_coding/neteq/tools/neteq_input.h
Normal file
@ -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 <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#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<int64_t> 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<int64_t> 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<int64_t> 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<int64_t>(std::min(*a, *b)) : a;
|
||||
}
|
||||
return b ? b : rtc::Optional<int64_t>();
|
||||
}
|
||||
|
||||
// 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<PacketData> 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<RTPHeader> NextHeader() const = 0;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_INPUT_H_
|
||||
@ -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 <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#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<int64_t> NetEqPacketSourceInput::NextPacketTime() const {
|
||||
return packet_
|
||||
? rtc::Optional<int64_t>(static_cast<int64_t>(packet_->time_ms()))
|
||||
: rtc::Optional<int64_t>();
|
||||
}
|
||||
|
||||
rtc::Optional<RTPHeader> NetEqPacketSourceInput::NextHeader() const {
|
||||
return packet_ ? rtc::Optional<RTPHeader>(packet_->header())
|
||||
: rtc::Optional<RTPHeader>();
|
||||
}
|
||||
|
||||
void NetEqPacketSourceInput::LoadNextPacket() {
|
||||
packet_ = source()->NextPacket();
|
||||
}
|
||||
|
||||
std::unique_ptr<NetEqInput::PacketData> NetEqPacketSourceInput::PopPacket() {
|
||||
if (!packet_) {
|
||||
return std::unique_ptr<PacketData>();
|
||||
}
|
||||
std::unique_ptr<PacketData> 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<int64_t> 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<int64_t>();
|
||||
}
|
||||
}
|
||||
|
||||
PacketSource* NetEqRtpDumpInput::source() {
|
||||
return source_.get();
|
||||
}
|
||||
|
||||
NetEqEventLogInput::NetEqEventLogInput(const std::string& file_name)
|
||||
: source_(RtcEventLogSource::Create(file_name)) {
|
||||
LoadNextPacket();
|
||||
AdvanceOutputEvent();
|
||||
}
|
||||
|
||||
rtc::Optional<int64_t> NetEqEventLogInput::NextOutputEventTime() const {
|
||||
return rtc::Optional<int64_t>(next_output_event_ms_);
|
||||
}
|
||||
|
||||
void NetEqEventLogInput::AdvanceOutputEvent() {
|
||||
next_output_event_ms_ =
|
||||
rtc::Optional<int64_t>(source_->NextAudioOutputEventMs());
|
||||
if (*next_output_event_ms_ == std::numeric_limits<int64_t>::max()) {
|
||||
next_output_event_ms_ = rtc::Optional<int64_t>();
|
||||
}
|
||||
}
|
||||
|
||||
PacketSource* NetEqEventLogInput::source() {
|
||||
return source_.get();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -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 <string>
|
||||
|
||||
#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<int64_t> NextPacketTime() const override;
|
||||
std::unique_ptr<PacketData> PopPacket() override;
|
||||
rtc::Optional<RTPHeader> NextHeader() const override;
|
||||
bool ended() const override { return !next_output_event_ms_; }
|
||||
|
||||
protected:
|
||||
virtual PacketSource* source() = 0;
|
||||
void LoadNextPacket();
|
||||
|
||||
rtc::Optional<int64_t> next_output_event_ms_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Packet> 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<int64_t> NextOutputEventTime() const override;
|
||||
void AdvanceOutputEvent() override;
|
||||
|
||||
protected:
|
||||
PacketSource* source() override;
|
||||
|
||||
private:
|
||||
static constexpr int64_t kOutputPeriodMs = 10;
|
||||
|
||||
std::unique_ptr<RtpFileSource> 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<int64_t> NextOutputEventTime() const override;
|
||||
void AdvanceOutputEvent() override;
|
||||
|
||||
protected:
|
||||
PacketSource* source() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<RtcEventLogSource> source_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PACKET_SOURCE_INPUT_H_
|
||||
@ -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<NetEqInput> source,
|
||||
uint8_t replacement_payload_type,
|
||||
const std::set<uint8_t>& comfort_noise_types,
|
||||
const std::set<uint8_t>& 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<int64_t> NetEqReplacementInput::NextPacketTime() const {
|
||||
return packet_
|
||||
? rtc::Optional<int64_t>(static_cast<int64_t>(packet_->time_ms))
|
||||
: rtc::Optional<int64_t>();
|
||||
}
|
||||
|
||||
rtc::Optional<int64_t> NetEqReplacementInput::NextOutputEventTime() const {
|
||||
return source_->NextOutputEventTime();
|
||||
}
|
||||
|
||||
std::unique_ptr<NetEqInput::PacketData> NetEqReplacementInput::PopPacket() {
|
||||
std::unique_ptr<PacketData> 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<RTPHeader> 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<int>(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<RTPHeader> 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
|
||||
@ -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 <set>
|
||||
|
||||
#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<NetEqInput> source,
|
||||
uint8_t replacement_payload_type,
|
||||
const std::set<uint8_t>& comfort_noise_types,
|
||||
const std::set<uint8_t>& forbidden_types);
|
||||
|
||||
rtc::Optional<int64_t> NextPacketTime() const override;
|
||||
rtc::Optional<int64_t> NextOutputEventTime() const override;
|
||||
std::unique_ptr<PacketData> PopPacket() override;
|
||||
void AdvanceOutputEvent() override;
|
||||
bool ended() const override;
|
||||
rtc::Optional<RTPHeader> NextHeader() const override;
|
||||
|
||||
private:
|
||||
void ReplacePacket();
|
||||
|
||||
std::unique_ptr<NetEqInput> source_;
|
||||
const uint8_t replacement_payload_type_;
|
||||
const std::set<uint8_t> comfort_noise_types_;
|
||||
const std::set<uint8_t> forbidden_types_;
|
||||
std::unique_ptr<PacketData> packet_; // The next packet to deliver.
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_REPLACEMENT_INPUT_H_
|
||||
@ -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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h> // For ULONG_MAX returned by strtoul.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // For strtoul.
|
||||
@ -20,24 +17,20 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#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<uint8_t>(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<NetEqInput> source, uint32_t ssrc)
|
||||
: source_(std::move(source)), ssrc_(ssrc) {
|
||||
FindNextWithCorrectSsrc();
|
||||
}
|
||||
|
||||
size_t ReplacePayload(InputAudioFile* replacement_audio_file,
|
||||
std::unique_ptr<int16_t[]>* replacement_audio,
|
||||
std::unique_ptr<uint8_t[]>* 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<uint8_t>(FLAGS_pcm16b);
|
||||
break;
|
||||
case 16000:
|
||||
rtp_header->header.payloadType = static_cast<uint8_t>(FLAGS_pcm16b_wb);
|
||||
break;
|
||||
case 32000:
|
||||
rtp_header->header.payloadType =
|
||||
static_cast<uint8_t>(FLAGS_pcm16b_swb32);
|
||||
break;
|
||||
case 48000:
|
||||
rtp_header->header.payloadType =
|
||||
static_cast<uint8_t>(FLAGS_pcm16b_swb48);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Payload type " <<
|
||||
static_cast<int>(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<int64_t> NextPacketTime() const override {
|
||||
return source_->NextPacketTime();
|
||||
}
|
||||
rtc::Optional<int64_t> 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<PacketData> PopPacket() override {
|
||||
std::unique_ptr<PacketData> 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<RTPHeader> NextHeader() const override {
|
||||
return source_->NextHeader();
|
||||
}
|
||||
|
||||
private:
|
||||
void FindNextWithCorrectSsrc() {
|
||||
while (source_->NextHeader() && source_->NextHeader()->ssrc != ssrc_) {
|
||||
source_->PopPacket();
|
||||
}
|
||||
}
|
||||
return payload_len;
|
||||
}
|
||||
|
||||
std::unique_ptr<NetEqInput> 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<PacketSource> 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<NetEqInput> 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<InputAudioFile> 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> 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<RTPHeader> 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<AudioSink> 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<AudioDecoder> 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<int32_t>& a) {
|
||||
std::set<uint8_t> b;
|
||||
for (auto& x : a) {
|
||||
b.insert(static_cast<uint8_t>(x));
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
std::set<uint8_t> cn_types = std_set_int32_to_uint8(
|
||||
{FLAGS_cn_nb, FLAGS_cn_wb, FLAGS_cn_swb32, FLAGS_cn_swb48});
|
||||
std::set<uint8_t> 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<InputAudioFile>(
|
||||
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<Packet> next_packet;
|
||||
bool next_packet_available = false;
|
||||
size_t input_frame_size_timestamps = 0;
|
||||
std::unique_ptr<int16_t[]> replacement_audio;
|
||||
std::unique_ptr<uint8_t[]> 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<int64_t>(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<int64_t>::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<const uint8_t>(payload_ptr, payload_len),
|
||||
static_cast<uint32_t>(packet->time_ms() * sample_rate_hz / 1000));
|
||||
if (error != NetEq::kOK) {
|
||||
if (neteq->LastError() == NetEq::kUnknownRtpPayloadType) {
|
||||
std::cerr << "RTP Payload type "
|
||||
<< static_cast<int>(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<int>(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<Packet> 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<int64_t>(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<int64_t>::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<int64_t>::max())
|
||||
output_event_available = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("Simulation done\n");
|
||||
printf("Produced %i ms of audio\n",
|
||||
static_cast<int>(time_now_ms - start_time_ms));
|
||||
|
||||
delete neteq;
|
||||
Trace::ReturnTrace();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
136
webrtc/modules/audio_coding/neteq/tools/neteq_test.cc
Normal file
136
webrtc/modules/audio_coding/neteq/tools/neteq_test.cc
Normal file
@ -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 <iostream>
|
||||
|
||||
#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<int>(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<int>(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<NetEqInput> input,
|
||||
std::unique_ptr<AudioSink> 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<NetEqInput::PacketData> packet_data = input_->PopPacket();
|
||||
RTC_CHECK(packet_data);
|
||||
int error = neteq_->InsertPacket(
|
||||
packet_data->header,
|
||||
rtc::ArrayView<const uint8_t>(packet_data->payload),
|
||||
static_cast<uint32_t>(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
|
||||
85
webrtc/modules/audio_coding/neteq/tools/neteq_test.h
Normal file
85
webrtc/modules/audio_coding/neteq/tools/neteq_test.h
Normal file
@ -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 <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#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<int, std::pair<NetEqDecoder, std::string> >;
|
||||
|
||||
struct ExternalDecoderInfo {
|
||||
AudioDecoder* decoder;
|
||||
NetEqDecoder codec;
|
||||
std::string codec_name;
|
||||
};
|
||||
|
||||
using ExtDecoderMap = std::map<int, ExternalDecoderInfo>;
|
||||
|
||||
// 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<NetEqInput> input,
|
||||
std::unique_ptr<AudioSink> 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> neteq_;
|
||||
std::unique_ptr<NetEqInput> input_;
|
||||
std::unique_ptr<AudioSink> output_;
|
||||
NetEqTestErrorCallback* error_callback_ = nullptr;
|
||||
int sample_rate_hz_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_TEST_H_
|
||||
Reference in New Issue
Block a user