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 "content/renderer/media/video_track_adapter.h"
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"
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;
28 // Min delta time between two frames allowed without being dropped if a max
29 // frame rate is specified.
30 const int kMinTimeInMsBetweenFrames = 5;
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) {
39 void ResetCallbackOnMainRenderThread(
40 scoped_ptr<VideoCaptureDeliverFrameCB> callback) {
41 // |callback| will be deleted when this exits.
44 } // anonymous namespace
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> {
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);
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);
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);
70 void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame,
71 const media::VideoCaptureFormat& format,
72 const base::TimeTicks& estimated_capture_time);
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;
83 virtual ~VideoFrameResolutionAdapter();
84 friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>;
86 virtual void DoDeliverFrame(
87 const scoped_refptr<media::VideoFrame>& frame,
88 const media::VideoCaptureFormat& format,
89 const base::TimeTicks& estimated_capture_time);
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);
96 // Bound to the IO-thread.
97 base::ThreadChecker io_thread_checker_;
99 // The task runner where we will release VideoCaptureDeliverFrameCB
100 // registered in AddCallback.
101 scoped_refptr<base::SingleThreadTaskRunner> renderer_task_runner_;
103 gfx::Size max_frame_size_;
104 double min_aspect_ratio_;
105 double max_aspect_ratio_;
108 base::TimeDelta last_time_stamp_;
109 double max_frame_rate_;
110 double keep_frame_counter_;
112 typedef std::pair<const void*, VideoCaptureDeliverFrameCB>
114 std::vector<VideoIdCallbackPair> callbacks_;
116 DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter);
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_ << "}) ";
146 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() {
147 DCHECK(io_thread_checker_.CalledOnValidThread());
148 DCHECK(callbacks_.empty());
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());
157 if (MaybeDropFrame(frame, format.frame_rate))
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);
166 scoped_refptr<media::VideoFrame> video_frame(frame);
168 static_cast<double>(frame->natural_size().width()) /
169 frame->natural_size().height();
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());
183 double resulting_ratio =
184 static_cast<double>(desired_width) / desired_height;
185 double requested_ratio = resulting_ratio;
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_;
192 if (resulting_ratio < requested_ratio) {
193 desired_height = static_cast<int>((desired_height * resulting_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) /
200 // Make sure we scale to an even width to avoid rounding errors.
201 desired_width = (desired_width + 1) & ~1;
204 gfx::Size desired_size(desired_width, desired_height);
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);
213 video_frame = media::VideoFrame::WrapVideoFrame(
217 base::Bind(&ReleaseOriginalFrame, frame));
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();
225 DoDeliverFrame(video_frame, format, estimated_capture_time);
228 bool VideoTrackAdapter::VideoFrameResolutionAdapter::MaybeDropFrame(
229 const scoped_refptr<media::VideoFrame>& frame,
230 float source_frame_rate) {
231 DCHECK(io_thread_checker_.CalledOnValidThread());
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_)) {
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
251 DVLOG(3) << "Drop frame since delta time since previous frame is "
252 << delta.InMilliseconds() << "ms.";
255 last_time_stamp_ = frame->timestamp();
256 if (delta == last_time_stamp_) // First received frame.
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_;
262 // Prefer to not drop frames.
263 if (max_frame_rate_ + 0.5f > frame_rate_)
264 return false; // Keep this frame.
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;
274 DVLOG(3) << "Drop frame. Input frame_rate_ " << frame_rate_ << ".";
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);
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));
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)));
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;
330 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const {
331 DCHECK(io_thread_checker_.CalledOnValidThread());
332 return callbacks_.empty();
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),
342 source_frame_rate_(0.0f) {
343 DCHECK(io_message_loop_.get());
346 VideoTrackAdapter::~VideoTrackAdapter() {
347 DCHECK(adapters_.empty());
350 void VideoTrackAdapter::AddTrack(
351 const MediaStreamVideoTrack* track,
352 VideoCaptureDeliverFrameCB frame_callback,
355 double min_aspect_ratio,
356 double max_aspect_ratio,
357 double max_frame_rate) {
358 DCHECK(thread_checker_.CalledOnValidThread());
360 io_message_loop_->PostTask(
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));
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)) {
384 if (!adapter.get()) {
385 adapter = new VideoFrameResolutionAdapter(renderer_task_runner_,
390 adapters_.push_back(adapter);
393 adapter->AddCallback(track, frame_callback);
396 void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) {
397 DCHECK(thread_checker_.CalledOnValidThread());
398 io_message_loop_->PostTask(
400 base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track));
403 void VideoTrackAdapter::StartFrameMonitoring(
404 double source_frame_rate,
405 const OnMutedCallback& on_muted_callback) {
406 DCHECK(thread_checker_.CalledOnValidThread());
408 VideoTrackAdapter::OnMutedCallback bound_on_muted_callback =
409 media::BindToCurrentLoop(on_muted_callback);
411 io_message_loop_->PostTask(
413 base::Bind(&VideoTrackAdapter::StartFrameMonitoringOnIO,
414 this, bound_on_muted_callback, source_frame_rate));
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_);
423 monitoring_frame_rate_ = true;
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_));
438 void VideoTrackAdapter::StopFrameMonitoring() {
439 DCHECK(thread_checker_.CalledOnValidThread());
440 io_message_loop_->PostTask(
442 base::Bind(&VideoTrackAdapter::StopFrameMonitoringOnIO, this));
445 void VideoTrackAdapter::StopFrameMonitoringOnIO() {
446 DCHECK(io_message_loop_->BelongsToCurrentThread());
447 monitoring_frame_rate_ = false;
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()) {
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");
469 for (FrameAdapters::iterator it = adapters_.begin();
470 it != adapters_.end(); ++it) {
471 (*it)->DeliverFrame(frame, format, estimated_capture_time);
475 void VideoTrackAdapter::CheckFramesReceivedOnIO(
476 const OnMutedCallback& set_muted_state_callback,
477 uint64 old_frame_counter_snapshot) {
478 DCHECK(io_message_loop_->BelongsToCurrentThread());
480 if (!monitoring_frame_rate_)
483 DVLOG_IF(1, old_frame_counter_snapshot == frame_counter_)
484 << "No frames have passed, setting source as Muted.";
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;
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_));
499 } // namespace content