Chromium Code Reviews

Side by Side 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: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/media/webrtc_rtp_dump_writer.h"
6
7 #include "base/big_endian.h"
8 #include "base/file_util.h"
9 #include "base/logging.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "third_party/zlib/zlib.h"
12
13 using content::BrowserThread;
14
15 namespace {
16
17 // The header of the dump file.
18 struct RtpDumpFileHeader {
tommi (sloooow) - chröme 2014/05/17 10:18:37 is there a certain packing that's assumed for thes
jiayl 2014/05/19 17:32:59 Make it independent of the packing by replacing si
19 static const unsigned char kFirstLine[];
20
21 explicit RtpDumpFileHeader(const base::TimeTicks& start)
22 : start_sec(0), start_usec(0), source(0), port(0), padding(0) {
23 base::TimeDelta interval(start - base::TimeTicks());
24 start_sec = interval.InSeconds();
25 start_usec =
26 interval.InMilliseconds() * base::Time::kMicrosecondsPerMillisecond;
27 }
28
29 void WriteBigEndian(std::vector<uint8>* output) {
30 size_t buffer_start_pos = output->size();
31 output->resize(output->size() + sizeof(RtpDumpFileHeader));
32
33 char* buffer = reinterpret_cast<char*>(output->data() + buffer_start_pos);
34
35 base::WriteBigEndian(buffer, start_sec);
36 buffer += sizeof(start_sec);
37
38 base::WriteBigEndian(buffer, start_usec);
39 buffer += sizeof(start_usec);
40
41 base::WriteBigEndian(buffer, source);
42 buffer += sizeof(source);
43
44 base::WriteBigEndian(buffer, port);
45 buffer += sizeof(port);
46
47 base::WriteBigEndian(buffer, padding);
48 }
49
50 uint32 start_sec; // start of recording, the seconds part.
tommi (sloooow) - chröme 2014/05/17 10:18:37 could these members all be const? (sorry, have to
jiayl 2014/05/19 17:32:59 Done.
51 uint32 start_usec; // start of recording, the microseconds part.
52 uint32 source; // network source (multicast address). Always 0.
53 uint16 port; // UDP port. Always 0.
54 uint16 padding; // 2 bytes padding.
55 };
56
57 const unsigned char RtpDumpFileHeader::kFirstLine[] =
58 "#!rtpplay1.0 0.0.0.0/0\n";
59
60 // The header for each packet dump.
61 struct PacketDumpHeader {
62 PacketDumpHeader(const base::TimeTicks& start,
63 uint16 dump_length,
64 uint16 packet_length)
65 : packet_dump_length(dump_length),
66 packet_length(packet_length),
67 offset_ms((base::TimeTicks::Now() - start).InMilliseconds()) {}
68
69 void WriteBigEndian(std::vector<uint8>* output) {
70 size_t buffer_start_pos = output->size();
71 output->resize(output->size() + sizeof(PacketDumpHeader));
72
73 char* buffer = reinterpret_cast<char*>(output->data() + buffer_start_pos);
74
75 base::WriteBigEndian(buffer, packet_dump_length);
76 buffer += sizeof(packet_dump_length);
77
78 base::WriteBigEndian(buffer, packet_length);
79 buffer += sizeof(packet_length);
80
81 base::WriteBigEndian(buffer, offset_ms);
82 }
83
84 // Length of the packet dump including this header.
85 uint16 packet_dump_length;
tommi (sloooow) - chröme 2014/05/17 10:18:37 and these ones?
jiayl 2014/05/19 17:32:59 Done.
86
87 // Length of header + payload of the RTP packet.
88 uint16 packet_length;
89
90 // Milliseconds since the start of recording.
91 uint32 offset_ms;
92 };
93
94 // Append |src_len| bytes from |src| to |dest|.
95 bool AppendToBuffer(const uint8* src,
96 size_t src_len,
97 std::vector<uint8>* dest) {
98 if (dest->capacity() < dest->size() + src_len)
99 return false;
100
101 for (size_t i = 0; i < src_len; ++i) {
tommi (sloooow) - chröme 2014/05/17 10:18:37 nit: no {}
jiayl 2014/05/19 17:32:59 Done.
102 dest->push_back(src[i]);
103 }
104 return true;
105 }
106
107 static const size_t kMinimumGzipOutputBufferSize = 256;
108
109 } // namespace
110
111 // This class is running on the FILE thread for compressing and writing the
112 // dump buffer to disk.
113 class WebRtcRtpDumpWriter::FileThreadWorker {
114 public:
115 explicit FileThreadWorker(const base::FilePath& dump_path)
116 : dump_path_(dump_path), stream_initialized_(false) {
117 thread_checker_.DetachFromThread();
118 }
119
120 ~FileThreadWorker() { DCHECK(thread_checker_.CalledOnValidThread()); }
121
122 // Compresses the data in |buffer| write to the dump file. If |end_stream| is
123 // true, the compression stream will be ended and the dump file cannot be
124 // written to any more.
125 void CompressAndWriteToFileOnFileThread(
126 const scoped_ptr<std::vector<uint8> >& buffer,
127 bool end_stream,
128 FlushResult* result,
129 size_t* bytes_written) {
130 DCHECK(thread_checker_.CalledOnValidThread());
131
132 *result = FLUSH_RESULT_SUCCESS;
133 *bytes_written = 0;
134
135 if (buffer->size()) {
136 *bytes_written = CompressAndWriteBufferToFile(buffer.get(), result);
137 } else if (!base::PathExists(dump_path_)) {
138 *result = FLUSH_RESULT_NO_DATA;
139 }
140
141 if (end_stream) {
142 if (!EndDumpFile())
143 *result = FLUSH_RESULT_FAILURE;
144 }
145 }
146
147 private:
148 // Helper for CompressAndWriteToFileOnFileThread to compress and write one
149 // dump.
150 size_t CompressAndWriteBufferToFile(std::vector<uint8>* buffer,
151 FlushResult* result) {
152 DCHECK(thread_checker_.CalledOnValidThread());
153 DCHECK(buffer->size());
154
155 *result = FLUSH_RESULT_SUCCESS;
156
157 std::vector<uint8> compressed_buffer;
158 if (!Compress(buffer, &compressed_buffer)) {
159 DVLOG(2) << "Compressing buffer failed.";
160 *result = FLUSH_RESULT_FAILURE;
161 return 0;
162 }
163
164 int bytes_written = -1;
165
166 if (base::PathExists(dump_path_)) {
167 bytes_written = base::AppendToFile(
168 dump_path_,
169 reinterpret_cast<const char*>(compressed_buffer.data()),
170 compressed_buffer.size());
171 } else {
172 bytes_written = base::WriteFile(
173 dump_path_,
174 reinterpret_cast<const char*>(compressed_buffer.data()),
175 compressed_buffer.size());
176 }
177
178 if (bytes_written == -1) {
179 DVLOG(2) << "Writing file failed: " << dump_path_.value();
180 *result = FLUSH_RESULT_FAILURE;
181 return 0;
182 }
183
184 DCHECK_EQ(static_cast<size_t>(bytes_written), compressed_buffer.size());
185
186 return bytes_written;
187 }
188
189 // Compresses |input| into |output|.
190 bool Compress(std::vector<uint8>* input, std::vector<uint8>* output) {
tommi (sloooow) - chröme 2014/05/17 10:18:37 why is |input| writable?
jiayl 2014/05/19 17:32:59 the zlib methods require z_stream::next_in to be w
191 DCHECK(thread_checker_.CalledOnValidThread());
192 int result = Z_OK;
193
194 output->resize(std::max(kMinimumGzipOutputBufferSize, input->size()));
195
196 if (!stream_initialized_) {
197 memset(&stream_, 0, sizeof(stream_));
198 result = deflateInit2(&stream_,
199 Z_DEFAULT_COMPRESSION,
200 Z_DEFLATED,
201 // windowBits = 15 is default, 16 is added to
202 // produce a gzip header + trailer.
203 15 + 16,
204 8, // memLevel = 8 is default.
205 Z_DEFAULT_STRATEGY);
206 DCHECK_EQ(Z_OK, result);
207 stream_initialized_ = true;
208 }
209
210 stream_.next_in = input->data();
211 stream_.avail_in = input->size();
212 stream_.next_out = output->data();
213 stream_.avail_out = output->size();
214
215 result = deflate(&stream_, Z_SYNC_FLUSH);
216 DCHECK_EQ(Z_OK, result);
217 DCHECK_EQ(0U, stream_.avail_in);
218
219 output->resize(output->size() - stream_.avail_out);
220
221 stream_.next_in = NULL;
222 stream_.next_out = NULL;
223 stream_.avail_out = 0;
224 return true;
225 }
226
227 // Ends the compression stream and completes the dump file.
228 bool EndDumpFile() {
229 DCHECK(thread_checker_.CalledOnValidThread());
230
231 if (!stream_initialized_)
232 return true;
233
234 std::vector<uint8> output_buffer;
235 output_buffer.resize(kMinimumGzipOutputBufferSize);
236
237 stream_.next_in = NULL;
238 stream_.avail_in = 0;
239 stream_.next_out = output_buffer.data();
240 stream_.avail_out = output_buffer.size();
241
242 int result = deflate(&stream_, Z_FINISH);
243 DCHECK_EQ(Z_STREAM_END, result);
244
245 result = deflateEnd(&stream_);
246 DCHECK_EQ(Z_OK, result);
247
248 output_buffer.resize(output_buffer.size() - stream_.avail_out);
249
250 int bytes_written =
251 base::AppendToFile(dump_path_,
252 reinterpret_cast<const char*>(output_buffer.data()),
253 output_buffer.size());
254
255 return bytes_written > 0;
256 }
257
258 const base::FilePath dump_path_;
259
260 z_stream stream_;
261 bool stream_initialized_;
262
263 base::ThreadChecker thread_checker_;
264
265 DISALLOW_COPY_AND_ASSIGN(FileThreadWorker);
266 };
267
268 WebRtcRtpDumpWriter::WebRtcRtpDumpWriter(
269 const base::FilePath& incoming_dump_path,
270 const base::FilePath& outgoing_dump_path,
271 size_t max_dump_size,
272 const base::Closure& max_dump_size_reached_callback)
273 : max_dump_size_(max_dump_size),
274 max_dump_size_reached_callback_(max_dump_size_reached_callback),
275 total_dump_size_on_disk_(0),
276 incoming_file_thread_worker_(new FileThreadWorker(incoming_dump_path)),
277 outgoing_file_thread_worker_(new FileThreadWorker(outgoing_dump_path)),
278 weak_ptr_factory_(this) {
279 }
280
281 WebRtcRtpDumpWriter::~WebRtcRtpDumpWriter() {
282 DCHECK(thread_checker_.CalledOnValidThread());
283 if (BrowserThread::DeleteSoon(
284 BrowserThread::FILE, FROM_HERE, incoming_file_thread_worker_.get())) {
285 ignore_result(incoming_file_thread_worker_.release());
286 } else {
287 incoming_file_thread_worker_.reset();
tommi (sloooow) - chröme 2014/05/17 10:18:37 if we get here, is that an error or expected case?
jiayl 2014/05/19 17:32:59 Done.
288 }
289
290 if (BrowserThread::DeleteSoon(
291 BrowserThread::FILE, FROM_HERE, outgoing_file_thread_worker_.get())) {
292 ignore_result(outgoing_file_thread_worker_.release());
293 } else {
294 outgoing_file_thread_worker_.reset();
295 }
296 }
297
298 void WebRtcRtpDumpWriter::WriteRtpPacket(const uint8* packet_header,
299 size_t header_length,
300 size_t packet_length,
301 bool incoming) {
302 DCHECK(thread_checker_.CalledOnValidThread());
303
304 static const size_t kMaxInMemoryBufferSize = 65536; // 64KB
305
306 std::vector<uint8>* dest_buffer =
307 incoming ? &incoming_buffer_ : &outgoing_buffer_;
308 bool succeeded = true;
309 if (!dest_buffer->capacity()) {
310 dest_buffer->reserve(std::min(kMaxInMemoryBufferSize, max_dump_size_));
311
312 start_time_ = base::TimeTicks::Now();
313
314 // Writes the dump file header.
315 succeeded = AppendToBuffer(RtpDumpFileHeader::kFirstLine,
316 arraysize(RtpDumpFileHeader::kFirstLine) - 1,
317 dest_buffer);
318 DCHECK(succeeded);
319
320 RtpDumpFileHeader header(start_time_);
321 header.WriteBigEndian(dest_buffer);
322 }
323
324 size_t packet_dump_length = sizeof(PacketDumpHeader) + header_length;
325
326 // Flushes the buffer to disk if the buffer is full.
327 if (dest_buffer->capacity() < dest_buffer->size() + packet_dump_length)
328 FlushBuffer(incoming, false, FlushDoneCallback());
329
330 // Writes the packet dump header.
331 PacketDumpHeader packet_dump_header(
332 start_time_, packet_dump_length, packet_length);
333 packet_dump_header.WriteBigEndian(dest_buffer);
334
335 // Writes the actual RTP packet header.
336 succeeded = AppendToBuffer(packet_header, header_length, dest_buffer);
337 DCHECK(succeeded);
338 }
339
340 void WebRtcRtpDumpWriter::EndDump(RtpDumpType type,
341 const EndDumpCallback& finished_callback) {
342 DCHECK(thread_checker_.CalledOnValidThread());
343
344 bool incoming = (type == RTP_DUMP_BOTH || type == RTP_DUMP_INCOMING);
345 EndDumpContext context(type, finished_callback);
346
347 FlushBuffer(incoming,
348 true,
349 base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
350 weak_ptr_factory_.GetWeakPtr(),
351 context,
352 incoming));
353 }
354
355 WebRtcRtpDumpWriter::EndDumpContext::EndDumpContext(
356 RtpDumpType type,
357 const EndDumpCallback& callback)
358 : type(type),
359 incoming_succeeded(false),
360 outgoing_succeeded(false),
361 callback(callback) {
362 }
363
364 WebRtcRtpDumpWriter::EndDumpContext::~EndDumpContext() {
365 }
366
367 void WebRtcRtpDumpWriter::FlushBuffer(bool incoming,
368 bool end_stream,
369 const FlushDoneCallback& callback) {
370 DCHECK(thread_checker_.CalledOnValidThread());
371
372 scoped_ptr<std::vector<uint8> > new_buffer(new std::vector<uint8>());
373
374 if (incoming) {
375 new_buffer->reserve(incoming_buffer_.capacity());
376 new_buffer->swap(incoming_buffer_);
377 } else {
378 new_buffer->reserve(outgoing_buffer_.capacity());
379 new_buffer->swap(outgoing_buffer_);
380 }
381
382 scoped_ptr<FlushResult> result(new FlushResult(FLUSH_RESULT_FAILURE));
383
384 scoped_ptr<size_t> bytes_written(new size_t(0));
385
386 FileThreadWorker* worker = incoming ? incoming_file_thread_worker_.get()
387 : outgoing_file_thread_worker_.get();
388
389 // Using "Unretained(worker)" because |worker| is owner by this object and it
390 // guaranteed to be deleted on the FILE thread before this object goes away.
391 BrowserThread::PostTaskAndReply(
392 BrowserThread::FILE,
393 FROM_HERE,
394 base::Bind(&FileThreadWorker::CompressAndWriteToFileOnFileThread,
395 base::Unretained(worker),
396 Passed(&new_buffer),
397 end_stream,
398 result.get(),
399 bytes_written.get()),
400 // OnFlushDone is necessary to avoid running the callback after this
401 // object is gone.
402 base::Bind(&WebRtcRtpDumpWriter::OnFlushDone,
403 weak_ptr_factory_.GetWeakPtr(),
404 callback,
405 Passed(&result),
406 Passed(&bytes_written)));
407 }
408
409 void WebRtcRtpDumpWriter::OnFlushDone(const FlushDoneCallback& callback,
410 const scoped_ptr<FlushResult>& result,
411 const scoped_ptr<size_t>& bytes_written) {
412 DCHECK(thread_checker_.CalledOnValidThread());
413
414 total_dump_size_on_disk_ += *bytes_written;
415
416 if (total_dump_size_on_disk_ >= max_dump_size_ &&
417 !max_dump_size_reached_callback_.is_null()) {
418 max_dump_size_reached_callback_.Run();
419 }
420
421 // Returns success for FLUSH_RESULT_MAX_SIZE_REACHED since the dump is still
422 // valid.
423 if (!callback.is_null()) {
424 callback.Run(*result != FLUSH_RESULT_FAILURE &&
425 *result != FLUSH_RESULT_NO_DATA);
426 }
427 }
428
429 void WebRtcRtpDumpWriter::OnDumpEnded(EndDumpContext context,
430 bool incoming,
431 bool success) {
432 DCHECK(thread_checker_.CalledOnValidThread());
433
434 DVLOG(2) << "Dump ended, incoming = " << incoming
435 << ", succeeded = " << success;
436
437 if (incoming)
438 context.incoming_succeeded = success;
439 else
440 context.outgoing_succeeded = success;
441
442 // End the outgoing dump if needed.
443 if (incoming && context.type == RTP_DUMP_BOTH) {
444 FlushBuffer(false,
445 true,
446 base::Bind(&WebRtcRtpDumpWriter::OnDumpEnded,
447 weak_ptr_factory_.GetWeakPtr(),
448 context,
449 false));
450 return;
451 }
452
453 context.callback.Run(context.incoming_succeeded, context.outgoing_succeeded);
454 }
OLDNEW

Powered by Google App Engine