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.
5 #include "chrome/browser/media/webrtc_rtp_dump_handler.h"
7 #include "base/files/file_util.h"
8 #include "base/logging.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/time/time.h"
11 #include "chrome/browser/media/webrtc_rtp_dump_writer.h"
12 #include "content/public/browser/browser_thread.h"
14 using content::BrowserThread
;
18 static const size_t kMaxOngoingRtpDumpsAllowed
= 5;
20 // The browser process wide total number of ongoing (i.e. started and not
21 // released) RTP dumps. Incoming and outgoing in one WebRtcDumpHandler are
22 // counted as one dump.
23 // Must be accessed on the browser IO thread.
24 static size_t g_ongoing_rtp_dumps
= 0;
26 void FireGenericDoneCallback(
27 const WebRtcRtpDumpHandler::GenericDoneCallback
& callback
,
29 const std::string
& error_message
) {
30 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
31 DCHECK(!callback
.is_null());
33 content::BrowserThread::PostTask(
34 content::BrowserThread::UI
,
36 base::Bind(callback
, success
, error_message
));
39 bool DumpTypeContainsIncoming(RtpDumpType type
) {
40 return type
== RTP_DUMP_INCOMING
|| type
== RTP_DUMP_BOTH
;
43 bool DumpTypeContainsOutgoing(RtpDumpType type
) {
44 return type
== RTP_DUMP_OUTGOING
|| type
== RTP_DUMP_BOTH
;
49 WebRtcRtpDumpHandler::WebRtcRtpDumpHandler(const base::FilePath
& dump_dir
)
50 : dump_dir_(dump_dir
),
51 incoming_state_(STATE_NONE
),
52 outgoing_state_(STATE_NONE
),
53 weak_ptr_factory_(this) {
54 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
57 WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() {
58 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
60 // Reset dump writer first to stop writing.
62 --g_ongoing_rtp_dumps
;
66 if (incoming_state_
!= STATE_NONE
&& !incoming_dump_path_
.empty()) {
67 BrowserThread::PostTask(
71 base::IgnoreResult(&base::DeleteFile
), incoming_dump_path_
, false));
74 if (outgoing_state_
!= STATE_NONE
&& !outgoing_dump_path_
.empty()) {
75 BrowserThread::PostTask(
79 base::IgnoreResult(&base::DeleteFile
), outgoing_dump_path_
, false));
83 bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type
,
84 std::string
* error_message
) {
85 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
87 if (!dump_writer_
&& g_ongoing_rtp_dumps
>= kMaxOngoingRtpDumpsAllowed
) {
88 *error_message
= "Max RTP dump limit reached.";
89 DVLOG(2) << *error_message
;
93 // Returns an error if any type of dump specified by the caller cannot be
95 if ((DumpTypeContainsIncoming(type
) && incoming_state_
!= STATE_NONE
) ||
96 (DumpTypeContainsOutgoing(type
) && outgoing_state_
!= STATE_NONE
)) {
98 "RTP dump already started for type " + base::IntToString(type
);
102 if (DumpTypeContainsIncoming(type
))
103 incoming_state_
= STATE_STARTED
;
105 if (DumpTypeContainsOutgoing(type
))
106 outgoing_state_
= STATE_STARTED
;
108 DVLOG(2) << "Start RTP dumping: type = " << type
;
111 ++g_ongoing_rtp_dumps
;
113 static const char kRecvDumpFilePrefix
[] = "rtpdump_recv_";
114 static const char kSendDumpFilePrefix
[] = "rtpdump_send_";
115 static const size_t kMaxDumpSize
= 5 * 1024 * 1024; // 5MB
117 std::string dump_id
= base::DoubleToString(base::Time::Now().ToDoubleT());
118 incoming_dump_path_
=
119 dump_dir_
.AppendASCII(std::string(kRecvDumpFilePrefix
) + dump_id
)
120 .AddExtension(FILE_PATH_LITERAL(".gz"));
122 outgoing_dump_path_
=
123 dump_dir_
.AppendASCII(std::string(kSendDumpFilePrefix
) + dump_id
)
124 .AddExtension(FILE_PATH_LITERAL(".gz"));
126 // WebRtcRtpDumpWriter does not support changing the dump path after it's
127 // created. So we assign both incoming and outgoing dump path even if only
128 // one type of dumping has been started.
129 // For "Unretained(this)", see comments StopDump.
130 dump_writer_
.reset(new WebRtcRtpDumpWriter(
134 base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached
,
135 base::Unretained(this))));
141 void WebRtcRtpDumpHandler::StopDump(RtpDumpType type
,
142 const GenericDoneCallback
& callback
) {
143 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
145 // Returns an error if any type of dump specified by the caller cannot be
147 if ((DumpTypeContainsIncoming(type
) && incoming_state_
!= STATE_STARTED
) ||
148 (DumpTypeContainsOutgoing(type
) && outgoing_state_
!= STATE_STARTED
)) {
149 if (!callback
.is_null()) {
150 FireGenericDoneCallback(
153 "RTP dump not started or already stopped for type " +
154 base::IntToString(type
));
159 DVLOG(2) << "Stopping RTP dumping: type = " << type
;
161 if (DumpTypeContainsIncoming(type
))
162 incoming_state_
= STATE_STOPPING
;
164 if (DumpTypeContainsOutgoing(type
))
165 outgoing_state_
= STATE_STOPPING
;
167 // Using "Unretained(this)" because the this object owns the writer and the
168 // writer is guaranteed to cancel the callback before it goes away. Same for
169 // the other posted tasks bound to the writer.
170 dump_writer_
->EndDump(
172 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded
,
173 base::Unretained(this),
176 : base::Bind(&FireGenericDoneCallback
, callback
, true, ""),
180 bool WebRtcRtpDumpHandler::ReadyToRelease() const {
181 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
183 return incoming_state_
!= STATE_STARTED
&&
184 incoming_state_
!= STATE_STOPPING
&&
185 outgoing_state_
!= STATE_STARTED
&& outgoing_state_
!= STATE_STOPPING
;
188 WebRtcRtpDumpHandler::ReleasedDumps
WebRtcRtpDumpHandler::ReleaseDumps() {
189 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
190 DCHECK(ReadyToRelease());
192 base::FilePath incoming_dump
, outgoing_dump
;
194 if (incoming_state_
== STATE_STOPPED
) {
195 DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_
.value();
197 incoming_state_
= STATE_NONE
;
198 incoming_dump
= incoming_dump_path_
;
201 if (outgoing_state_
== STATE_STOPPED
) {
202 DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_
.value();
204 outgoing_state_
= STATE_NONE
;
205 outgoing_dump
= outgoing_dump_path_
;
207 return ReleasedDumps(incoming_dump
, outgoing_dump
);
210 void WebRtcRtpDumpHandler::OnRtpPacket(const uint8
* packet_header
,
211 size_t header_length
,
212 size_t packet_length
,
214 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
216 if ((incoming
&& incoming_state_
!= STATE_STARTED
) ||
217 (!incoming
&& outgoing_state_
!= STATE_STARTED
)) {
221 dump_writer_
->WriteRtpPacket(
222 packet_header
, header_length
, packet_length
, incoming
);
225 void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure
& callback
) {
226 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
227 DCHECK(!callback
.is_null());
229 // No ongoing dumps, return directly.
230 if ((incoming_state_
== STATE_NONE
|| incoming_state_
== STATE_STOPPED
) &&
231 (outgoing_state_
== STATE_NONE
|| outgoing_state_
== STATE_STOPPED
)) {
236 // If the FILE thread is working on stopping the dumps, wait for the FILE
237 // thread to return and check the states again.
238 if (incoming_state_
== STATE_STOPPING
|| outgoing_state_
== STATE_STOPPING
) {
239 BrowserThread::PostTaskAndReply(
242 base::Bind(&base::DoNothing
),
243 base::Bind(&WebRtcRtpDumpHandler::StopOngoingDumps
,
244 weak_ptr_factory_
.GetWeakPtr(),
249 // Either incoming or outgoing dump must be ongoing.
251 (incoming_state_
== STATE_STARTED
)
252 ? (outgoing_state_
== STATE_STARTED
? RTP_DUMP_BOTH
256 if (incoming_state_
== STATE_STARTED
)
257 incoming_state_
= STATE_STOPPING
;
259 if (outgoing_state_
== STATE_STARTED
)
260 outgoing_state_
= STATE_STOPPING
;
262 DVLOG(2) << "Stopping ongoing dumps: type = " << type
;
264 dump_writer_
->EndDump(type
,
265 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded
,
266 base::Unretained(this),
271 void WebRtcRtpDumpHandler::SetDumpWriterForTesting(
272 scoped_ptr
<WebRtcRtpDumpWriter
> writer
) {
273 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
275 dump_writer_
= writer
.Pass();
276 ++g_ongoing_rtp_dumps
;
278 incoming_dump_path_
= dump_dir_
.AppendASCII("recv");
279 outgoing_dump_path_
= dump_dir_
.AppendASCII("send");
282 void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() {
283 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
286 (incoming_state_
== STATE_STARTED
)
287 ? (outgoing_state_
== STATE_STARTED
? RTP_DUMP_BOTH
290 StopDump(type
, GenericDoneCallback());
293 void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure
& callback
,
294 RtpDumpType ended_type
,
295 bool incoming_success
,
296 bool outgoing_success
) {
297 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
299 if (DumpTypeContainsIncoming(ended_type
)) {
300 DCHECK_EQ(STATE_STOPPING
, incoming_state_
);
301 incoming_state_
= STATE_STOPPED
;
303 if (!incoming_success
) {
304 BrowserThread::PostTask(BrowserThread::FILE,
306 base::Bind(base::IgnoreResult(&base::DeleteFile
),
310 DVLOG(2) << "Deleted invalid incoming dump "
311 << incoming_dump_path_
.value();
312 incoming_dump_path_
.clear();
316 if (DumpTypeContainsOutgoing(ended_type
)) {
317 DCHECK_EQ(STATE_STOPPING
, outgoing_state_
);
318 outgoing_state_
= STATE_STOPPED
;
320 if (!outgoing_success
) {
321 BrowserThread::PostTask(BrowserThread::FILE,
323 base::Bind(base::IgnoreResult(&base::DeleteFile
),
327 DVLOG(2) << "Deleted invalid outgoing dump "
328 << outgoing_dump_path_
.value();
329 outgoing_dump_path_
.clear();
333 // Release the writer when it's no longer needed.
334 if (incoming_state_
!= STATE_STOPPING
&& outgoing_state_
!= STATE_STOPPING
&&
335 incoming_state_
!= STATE_STARTED
&& outgoing_state_
!= STATE_STARTED
) {
336 dump_writer_
.reset();
337 --g_ongoing_rtp_dumps
;
340 // This object might be deleted after running the callback.
341 if (!callback
.is_null())