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:
Bjorn Terelius
2015-07-30 11:06:02 +02:00
parent ee66016930
commit c159b046d7
15 changed files with 1235 additions and 657 deletions

View File

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

View File

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

View File

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

View File

@ -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': [
{

View File

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