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