Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2162)

Unified Diff: chrome/browser/media/webrtc_rtp_dump_writer.cc

Issue 264793017: Implements RTP header dumping. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: for tommi's Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/media/webrtc_rtp_dump_writer.cc
diff --git a/chrome/browser/media/webrtc_rtp_dump_writer.cc b/chrome/browser/media/webrtc_rtp_dump_writer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fc64f3e22ad09b53f85231658bd854e8f18b9b92
--- /dev/null
+++ b/chrome/browser/media/webrtc_rtp_dump_writer.cc
@@ -0,0 +1,458 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc_rtp_dump_writer.h"
+
+#include "base/big_endian.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/zlib/zlib.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// The header of the dump file.
+struct RtpDumpFileHeader {
Henrik Grunell 2014/05/23 12:17:51 This is more than a data container, make it a clas
jiayl 2014/05/23 17:48:36 Good point. Changed to helper functions.
+ static const unsigned char kFirstLine[];
+ static const size_t kRtpDumpFileHeaderSize = 16;
+
+ explicit RtpDumpFileHeader(const base::TimeTicks& start)
+ : start_time(start - base::TimeTicks()), source(0), port(0), padding(0) {}
+
+ // Append the RTP dump file header to |output|.
+ void WriteBigEndian(std::vector<uint8>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kRtpDumpFileHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(output->data() + buffer_start_pos);
+
+ uint32 start_sec = start_time.InSeconds();
+ base::WriteBigEndian(buffer, start_sec);
+ buffer += sizeof(start_sec);
+
+ uint32 start_usec =
+ start_time.InMilliseconds() * base::Time::kMicrosecondsPerMillisecond;
+ base::WriteBigEndian(buffer, start_usec);
+ buffer += sizeof(start_usec);
+
+ base::WriteBigEndian(buffer, source);
+ buffer += sizeof(source);
+
+ base::WriteBigEndian(buffer, port);
+ buffer += sizeof(port);
+
+ base::WriteBigEndian(buffer, padding);
+ }
+
+ const base::TimeDelta start_time; // start of recording.
+ const uint32 source; // network source (multicast address). Always 0.
+ const uint16 port; // UDP port. Always 0.
+ const uint16 padding; // 2 bytes padding.
+};
+
+const unsigned char RtpDumpFileHeader::kFirstLine[] =
+ "#!rtpplay1.0 0.0.0.0/0\n";
+
+// The header for each packet dump.
+struct PacketDumpHeader {
Henrik Grunell 2014/05/23 12:17:51 Same here.
jiayl 2014/05/23 17:48:36 Done.
+ static const size_t kPacketDumpHeaderSize = 8;
+
+ PacketDumpHeader(const base::TimeTicks& start,
+ uint16 dump_length,
+ uint16 packet_length)
+ : packet_dump_length(dump_length),
+ packet_length(packet_length),
+ offset_ms((base::TimeTicks::Now() - start).InMilliseconds()) {}
+
+ void WriteBigEndian(std::vector<uint8>* output) {
+ size_t buffer_start_pos = output->size();
+ output->resize(output->size() + kPacketDumpHeaderSize);
+
+ char* buffer = reinterpret_cast<char*>(output->data() + buffer_start_pos);
+
+ base::WriteBigEndian(buffer, packet_dump_length);
+ buffer += sizeof(packet_dump_length);
+
+ base::WriteBigEndian(buffer, packet_length);
+ buffer += sizeof(packet_length);
+
+ base::WriteBigEndian(buffer, offset_ms);
+ }
+
+ // Length of the packet dump including this header.
+ const uint16 packet_dump_length;
+
+ // Length of header + payload of the RTP packet.
+ const uint16 packet_length;
+
+ // Milliseconds since the start of recording.
+ const uint32 offset_ms;
+};
+
+// Append |src_len| bytes from |src| to |dest|.
+bool AppendToBuffer(const uint8* src,
+ size_t src_len,
+ std::vector<uint8>* dest) {
+ if (dest->capacity() < dest->size() + src_len)
+ return false;
+
+ for (size_t i = 0; i < src_len; ++i)
Henrik Grunell 2014/05/23 12:17:51 This should be optimizable. Use resize and pointer
jiayl 2014/05/23 17:48:36 Done.
+ dest->push_back(src[i]);
+
+ return true;
+}
+
+static const size_t kMinimumGzipOutputBufferSize = 256;
+
+} // namespace
+
+// This class is running on the FILE thread for compressing and writing the
+// dump buffer to disk.
+class WebRtcRtpDumpWriter::FileThreadWorker {
+ public:
+ explicit FileThreadWorker(const base::FilePath& dump_path)
+ : dump_path_(dump_path), stream_initialized_(false) {
+ thread_checker_.DetachFromThread();
+ }
+
+ ~FileThreadWorker() { DCHECK(thread_checker_.CalledOnValidThread()); }
+
+ // Compresses the data in |buffer| write to the dump file. If |end_stream| is
+ // true, the compression stream will be ended and the dump file cannot be
+ // written to any more.
+ void CompressAndWriteToFileOnFileThread(
+ scoped_ptr<std::vector<uint8> > buffer,
+ bool end_stream,
+ FlushResult* result,
+ size_t* bytes_written) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ *result = FLUSH_RESULT_SUCCESS;
+ *bytes_written = 0;
+
+ if (buffer->size()) {
+ *bytes_written = CompressAndWriteBufferToFile(buffer.get(), result);
+ } else if (!base::PathExists(dump_path_)) {
+ *result = FLUSH_RESULT_NO_DATA;
+ }
+
+ if (end_stream) {
+ if (!EndDumpFile())
+ *result = FLUSH_RESULT_FAILURE;
+ }
+ }
+
+ private:
+ // Helper for CompressAndWriteToFileOnFileThread to compress and write one
+ // dump.
+ size_t CompressAndWriteBufferToFile(std::vector<uint8>* buffer,
+ FlushResult* result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(buffer->size());
+
+ *result = FLUSH_RESULT_SUCCESS;
+
+ std::vector<uint8> compressed_buffer;
+ if (!Compress(buffer, &compressed_buffer)) {
+ DVLOG(2) << "Compressing buffer failed.";
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ int bytes_written = -1;
+
+ if (base::PathExists(dump_path_)) {
+ bytes_written = base::AppendToFile(
+ dump_path_,
+ reinterpret_cast<const char*>(compressed_buffer.data()),
+ compressed_buffer.size());
+ } else {
+ bytes_written = base::WriteFile(
+ dump_path_,
+ reinterpret_cast<const char*>(compressed_buffer.data()),
+ compressed_buffer.size());
+ }
+
+ if (bytes_written == -1) {
+ DVLOG(2) << "Writing file failed: " << dump_path_.value();
+ *result = FLUSH_RESULT_FAILURE;
+ return 0;
+ }
+
+ DCHECK_EQ(static_cast<size_t>(bytes_written), compressed_buffer.size());
+
+ return bytes_written;
+ }
+
+ // Compresses |input| into |output|.
+ bool Compress(std::vector<uint8>* input, std::vector<uint8>* output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ int result = Z_OK;
+
+ output->resize(std::max(kMinimumGzipOutputBufferSize, input->size()));
+
+ if (!stream_initialized_) {
+ memset(&stream_, 0, sizeof(stream_));
+ result = deflateInit2(&stream_,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ // windowBits = 15 is default, 16 is added to
+ // produce a gzip header + trailer.
+ 15 + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+ stream_initialized_ = true;
+ }
+
+ stream_.next_in = input->data();
+ stream_.avail_in = input->size();
+ stream_.next_out = output->data();
+ stream_.avail_out = output->size();
+
+ result = deflate(&stream_, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, result);
+ DCHECK_EQ(0U, stream_.avail_in);
+
+ output->resize(output->size() - stream_.avail_out);
+
+ stream_.next_in = NULL;
+ stream_.next_out = NULL;
+ stream_.avail_out = 0;
+ return true;
+ }
+
+ // Ends the compression stream and completes the dump file.
+ bool EndDumpFile() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!stream_initialized_)
+ return true;
+
+ std::vector<uint8> output_buffer;
+ output_buffer.resize(kMinimumGzipOutputBufferSize);
+
+ stream_.next_in = NULL;
+ stream_.avail_in = 0;
+ stream_.next_out = output_buffer.data();
+ stream_.avail_out = output_buffer.size();
+
+ int result = deflate(&stream_, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, result);
+
+ result = deflateEnd(&stream_);
+ DCHECK_EQ(Z_OK, result);
+
+ output_buffer.resize(output_buffer.size() - stream_.avail_out);
+
+ int bytes_written =
+ base::AppendToFile(dump_path_,
+ reinterpret_cast<const char*>(output_buffer.data()),
+ output_buffer.size());
+
+ return bytes_written > 0;
+ }
+
+ const base::FilePath dump_path_;
+
+ z_stream stream_;
+ bool stream_initialized_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileThreadWorker);
+};
+
+WebRtcRtpDumpWriter::WebRtcRtpDumpWriter(
+ const base::FilePath& incoming_dump_path,
+ const base::FilePath& outgoing_dump_path,
+ size_t max_dump_size,
+ const base::Closure& max_dump_size_reached_callback)
+ : max_dump_size_(max_dump_size),
+ max_dump_size_reached_callback_(max_dump_size_reached_callback),
+ total_dump_size_on_disk_(0),
+ incoming_file_thread_worker_(new FileThreadWorker(incoming_dump_path)),
+ outgoing_file_thread_worker_(new FileThreadWorker(outgoing_dump_path)),
+ weak_ptr_factory_(this) {
+}
+
+WebRtcRtpDumpWriter::~WebRtcRtpDumpWriter() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!BrowserThread::DeleteSoon(BrowserThread::FILE,
+ FROM_HERE,
+ incoming_file_thread_worker_.release())) {
+ DCHECK(false);
+ }
+
+ if (!BrowserThread::DeleteSoon(BrowserThread::FILE,
+ FROM_HERE,
+ outgoing_file_thread_worker_.release())) {
+ DCHECK(false);
+ }
+}
+
+void WebRtcRtpDumpWriter::WriteRtpPacket(const uint8* packet_header,
+ size_t header_length,
+ size_t packet_length,
+ bool incoming) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ static const size_t kMaxInMemoryBufferSize = 65536; // 64KB
+
+ std::vector<uint8>* dest_buffer =
+ incoming ? &incoming_buffer_ : &outgoing_buffer_;
+ bool succeeded = true;
+ if (!dest_buffer->capacity()) {
+ dest_buffer->reserve(std::min(kMaxInMemoryBufferSize, max_dump_size_));
+
+ start_time_ = base::TimeTicks::Now();
+
+ // Writes the dump file header.
+ succeeded = AppendToBuffer(RtpDumpFileHeader::kFirstLine,
+ arraysize(RtpDumpFileHeader::kFirstLine) - 1,
+ dest_buffer);
+ DCHECK(succeeded);
+
+ RtpDumpFileHeader header(start_time_);
+ header.WriteBigEndian(dest_buffer);
+ }
+
+ size_t packet_dump_length = sizeof(PacketDumpHeader) + header_length;
+
+ // Flushes the buffer to disk if the buffer is full.
+ if (dest_buffer->capacity() < dest_buffer->size() + packet_dump_length)
+ FlushBuffer(incoming, false, FlushDoneCallback());
+
+ // Writes the packet dump header.
+ PacketDumpHeader packet_dump_header(
+ start_time_, packet_dump_length, packet_length);
+ packet_dump_header.WriteBigEndian(dest_buffer);
+
+ // Writes the actual RTP packet header.
+ succeeded = AppendToBuffer(packet_header, header_length, dest_buffer);
+ DCHECK(succeeded);
+}
+
+void WebRtcRtpDumpWriter::EndDump(RtpDumpType type,
+ const EndDumpCallback& finished_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ bool incoming = (type == RTP_DUMP_BOTH || type == RTP_DUMP_INCOMING);
+ EndDumpContext context(type, finished_callback);
+
+ FlushBuffer(incoming,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ incoming));
+}
+
+size_t WebRtcRtpDumpWriter::max_dump_size() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return max_dump_size_;
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
+ RtpDumpType type,
+ const EndDumpCallback& callback)
+ : type(type),
+ incoming_succeeded(false),
+ outgoing_succeeded(false),
+ callback(callback) {
+}
+
+WebRtcRtpDumpWriter::EndDumpContext::~EndDumpContext() {
+}
+
+void WebRtcRtpDumpWriter::FlushBuffer(bool incoming,
+ bool end_stream,
+ const FlushDoneCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_ptr<std::vector<uint8> > new_buffer(new std::vector<uint8>());
+
+ if (incoming) {
+ new_buffer->reserve(incoming_buffer_.capacity());
+ new_buffer->swap(incoming_buffer_);
+ } else {
+ new_buffer->reserve(outgoing_buffer_.capacity());
+ new_buffer->swap(outgoing_buffer_);
+ }
+
+ scoped_ptr<FlushResult> result(new FlushResult(FLUSH_RESULT_FAILURE));
+
+ scoped_ptr<size_t> bytes_written(new size_t(0));
+
+ FileThreadWorker* worker = incoming ? incoming_file_thread_worker_.get()
+ : outgoing_file_thread_worker_.get();
+
+ // Using "Unretained(worker)" because |worker| is owner by this object and it
+ // guaranteed to be deleted on the FILE thread before this object goes away.
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&FileThreadWorker::CompressAndWriteToFileOnFileThread,
+ base::Unretained(worker),
+ Passed(&new_buffer),
+ end_stream,
+ result.get(),
+ bytes_written.get()),
+ // OnFlushDone is necessary to avoid running the callback after this
+ // object is gone.
+ base::Bind(&WebRtcRtpDumpWriter::OnFlushDone,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ Passed(&result),
+ Passed(&bytes_written)));
+}
+
+void WebRtcRtpDumpWriter::OnFlushDone(const FlushDoneCallback& callback,
+ const scoped_ptr<FlushResult>& result,
+ const scoped_ptr<size_t>& bytes_written) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ total_dump_size_on_disk_ += *bytes_written;
+
+ if (total_dump_size_on_disk_ >= max_dump_size_ &&
+ !max_dump_size_reached_callback_.is_null()) {
+ max_dump_size_reached_callback_.Run();
+ }
+
+ // Returns success for FLUSH_RESULT_MAX_SIZE_REACHED since the dump is still
+ // valid.
+ if (!callback.is_null()) {
+ callback.Run(*result != FLUSH_RESULT_FAILURE &&
+ *result != FLUSH_RESULT_NO_DATA);
+ }
+}
+
+void WebRtcRtpDumpWriter::OnDumpEnded(EndDumpContext context,
+ bool incoming,
+ bool success) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DVLOG(2) << "Dump ended, incoming = " << incoming
+ << ", succeeded = " << success;
+
+ if (incoming)
+ context.incoming_succeeded = success;
+ else
+ context.outgoing_succeeded = success;
+
+ // End the outgoing dump if needed.
+ if (incoming && context.type == RTP_DUMP_BOTH) {
+ FlushBuffer(false,
+ true,
+ base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ false));
+ return;
+ }
+
+ context.callback.Run(context.incoming_succeeded, context.outgoing_succeeded);
+}

Powered by Google App Engine
This is Rietveld 408576698