diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index 582937eb25..a3793f5ebb 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -11,6 +11,7 @@ import("//build/config/crypto.gni") import("//build/config/linux/pkg_config.gni") import("build/webrtc.gni") +import("//third_party/protobuf/proto_library.gni") # Contains the defines and includes in common.gypi that are duplicated both as # target_defaults and direct_dependent_settings. @@ -175,6 +176,7 @@ source_set("webrtc") { "transport.h", ] + defines = [] configs += [ ":common_config" ] public_configs = [ ":common_inherited_config" ] @@ -206,6 +208,11 @@ source_set("webrtc") { "modules/video_render", ] } + + if (rtc_enable_protobuf) { + defines += [ "ENABLE_RTC_EVENT_LOG" ] + deps += [ ":rtc_event_log_proto" ] + } } if (!build_with_chromium) { @@ -239,3 +246,37 @@ source_set("gtest_prod") { "test/testsupport/gtest_prod_util.h", ] } + +if (rtc_enable_protobuf) { + proto_library("rtc_event_log_proto") { + sources = [ + "video/rtc_event_log.proto", + ] + proto_out_dir = "webrtc/video" + } +} + +source_set("rtc_event_log") { + sources = [ + "video/rtc_event_log.cc", + "video/rtc_event_log.h", + ] + + defines = [] + configs += [ ":common_config" ] + public_configs = [ ":common_inherited_config" ] + + deps = [ + ":webrtc_common", + ] + + if (rtc_enable_protobuf) { + defines += [ "ENABLE_RTC_EVENT_LOG" ] + deps += [ ":rtc_event_log_proto" ] + } + if (is_clang) { + # Suppress warnings from Chrome's Clang plugins. + # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. + configs -= [ "//build/config/clang:find_bad_constructs" ] + } +} diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn index 4b7e417a92..8a27900edf 100644 --- a/webrtc/modules/audio_coding/BUILD.gn +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -7,7 +7,6 @@ # be found in the AUTHORS file in the root of the source tree. import("//build/config/arm.gni") -import("//third_party/protobuf/proto_library.gni") import("../../build/webrtc.gni") config("audio_coding_config") { @@ -80,35 +79,6 @@ source_set("audio_coding") { } } -proto_library("acm_dump_proto") { - sources = [ - "main/acm2/dump.proto", - ] - proto_out_dir = "webrtc/audio_coding" -} - -source_set("acm_dump") { - sources = [ - "main/acm2/acm_dump.cc", - "main/acm2/acm_dump.h", - ] - - defines = [] - - configs += [ "../..:common_config" ] - - public_configs = [ "../..:common_inherited_config" ] - - deps = [ - ":acm_dump_proto", - "../..:webrtc_common", - ] - - if (rtc_enable_protobuf) { - defines += [ "RTC_AUDIOCODING_DEBUG_DUMP" ] - } -} - source_set("audio_decoder_interface") { sources = [ "codecs/audio_decoder.cc", diff --git a/webrtc/modules/audio_coding/main/acm2/acm_dump.cc b/webrtc/modules/audio_coding/main/acm2/acm_dump.cc deleted file mode 100644 index 9c624d97b6..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/acm_dump.cc +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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/main/acm2/acm_dump.h" - -#include - -#include "webrtc/base/checks.h" -#include "webrtc/base/thread_annotations.h" -#include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/file_wrapper.h" - -#ifdef RTC_AUDIOCODING_DEBUG_DUMP -// Files generated at build-time by the protobuf compiler. -#ifdef WEBRTC_ANDROID_PLATFORM_BUILD -#include "external/webrtc/webrtc/modules/audio_coding/dump.pb.h" -#else -#include "webrtc/audio_coding/dump.pb.h" -#endif -#endif - -namespace webrtc { - -// Noop implementation if flag is not set -#ifndef RTC_AUDIOCODING_DEBUG_DUMP -class AcmDumpImpl final : public AcmDump { - public: - void StartLogging(const std::string& file_name, int duration_ms) override{}; - void LogRtpPacket(bool incoming, - const uint8_t* packet, - size_t length) override{}; - void LogDebugEvent(DebugEvent event_type, - const std::string& event_message) override{}; - void LogDebugEvent(DebugEvent event_type) override{}; -}; -#else - -class AcmDumpImpl final : public AcmDump { - public: - AcmDumpImpl(); - - void StartLogging(const std::string& file_name, int duration_ms) override; - void LogRtpPacket(bool incoming, - const uint8_t* packet, - size_t length) override; - void LogDebugEvent(DebugEvent event_type, - const std::string& event_message) override; - void LogDebugEvent(DebugEvent event_type) override; - - private: - // This function is identical to LogDebugEvent, but requires holding the lock. - void LogDebugEventLocked(DebugEvent event_type, - const std::string& event_message) - EXCLUSIVE_LOCKS_REQUIRED(crit_); - // Stops logging and clears the stored data and buffers. - void Clear() EXCLUSIVE_LOCKS_REQUIRED(crit_); - // Adds a new event to the logfile if logging is active, or adds it to the - // list of recent log events otherwise. - void HandleEvent(ACMDumpEvent* event) EXCLUSIVE_LOCKS_REQUIRED(crit_); - // Writes the event to the file. Note that this will destroy the state of the - // input argument. - void StoreToFile(ACMDumpEvent* event) EXCLUSIVE_LOCKS_REQUIRED(crit_); - // Adds the event to the list of recent events, and removes any events that - // are too old and no longer fall in the time window. - void AddRecentEvent(const ACMDumpEvent& event) - EXCLUSIVE_LOCKS_REQUIRED(crit_); - - // Amount of time in microseconds to record log events, before starting the - // actual log. - const int recent_log_duration_us = 10000000; - - rtc::scoped_ptr crit_; - rtc::scoped_ptr file_ GUARDED_BY(crit_); - rtc::scoped_ptr stream_ GUARDED_BY(crit_); - std::deque recent_log_events_ GUARDED_BY(crit_); - bool currently_logging_ GUARDED_BY(crit_); - int64_t start_time_us_ GUARDED_BY(crit_); - int64_t duration_us_ GUARDED_BY(crit_); - const webrtc::Clock* const clock_; -}; - -namespace { - -// Convert from AcmDump's debug event enum (runtime format) to the corresponding -// protobuf enum (serialized format). -ACMDumpDebugEvent_EventType convertDebugEvent(AcmDump::DebugEvent event_type) { - switch (event_type) { - case AcmDump::DebugEvent::kLogStart: - return ACMDumpDebugEvent::LOG_START; - case AcmDump::DebugEvent::kLogEnd: - return ACMDumpDebugEvent::LOG_END; - case AcmDump::DebugEvent::kAudioPlayout: - return ACMDumpDebugEvent::AUDIO_PLAYOUT; - } - return ACMDumpDebugEvent::UNKNOWN_EVENT; -} - -} // Anonymous namespace. - -// AcmDumpImpl member functions. -AcmDumpImpl::AcmDumpImpl() - : crit_(webrtc::CriticalSectionWrapper::CreateCriticalSection()), - file_(webrtc::FileWrapper::Create()), - stream_(new webrtc::ACMDumpEventStream()), - currently_logging_(false), - start_time_us_(0), - duration_us_(0), - clock_(webrtc::Clock::GetRealTimeClock()) { -} - -void AcmDumpImpl::StartLogging(const std::string& file_name, int duration_ms) { - CriticalSectionScoped lock(crit_.get()); - Clear(); - if (file_->OpenFile(file_name.c_str(), false) != 0) { - return; - } - // Add LOG_START event to the recent event list. This call will also remove - // any events that are too old from the recent event list. - LogDebugEventLocked(DebugEvent::kLogStart, ""); - currently_logging_ = true; - start_time_us_ = clock_->TimeInMicroseconds(); - duration_us_ = static_cast(duration_ms) * 1000; - // Write all the recent events to the log file. - for (auto&& event : recent_log_events_) { - StoreToFile(&event); - } - recent_log_events_.clear(); -} - -void AcmDumpImpl::LogRtpPacket(bool incoming, - const uint8_t* packet, - size_t length) { - CriticalSectionScoped lock(crit_.get()); - ACMDumpEvent rtp_event; - const int64_t timestamp = clock_->TimeInMicroseconds(); - rtp_event.set_timestamp_us(timestamp); - rtp_event.set_type(webrtc::ACMDumpEvent::RTP_EVENT); - rtp_event.mutable_packet()->set_direction( - incoming ? ACMDumpRTPPacket::INCOMING : ACMDumpRTPPacket::OUTGOING); - rtp_event.mutable_packet()->set_rtp_data(packet, length); - HandleEvent(&rtp_event); -} - -void AcmDumpImpl::LogDebugEvent(DebugEvent event_type, - const std::string& event_message) { - CriticalSectionScoped lock(crit_.get()); - LogDebugEventLocked(event_type, event_message); -} - -void AcmDumpImpl::LogDebugEvent(DebugEvent event_type) { - CriticalSectionScoped lock(crit_.get()); - LogDebugEventLocked(event_type, ""); -} - -void AcmDumpImpl::LogDebugEventLocked(DebugEvent event_type, - const std::string& event_message) { - ACMDumpEvent event; - int64_t timestamp = clock_->TimeInMicroseconds(); - event.set_timestamp_us(timestamp); - event.set_type(webrtc::ACMDumpEvent::DEBUG_EVENT); - auto debug_event = event.mutable_debug_event(); - debug_event->set_type(convertDebugEvent(event_type)); - debug_event->set_message(event_message); - HandleEvent(&event); -} - -void AcmDumpImpl::Clear() { - if (file_->Open()) { - file_->CloseFile(); - } - currently_logging_ = false; - stream_->Clear(); -} - -void AcmDumpImpl::HandleEvent(ACMDumpEvent* event) { - if (currently_logging_) { - if (clock_->TimeInMicroseconds() < start_time_us_ + duration_us_) { - StoreToFile(event); - } else { - LogDebugEventLocked(DebugEvent::kLogEnd, ""); - Clear(); - AddRecentEvent(*event); - } - } else { - AddRecentEvent(*event); - } -} - -void AcmDumpImpl::StoreToFile(ACMDumpEvent* event) { - // Reuse the same object at every log event. - if (stream_->stream_size() < 1) { - stream_->add_stream(); - } - DCHECK_EQ(stream_->stream_size(), 1); - stream_->mutable_stream(0)->Swap(event); - - std::string dump_buffer; - stream_->SerializeToString(&dump_buffer); - file_->Write(dump_buffer.data(), dump_buffer.size()); -} - -void AcmDumpImpl::AddRecentEvent(const ACMDumpEvent& event) { - recent_log_events_.push_back(event); - while (recent_log_events_.front().timestamp_us() < - event.timestamp_us() - recent_log_duration_us) { - recent_log_events_.pop_front(); - } -} - -bool AcmDump::ParseAcmDump(const std::string& file_name, - ACMDumpEventStream* result) { - char tmp_buffer[1024]; - int bytes_read = 0; - rtc::scoped_ptr dump_file(FileWrapper::Create()); - if (dump_file->OpenFile(file_name.c_str(), true) != 0) { - return false; - } - std::string dump_buffer; - while ((bytes_read = dump_file->Read(tmp_buffer, sizeof(tmp_buffer))) > 0) { - dump_buffer.append(tmp_buffer, bytes_read); - } - dump_file->CloseFile(); - return result->ParseFromString(dump_buffer); -} - -#endif // RTC_AUDIOCODING_DEBUG_DUMP - -// AcmDump member functions. -rtc::scoped_ptr AcmDump::Create() { - return rtc::scoped_ptr(new AcmDumpImpl()); -} -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/acm_dump.h b/webrtc/modules/audio_coding/main/acm2/acm_dump.h deleted file mode 100644 index c72c387096..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/acm_dump.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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_MAIN_ACM2_ACM_DUMP_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_DUMP_H_ - -#include - -#include "webrtc/base/scoped_ptr.h" - -namespace webrtc { - -// Forward declaration of storage class that is automatically generated from -// the protobuf file. -class ACMDumpEventStream; - -class AcmDumpImpl; - -class AcmDump { - public: - // The types of debug events that are currently supported for logging. - enum class DebugEvent { kLogStart, kLogEnd, kAudioPlayout }; - - virtual ~AcmDump() {} - - static rtc::scoped_ptr Create(); - - // Starts logging for the specified duration to the specified file. - // The logging will stop automatically after the specified duration. - // If the file already exists it will be overwritten. - // The function will return false on failure. - virtual void StartLogging(const std::string& file_name, int duration_ms) = 0; - - // Logs an incoming or outgoing RTP packet. - virtual void LogRtpPacket(bool incoming, - const uint8_t* packet, - size_t length) = 0; - - // Logs a debug event, with optional message. - virtual void LogDebugEvent(DebugEvent event_type, - const std::string& event_message) = 0; - virtual void LogDebugEvent(DebugEvent event_type) = 0; - - // Reads an AcmDump file and returns true when reading was successful. - // The result is stored in the given ACMDumpEventStream object. - static bool ParseAcmDump(const std::string& file_name, - ACMDumpEventStream* result); -}; - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_DUMP_H_ diff --git a/webrtc/modules/audio_coding/main/acm2/acm_dump_unittest.cc b/webrtc/modules/audio_coding/main/acm2/acm_dump_unittest.cc deleted file mode 100644 index 98d0e622a8..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/acm_dump_unittest.cc +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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. - */ - -#ifdef RTC_AUDIOCODING_DEBUG_DUMP - -#include -#include -#include - -#include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/base/scoped_ptr.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_dump.h" -#include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/test/test_suite.h" -#include "webrtc/test/testsupport/fileutils.h" -#include "webrtc/test/testsupport/gtest_disable.h" - -// Files generated at build-time by the protobuf compiler. -#ifdef WEBRTC_ANDROID_PLATFORM_BUILD -#include "external/webrtc/webrtc/modules/audio_coding/dump.pb.h" -#else -#include "webrtc/audio_coding/dump.pb.h" -#endif - -namespace webrtc { - -// Test for the acm dump class. Dumps some RTP packets to disk, then reads them -// back to see if they match. -class AcmDumpTest : public ::testing::Test { - public: - void VerifyResults(const ACMDumpEventStream& parsed_stream, - size_t packet_size) { - // Verify the result. - EXPECT_EQ(5, parsed_stream.stream_size()); - const ACMDumpEvent& start_event = parsed_stream.stream(2); - ASSERT_TRUE(start_event.has_type()); - EXPECT_EQ(ACMDumpEvent::DEBUG_EVENT, start_event.type()); - EXPECT_TRUE(start_event.has_timestamp_us()); - EXPECT_FALSE(start_event.has_packet()); - ASSERT_TRUE(start_event.has_debug_event()); - auto start_debug_event = start_event.debug_event(); - ASSERT_TRUE(start_debug_event.has_type()); - EXPECT_EQ(ACMDumpDebugEvent::LOG_START, start_debug_event.type()); - ASSERT_TRUE(start_debug_event.has_message()); - - for (int i = 0; i < parsed_stream.stream_size(); i++) { - if (i == 2) { - // This is the LOG_START packet that was already verified. - continue; - } - const ACMDumpEvent& test_event = parsed_stream.stream(i); - ASSERT_TRUE(test_event.has_type()); - EXPECT_EQ(ACMDumpEvent::RTP_EVENT, test_event.type()); - EXPECT_TRUE(test_event.has_timestamp_us()); - EXPECT_FALSE(test_event.has_debug_event()); - ASSERT_TRUE(test_event.has_packet()); - const ACMDumpRTPPacket& test_packet = test_event.packet(); - ASSERT_TRUE(test_packet.has_direction()); - if (i <= 1) { - EXPECT_EQ(ACMDumpRTPPacket::INCOMING, test_packet.direction()); - } else if (i >= 3) { - EXPECT_EQ(ACMDumpRTPPacket::OUTGOING, test_packet.direction()); - } - ASSERT_TRUE(test_packet.has_rtp_data()); - ASSERT_EQ(packet_size, test_packet.rtp_data().size()); - for (size_t i = 0; i < packet_size; i++) { - EXPECT_EQ(rtp_packet_[i], - static_cast(test_packet.rtp_data()[i])); - } - } - } - - void Run(int packet_size, int random_seed) { - rtp_packet_.clear(); - rtp_packet_.reserve(packet_size); - srand(random_seed); - // Fill the packet vector with random data. - for (int i = 0; i < packet_size; i++) { - rtp_packet_.push_back(rand()); - } - // Find the name of the current test, in order to use it as a temporary - // filename. - auto test_info = ::testing::UnitTest::GetInstance()->current_test_info(); - const std::string temp_filename = - test::OutputPath() + test_info->test_case_name() + test_info->name(); - - // When log_dumper goes out of scope, it causes the log file to be flushed - // to disk. - { - rtc::scoped_ptr log_dumper(AcmDump::Create()); - log_dumper->LogRtpPacket(true, rtp_packet_.data(), rtp_packet_.size()); - log_dumper->LogRtpPacket(true, rtp_packet_.data(), rtp_packet_.size()); - log_dumper->StartLogging(temp_filename, 10000000); - log_dumper->LogRtpPacket(false, rtp_packet_.data(), rtp_packet_.size()); - log_dumper->LogRtpPacket(false, rtp_packet_.data(), rtp_packet_.size()); - } - - // Read the generated file from disk. - ACMDumpEventStream parsed_stream; - - ASSERT_EQ(true, AcmDump::ParseAcmDump(temp_filename, &parsed_stream)); - - VerifyResults(parsed_stream, packet_size); - - // Clean up temporary file - can be pretty slow. - remove(temp_filename.c_str()); - } - std::vector rtp_packet_; -}; - -TEST_F(AcmDumpTest, DumpAndRead) { - Run(256, 321); -} - -} // namespace webrtc - -#endif // RTC_AUDIOCODING_DEBUG_DUMP diff --git a/webrtc/modules/audio_coding/main/acm2/dump.proto b/webrtc/modules/audio_coding/main/acm2/dump.proto deleted file mode 100644 index 232faec428..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/dump.proto +++ /dev/null @@ -1,169 +0,0 @@ -syntax = "proto2"; -option optimize_for = LITE_RUNTIME; -package webrtc; - -// This is the main message to dump to a file, it can contain multiple event -// messages, but it is possible to append multiple EventStreams (each with a -// single event) to a file. -// This has the benefit that there's no need to keep all data in memory. -message ACMDumpEventStream { - repeated ACMDumpEvent stream = 1; -} - - -message ACMDumpEvent { - // required - Elapsed wallclock time in us since the start of the log. - optional int64 timestamp_us = 1; - - // The different types of events that can occur, the UNKNOWN_EVENT entry - // is added in case future EventTypes are added, in that case old code will - // receive the new events as UNKNOWN_EVENT. - enum EventType { - UNKNOWN_EVENT = 0; - RTP_EVENT = 1; - DEBUG_EVENT = 2; - CONFIG_EVENT = 3; - } - - // required - Indicates the type of this event - optional EventType type = 2; - - // optional - but required if type == RTP_EVENT - optional ACMDumpRTPPacket packet = 3; - - // optional - but required if type == DEBUG_EVENT - optional ACMDumpDebugEvent debug_event = 4; - - // optional - but required if type == CONFIG_EVENT - optional ACMDumpConfigEvent config = 5; -} - - -message ACMDumpRTPPacket { - // Indicates if the packet is incoming or outgoing with respect to the user - // that is logging the data. - enum Direction { - UNKNOWN_DIRECTION = 0; - OUTGOING = 1; - INCOMING = 2; - } - enum PayloadType { - UNKNOWN_TYPE = 0; - AUDIO = 1; - VIDEO = 2; - RTX = 3; - } - - // required - optional Direction direction = 1; - - // required - optional PayloadType type = 2; - - // required - Contains the whole RTP packet (header+payload). - optional bytes RTP_data = 3; -} - - -message ACMDumpDebugEvent { - // Indicates the type of the debug event. - // LOG_START and LOG_END indicate the start and end of the log respectively. - // AUDIO_PLAYOUT indicates a call to the PlayoutData10Ms() function in ACM. - enum EventType { - UNKNOWN_EVENT = 0; - LOG_START = 1; - LOG_END = 2; - AUDIO_PLAYOUT = 3; - } - - // required - optional EventType type = 1; - - // An optional message that can be used to store additional information about - // the debug event. - optional string message = 2; -} - - -// TODO(terelius): Video and audio streams could in principle share SSRC, -// so identifying a stream based only on SSRC might not work. -// It might be better to use a combination of SSRC and media type -// or SSRC and port number, but for now we will rely on SSRC only. -message ACMDumpConfigEvent { - // Synchronization source (stream identifier) to be received. - optional uint32 remote_ssrc = 1; - - // RTX settings for incoming video payloads that may be received. RTX is - // disabled if there's no config present. - optional RtcpConfig rtcp_config = 3; - - // Map from video RTP payload type -> RTX config. - repeated RtxMap rtx_map = 4; - - // RTP header extensions used for the received stream. - repeated RtpHeaderExtension header_extensions = 5; - - // List of decoders associated with the stream. - repeated DecoderConfig decoders = 6; -} - - -// Maps decoder names to payload types. -message DecoderConfig { - // required - optional string name = 1; - - // required - optional sint32 payload_type = 2; -} - - -// Maps RTP header extension names to numerical ids. -message RtpHeaderExtension { - // required - optional string name = 1; - - // required - optional sint32 id = 2; -} - - -// RTX settings for incoming video payloads that may be received. -// RTX is disabled if there's no config present. -message RtxConfig { - // required - SSRCs to use for the RTX streams. - optional uint32 ssrc = 1; - - // required - Payload type to use for the RTX stream. - optional sint32 payload_type = 2; -} - - -message RtxMap { - // required - optional sint32 payload_type = 1; - - // required - optional RtxConfig config = 2; -} - - -// Configuration information for RTCP. -// For bandwidth estimation purposes it is more interesting to log the -// RTCP messages that the sender receives, but we will support logging -// at the receiver side too. -message RtcpConfig { - // Sender SSRC used for sending RTCP (such as receiver reports). - optional uint32 local_ssrc = 1; - - // RTCP mode to use. Compound mode is described by RFC 4585 and reduced-size - // RTCP mode is described by RFC 5506. - enum RtcpMode {RTCP_COMPOUND = 1; RTCP_REDUCEDSIZE = 2;} - optional RtcpMode rtcp_mode = 2; - - // Extended RTCP settings. - optional bool receiver_reference_time_report = 3; - - // Receiver estimated maximum bandwidth. - optional bool remb = 4; -} diff --git a/webrtc/modules/audio_coding/main/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/audio_coding_module.gypi index d934868ad5..43d99f89fc 100644 --- a/webrtc/modules/audio_coding/main/audio_coding_module.gypi +++ b/webrtc/modules/audio_coding/main/audio_coding_module.gypi @@ -78,40 +78,8 @@ 'interface/audio_coding_module_typedefs.h', ], }, - { - 'target_name': 'acm_dump', - 'type': 'static_library', - 'conditions': [ - ['enable_protobuf==1', { - 'defines': ['RTC_AUDIOCODING_DEBUG_DUMP'], - 'dependencies': ['acm_dump_proto'], - } - ], - ], - 'sources': [ - 'acm_dump.h', - 'acm_dump.cc' - ], - }, ], 'conditions': [ - ['enable_protobuf==1', { - 'targets': [ - { - 'target_name': 'acm_dump_proto', - 'type': 'static_library', - 'sources': ['dump.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', - 'proto_out_dir': '<(proto_out_protected)', - }, - 'includes': ['../../../../build/protoc.gypi',], - }, - ] - }], ['include_tests==1', { 'targets': [ { diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index 5cbe691be6..a769aa9619 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/modules/modules.gyp @@ -323,15 +323,12 @@ ['enable_protobuf==1', { 'defines': [ 'WEBRTC_AUDIOPROC_DEBUG_DUMP', - 'RTC_AUDIOCODING_DEBUG_DUMP', ], 'dependencies': [ - 'acm_dump', 'audioproc_protobuf_utils', 'audioproc_unittest_proto', ], 'sources': [ - 'audio_coding/main/acm2/acm_dump_unittest.cc', 'audio_processing/audio_processing_impl_unittest.cc', 'audio_processing/test/audio_processing_unittest.cc', 'audio_processing/test/test_utils.h', diff --git a/webrtc/video/BUILD.gn b/webrtc/video/BUILD.gn index 10f9510e71..1d26b41993 100644 --- a/webrtc/video/BUILD.gn +++ b/webrtc/video/BUILD.gn @@ -66,6 +66,7 @@ source_set("video") { } deps = [ + "..:rtc_event_log", "..:webrtc_common", "../common_video", "../modules/bitrate_controller", diff --git a/webrtc/video/rtc_event_log.cc b/webrtc/video/rtc_event_log.cc new file mode 100644 index 0000000000..476ee2afd7 --- /dev/null +++ b/webrtc/video/rtc_event_log.cc @@ -0,0 +1,406 @@ +/* + * 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/video/rtc_event_log.h" + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/call.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/file_wrapper.h" + +#ifdef ENABLE_RTC_EVENT_LOG +// 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 +#endif + +namespace webrtc { + +#ifndef ENABLE_RTC_EVENT_LOG + +// No-op implementation if flag is not set. +class RtcEventLogImpl final : public RtcEventLog { + public: + void StartLogging(const std::string& file_name, int duration_ms) override {} + void StopLogging(void) override {} + void LogVideoReceiveStreamConfig( + const VideoReceiveStream::Config& config) override {} + void LogVideoSendStreamConfig( + const VideoSendStream::Config& config) override {} + void LogRtpHeader(bool incoming, + MediaType media_type, + const uint8_t* header, + size_t header_length, + size_t total_length) override {} + void LogRtcpPacket(bool incoming, + MediaType media_type, + const uint8_t* packet, + size_t length) override {} + void LogDebugEvent(DebugEvent event_type) override {} +}; + +#else // ENABLE_RTC_EVENT_LOG is defined + +class RtcEventLogImpl final : public RtcEventLog { + public: + RtcEventLogImpl(); + + void StartLogging(const std::string& file_name, int duration_ms) override; + void StopLogging() override; + void LogVideoReceiveStreamConfig( + const VideoReceiveStream::Config& config) override; + void LogVideoSendStreamConfig(const VideoSendStream::Config& config) override; + void LogRtpHeader(bool incoming, + MediaType media_type, + const uint8_t* header, + size_t header_length, + size_t total_length) override; + void LogRtcpPacket(bool incoming, + MediaType media_type, + const uint8_t* packet, + size_t length) override; + void LogDebugEvent(DebugEvent event_type) override; + + private: + // Stops logging and clears the stored data and buffers. + void StopLoggingLocked() EXCLUSIVE_LOCKS_REQUIRED(crit_); + // Adds a new event to the logfile if logging is active, or adds it to the + // list of recent log events otherwise. + void HandleEvent(rtclog::Event* event) EXCLUSIVE_LOCKS_REQUIRED(crit_); + // Writes the event to the file. Note that this will destroy the state of the + // input argument. + void StoreToFile(rtclog::Event* event) EXCLUSIVE_LOCKS_REQUIRED(crit_); + // Adds the event to the list of recent events, and removes any events that + // are too old and no longer fall in the time window. + void AddRecentEvent(const rtclog::Event& event) + EXCLUSIVE_LOCKS_REQUIRED(crit_); + + // Amount of time in microseconds to record log events, before starting the + // actual log. + const int recent_log_duration_us = 10000000; + + rtc::CriticalSection crit_; + rtc::scoped_ptr file_ GUARDED_BY(crit_); + rtclog::EventStream stream_ GUARDED_BY(crit_); + std::deque recent_log_events_ GUARDED_BY(crit_); + bool currently_logging_ GUARDED_BY(crit_); + int64_t start_time_us_ GUARDED_BY(crit_); + int64_t duration_us_ GUARDED_BY(crit_); + const Clock* const clock_; +}; + +namespace { +// The functions in this namespace convert enums from the runtime format +// that the rest of the WebRtc project can use, to the corresponding +// serialized enum which is defined by the protobuf. + +// Do not add default return values to the conversion functions in this +// unnamed namespace. The intention is to make the compiler warn if anyone +// adds unhandled new events/modes/etc. + +rtclog::DebugEvent_EventType ConvertDebugEvent( + RtcEventLog::DebugEvent event_type) { + switch (event_type) { + case RtcEventLog::DebugEvent::kLogStart: + return rtclog::DebugEvent::LOG_START; + case RtcEventLog::DebugEvent::kLogEnd: + return rtclog::DebugEvent::LOG_END; + case RtcEventLog::DebugEvent::kAudioPlayout: + return rtclog::DebugEvent::AUDIO_PLAYOUT; + } + RTC_NOTREACHED(); + return rtclog::DebugEvent::UNKNOWN_EVENT; +} + +rtclog::VideoReceiveConfig_RtcpMode ConvertRtcpMode( + newapi::RtcpMode rtcp_mode) { + switch (rtcp_mode) { + case newapi::kRtcpCompound: + return rtclog::VideoReceiveConfig::RTCP_COMPOUND; + case newapi::kRtcpReducedSize: + return rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE; + } + RTC_NOTREACHED(); + return rtclog::VideoReceiveConfig::RTCP_COMPOUND; +} + +rtclog::MediaType ConvertMediaType(MediaType media_type) { + switch (media_type) { + case MediaType::ANY: + return rtclog::MediaType::ANY; + case MediaType::AUDIO: + return rtclog::MediaType::AUDIO; + case MediaType::VIDEO: + return rtclog::MediaType::VIDEO; + case MediaType::DATA: + return rtclog::MediaType::DATA; + } + RTC_NOTREACHED(); + return rtclog::ANY; +} + +} // namespace + +// RtcEventLogImpl member functions. +RtcEventLogImpl::RtcEventLogImpl() + : file_(FileWrapper::Create()), + stream_(), + currently_logging_(false), + start_time_us_(0), + duration_us_(0), + clock_(Clock::GetRealTimeClock()) { +} + +void RtcEventLogImpl::StartLogging(const std::string& file_name, + int duration_ms) { + rtc::CritScope lock(&crit_); + if (currently_logging_) { + StopLoggingLocked(); + } + if (file_->OpenFile(file_name.c_str(), false) != 0) { + return; + } + currently_logging_ = true; + start_time_us_ = clock_->TimeInMicroseconds(); + duration_us_ = static_cast(duration_ms) * 1000; + // Write all the recent events to the log file, ignoring any old events. + for (auto& event : recent_log_events_) { + if (event.timestamp_us() >= start_time_us_ - recent_log_duration_us) { + StoreToFile(&event); + } + } + recent_log_events_.clear(); + // Write a LOG_START event to the file. + rtclog::Event start_event; + start_event.set_timestamp_us(start_time_us_); + start_event.set_type(rtclog::Event::DEBUG_EVENT); + auto debug_event = start_event.mutable_debug_event(); + debug_event->set_type(ConvertDebugEvent(DebugEvent::kLogStart)); + StoreToFile(&start_event); +} + +void RtcEventLogImpl::StopLogging() { + rtc::CritScope lock(&crit_); + StopLoggingLocked(); +} + +void RtcEventLogImpl::LogVideoReceiveStreamConfig( + const VideoReceiveStream::Config& config) { + rtc::CritScope lock(&crit_); + + rtclog::Event event; + const int64_t timestamp = clock_->TimeInMicroseconds(); + event.set_timestamp_us(timestamp); + event.set_type(rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT); + + rtclog::VideoReceiveConfig* receiver_config = + event.mutable_video_receiver_config(); + receiver_config->set_remote_ssrc(config.rtp.remote_ssrc); + receiver_config->set_local_ssrc(config.rtp.local_ssrc); + + receiver_config->set_rtcp_mode(ConvertRtcpMode(config.rtp.rtcp_mode)); + + receiver_config->set_receiver_reference_time_report( + config.rtp.rtcp_xr.receiver_reference_time_report); + receiver_config->set_remb(config.rtp.remb); + + for (const auto& kv : config.rtp.rtx) { + rtclog::RtxMap* rtx = receiver_config->add_rtx_map(); + rtx->set_payload_type(kv.first); + rtx->mutable_config()->set_rtx_ssrc(kv.second.ssrc); + rtx->mutable_config()->set_rtx_payload_type(kv.second.payload_type); + } + + for (const auto& e : config.rtp.extensions) { + rtclog::RtpHeaderExtension* extension = + receiver_config->add_header_extensions(); + extension->set_name(e.name); + extension->set_id(e.id); + } + + for (const auto& d : config.decoders) { + rtclog::DecoderConfig* decoder = receiver_config->add_decoders(); + decoder->set_name(d.payload_name); + decoder->set_payload_type(d.payload_type); + } + // TODO(terelius): We should use a separate event queue for config events. + // The current approach of storing the configuration together with the + // RTP events causes the configuration information to be removed 10s + // after the ReceiveStream is created. + HandleEvent(&event); +} + +void RtcEventLogImpl::LogVideoSendStreamConfig( + const VideoSendStream::Config& config) { + rtc::CritScope lock(&crit_); + + rtclog::Event event; + const int64_t timestamp = clock_->TimeInMicroseconds(); + event.set_timestamp_us(timestamp); + event.set_type(rtclog::Event::VIDEO_SENDER_CONFIG_EVENT); + + rtclog::VideoSendConfig* sender_config = event.mutable_video_sender_config(); + + for (const auto& ssrc : config.rtp.ssrcs) { + sender_config->add_ssrcs(ssrc); + } + + for (const auto& e : config.rtp.extensions) { + rtclog::RtpHeaderExtension* extension = + sender_config->add_header_extensions(); + extension->set_name(e.name); + extension->set_id(e.id); + } + + for (const auto& rtx_ssrc : config.rtp.rtx.ssrcs) { + sender_config->add_rtx_ssrcs(rtx_ssrc); + } + sender_config->set_rtx_payload_type(config.rtp.rtx.payload_type); + + sender_config->set_c_name(config.rtp.c_name); + + rtclog::EncoderConfig* encoder = sender_config->mutable_encoder(); + encoder->set_name(config.encoder_settings.payload_name); + encoder->set_payload_type(config.encoder_settings.payload_type); + + // TODO(terelius): We should use a separate event queue for config events. + // The current approach of storing the configuration together with the + // RTP events causes the configuration information to be removed 10s + // after the ReceiveStream is created. + HandleEvent(&event); +} + +// TODO(terelius): It is more convenient and less error prone to parse the +// header length from the packet instead of relying on the caller to provide it. +void RtcEventLogImpl::LogRtpHeader(bool incoming, + MediaType media_type, + const uint8_t* header, + size_t header_length, + size_t total_length) { + rtc::CritScope lock(&crit_); + rtclog::Event rtp_event; + const int64_t timestamp = clock_->TimeInMicroseconds(); + rtp_event.set_timestamp_us(timestamp); + rtp_event.set_type(rtclog::Event::RTP_EVENT); + rtp_event.mutable_rtp_packet()->set_incoming(incoming); + rtp_event.mutable_rtp_packet()->set_type(ConvertMediaType(media_type)); + rtp_event.mutable_rtp_packet()->set_packet_length(total_length); + rtp_event.mutable_rtp_packet()->set_header(header, header_length); + HandleEvent(&rtp_event); +} + +void RtcEventLogImpl::LogRtcpPacket(bool incoming, + MediaType media_type, + const uint8_t* packet, + size_t length) { + rtc::CritScope lock(&crit_); + rtclog::Event rtcp_event; + const int64_t timestamp = clock_->TimeInMicroseconds(); + rtcp_event.set_timestamp_us(timestamp); + rtcp_event.set_type(rtclog::Event::RTCP_EVENT); + rtcp_event.mutable_rtcp_packet()->set_incoming(incoming); + rtcp_event.mutable_rtcp_packet()->set_type(ConvertMediaType(media_type)); + rtcp_event.mutable_rtcp_packet()->set_packet_data(packet, length); + HandleEvent(&rtcp_event); +} + +void RtcEventLogImpl::LogDebugEvent(DebugEvent event_type) { + rtc::CritScope lock(&crit_); + rtclog::Event event; + const int64_t timestamp = clock_->TimeInMicroseconds(); + event.set_timestamp_us(timestamp); + event.set_type(rtclog::Event::DEBUG_EVENT); + auto debug_event = event.mutable_debug_event(); + debug_event->set_type(ConvertDebugEvent(event_type)); + HandleEvent(&event); +} + +void RtcEventLogImpl::StopLoggingLocked() { + if (currently_logging_) { + currently_logging_ = false; + // Create a LogEnd debug event + rtclog::Event event; + int64_t timestamp = clock_->TimeInMicroseconds(); + event.set_timestamp_us(timestamp); + event.set_type(rtclog::Event::DEBUG_EVENT); + auto debug_event = event.mutable_debug_event(); + debug_event->set_type(ConvertDebugEvent(DebugEvent::kLogEnd)); + // Store the event and close the file + DCHECK(file_->Open()); + StoreToFile(&event); + file_->CloseFile(); + } + DCHECK(!file_->Open()); + stream_.Clear(); +} + +void RtcEventLogImpl::HandleEvent(rtclog::Event* event) { + if (currently_logging_) { + if (clock_->TimeInMicroseconds() < start_time_us_ + duration_us_) { + StoreToFile(event); + return; + } + StopLoggingLocked(); + } + AddRecentEvent(*event); +} + +void RtcEventLogImpl::StoreToFile(rtclog::Event* event) { + // Reuse the same object at every log event. + if (stream_.stream_size() < 1) { + stream_.add_stream(); + } + DCHECK_EQ(stream_.stream_size(), 1); + stream_.mutable_stream(0)->Swap(event); + // TODO(terelius): Doesn't this create a new EventStream per event? + // Is this guaranteed to work e.g. in future versions of protobuf? + std::string dump_buffer; + stream_.SerializeToString(&dump_buffer); + file_->Write(dump_buffer.data(), dump_buffer.size()); +} + +void RtcEventLogImpl::AddRecentEvent(const rtclog::Event& event) { + recent_log_events_.push_back(event); + while (recent_log_events_.front().timestamp_us() < + event.timestamp_us() - recent_log_duration_us) { + recent_log_events_.pop_front(); + } +} + +bool RtcEventLog::ParseRtcEventLog(const std::string& file_name, + rtclog::EventStream* result) { + char tmp_buffer[1024]; + int bytes_read = 0; + rtc::scoped_ptr dump_file(FileWrapper::Create()); + if (dump_file->OpenFile(file_name.c_str(), true) != 0) { + return false; + } + std::string dump_buffer; + while ((bytes_read = dump_file->Read(tmp_buffer, sizeof(tmp_buffer))) > 0) { + dump_buffer.append(tmp_buffer, bytes_read); + } + dump_file->CloseFile(); + return result->ParseFromString(dump_buffer); +} + +#endif // ENABLE_RTC_EVENT_LOG + +// RtcEventLog member functions. +rtc::scoped_ptr RtcEventLog::Create() { + return rtc::scoped_ptr(new RtcEventLogImpl()); +} +} // namespace webrtc diff --git a/webrtc/video/rtc_event_log.h b/webrtc/video/rtc_event_log.h new file mode 100644 index 0000000000..a6bf2e3d88 --- /dev/null +++ b/webrtc/video/rtc_event_log.h @@ -0,0 +1,81 @@ +/* + * 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_VIDEO_RTC_EVENT_LOG_H_ +#define WEBRTC_VIDEO_RTC_EVENT_LOG_H_ + +#include + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/video_receive_stream.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { + +// Forward declaration of storage class that is automatically generated from +// the protobuf file. +namespace rtclog { +class EventStream; +} // namespace rtclog + +class RtcEventLogImpl; + +enum class MediaType; + +class RtcEventLog { + public: + // The types of debug events that are currently supported for logging. + enum class DebugEvent { kLogStart, kLogEnd, kAudioPlayout }; + + virtual ~RtcEventLog() {} + + static rtc::scoped_ptr Create(); + + // Starts logging for the specified duration to the specified file. + // The logging will stop automatically after the specified duration. + // If the file already exists it will be overwritten. + // If the file cannot be opened, the RtcEventLog will not start logging. + virtual void StartLogging(const std::string& file_name, int duration_ms) = 0; + + virtual void StopLogging() = 0; + + // Logs configuration information for webrtc::VideoReceiveStream + virtual void LogVideoReceiveStreamConfig( + const webrtc::VideoReceiveStream::Config& config) = 0; + + // Logs configuration information for webrtc::VideoSendStream + virtual void LogVideoSendStreamConfig( + const webrtc::VideoSendStream::Config& config) = 0; + + // Logs the header of an incoming or outgoing RTP packet. + virtual void LogRtpHeader(bool incoming, + MediaType media_type, + const uint8_t* header, + size_t header_length, + size_t total_length) = 0; + + // Logs an incoming or outgoing RTCP packet. + virtual void LogRtcpPacket(bool incoming, + MediaType media_type, + const uint8_t* packet, + size_t length) = 0; + + // Logs a debug event. + virtual void LogDebugEvent(DebugEvent event_type) = 0; + + // Reads an RtcEventLog file and returns true when reading was successful. + // The result is stored in the given EventStream object. + static bool ParseRtcEventLog(const std::string& file_name, + rtclog::EventStream* result); +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_RTC_EVENT_LOG_H_ diff --git a/webrtc/video/rtc_event_log.proto b/webrtc/video/rtc_event_log.proto new file mode 100644 index 0000000000..7e4e699e33 --- /dev/null +++ b/webrtc/video/rtc_event_log.proto @@ -0,0 +1,228 @@ +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; +package webrtc.rtclog; + + +enum MediaType { + ANY = 0; + AUDIO = 1; + VIDEO = 2; + DATA = 3; +} + + +// This is the main message to dump to a file, it can contain multiple event +// messages, but it is possible to append multiple EventStreams (each with a +// single event) to a file. +// This has the benefit that there's no need to keep all data in memory. +message EventStream { + repeated Event stream = 1; +} + + +message Event { + // required - Elapsed wallclock time in us since the start of the log. + optional int64 timestamp_us = 1; + + // The different types of events that can occur, the UNKNOWN_EVENT entry + // is added in case future EventTypes are added, in that case old code will + // receive the new events as UNKNOWN_EVENT. + enum EventType { + UNKNOWN_EVENT = 0; + RTP_EVENT = 1; + RTCP_EVENT = 2; + DEBUG_EVENT = 3; + VIDEO_RECEIVER_CONFIG_EVENT = 4; + VIDEO_SENDER_CONFIG_EVENT = 5; + AUDIO_RECEIVER_CONFIG_EVENT = 6; + AUDIO_SENDER_CONFIG_EVENT = 7; + } + + // required - Indicates the type of this event + optional EventType type = 2; + + // optional - but required if type == RTP_EVENT + optional RtpPacket rtp_packet = 3; + + // optional - but required if type == RTCP_EVENT + optional RtcpPacket rtcp_packet = 4; + + // optional - but required if type == DEBUG_EVENT + optional DebugEvent debug_event = 5; + + // optional - but required if type == VIDEO_RECEIVER_CONFIG_EVENT + optional VideoReceiveConfig video_receiver_config = 6; + + // optional - but required if type == VIDEO_SENDER_CONFIG_EVENT + optional VideoSendConfig video_sender_config = 7; + + // optional - but required if type == AUDIO_RECEIVER_CONFIG_EVENT + optional AudioReceiveConfig audio_receiver_config = 8; + + // optional - but required if type == AUDIO_SENDER_CONFIG_EVENT + optional AudioSendConfig audio_sender_config = 9; +} + + +message RtpPacket { + // required - True if the packet is incoming w.r.t. the user logging the data + optional bool incoming = 1; + + // required + optional MediaType type = 2; + + // required - The size of the packet including both payload and header. + optional uint32 packet_length = 3; + + // required - The RTP header only. + optional bytes header = 4; + + // Do not add code to log user payload data without a privacy review! +} + + +message RtcpPacket { + // required - True if the packet is incoming w.r.t. the user logging the data + optional bool incoming = 1; + + // required + optional MediaType type = 2; + + // required - The whole packet including both payload and header. + optional bytes packet_data = 3; +} + + +message DebugEvent { + // Indicates the type of the debug event. + // LOG_START and LOG_END indicate the start and end of the log respectively. + // AUDIO_PLAYOUT indicates a call to the PlayoutData10Ms() function in ACM. + enum EventType { + UNKNOWN_EVENT = 0; + LOG_START = 1; + LOG_END = 2; + AUDIO_PLAYOUT = 3; + } + + // required + optional EventType type = 1; +} + + +// TODO(terelius): Video and audio streams could in principle share SSRC, +// so identifying a stream based only on SSRC might not work. +// It might be better to use a combination of SSRC and media type +// or SSRC and port number, but for now we will rely on SSRC only. +message VideoReceiveConfig { + // required - Synchronization source (stream identifier) to be received. + optional uint32 remote_ssrc = 1; + // required - Sender SSRC used for sending RTCP (such as receiver reports). + optional uint32 local_ssrc = 2; + + // Compound mode is described by RFC 4585 and reduced-size + // RTCP mode is described by RFC 5506. + enum RtcpMode { + RTCP_COMPOUND = 1; + RTCP_REDUCEDSIZE = 2; + } + // required - RTCP mode to use. + optional RtcpMode rtcp_mode = 3; + + // required - Extended RTCP settings. + optional bool receiver_reference_time_report = 4; + + // required - Receiver estimated maximum bandwidth. + optional bool remb = 5; + + // Map from video RTP payload type -> RTX config. + repeated RtxMap rtx_map = 6; + + // RTP header extensions used for the received stream. + repeated RtpHeaderExtension header_extensions = 7; + + // List of decoders associated with the stream. + repeated DecoderConfig decoders = 8; +} + + +// Maps decoder names to payload types. +message DecoderConfig { + // required + optional string name = 1; + + // required + optional sint32 payload_type = 2; +} + + +// Maps RTP header extension names to numerical IDs. +message RtpHeaderExtension { + // required + optional string name = 1; + + // required + optional sint32 id = 2; +} + + +// RTX settings for incoming video payloads that may be received. +// RTX is disabled if there's no config present. +message RtxConfig { + // required - SSRC to use for the RTX stream. + optional uint32 rtx_ssrc = 1; + + // required - Payload type to use for the RTX stream. + optional sint32 rtx_payload_type = 2; +} + + +message RtxMap { + // required + optional sint32 payload_type = 1; + + // required + optional RtxConfig config = 2; +} + + +message VideoSendConfig { + // Synchronization source (stream identifier) for outgoing stream. + // One stream can have several ssrcs for e.g. simulcast. + // At least one ssrc is required. + repeated uint32 ssrcs = 1; + + // RTP header extensions used for the outgoing stream. + repeated RtpHeaderExtension header_extensions = 2; + + // List of SSRCs for retransmitted packets. + repeated uint32 rtx_ssrcs = 3; + + // required if rtx_ssrcs is used - Payload type for retransmitted packets. + optional sint32 rtx_payload_type = 4; + + // required - Canonical end-point identifier. + optional string c_name = 5; + + // required - Encoder associated with the stream. + optional EncoderConfig encoder = 6; +} + + +// Maps encoder names to payload types. +message EncoderConfig { + // required + optional string name = 1; + + // required + optional sint32 payload_type = 2; +} + + +message AudioReceiveConfig { + // TODO(terelius): Add audio-receive config. +} + + +message AudioSendConfig { + // TODO(terelius): Add audio-receive config. +} diff --git a/webrtc/video/rtc_event_log_unittest.cc b/webrtc/video/rtc_event_log_unittest.cc new file mode 100644 index 0000000000..0c18e750cc --- /dev/null +++ b/webrtc/video/rtc_event_log_unittest.cc @@ -0,0 +1,429 @@ +/* + * 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. + */ + +#ifdef ENABLE_RTC_EVENT_LOG + +#include +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/call.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/test/test_suite.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/gtest_disable.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 { + +// TODO(terelius): Place this definition with other parsing functions? +MediaType GetRuntimeMediaType(rtclog::MediaType media_type) { + switch (media_type) { + case rtclog::MediaType::ANY: + return MediaType::ANY; + case rtclog::MediaType::AUDIO: + return MediaType::AUDIO; + case rtclog::MediaType::VIDEO: + return MediaType::VIDEO; + case rtclog::MediaType::DATA: + return MediaType::DATA; + } + RTC_NOTREACHED(); + return MediaType::ANY; +} + +// Checks that the event has a timestamp, a type and exactly the data field +// corresponding to the type. +::testing::AssertionResult IsValidBasicEvent(const rtclog::Event& event) { + if (!event.has_timestamp_us()) + return ::testing::AssertionFailure() << "Event has no timestamp"; + if (!event.has_type()) + return ::testing::AssertionFailure() << "Event has no event type"; + rtclog::Event_EventType type = event.type(); + if ((type == rtclog::Event::RTP_EVENT) != event.has_rtp_packet()) + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_rtp_packet() ? "" : "no ") << "RTP packet"; + if ((type == rtclog::Event::RTCP_EVENT) != event.has_rtcp_packet()) + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_rtcp_packet() ? "" : "no ") << "RTCP packet"; + if ((type == rtclog::Event::DEBUG_EVENT) != event.has_debug_event()) + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_debug_event() ? "" : "no ") << "debug event"; + if ((type == rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT) != + event.has_video_receiver_config()) + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_video_receiver_config() ? "" : "no ") + << "receiver config"; + if ((type == rtclog::Event::VIDEO_SENDER_CONFIG_EVENT) != + event.has_video_sender_config()) + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_video_sender_config() ? "" : "no ") << "sender config"; + if ((type == rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT) != + event.has_audio_receiver_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_audio_receiver_config() ? "" : "no ") + << "audio receiver config"; + } + if ((type == rtclog::Event::AUDIO_SENDER_CONFIG_EVENT) != + event.has_audio_sender_config()) { + return ::testing::AssertionFailure() + << "Event of type " << type << " has " + << (event.has_audio_sender_config() ? "" : "no ") + << "audio sender config"; + } + return ::testing::AssertionSuccess(); +} + +void VerifyReceiveStreamConfig(const rtclog::Event& event, + const VideoReceiveStream::Config& config) { + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT, event.type()); + const rtclog::VideoReceiveConfig& receiver_config = + event.video_receiver_config(); + // Check SSRCs. + ASSERT_TRUE(receiver_config.has_remote_ssrc()); + EXPECT_EQ(config.rtp.remote_ssrc, receiver_config.remote_ssrc()); + ASSERT_TRUE(receiver_config.has_local_ssrc()); + EXPECT_EQ(config.rtp.local_ssrc, receiver_config.local_ssrc()); + // Check RTCP settings. + ASSERT_TRUE(receiver_config.has_rtcp_mode()); + if (config.rtp.rtcp_mode == newapi::kRtcpCompound) + EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_COMPOUND, + receiver_config.rtcp_mode()); + else + EXPECT_EQ(rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE, + receiver_config.rtcp_mode()); + ASSERT_TRUE(receiver_config.has_receiver_reference_time_report()); + EXPECT_EQ(config.rtp.rtcp_xr.receiver_reference_time_report, + receiver_config.receiver_reference_time_report()); + ASSERT_TRUE(receiver_config.has_remb()); + EXPECT_EQ(config.rtp.remb, receiver_config.remb()); + // Check RTX map. + ASSERT_EQ(static_cast(config.rtp.rtx.size()), + receiver_config.rtx_map_size()); + for (const rtclog::RtxMap& rtx_map : receiver_config.rtx_map()) { + ASSERT_TRUE(rtx_map.has_payload_type()); + ASSERT_TRUE(rtx_map.has_config()); + EXPECT_EQ(1u, config.rtp.rtx.count(rtx_map.payload_type())); + const rtclog::RtxConfig& rtx_config = rtx_map.config(); + const VideoReceiveStream::Config::Rtp::Rtx& rtx = + config.rtp.rtx.at(rtx_map.payload_type()); + ASSERT_TRUE(rtx_config.has_rtx_ssrc()); + ASSERT_TRUE(rtx_config.has_rtx_payload_type()); + EXPECT_EQ(rtx.ssrc, rtx_config.rtx_ssrc()); + EXPECT_EQ(rtx.payload_type, rtx_config.rtx_payload_type()); + } + // Check header extensions. + ASSERT_EQ(static_cast(config.rtp.extensions.size()), + receiver_config.header_extensions_size()); + for (int i = 0; i < receiver_config.header_extensions_size(); i++) { + ASSERT_TRUE(receiver_config.header_extensions(i).has_name()); + ASSERT_TRUE(receiver_config.header_extensions(i).has_id()); + const std::string& name = receiver_config.header_extensions(i).name(); + int id = receiver_config.header_extensions(i).id(); + EXPECT_EQ(config.rtp.extensions[i].id, id); + EXPECT_EQ(config.rtp.extensions[i].name, name); + } + // Check decoders. + ASSERT_EQ(static_cast(config.decoders.size()), + receiver_config.decoders_size()); + for (int i = 0; i < receiver_config.decoders_size(); i++) { + ASSERT_TRUE(receiver_config.decoders(i).has_name()); + ASSERT_TRUE(receiver_config.decoders(i).has_payload_type()); + const std::string& decoder_name = receiver_config.decoders(i).name(); + int decoder_type = receiver_config.decoders(i).payload_type(); + EXPECT_EQ(config.decoders[i].payload_name, decoder_name); + EXPECT_EQ(config.decoders[i].payload_type, decoder_type); + } +} + +void VerifySendStreamConfig(const rtclog::Event& event, + const VideoSendStream::Config& config) { + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::VIDEO_SENDER_CONFIG_EVENT, event.type()); + const rtclog::VideoSendConfig& sender_config = event.video_sender_config(); + // Check SSRCs. + ASSERT_EQ(static_cast(config.rtp.ssrcs.size()), + sender_config.ssrcs_size()); + for (int i = 0; i < sender_config.ssrcs_size(); i++) { + EXPECT_EQ(config.rtp.ssrcs[i], sender_config.ssrcs(i)); + } + // Check header extensions. + ASSERT_EQ(static_cast(config.rtp.extensions.size()), + sender_config.header_extensions_size()); + for (int i = 0; i < sender_config.header_extensions_size(); i++) { + ASSERT_TRUE(sender_config.header_extensions(i).has_name()); + ASSERT_TRUE(sender_config.header_extensions(i).has_id()); + const std::string& name = sender_config.header_extensions(i).name(); + int id = sender_config.header_extensions(i).id(); + EXPECT_EQ(config.rtp.extensions[i].id, id); + EXPECT_EQ(config.rtp.extensions[i].name, name); + } + // Check RTX settings. + ASSERT_EQ(static_cast(config.rtp.rtx.ssrcs.size()), + sender_config.rtx_ssrcs_size()); + for (int i = 0; i < sender_config.rtx_ssrcs_size(); i++) { + EXPECT_EQ(config.rtp.rtx.ssrcs[i], sender_config.rtx_ssrcs(i)); + } + if (sender_config.rtx_ssrcs_size() > 0) { + ASSERT_TRUE(sender_config.has_rtx_payload_type()); + EXPECT_EQ(config.rtp.rtx.payload_type, sender_config.rtx_payload_type()); + } + // Check CNAME. + ASSERT_TRUE(sender_config.has_c_name()); + EXPECT_EQ(config.rtp.c_name, sender_config.c_name()); + // Check encoder. + ASSERT_TRUE(sender_config.has_encoder()); + ASSERT_TRUE(sender_config.encoder().has_name()); + ASSERT_TRUE(sender_config.encoder().has_payload_type()); + EXPECT_EQ(config.encoder_settings.payload_name, + sender_config.encoder().name()); + EXPECT_EQ(config.encoder_settings.payload_type, + sender_config.encoder().payload_type()); +} + +void VerifyRtpEvent(const rtclog::Event& event, + bool incoming, + MediaType media_type, + uint8_t* header, + size_t header_size, + size_t total_size) { + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::RTP_EVENT, event.type()); + const rtclog::RtpPacket& rtp_packet = event.rtp_packet(); + ASSERT_TRUE(rtp_packet.has_incoming()); + EXPECT_EQ(incoming, rtp_packet.incoming()); + ASSERT_TRUE(rtp_packet.has_type()); + EXPECT_EQ(media_type, GetRuntimeMediaType(rtp_packet.type())); + ASSERT_TRUE(rtp_packet.has_packet_length()); + EXPECT_EQ(total_size, rtp_packet.packet_length()); + ASSERT_TRUE(rtp_packet.has_header()); + ASSERT_EQ(header_size, rtp_packet.header().size()); + for (size_t i = 0; i < header_size; i++) { + EXPECT_EQ(header[i], static_cast(rtp_packet.header()[i])); + } +} + +void VerifyRtcpEvent(const rtclog::Event& event, + bool incoming, + MediaType media_type, + uint8_t* packet, + size_t total_size) { + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::RTCP_EVENT, event.type()); + const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet(); + ASSERT_TRUE(rtcp_packet.has_incoming()); + EXPECT_EQ(incoming, rtcp_packet.incoming()); + ASSERT_TRUE(rtcp_packet.has_type()); + EXPECT_EQ(media_type, GetRuntimeMediaType(rtcp_packet.type())); + ASSERT_TRUE(rtcp_packet.has_packet_data()); + ASSERT_EQ(total_size, rtcp_packet.packet_data().size()); + for (size_t i = 0; i < total_size; i++) { + EXPECT_EQ(packet[i], static_cast(rtcp_packet.packet_data()[i])); + } +} + +void VerifyLogStartEvent(const rtclog::Event& event) { + ASSERT_TRUE(IsValidBasicEvent(event)); + ASSERT_EQ(rtclog::Event::DEBUG_EVENT, event.type()); + const rtclog::DebugEvent& debug_event = event.debug_event(); + ASSERT_TRUE(debug_event.has_type()); + EXPECT_EQ(rtclog::DebugEvent::LOG_START, debug_event.type()); +} + +void GenerateVideoReceiveConfig(VideoReceiveStream::Config* config) { + // Create a map from a payload type to an encoder name. + VideoReceiveStream::Decoder decoder; + decoder.payload_type = rand(); + decoder.payload_name = (rand() % 2 ? "VP8" : "H264"); + config->decoders.push_back(decoder); + // Add SSRCs for the stream. + config->rtp.remote_ssrc = rand(); + config->rtp.local_ssrc = rand(); + // Add extensions and settings for RTCP. + config->rtp.rtcp_mode = rand() % 2 ? newapi::kRtcpCompound + : newapi::kRtcpReducedSize; + config->rtp.rtcp_xr.receiver_reference_time_report = + static_cast(rand() % 2); + config->rtp.remb = static_cast(rand() % 2); + // Add a map from a payload type to a new ssrc and a new payload type for RTX. + VideoReceiveStream::Config::Rtp::Rtx rtx_pair; + rtx_pair.ssrc = rand(); + rtx_pair.payload_type = rand(); + config->rtp.rtx.insert(std::make_pair(rand(), rtx_pair)); + // Add two random header extensions. + const char* extension_name = rand() % 2 ? RtpExtension::kTOffset + : RtpExtension::kVideoRotation; + config->rtp.extensions.push_back(RtpExtension(extension_name, rand())); + extension_name = rand() % 2 ? RtpExtension::kAudioLevel + : RtpExtension::kAbsSendTime; + config->rtp.extensions.push_back(RtpExtension(extension_name, rand())); +} + +void GenerateVideoSendConfig(VideoSendStream::Config* config) { + // Create a map from a payload type to an encoder name. + config->encoder_settings.payload_type = rand(); + config->encoder_settings.payload_name = (rand() % 2 ? "VP8" : "H264"); + // Add SSRCs for the stream. + config->rtp.ssrcs.push_back(rand()); + // Add a map from a payload type to new ssrcs and a new payload type for RTX. + config->rtp.rtx.ssrcs.push_back(rand()); + config->rtp.rtx.payload_type = rand(); + // Add a CNAME. + config->rtp.c_name = "some.user@some.host"; + // Add two random header extensions. + const char* extension_name = rand() % 2 ? RtpExtension::kTOffset + : RtpExtension::kVideoRotation; + config->rtp.extensions.push_back(RtpExtension(extension_name, rand())); + extension_name = rand() % 2 ? RtpExtension::kAudioLevel + : RtpExtension::kAbsSendTime; + config->rtp.extensions.push_back(RtpExtension(extension_name, rand())); +} + +// Test for the RtcEventLog class. Dumps some RTP packets to disk, then reads +// them back to see if they match. +void LogSessionAndReadBack(size_t rtp_count, unsigned random_seed) { + std::vector> rtp_packets; + std::vector incoming_rtcp_packet; + std::vector outgoing_rtcp_packet; + + VideoReceiveStream::Config receiver_config; + VideoSendStream::Config sender_config; + + srand(random_seed); + + // Create rtp_count RTP packets containing random data. + const size_t rtp_header_size = 20; + for (size_t i = 0; i < rtp_count; i++) { + size_t packet_size = 1000 + rand() % 30; + rtp_packets.push_back(std::vector()); + rtp_packets[i].reserve(packet_size); + for (size_t j = 0; j < packet_size; j++) { + rtp_packets[i].push_back(rand()); + } + } + // Create two RTCP packets containing random data. + size_t packet_size = 1000 + rand() % 30; + outgoing_rtcp_packet.reserve(packet_size); + for (size_t j = 0; j < packet_size; j++) { + outgoing_rtcp_packet.push_back(rand()); + } + packet_size = 1000 + rand() % 30; + incoming_rtcp_packet.reserve(packet_size); + for (size_t j = 0; j < packet_size; j++) { + incoming_rtcp_packet.push_back(rand()); + } + // Create configurations for the video streams. + GenerateVideoReceiveConfig(&receiver_config); + GenerateVideoSendConfig(&sender_config); + + // Find the name of the current test, in order to use it as a temporary + // filename. + auto test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + const std::string temp_filename = + test::OutputPath() + test_info->test_case_name() + test_info->name(); + + // When log_dumper goes out of scope, it causes the log file to be flushed + // to disk. + { + rtc::scoped_ptr log_dumper(RtcEventLog::Create()); + log_dumper->LogVideoReceiveStreamConfig(receiver_config); + log_dumper->LogVideoSendStreamConfig(sender_config); + size_t i = 0; + for (; i < rtp_count / 2; i++) { + log_dumper->LogRtpHeader( + (i % 2 == 0), // Every second packet is incoming. + (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, + rtp_packets[i].data(), rtp_header_size, rtp_packets[i].size()); + } + log_dumper->LogRtcpPacket(false, MediaType::AUDIO, + outgoing_rtcp_packet.data(), + outgoing_rtcp_packet.size()); + log_dumper->StartLogging(temp_filename, 10000000); + for (; i < rtp_count; i++) { + log_dumper->LogRtpHeader( + (i % 2 == 0), // Every second packet is incoming, + (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, + rtp_packets[i].data(), rtp_header_size, rtp_packets[i].size()); + } + log_dumper->LogRtcpPacket(true, MediaType::VIDEO, + incoming_rtcp_packet.data(), + incoming_rtcp_packet.size()); + } + + const int config_count = 2; + const int rtcp_count = 2; + const int debug_count = 1; // Only LogStart event, + const int event_count = config_count + debug_count + rtcp_count + rtp_count; + + // Read the generated file from disk. + rtclog::EventStream parsed_stream; + + ASSERT_TRUE(RtcEventLog::ParseRtcEventLog(temp_filename, &parsed_stream)); + + // Verify the result. + EXPECT_EQ(event_count, parsed_stream.stream_size()); + VerifyReceiveStreamConfig(parsed_stream.stream(0), receiver_config); + VerifySendStreamConfig(parsed_stream.stream(1), sender_config); + size_t i = 0; + for (; i < rtp_count / 2; i++) { + VerifyRtpEvent(parsed_stream.stream(config_count + i), + (i % 2 == 0), // Every second packet is incoming. + (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, + rtp_packets[i].data(), rtp_header_size, + rtp_packets[i].size()); + } + VerifyRtcpEvent(parsed_stream.stream(config_count + rtp_count / 2), + false, // Outgoing RTCP packet. + MediaType::AUDIO, outgoing_rtcp_packet.data(), + outgoing_rtcp_packet.size()); + + VerifyLogStartEvent(parsed_stream.stream(1 + config_count + rtp_count / 2)); + for (; i < rtp_count; i++) { + VerifyRtpEvent(parsed_stream.stream(2 + config_count + i), + (i % 2 == 0), // Every second packet is incoming. + (i % 3 == 0) ? MediaType::AUDIO : MediaType::VIDEO, + rtp_packets[i].data(), rtp_header_size, + rtp_packets[i].size()); + } + VerifyRtcpEvent(parsed_stream.stream(2 + config_count + rtp_count), + true, // Incoming RTCP packet. + MediaType::VIDEO, incoming_rtcp_packet.data(), + incoming_rtcp_packet.size()); + + // Clean up temporary file - can be pretty slow. + remove(temp_filename.c_str()); +} + +TEST(RtcEventLogTest, LogSessionAndReadBack) { + LogSessionAndReadBack(5, 321); + LogSessionAndReadBack(8, 3141592653u); + LogSessionAndReadBack(9, 2718281828u); +} + +} // namespace webrtc + +#endif // ENABLE_RTC_EVENT_LOG diff --git a/webrtc/webrtc.gyp b/webrtc/webrtc.gyp index fef36871af..49a66c3403 100644 --- a/webrtc/webrtc.gyp +++ b/webrtc/webrtc.gyp @@ -16,6 +16,21 @@ 'webrtc_tests.gypi', ], }], + ['enable_protobuf==1', { + 'targets': [ + { + # This target should only be built if enable_protobuf is defined + 'target_name': 'rtc_event_log_proto', + 'type': 'static_library', + 'sources': ['video/rtc_event_log.proto',], + 'variables': { + 'proto_in_dir': 'video', + 'proto_out_dir': 'webrtc/video', + }, + 'includes': ['build/protoc.gypi'], + }, + ], + }], ], 'includes': [ 'build/common.gypi', @@ -80,6 +95,7 @@ 'dependencies': [ 'common.gyp:*', '<@(webrtc_video_dependencies)', + 'rtc_event_log', ], 'conditions': [ # TODO(andresp): Chromium libpeerconnection should link directly with @@ -92,5 +108,26 @@ }], ], }, + { + 'target_name': 'rtc_event_log', + 'type': 'static_library', + 'sources': [ + 'video/rtc_event_log.cc', + 'video/rtc_event_log.h', + ], + 'conditions': [ + # If enable_protobuf is defined, we want to compile the protobuf + # and add rtc_event_log.pb.h and rtc_event_log.pb.cc to the sources. + ['enable_protobuf==1', { + 'dependencies': [ + 'rtc_event_log_proto', + ], + 'defines': [ + 'ENABLE_RTC_EVENT_LOG', + ], + }], + ], + }, + ], } diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi index 9d302f18ad..489df750de 100644 --- a/webrtc/webrtc_tests.gypi +++ b/webrtc/webrtc_tests.gypi @@ -177,6 +177,18 @@ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], }], + ['enable_protobuf==1', { + 'defines': [ + 'ENABLE_RTC_EVENT_LOG', + ], + 'dependencies': [ + 'webrtc.gyp:rtc_event_log', + 'webrtc.gyp:rtc_event_log_proto', + ], + 'sources': [ + 'video/rtc_event_log_unittest.cc', + ], + }], ], }, {