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_handler.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/files/file_util.h" | |
10 #include "base/logging.h" | |
11 #include "base/strings/string_number_conversions.h" | |
12 #include "base/time/time.h" | |
13 #include "chrome/browser/media/webrtc_rtp_dump_writer.h" | |
14 #include "content/public/browser/browser_thread.h" | |
15 | |
16 using content::BrowserThread; | |
17 | |
18 namespace { | |
19 | |
20 static const size_t kMaxOngoingRtpDumpsAllowed = 5; | |
21 | |
22 // The browser process wide total number of ongoing (i.e. started and not | |
23 // released) RTP dumps. Incoming and outgoing in one WebRtcDumpHandler are | |
24 // counted as one dump. | |
25 // Must be accessed on the browser IO thread. | |
26 static size_t g_ongoing_rtp_dumps = 0; | |
27 | |
28 void FireGenericDoneCallback( | |
29 const WebRtcRtpDumpHandler::GenericDoneCallback& callback, | |
30 bool success, | |
31 const std::string& error_message) { | |
32 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
33 DCHECK(!callback.is_null()); | |
34 | |
35 content::BrowserThread::PostTask( | |
36 content::BrowserThread::UI, | |
37 FROM_HERE, | |
38 base::Bind(callback, success, error_message)); | |
39 } | |
40 | |
41 bool DumpTypeContainsIncoming(RtpDumpType type) { | |
42 return type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH; | |
43 } | |
44 | |
45 bool DumpTypeContainsOutgoing(RtpDumpType type) { | |
46 return type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH; | |
47 } | |
48 | |
49 } // namespace | |
50 | |
51 WebRtcRtpDumpHandler::WebRtcRtpDumpHandler(const base::FilePath& dump_dir) | |
52 : dump_dir_(dump_dir), | |
53 incoming_state_(STATE_NONE), | |
54 outgoing_state_(STATE_NONE), | |
55 weak_ptr_factory_(this) { | |
56 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
57 } | |
58 | |
59 WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() { | |
60 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
61 | |
62 // Reset dump writer first to stop writing. | |
63 if (dump_writer_) { | |
64 --g_ongoing_rtp_dumps; | |
65 dump_writer_.reset(); | |
66 } | |
67 | |
68 if (incoming_state_ != STATE_NONE && !incoming_dump_path_.empty()) { | |
69 BrowserThread::PostTask( | |
70 BrowserThread::FILE, | |
71 FROM_HERE, | |
72 base::Bind( | |
73 base::IgnoreResult(&base::DeleteFile), incoming_dump_path_, false)); | |
74 } | |
75 | |
76 if (outgoing_state_ != STATE_NONE && !outgoing_dump_path_.empty()) { | |
77 BrowserThread::PostTask( | |
78 BrowserThread::FILE, | |
79 FROM_HERE, | |
80 base::Bind( | |
81 base::IgnoreResult(&base::DeleteFile), outgoing_dump_path_, false)); | |
82 } | |
83 } | |
84 | |
85 bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type, | |
86 std::string* error_message) { | |
87 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
88 | |
89 if (!dump_writer_ && g_ongoing_rtp_dumps >= kMaxOngoingRtpDumpsAllowed) { | |
90 *error_message = "Max RTP dump limit reached."; | |
91 DVLOG(2) << *error_message; | |
92 return false; | |
93 } | |
94 | |
95 // Returns an error if any type of dump specified by the caller cannot be | |
96 // started. | |
97 if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_NONE) || | |
98 (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_NONE)) { | |
99 *error_message = | |
100 "RTP dump already started for type " + base::IntToString(type); | |
101 return false; | |
102 } | |
103 | |
104 if (DumpTypeContainsIncoming(type)) | |
105 incoming_state_ = STATE_STARTED; | |
106 | |
107 if (DumpTypeContainsOutgoing(type)) | |
108 outgoing_state_ = STATE_STARTED; | |
109 | |
110 DVLOG(2) << "Start RTP dumping: type = " << type; | |
111 | |
112 if (!dump_writer_) { | |
113 ++g_ongoing_rtp_dumps; | |
114 | |
115 static const char kRecvDumpFilePrefix[] = "rtpdump_recv_"; | |
116 static const char kSendDumpFilePrefix[] = "rtpdump_send_"; | |
117 static const size_t kMaxDumpSize = 5 * 1024 * 1024; // 5MB | |
118 | |
119 std::string dump_id = base::DoubleToString(base::Time::Now().ToDoubleT()); | |
120 incoming_dump_path_ = | |
121 dump_dir_.AppendASCII(std::string(kRecvDumpFilePrefix) + dump_id) | |
122 .AddExtension(FILE_PATH_LITERAL(".gz")); | |
123 | |
124 outgoing_dump_path_ = | |
125 dump_dir_.AppendASCII(std::string(kSendDumpFilePrefix) + dump_id) | |
126 .AddExtension(FILE_PATH_LITERAL(".gz")); | |
127 | |
128 // WebRtcRtpDumpWriter does not support changing the dump path after it's | |
129 // created. So we assign both incoming and outgoing dump path even if only | |
130 // one type of dumping has been started. | |
131 // For "Unretained(this)", see comments StopDump. | |
132 dump_writer_.reset(new WebRtcRtpDumpWriter( | |
133 incoming_dump_path_, | |
134 outgoing_dump_path_, | |
135 kMaxDumpSize, | |
136 base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached, | |
137 base::Unretained(this)))); | |
138 } | |
139 | |
140 return true; | |
141 } | |
142 | |
143 void WebRtcRtpDumpHandler::StopDump(RtpDumpType type, | |
144 const GenericDoneCallback& callback) { | |
145 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
146 | |
147 // Returns an error if any type of dump specified by the caller cannot be | |
148 // stopped. | |
149 if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_STARTED) || | |
150 (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_STARTED)) { | |
151 if (!callback.is_null()) { | |
152 FireGenericDoneCallback( | |
153 callback, | |
154 false, | |
155 "RTP dump not started or already stopped for type " + | |
156 base::IntToString(type)); | |
157 } | |
158 return; | |
159 } | |
160 | |
161 DVLOG(2) << "Stopping RTP dumping: type = " << type; | |
162 | |
163 if (DumpTypeContainsIncoming(type)) | |
164 incoming_state_ = STATE_STOPPING; | |
165 | |
166 if (DumpTypeContainsOutgoing(type)) | |
167 outgoing_state_ = STATE_STOPPING; | |
168 | |
169 // Using "Unretained(this)" because the this object owns the writer and the | |
170 // writer is guaranteed to cancel the callback before it goes away. Same for | |
171 // the other posted tasks bound to the writer. | |
172 dump_writer_->EndDump( | |
173 type, | |
174 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded, | |
175 base::Unretained(this), | |
176 callback.is_null() | |
177 ? base::Closure() | |
178 : base::Bind(&FireGenericDoneCallback, callback, true, ""), | |
179 type)); | |
180 } | |
181 | |
182 bool WebRtcRtpDumpHandler::ReadyToRelease() const { | |
183 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
184 | |
185 return incoming_state_ != STATE_STARTED && | |
186 incoming_state_ != STATE_STOPPING && | |
187 outgoing_state_ != STATE_STARTED && outgoing_state_ != STATE_STOPPING; | |
188 } | |
189 | |
190 WebRtcRtpDumpHandler::ReleasedDumps WebRtcRtpDumpHandler::ReleaseDumps() { | |
191 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
192 DCHECK(ReadyToRelease()); | |
193 | |
194 base::FilePath incoming_dump, outgoing_dump; | |
195 | |
196 if (incoming_state_ == STATE_STOPPED) { | |
197 DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_.value(); | |
198 | |
199 incoming_state_ = STATE_NONE; | |
200 incoming_dump = incoming_dump_path_; | |
201 } | |
202 | |
203 if (outgoing_state_ == STATE_STOPPED) { | |
204 DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_.value(); | |
205 | |
206 outgoing_state_ = STATE_NONE; | |
207 outgoing_dump = outgoing_dump_path_; | |
208 } | |
209 return ReleasedDumps(incoming_dump, outgoing_dump); | |
210 } | |
211 | |
212 void WebRtcRtpDumpHandler::OnRtpPacket(const uint8_t* packet_header, | |
213 size_t header_length, | |
214 size_t packet_length, | |
215 bool incoming) { | |
216 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
217 | |
218 if ((incoming && incoming_state_ != STATE_STARTED) || | |
219 (!incoming && outgoing_state_ != STATE_STARTED)) { | |
220 return; | |
221 } | |
222 | |
223 dump_writer_->WriteRtpPacket( | |
224 packet_header, header_length, packet_length, incoming); | |
225 } | |
226 | |
227 void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure& callback) { | |
228 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
229 DCHECK(!callback.is_null()); | |
230 | |
231 // No ongoing dumps, return directly. | |
232 if ((incoming_state_ == STATE_NONE || incoming_state_ == STATE_STOPPED) && | |
233 (outgoing_state_ == STATE_NONE || outgoing_state_ == STATE_STOPPED)) { | |
234 callback.Run(); | |
235 return; | |
236 } | |
237 | |
238 // If the FILE thread is working on stopping the dumps, wait for the FILE | |
239 // thread to return and check the states again. | |
240 if (incoming_state_ == STATE_STOPPING || outgoing_state_ == STATE_STOPPING) { | |
241 BrowserThread::PostTaskAndReply( | |
242 BrowserThread::FILE, | |
243 FROM_HERE, | |
244 base::Bind(&base::DoNothing), | |
245 base::Bind(&WebRtcRtpDumpHandler::StopOngoingDumps, | |
246 weak_ptr_factory_.GetWeakPtr(), | |
247 callback)); | |
248 return; | |
249 } | |
250 | |
251 // Either incoming or outgoing dump must be ongoing. | |
252 RtpDumpType type = | |
253 (incoming_state_ == STATE_STARTED) | |
254 ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH | |
255 : RTP_DUMP_INCOMING) | |
256 : RTP_DUMP_OUTGOING; | |
257 | |
258 if (incoming_state_ == STATE_STARTED) | |
259 incoming_state_ = STATE_STOPPING; | |
260 | |
261 if (outgoing_state_ == STATE_STARTED) | |
262 outgoing_state_ = STATE_STOPPING; | |
263 | |
264 DVLOG(2) << "Stopping ongoing dumps: type = " << type; | |
265 | |
266 dump_writer_->EndDump(type, | |
267 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded, | |
268 base::Unretained(this), | |
269 callback, | |
270 type)); | |
271 } | |
272 | |
273 void WebRtcRtpDumpHandler::SetDumpWriterForTesting( | |
274 std::unique_ptr<WebRtcRtpDumpWriter> writer) { | |
275 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
276 | |
277 dump_writer_ = std::move(writer); | |
278 ++g_ongoing_rtp_dumps; | |
279 | |
280 incoming_dump_path_ = dump_dir_.AppendASCII("recv"); | |
281 outgoing_dump_path_ = dump_dir_.AppendASCII("send"); | |
282 } | |
283 | |
284 void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() { | |
285 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
286 | |
287 RtpDumpType type = | |
288 (incoming_state_ == STATE_STARTED) | |
289 ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH | |
290 : RTP_DUMP_INCOMING) | |
291 : RTP_DUMP_OUTGOING; | |
292 StopDump(type, GenericDoneCallback()); | |
293 } | |
294 | |
295 void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure& callback, | |
296 RtpDumpType ended_type, | |
297 bool incoming_success, | |
298 bool outgoing_success) { | |
299 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
300 | |
301 if (DumpTypeContainsIncoming(ended_type)) { | |
302 DCHECK_EQ(STATE_STOPPING, incoming_state_); | |
303 incoming_state_ = STATE_STOPPED; | |
304 | |
305 if (!incoming_success) { | |
306 BrowserThread::PostTask(BrowserThread::FILE, | |
307 FROM_HERE, | |
308 base::Bind(base::IgnoreResult(&base::DeleteFile), | |
309 incoming_dump_path_, | |
310 false)); | |
311 | |
312 DVLOG(2) << "Deleted invalid incoming dump " | |
313 << incoming_dump_path_.value(); | |
314 incoming_dump_path_.clear(); | |
315 } | |
316 } | |
317 | |
318 if (DumpTypeContainsOutgoing(ended_type)) { | |
319 DCHECK_EQ(STATE_STOPPING, outgoing_state_); | |
320 outgoing_state_ = STATE_STOPPED; | |
321 | |
322 if (!outgoing_success) { | |
323 BrowserThread::PostTask(BrowserThread::FILE, | |
324 FROM_HERE, | |
325 base::Bind(base::IgnoreResult(&base::DeleteFile), | |
326 outgoing_dump_path_, | |
327 false)); | |
328 | |
329 DVLOG(2) << "Deleted invalid outgoing dump " | |
330 << outgoing_dump_path_.value(); | |
331 outgoing_dump_path_.clear(); | |
332 } | |
333 } | |
334 | |
335 // Release the writer when it's no longer needed. | |
336 if (incoming_state_ != STATE_STOPPING && outgoing_state_ != STATE_STOPPING && | |
337 incoming_state_ != STATE_STARTED && outgoing_state_ != STATE_STARTED) { | |
338 dump_writer_.reset(); | |
339 --g_ongoing_rtp_dumps; | |
340 } | |
341 | |
342 // This object might be deleted after running the callback. | |
343 if (!callback.is_null()) | |
344 callback.Run(); | |
345 } | |
OLD | NEW |