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 "content/renderer/media/video_track_adapter.h"
      6 
      7 #include <algorithm>
      8 #include <limits>
      9 #include <utility>
     10 
     11 #include "base/bind.h"
     12 #include "base/debug/trace_event.h"
     13 #include "base/location.h"
     14 #include "base/metrics/histogram.h"
     15 #include "media/base/bind_to_current_loop.h"
     16 #include "media/base/video_util.h"
     17 
     18 namespace content {
     19 
     20 namespace {
     21 
     22 // Amount of frame intervals to wait before considering the source as muted, for
     23 // the first frame and under normal conditions, respectively. First frame might
     24 // take longer to arrive due to source startup.
     25 const float kFirstFrameTimeoutInFrameIntervals = 100.0f;
     26 const float kNormalFrameTimeoutInFrameIntervals = 25.0f;
     27 
     28 // Min delta time between two frames allowed without being dropped if a max
     29 // frame rate is specified.
     30 const int kMinTimeInMsBetweenFrames = 5;
     31 
     32 // Empty method used for keeping a reference to the original media::VideoFrame
     33 // in VideoFrameResolutionAdapter::DeliverFrame if cropping is needed.
     34 // The reference to |frame| is kept in the closure that calls this method.
     35 void ReleaseOriginalFrame(
     36     const scoped_refptr<media::VideoFrame>& frame) {
     37 }
     38 
     39 void ResetCallbackOnMainRenderThread(
     40     scoped_ptr<VideoCaptureDeliverFrameCB> callback) {
     41   // |callback| will be deleted when this exits.
     42 }
     43 
     44 }  // anonymous namespace
     45 
     46 // VideoFrameResolutionAdapter is created on and lives on
     47 // on the IO-thread. It does the resolution adaptation and delivers frames to
     48 // all registered tracks on the IO-thread.
     49 // All method calls must be on the IO-thread.
     50 class VideoTrackAdapter::VideoFrameResolutionAdapter
     51     : public base::RefCountedThreadSafe<VideoFrameResolutionAdapter> {
     52  public:
     53   VideoFrameResolutionAdapter(
     54       scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
     55       const gfx::Size& max_size,
     56       double min_aspect_ratio,
     57       double max_aspect_ratio,
     58       double max_frame_rate);
     59 
     60   // Add |callback| to receive video frames on the IO-thread.
     61   // |callback| will however be released on the main render thread.
     62   void AddCallback(const MediaStreamVideoTrack* track,
     63                    const VideoCaptureDeliverFrameCB& callback);
     64 
     65   // Removes |callback| associated with |track| from receiving video frames if
     66   // |track| has been added. It is ok to call RemoveCallback even if the |track|
     67   // has not been added. The |callback| is released on the main render thread.
     68   void RemoveCallback(const MediaStreamVideoTrack* track);
     69 
     70   void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame,
     71                     const media::VideoCaptureFormat& format,
     72                     const base::TimeTicks& estimated_capture_time);
     73 
     74   // Returns true if all arguments match with the output of this adapter.
     75   bool ConstraintsMatch(const gfx::Size& max_size,
     76                         double min_aspect_ratio,
     77                         double max_aspect_ratio,
     78                         double max_frame_rate) const;
     79 
     80   bool IsEmpty() const;
     81 
     82  private:
     83   virtual ~VideoFrameResolutionAdapter();
     84   friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>;
     85 
     86   virtual void DoDeliverFrame(
     87       const scoped_refptr<media::VideoFrame>& frame,
     88       const media::VideoCaptureFormat& format,
     89       const base::TimeTicks& estimated_capture_time);
     90 
     91   // Returns |true| if the input frame rate is higher that the requested max
     92   // frame rate and |frame| should be dropped.
     93   bool MaybeDropFrame(const scoped_refptr<media::VideoFrame>& frame,
     94                       float source_frame_rate);
     95 
     96   // Bound to the IO-thread.
     97   base::ThreadChecker io_thread_checker_;
     98 
     99   // The task runner where we will release VideoCaptureDeliverFrameCB
    100   // registered in AddCallback.
    101   scoped_refptr<base::SingleThreadTaskRunner> renderer_task_runner_;
    102 
    103   gfx::Size max_frame_size_;
    104   double min_aspect_ratio_;
    105   double max_aspect_ratio_;
    106 
    107   double frame_rate_;
    108   base::TimeDelta last_time_stamp_;
    109   double max_frame_rate_;
    110   double keep_frame_counter_;
    111 
    112   typedef std::pair<const void*, VideoCaptureDeliverFrameCB>
    113       VideoIdCallbackPair;
    114   std::vector<VideoIdCallbackPair> callbacks_;
    115 
    116   DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter);
    117 };
    118 
    119 VideoTrackAdapter::
    120 VideoFrameResolutionAdapter::VideoFrameResolutionAdapter(
    121     scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
    122     const gfx::Size& max_size,
    123     double min_aspect_ratio,
    124     double max_aspect_ratio,
    125     double max_frame_rate)
    126     : renderer_task_runner_(render_message_loop),
    127       max_frame_size_(max_size),
    128       min_aspect_ratio_(min_aspect_ratio),
    129       max_aspect_ratio_(max_aspect_ratio),
    130       frame_rate_(MediaStreamVideoSource::kDefaultFrameRate),
    131       max_frame_rate_(max_frame_rate),
    132       keep_frame_counter_(0.0f) {
    133   DCHECK(renderer_task_runner_.get());
    134   DCHECK(io_thread_checker_.CalledOnValidThread());
    135   DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_);
    136   CHECK_NE(0, max_aspect_ratio_);
    137   DVLOG(3) << "VideoFrameResolutionAdapter("
    138           << "{ max_width =" << max_frame_size_.width() << "}, "
    139           << "{ max_height =" << max_frame_size_.height() << "}, "
    140           << "{ min_aspect_ratio =" << min_aspect_ratio << "}, "
    141           << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}"
    142           << "{ max_frame_rate_ =" << max_frame_rate_ << "}) ";
    143 }
    144 
    145 VideoTrackAdapter::
    146 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() {
    147   DCHECK(io_thread_checker_.CalledOnValidThread());
    148   DCHECK(callbacks_.empty());
    149 }
    150 
    151 void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame(
    152     const scoped_refptr<media::VideoFrame>& frame,
    153     const media::VideoCaptureFormat& format,
    154     const base::TimeTicks& estimated_capture_time) {
    155   DCHECK(io_thread_checker_.CalledOnValidThread());
    156 
    157   if (MaybeDropFrame(frame, format.frame_rate))
    158     return;
    159 
    160   // TODO(perkj): Allow cropping / scaling of textures once
    161   // http://crbug/362521 is fixed.
    162   if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) {
    163     DoDeliverFrame(frame, format, estimated_capture_time);
    164     return;
    165   }
    166   scoped_refptr<media::VideoFrame> video_frame(frame);
    167   double input_ratio =
    168       static_cast<double>(frame->natural_size().width()) /
    169       frame->natural_size().height();
    170 
    171   // If |frame| has larger width or height than requested, or the aspect ratio
    172   // does not match the requested, we want to create a wrapped version of this
    173   // frame with a size that fulfills the constraints.
    174   if (frame->natural_size().width() > max_frame_size_.width() ||
    175       frame->natural_size().height() > max_frame_size_.height() ||
    176       input_ratio > max_aspect_ratio_ ||
    177       input_ratio < min_aspect_ratio_) {
    178     int desired_width = std::min(max_frame_size_.width(),
    179                                  frame->natural_size().width());
    180     int desired_height = std::min(max_frame_size_.height(),
    181                                   frame->natural_size().height());
    182 
    183     double resulting_ratio =
    184         static_cast<double>(desired_width) / desired_height;
    185     double requested_ratio = resulting_ratio;
    186 
    187     if (requested_ratio > max_aspect_ratio_)
    188       requested_ratio = max_aspect_ratio_;
    189     else if (requested_ratio < min_aspect_ratio_)
    190       requested_ratio = min_aspect_ratio_;
    191 
    192     if (resulting_ratio < requested_ratio) {
    193       desired_height = static_cast<int>((desired_height * resulting_ratio) /
    194                                         requested_ratio);
    195       // Make sure we scale to an even height to avoid rounding errors
    196       desired_height = (desired_height + 1) & ~1;
    197     } else if (resulting_ratio > requested_ratio) {
    198       desired_width = static_cast<int>((desired_width * requested_ratio) /
    199                                        resulting_ratio);
    200       // Make sure we scale to an even width to avoid rounding errors.
    201       desired_width = (desired_width + 1) & ~1;
    202     }
    203 
    204     gfx::Size desired_size(desired_width, desired_height);
    205 
    206     // Get the largest centered rectangle with the same aspect ratio of
    207     // |desired_size| that fits entirely inside of |frame->visible_rect()|.
    208     // This will be the rect we need to crop the original frame to.
    209     // From this rect, the original frame can be scaled down to |desired_size|.
    210     gfx::Rect region_in_frame =
    211         media::ComputeLetterboxRegion(frame->visible_rect(), desired_size);
    212 
    213     video_frame = media::VideoFrame::WrapVideoFrame(
    214         frame,
    215         region_in_frame,
    216         desired_size,
    217         base::Bind(&ReleaseOriginalFrame, frame));
    218 
    219     DVLOG(3) << "desired size  " << desired_size.ToString()
    220              << " output natural size "
    221              << video_frame->natural_size().ToString()
    222              << " output visible rect  "
    223              << video_frame->visible_rect().ToString();
    224   }
    225   DoDeliverFrame(video_frame, format, estimated_capture_time);
    226 }
    227 
    228 bool VideoTrackAdapter::VideoFrameResolutionAdapter::MaybeDropFrame(
    229     const scoped_refptr<media::VideoFrame>& frame,
    230     float source_frame_rate) {
    231   DCHECK(io_thread_checker_.CalledOnValidThread());
    232 
    233   // Do not drop frames if max frame rate hasn't been specified or the source
    234   // frame rate is known and is lower than max.
    235   if (max_frame_rate_ == 0.0f ||
    236       (source_frame_rate > 0 &&
    237           source_frame_rate <= max_frame_rate_)) {
    238     return false;
    239   }
    240 
    241   base::TimeDelta delta = frame->timestamp() - last_time_stamp_;
    242   if (delta.InMilliseconds() < kMinTimeInMsBetweenFrames) {
    243     // We have seen video frames being delivered from camera devices back to
    244     // back. The simple AR filter for frame rate calculation is too short to
    245     // handle that. http://crbug/394315
    246     // TODO(perkj): Can we come up with a way to fix the times stamps and the
    247     // timing when frames are delivered so all frames can be used?
    248     // The time stamps are generated by Chrome and not the actual device.
    249     // Most likely the back to back problem is caused by software and not the
    250     // actual camera.
    251     DVLOG(3) << "Drop frame since delta time since previous frame is "
    252              << delta.InMilliseconds() << "ms.";
    253     return true;
    254   }
    255   last_time_stamp_ = frame->timestamp();
    256   if (delta == last_time_stamp_)  // First received frame.
    257     return false;
    258   // Calculate the frame rate using a simple AR filter.
    259   // Use a simple filter with 0.1 weight of the current sample.
    260   frame_rate_ = 100 / delta.InMillisecondsF() + 0.9 * frame_rate_;
    261 
    262   // Prefer to not drop frames.
    263   if (max_frame_rate_ + 0.5f > frame_rate_)
    264     return false;  // Keep this frame.
    265 
    266   // The input frame rate is higher than requested.
    267   // Decide if we should keep this frame or drop it.
    268   keep_frame_counter_ += max_frame_rate_ / frame_rate_;
    269   if (keep_frame_counter_ >= 1) {
    270     keep_frame_counter_ -= 1;
    271     // Keep the frame.
    272     return false;
    273   }
    274   DVLOG(3) << "Drop frame. Input frame_rate_ " << frame_rate_ << ".";
    275   return true;
    276 }
    277 
    278 void VideoTrackAdapter::
    279 VideoFrameResolutionAdapter::DoDeliverFrame(
    280     const scoped_refptr<media::VideoFrame>& frame,
    281     const media::VideoCaptureFormat& format,
    282     const base::TimeTicks& estimated_capture_time) {
    283   DCHECK(io_thread_checker_.CalledOnValidThread());
    284   for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin();
    285        it != callbacks_.end(); ++it) {
    286     it->second.Run(frame, format, estimated_capture_time);
    287   }
    288 }
    289 
    290 void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback(
    291     const MediaStreamVideoTrack* track,
    292     const VideoCaptureDeliverFrameCB& callback) {
    293   DCHECK(io_thread_checker_.CalledOnValidThread());
    294   callbacks_.push_back(std::make_pair(track, callback));
    295 }
    296 
    297 void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback(
    298     const MediaStreamVideoTrack* track) {
    299   DCHECK(io_thread_checker_.CalledOnValidThread());
    300   std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin();
    301   for (; it != callbacks_.end(); ++it) {
    302     if (it->first == track) {
    303       // Make sure the VideoCaptureDeliverFrameCB is released on the main
    304       // render thread since it was added on the main render thread in
    305       // VideoTrackAdapter::AddTrack.
    306       scoped_ptr<VideoCaptureDeliverFrameCB> callback(
    307           new VideoCaptureDeliverFrameCB(it->second));
    308       callbacks_.erase(it);
    309       renderer_task_runner_->PostTask(
    310           FROM_HERE, base::Bind(&ResetCallbackOnMainRenderThread,
    311                                 base::Passed(&callback)));
    312 
    313       return;
    314     }
    315   }
    316 }
    317 
    318 bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch(
    319     const gfx::Size& max_size,
    320     double min_aspect_ratio,
    321     double max_aspect_ratio,
    322     double max_frame_rate) const {
    323   DCHECK(io_thread_checker_.CalledOnValidThread());
    324   return max_frame_size_ == max_size &&
    325       min_aspect_ratio_ == min_aspect_ratio &&
    326       max_aspect_ratio_ == max_aspect_ratio &&
    327       max_frame_rate_ == max_frame_rate;
    328 }
    329 
    330 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const {
    331   DCHECK(io_thread_checker_.CalledOnValidThread());
    332   return callbacks_.empty();
    333 }
    334 
    335 VideoTrackAdapter::VideoTrackAdapter(
    336     const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
    337     : io_message_loop_(io_message_loop),
    338       renderer_task_runner_(base::MessageLoopProxy::current()),
    339       monitoring_frame_rate_(false),
    340       muted_state_(false),
    341       frame_counter_(0),
    342       source_frame_rate_(0.0f) {
    343   DCHECK(io_message_loop_.get());
    344 }
    345 
    346 VideoTrackAdapter::~VideoTrackAdapter() {
    347   DCHECK(adapters_.empty());
    348 }
    349 
    350 void VideoTrackAdapter::AddTrack(
    351     const MediaStreamVideoTrack* track,
    352     VideoCaptureDeliverFrameCB frame_callback,
    353     int max_width,
    354     int max_height,
    355     double min_aspect_ratio,
    356     double max_aspect_ratio,
    357     double max_frame_rate) {
    358   DCHECK(thread_checker_.CalledOnValidThread());
    359 
    360   io_message_loop_->PostTask(
    361       FROM_HERE,
    362       base::Bind(&VideoTrackAdapter::AddTrackOnIO,
    363                  this, track, frame_callback, gfx::Size(max_width, max_height),
    364                  min_aspect_ratio, max_aspect_ratio, max_frame_rate));
    365 }
    366 
    367 void VideoTrackAdapter::AddTrackOnIO(
    368     const MediaStreamVideoTrack* track,
    369     VideoCaptureDeliverFrameCB frame_callback,
    370     const gfx::Size& max_frame_size,
    371     double min_aspect_ratio,
    372     double max_aspect_ratio,
    373     double max_frame_rate) {
    374   DCHECK(io_message_loop_->BelongsToCurrentThread());
    375   scoped_refptr<VideoFrameResolutionAdapter> adapter;
    376   for (FrameAdapters::const_iterator it = adapters_.begin();
    377        it != adapters_.end(); ++it) {
    378     if ((*it)->ConstraintsMatch(max_frame_size, min_aspect_ratio,
    379                                 max_aspect_ratio, max_frame_rate)) {
    380       adapter = it->get();
    381       break;
    382     }
    383   }
    384   if (!adapter.get()) {
    385     adapter = new VideoFrameResolutionAdapter(renderer_task_runner_,
    386                                               max_frame_size,
    387                                               min_aspect_ratio,
    388                                               max_aspect_ratio,
    389                                               max_frame_rate);
    390     adapters_.push_back(adapter);
    391   }
    392 
    393   adapter->AddCallback(track, frame_callback);
    394 }
    395 
    396 void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) {
    397   DCHECK(thread_checker_.CalledOnValidThread());
    398   io_message_loop_->PostTask(
    399       FROM_HERE,
    400       base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track));
    401 }
    402 
    403 void VideoTrackAdapter::StartFrameMonitoring(
    404     double source_frame_rate,
    405     const OnMutedCallback& on_muted_callback) {
    406   DCHECK(thread_checker_.CalledOnValidThread());
    407 
    408   VideoTrackAdapter::OnMutedCallback bound_on_muted_callback =
    409       media::BindToCurrentLoop(on_muted_callback);
    410 
    411   io_message_loop_->PostTask(
    412       FROM_HERE,
    413       base::Bind(&VideoTrackAdapter::StartFrameMonitoringOnIO,
    414                  this, bound_on_muted_callback, source_frame_rate));
    415 }
    416 
    417 void VideoTrackAdapter::StartFrameMonitoringOnIO(
    418     const OnMutedCallback& on_muted_callback,
    419     double source_frame_rate) {
    420   DCHECK(io_message_loop_->BelongsToCurrentThread());
    421   DCHECK(!monitoring_frame_rate_);
    422 
    423   monitoring_frame_rate_ = true;
    424 
    425   // If the source does not know the frame rate, set one by default.
    426   if (source_frame_rate == 0.0f)
    427     source_frame_rate = MediaStreamVideoSource::kDefaultFrameRate;
    428   source_frame_rate_ = source_frame_rate;
    429   DVLOG(1) << "Monitoring frame creation, first (large) delay: "
    430       << (kFirstFrameTimeoutInFrameIntervals / source_frame_rate_) << "s";
    431   io_message_loop_->PostDelayedTask(FROM_HERE,
    432        base::Bind(&VideoTrackAdapter::CheckFramesReceivedOnIO, this,
    433                   on_muted_callback, frame_counter_),
    434        base::TimeDelta::FromSecondsD(kFirstFrameTimeoutInFrameIntervals /
    435                                      source_frame_rate_));
    436 }
    437 
    438 void VideoTrackAdapter::StopFrameMonitoring() {
    439   DCHECK(thread_checker_.CalledOnValidThread());
    440   io_message_loop_->PostTask(
    441       FROM_HERE,
    442       base::Bind(&VideoTrackAdapter::StopFrameMonitoringOnIO, this));
    443 }
    444 
    445 void VideoTrackAdapter::StopFrameMonitoringOnIO() {
    446   DCHECK(io_message_loop_->BelongsToCurrentThread());
    447   monitoring_frame_rate_ = false;
    448 }
    449 
    450 void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) {
    451   DCHECK(io_message_loop_->BelongsToCurrentThread());
    452   for (FrameAdapters::iterator it = adapters_.begin();
    453        it != adapters_.end(); ++it) {
    454     (*it)->RemoveCallback(track);
    455     if ((*it)->IsEmpty()) {
    456       adapters_.erase(it);
    457       break;
    458     }
    459   }
    460 }
    461 
    462 void VideoTrackAdapter::DeliverFrameOnIO(
    463     const scoped_refptr<media::VideoFrame>& frame,
    464     const media::VideoCaptureFormat& format,
    465     const base::TimeTicks& estimated_capture_time) {
    466   DCHECK(io_message_loop_->BelongsToCurrentThread());
    467   TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO");
    468   ++frame_counter_;
    469   for (FrameAdapters::iterator it = adapters_.begin();
    470        it != adapters_.end(); ++it) {
    471     (*it)->DeliverFrame(frame, format, estimated_capture_time);
    472   }
    473 }
    474 
    475 void VideoTrackAdapter::CheckFramesReceivedOnIO(
    476     const OnMutedCallback& set_muted_state_callback,
    477     uint64 old_frame_counter_snapshot) {
    478   DCHECK(io_message_loop_->BelongsToCurrentThread());
    479 
    480   if (!monitoring_frame_rate_)
    481     return;
    482 
    483   DVLOG_IF(1, old_frame_counter_snapshot == frame_counter_)
    484       << "No frames have passed, setting source as Muted.";
    485 
    486   bool muted_state = old_frame_counter_snapshot == frame_counter_;
    487   if (muted_state_ != muted_state) {
    488     set_muted_state_callback.Run(muted_state);
    489     muted_state_ = muted_state;
    490   }
    491 
    492   io_message_loop_->PostDelayedTask(FROM_HERE,
    493       base::Bind(&VideoTrackAdapter::CheckFramesReceivedOnIO, this,
    494           set_muted_state_callback, frame_counter_),
    495       base::TimeDelta::FromSecondsD(kNormalFrameTimeoutInFrameIntervals /
    496                                     source_frame_rate_));
    497 }
    498 
    499 }  // namespace content
    500