diff --git a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi index 4b04092162..50ebbd35ef 100644 --- a/webrtc/modules/audio_coding/neteq/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi @@ -7,24 +7,42 @@ # be found in the AUTHORS file in the root of the source tree. { + 'conditions': [ + ['enable_protobuf==1', { + 'targets': [ + { + 'target_name': 'rtc_event_log_source', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/webrtc.gyp:rtc_event_log', + '<(webrtc_root)/webrtc.gyp:rtc_event_log_proto', + ], + 'sources': [ + 'tools/rtc_event_log_source.h', + 'tools/rtc_event_log_source.cc', + ], + }, + { + 'target_name': 'neteq_rtpplay', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/test/test.gyp:test_support_main', + 'rtc_event_log_source', + 'neteq', + 'neteq_unittest_tools', + 'pcm16b', + ], + 'sources': [ + 'tools/neteq_rtpplay.cc', + ], + 'defines': [ + ], + }, # neteq_rtpplay + ], + }], + ], 'targets': [ - { - 'target_name': 'neteq_rtpplay', - 'type': 'executable', - 'dependencies': [ - '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', - '<(webrtc_root)/test/test.gyp:test_support_main', - 'neteq', - 'neteq_unittest_tools', - 'pcm16b', - ], - 'sources': [ - 'tools/neteq_rtpplay.cc', - ], - 'defines': [ - ], - }, # neteq_rtpplay - { 'target_name': 'RTPencode', 'type': 'executable', diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc index 1c0807843f..d4219767cf 100644 --- a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include "google/gflags.h" @@ -31,9 +32,11 @@ #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/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/test/rtp_file_reader.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/typedefs.h" @@ -385,8 +388,30 @@ int main(int argc, char* argv[]) { } printf("Input file: %s\n", argv[1]); - rtc::scoped_ptr file_source( - webrtc::test::RtpFileSource::Create(argv[1])); + + // TODO(ivoc): Modify the RtpFileSource::Create and RtcEventLogSource::Create + // functions to return a nullptr on failure instead of crashing + // the program. + + // This temporary solution uses a RtpFileReader directly to check if the file + // is a valid RtpDump file. + bool is_rtp_dump = false; + { + rtc::scoped_ptr rtp_reader( + webrtc::test::RtpFileReader::Create( + webrtc::test::RtpFileReader::kRtpDump, argv[1])); + if (rtp_reader) + is_rtp_dump = true; + } + rtc::scoped_ptr file_source; + webrtc::test::RtcEventLogSource* event_log_source = nullptr; + if (is_rtp_dump) { + file_source.reset(webrtc::test::RtpFileSource::Create(argv[1])); + } else { + event_log_source = webrtc::test::RtcEventLogSource::Create(argv[1]); + file_source.reset(event_log_source); + } + assert(file_source.get()); // Check if an SSRC value was provided. @@ -414,7 +439,12 @@ int main(int argc, char* argv[]) { webrtc::Trace::ReturnTrace(); return 0; } - bool packet_available = true; + 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; + webrtc::Trace::ReturnTrace(); + return -1; + } // Check the sample rate. int sample_rate_hz = CodecSampleRate(packet->header().payloadType); @@ -476,17 +506,29 @@ int main(int argc, char* argv[]) { // This is the main simulation loop. // Set the simulation clock to start immediately with the first packet. - int start_time_ms = packet->time_ms(); - int time_now_ms = packet->time_ms(); - int next_input_time_ms = time_now_ms; - int next_output_time_ms = time_now_ms; + int64_t start_time_ms = rtc::checked_cast(packet->time_ms()); + int64_t time_now_ms = start_time_ms; + int64_t next_input_time_ms = time_now_ms; + int64_t next_output_time_ms = time_now_ms; if (time_now_ms % kOutputBlockSizeMs != 0) { // Make sure that next_output_time_ms is rounded up to the next multiple // of kOutputBlockSizeMs. (Legacy bit-exactness.) next_output_time_ms += kOutputBlockSizeMs - time_now_ms % kOutputBlockSizeMs; } - while (packet_available) { + + bool packet_available = true; + bool output_event_available = true; + if (!is_rtp_dump) { + next_output_time_ms = event_log_source->NextAudioOutputEventMs(); + if (next_output_time_ms == std::numeric_limits::max()) + output_event_available = false; + start_time_ms = time_now_ms = + std::min(next_input_time_ms, next_output_time_ms); + } + while (packet_available || output_event_available) { + // Advance time to next event. + time_now_ms = std::min(next_input_time_ms, next_output_time_ms); // Check if it is time to insert packet. while (time_now_ms >= next_input_time_ms && packet_available) { assert(packet->virtual_payload_length_bytes() > 0); @@ -505,11 +547,9 @@ int main(int argc, char* argv[]) { next_packet.get()); payload_ptr = payload.get(); } - int error = - neteq->InsertPacket(rtp_header, - payload_ptr, - payload_len, - packet->time_ms() * sample_rate_hz / 1000); + int error = neteq->InsertPacket( + rtp_header, payload_ptr, payload_len, + static_cast(packet->time_ms() * sample_rate_hz / 1000)); if (error != NetEq::kOK) { if (neteq->LastError() == NetEq::kUnknownRtpPayloadType) { std::cerr << "RTP Payload type " @@ -535,24 +575,27 @@ int main(int argc, char* argv[]) { webrtc::test::Packet* temp_packet = file_source->NextPacket(); if (temp_packet) { packet.reset(temp_packet); + if (replace_payload) { + // At this point |packet| contains the packet *after* |next_packet|. + // Swap Packet objects between |packet| and |next_packet|. + packet.swap(next_packet); + // Swap the status indicators unless they're already the same. + if (packet_available != next_packet_available) { + packet_available = !packet_available; + next_packet_available = !next_packet_available; + } + } + next_input_time_ms = rtc::checked_cast(packet->time_ms()); } else { + // Set next input time to the maximum value of int64_t to prevent the + // time_now_ms from becoming stuck at the final value. + next_input_time_ms = std::numeric_limits::max(); packet_available = false; } - 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 = packet->time_ms(); } // Check if it is time to get output audio. - if (time_now_ms >= next_output_time_ms) { + while (time_now_ms >= next_output_time_ms && output_event_available) { static const size_t kOutDataLen = kOutputBlockSizeMs * kMaxSamplesPerMs * kMaxChannels; int16_t out_data[kOutDataLen]; @@ -577,14 +620,20 @@ int main(int argc, char* argv[]) { webrtc::Trace::ReturnTrace(); exit(1); } - next_output_time_ms += kOutputBlockSizeMs; + if (is_rtp_dump) { + next_output_time_ms += kOutputBlockSizeMs; + if (!packet_available) + output_event_available = false; + } else { + next_output_time_ms = event_log_source->NextAudioOutputEventMs(); + if (next_output_time_ms == std::numeric_limits::max()) + output_event_available = false; + } } - // Advance time to next event. - time_now_ms = std::min(next_input_time_ms, next_output_time_ms); } - printf("Simulation done\n"); - printf("Produced %i ms of audio\n", time_now_ms - start_time_ms); + printf("Produced %i ms of audio\n", + static_cast(time_now_ms - start_time_ms)); delete neteq; webrtc::Trace::ReturnTrace(); diff --git a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc new file mode 100644 index 0000000000..c2bcccaaa3 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.cc @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h" + +#include +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" +#include "webrtc/video/rtc_event_log.h" + +// Files generated at build-time by the protobuf compiler. +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/video/rtc_event_log.pb.h" +#else +#include "webrtc/video/rtc_event_log.pb.h" +#endif + +namespace webrtc { +namespace test { + +namespace { + +const rtclog::RtpPacket* GetRtpPacket(const rtclog::Event& event) { + if (!event.has_type() || event.type() != rtclog::Event::RTP_EVENT) + return nullptr; + if (!event.has_timestamp_us() || !event.has_rtp_packet()) + return nullptr; + const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); + if (!rtp_packet.has_type() || rtp_packet.type() != rtclog::AUDIO || + !rtp_packet.has_incoming() || !rtp_packet.incoming() || + !rtp_packet.has_packet_length() || rtp_packet.packet_length() == 0 || + !rtp_packet.has_header() || rtp_packet.header().size() == 0 || + rtp_packet.packet_length() < rtp_packet.header().size()) + return nullptr; + return &rtp_packet; +} + +const rtclog::DebugEvent* GetAudioOutputEvent(const rtclog::Event& event) { + if (!event.has_type() || event.type() != rtclog::Event::DEBUG_EVENT) + return nullptr; + if (!event.has_timestamp_us() || !event.has_debug_event()) + return nullptr; + const rtclog::DebugEvent& debug_event = event.debug_event(); + if (!debug_event.has_type() || + debug_event.type() != rtclog::DebugEvent::AUDIO_PLAYOUT) + return nullptr; + return &debug_event; +} + +} // namespace + +RtcEventLogSource* RtcEventLogSource::Create(const std::string& file_name) { + RtcEventLogSource* source = new RtcEventLogSource(); + CHECK(source->OpenFile(file_name)); + return source; +} + +RtcEventLogSource::~RtcEventLogSource() {} + +bool RtcEventLogSource::RegisterRtpHeaderExtension(RTPExtensionType type, + uint8_t id) { + CHECK(parser_.get()); + return parser_->RegisterRtpHeaderExtension(type, id); +} + +Packet* RtcEventLogSource::NextPacket() { + while (rtp_packet_index_ < event_log_->stream_size()) { + const rtclog::Event& event = event_log_->stream(rtp_packet_index_); + const rtclog::RtpPacket* rtp_packet = GetRtpPacket(event); + rtp_packet_index_++; + if (rtp_packet) { + uint8_t* packet_header = new uint8_t[rtp_packet->header().size()]; + memcpy(packet_header, rtp_packet->header().data(), + rtp_packet->header().size()); + Packet* packet = new Packet(packet_header, rtp_packet->header().size(), + rtp_packet->packet_length(), + event.timestamp_us() / 1000, *parser_.get()); + if (packet->valid_header()) { + // Check if the packet should not be filtered out. + if (!filter_.test(packet->header().payloadType) && + !(use_ssrc_filter_ && packet->header().ssrc != ssrc_)) + return packet; + } else { + std::cout << "Warning: Packet with index " << (rtp_packet_index_ - 1) + << " has an invalid header and will be ignored." << std::endl; + } + // The packet has either an invalid header or needs to be filtered out, so + // it can be deleted. + delete packet; + } + } + return nullptr; +} + +int64_t RtcEventLogSource::NextAudioOutputEventMs() { + while (audio_output_index_ < event_log_->stream_size()) { + const rtclog::Event& event = event_log_->stream(audio_output_index_); + const rtclog::DebugEvent* debug_event = GetAudioOutputEvent(event); + audio_output_index_++; + if (debug_event) + return event.timestamp_us() / 1000; + } + return std::numeric_limits::max(); +} + +RtcEventLogSource::RtcEventLogSource() + : PacketSource(), parser_(RtpHeaderParser::Create()) {} + +bool RtcEventLogSource::OpenFile(const std::string& file_name) { + event_log_.reset(new rtclog::EventStream()); + return RtcEventLog::ParseRtcEventLog(file_name, event_log_.get()); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h new file mode 100644 index 0000000000..d144b516a2 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/rtc_event_log_source.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTC_EVENT_LOG_SOURCE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTC_EVENT_LOG_SOURCE_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" + +namespace webrtc { + +class RtpHeaderParser; + +namespace rtclog { +class EventStream; +} // namespace rtclog + +namespace test { + +class Packet; + +class RtcEventLogSource : public PacketSource { + public: + // Creates an RtcEventLogSource reading from |file_name|. If the file cannot + // be opened, or has the wrong format, NULL will be returned. + static RtcEventLogSource* Create(const std::string& file_name); + + virtual ~RtcEventLogSource(); + + // Registers an RTP header extension and binds it to |id|. + virtual bool RegisterRtpHeaderExtension(RTPExtensionType type, uint8_t id); + + // Returns a pointer to the next packet. Returns NULL if end of file was + // reached. + Packet* NextPacket() override; + + // Returns the timestamp of the next audio output event, in milliseconds. The + // maximum value of int64_t is returned if there are no more audio output + // events available. + int64_t NextAudioOutputEventMs(); + + private: + RtcEventLogSource(); + + bool OpenFile(const std::string& file_name); + + int rtp_packet_index_ = 0; + int audio_output_index_ = 0; + + rtc::scoped_ptr event_log_; + rtc::scoped_ptr parser_; + + DISALLOW_COPY_AND_ASSIGN(RtcEventLogSource); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTC_EVENT_LOG_SOURCE_H_