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:
henrik.lundin
2016-06-22 06:34:03 -07:00
committed by Commit bot
parent dfb63ec353
commit e8a77e3309
13 changed files with 963 additions and 385 deletions

View File

@ -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" ]
}
}
}

View File

@ -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',

View File

@ -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',],
},
],
}],
],

View File

@ -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

View File

@ -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_

View 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_

View File

@ -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

View 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_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_

View File

@ -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

View File

@ -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_

View File

@ -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;
}

View 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

View 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_