Home | History | Annotate | Download | only in media
      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 "base/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"
     13 
     14 using content::BrowserThread;
     15 
     16 namespace {
     17 
     18 static const size_t kMaxOngoingRtpDumpsAllowed = 5;
     19 
     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;
     25 
     26 void FireGenericDoneCallback(
     27     const WebRtcRtpDumpHandler::GenericDoneCallback& callback,
     28     bool success,
     29     const std::string& error_message) {
     30   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     31   DCHECK(!callback.is_null());
     32 
     33   content::BrowserThread::PostTask(
     34       content::BrowserThread::UI,
     35       FROM_HERE,
     36       base::Bind(callback, success, error_message));
     37 }
     38 
     39 bool DumpTypeContainsIncoming(RtpDumpType type) {
     40   return type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH;
     41 }
     42 
     43 bool DumpTypeContainsOutgoing(RtpDumpType type) {
     44   return type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH;
     45 }
     46 
     47 }  // namespace
     48 
     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(BrowserThread::CurrentlyOn(BrowserThread::IO));
     55 }
     56 
     57 WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() {
     58   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     59 
     60   // Reset dump writer first to stop writing.
     61   if (dump_writer_) {
     62     --g_ongoing_rtp_dumps;
     63     dump_writer_.reset();
     64   }
     65 
     66   if (incoming_state_ != STATE_NONE && !incoming_dump_path_.empty()) {
     67     BrowserThread::PostTask(
     68         BrowserThread::FILE,
     69         FROM_HERE,
     70         base::Bind(
     71             base::IgnoreResult(&base::DeleteFile), incoming_dump_path_, false));
     72   }
     73 
     74   if (outgoing_state_ != STATE_NONE && !outgoing_dump_path_.empty()) {
     75     BrowserThread::PostTask(
     76         BrowserThread::FILE,
     77         FROM_HERE,
     78         base::Bind(
     79             base::IgnoreResult(&base::DeleteFile), outgoing_dump_path_, false));
     80   }
     81 }
     82 
     83 bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type,
     84                                      std::string* error_message) {
     85   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     86 
     87   if (!dump_writer_ && g_ongoing_rtp_dumps >= kMaxOngoingRtpDumpsAllowed) {
     88     *error_message = "Max RTP dump limit reached.";
     89     DVLOG(2) << *error_message;
     90     return false;
     91   }
     92 
     93   // Returns an error if any type of dump specified by the caller cannot be
     94   // started.
     95   if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_NONE) ||
     96       (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_NONE)) {
     97     *error_message =
     98         "RTP dump already started for type " + base::IntToString(type);
     99     return false;
    100   }
    101 
    102   if (DumpTypeContainsIncoming(type))
    103     incoming_state_ = STATE_STARTED;
    104 
    105   if (DumpTypeContainsOutgoing(type))
    106     outgoing_state_ = STATE_STARTED;
    107 
    108   DVLOG(2) << "Start RTP dumping: type = " << type;
    109 
    110   if (!dump_writer_) {
    111     ++g_ongoing_rtp_dumps;
    112 
    113     static const char kRecvDumpFilePrefix[] = "rtpdump_recv_";
    114     static const char kSendDumpFilePrefix[] = "rtpdump_send_";
    115     static const size_t kMaxDumpSize = 5 * 1024 * 1024;  // 5MB
    116 
    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"));
    121 
    122     outgoing_dump_path_ =
    123         dump_dir_.AppendASCII(std::string(kSendDumpFilePrefix) + dump_id)
    124             .AddExtension(FILE_PATH_LITERAL(".gz"));
    125 
    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(
    131         incoming_dump_path_,
    132         outgoing_dump_path_,
    133         kMaxDumpSize,
    134         base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached,
    135                    base::Unretained(this))));
    136   }
    137 
    138   return true;
    139 }
    140 
    141 void WebRtcRtpDumpHandler::StopDump(RtpDumpType type,
    142                                     const GenericDoneCallback& callback) {
    143   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    144 
    145   // Returns an error if any type of dump specified by the caller cannot be
    146   // stopped.
    147   if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_STARTED) ||
    148       (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_STARTED)) {
    149     if (!callback.is_null()) {
    150       FireGenericDoneCallback(
    151           callback,
    152           false,
    153           "RTP dump not started or already stopped for type " +
    154               base::IntToString(type));
    155     }
    156     return;
    157   }
    158 
    159   DVLOG(2) << "Stopping RTP dumping: type = " << type;
    160 
    161   if (DumpTypeContainsIncoming(type))
    162     incoming_state_ = STATE_STOPPING;
    163 
    164   if (DumpTypeContainsOutgoing(type))
    165     outgoing_state_ = STATE_STOPPING;
    166 
    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(
    171       type,
    172       base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
    173                  base::Unretained(this),
    174                  callback.is_null()
    175                      ? base::Closure()
    176                      : base::Bind(&FireGenericDoneCallback, callback, true, ""),
    177                  type));
    178 }
    179 
    180 bool WebRtcRtpDumpHandler::ReadyToRelease() const {
    181   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    182 
    183   return incoming_state_ != STATE_STARTED &&
    184          incoming_state_ != STATE_STOPPING &&
    185          outgoing_state_ != STATE_STARTED && outgoing_state_ != STATE_STOPPING;
    186 }
    187 
    188 WebRtcRtpDumpHandler::ReleasedDumps WebRtcRtpDumpHandler::ReleaseDumps() {
    189   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    190   DCHECK(ReadyToRelease());
    191 
    192   base::FilePath incoming_dump, outgoing_dump;
    193 
    194   if (incoming_state_ == STATE_STOPPED) {
    195     DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_.value();
    196 
    197     incoming_state_ = STATE_NONE;
    198     incoming_dump = incoming_dump_path_;
    199   }
    200 
    201   if (outgoing_state_ == STATE_STOPPED) {
    202     DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_.value();
    203 
    204     outgoing_state_ = STATE_NONE;
    205     outgoing_dump = outgoing_dump_path_;
    206   }
    207   return ReleasedDumps(incoming_dump, outgoing_dump);
    208 }
    209 
    210 void WebRtcRtpDumpHandler::OnRtpPacket(const uint8* packet_header,
    211                                        size_t header_length,
    212                                        size_t packet_length,
    213                                        bool incoming) {
    214   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    215 
    216   if ((incoming && incoming_state_ != STATE_STARTED) ||
    217       (!incoming && outgoing_state_ != STATE_STARTED)) {
    218     return;
    219   }
    220 
    221   dump_writer_->WriteRtpPacket(
    222       packet_header, header_length, packet_length, incoming);
    223 }
    224 
    225 void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure& callback) {
    226   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    227   DCHECK(!callback.is_null());
    228 
    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)) {
    232     callback.Run();
    233     return;
    234   }
    235 
    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(
    240         BrowserThread::FILE,
    241         FROM_HERE,
    242         base::Bind(&base::DoNothing),
    243         base::Bind(&WebRtcRtpDumpHandler::StopOngoingDumps,
    244                    weak_ptr_factory_.GetWeakPtr(),
    245                    callback));
    246     return;
    247   }
    248 
    249   // Either incoming or outgoing dump must be ongoing.
    250   RtpDumpType type =
    251       (incoming_state_ == STATE_STARTED)
    252           ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
    253                                               : RTP_DUMP_INCOMING)
    254           : RTP_DUMP_OUTGOING;
    255 
    256   if (incoming_state_ == STATE_STARTED)
    257     incoming_state_ = STATE_STOPPING;
    258 
    259   if (outgoing_state_ == STATE_STARTED)
    260     outgoing_state_ = STATE_STOPPING;
    261 
    262   DVLOG(2) << "Stopping ongoing dumps: type = " << type;
    263 
    264   dump_writer_->EndDump(type,
    265                         base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded,
    266                                    base::Unretained(this),
    267                                    callback,
    268                                    type));
    269 }
    270 
    271 void WebRtcRtpDumpHandler::SetDumpWriterForTesting(
    272     scoped_ptr<WebRtcRtpDumpWriter> writer) {
    273   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    274 
    275   dump_writer_ = writer.Pass();
    276   ++g_ongoing_rtp_dumps;
    277 
    278   incoming_dump_path_ = dump_dir_.AppendASCII("recv");
    279   outgoing_dump_path_ = dump_dir_.AppendASCII("send");
    280 }
    281 
    282 void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() {
    283   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    284 
    285   RtpDumpType type =
    286       (incoming_state_ == STATE_STARTED)
    287           ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH
    288                                               : RTP_DUMP_INCOMING)
    289           : RTP_DUMP_OUTGOING;
    290   StopDump(type, GenericDoneCallback());
    291 }
    292 
    293 void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure& callback,
    294                                        RtpDumpType ended_type,
    295                                        bool incoming_success,
    296                                        bool outgoing_success) {
    297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    298 
    299   if (DumpTypeContainsIncoming(ended_type)) {
    300     DCHECK_EQ(STATE_STOPPING, incoming_state_);
    301     incoming_state_ = STATE_STOPPED;
    302 
    303     if (!incoming_success) {
    304       BrowserThread::PostTask(BrowserThread::FILE,
    305                               FROM_HERE,
    306                               base::Bind(base::IgnoreResult(&base::DeleteFile),
    307                                          incoming_dump_path_,
    308                                          false));
    309 
    310       DVLOG(2) << "Deleted invalid incoming dump "
    311                << incoming_dump_path_.value();
    312       incoming_dump_path_.clear();
    313     }
    314   }
    315 
    316   if (DumpTypeContainsOutgoing(ended_type)) {
    317     DCHECK_EQ(STATE_STOPPING, outgoing_state_);
    318     outgoing_state_ = STATE_STOPPED;
    319 
    320     if (!outgoing_success) {
    321       BrowserThread::PostTask(BrowserThread::FILE,
    322                               FROM_HERE,
    323                               base::Bind(base::IgnoreResult(&base::DeleteFile),
    324                                          outgoing_dump_path_,
    325                                          false));
    326 
    327       DVLOG(2) << "Deleted invalid outgoing dump "
    328                << outgoing_dump_path_.value();
    329       outgoing_dump_path_.clear();
    330     }
    331   }
    332 
    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;
    338   }
    339 
    340   // This object might be deleted after running the callback.
    341   if (!callback.is_null())
    342     callback.Run();
    343 }
    344