Renamed the ACMDump to RtcEventLog and moved it to webrtc/video, since it is not specific to the audio coding module. Updated .gyp and .gn files accordingly.
Placed the protobuf structures in the namespace webrtc::rtclog. Removed the message field from the DebugEvent structure, since it was not used. Added an interface to set config information for VideoReceiveStream and VideoSendStream in the event log. Added function to log full RTCP packets and changed RTP-logging to only log headers. Significantly extended the unit tests for RtcEventLog. R=ivoc@webrtc.org, minyue@webrtc.org, pbos@webrtc.org, stefan@webrtc.org Review URL: https://codereview.webrtc.org/1230973005 . Cr-Commit-Position: refs/heads/master@{#9656}
This commit is contained in:
parent
ee66016930
commit
c159b046d7
@ -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" ]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 <deque>
|
||||
|
||||
#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<webrtc::CriticalSectionWrapper> crit_;
|
||||
rtc::scoped_ptr<webrtc::FileWrapper> file_ GUARDED_BY(crit_);
|
||||
rtc::scoped_ptr<ACMDumpEventStream> stream_ GUARDED_BY(crit_);
|
||||
std::deque<ACMDumpEvent> 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<int64_t>(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<FileWrapper> 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> AcmDump::Create() {
|
||||
return rtc::scoped_ptr<AcmDump>(new AcmDumpImpl());
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -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 <string>
|
||||
|
||||
#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<AcmDump> 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_
|
||||
@ -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 <stdio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t>(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<AcmDump> 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<uint8_t> rtp_packet_;
|
||||
};
|
||||
|
||||
TEST_F(AcmDumpTest, DumpAndRead) {
|
||||
Run(256, 321);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // RTC_AUDIOCODING_DEBUG_DUMP
|
||||
@ -78,40 +78,8 @@
|
||||
'nack.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': [
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -322,15 +322,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',
|
||||
|
||||
@ -66,6 +66,7 @@ source_set("video") {
|
||||
}
|
||||
|
||||
deps = [
|
||||
"..:rtc_event_log",
|
||||
"..:webrtc_common",
|
||||
"../common_video",
|
||||
"../modules/bitrate_controller",
|
||||
|
||||
406
webrtc/video/rtc_event_log.cc
Normal file
406
webrtc/video/rtc_event_log.cc
Normal file
@ -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 <deque>
|
||||
|
||||
#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<FileWrapper> file_ GUARDED_BY(crit_);
|
||||
rtclog::EventStream stream_ GUARDED_BY(crit_);
|
||||
std::deque<rtclog::Event> 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<int64_t>(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<FileWrapper> 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> RtcEventLog::Create() {
|
||||
return rtc::scoped_ptr<RtcEventLog>(new RtcEventLogImpl());
|
||||
}
|
||||
} // namespace webrtc
|
||||
81
webrtc/video/rtc_event_log.h
Normal file
81
webrtc/video/rtc_event_log.h
Normal file
@ -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 <string>
|
||||
|
||||
#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<RtcEventLog> 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_
|
||||
228
webrtc/video/rtc_event_log.proto
Normal file
228
webrtc/video/rtc_event_log.proto
Normal file
@ -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.
|
||||
}
|
||||
429
webrtc/video/rtc_event_log_unittest.cc
Normal file
429
webrtc/video/rtc_event_log_unittest.cc
Normal file
@ -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 <stdio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<uint8_t>(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<uint8_t>(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<bool>(rand() % 2);
|
||||
config->rtp.remb = static_cast<bool>(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<std::vector<uint8_t>> rtp_packets;
|
||||
std::vector<uint8_t> incoming_rtcp_packet;
|
||||
std::vector<uint8_t> 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<uint8_t>());
|
||||
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<RtcEventLog> 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
|
||||
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
|
||||
],
|
||||
}
|
||||
|
||||
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user