Chromium Code Reviews| Index: content/renderer/media/video_track_adapter.cc |
| diff --git a/content/renderer/media/video_track_adapter.cc b/content/renderer/media/video_track_adapter.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..31c791160944cd90401e94c70a76a286542904dc |
| --- /dev/null |
| +++ b/content/renderer/media/video_track_adapter.cc |
| @@ -0,0 +1,335 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/renderer/media/video_track_adapter.h" |
| + |
| +#include <algorithm> |
| +#include <limits> |
| +#include <utility> |
| + |
| +#include "base/bind.h" |
| +#include "base/debug/trace_event.h" |
| +#include "base/location.h" |
| +#include "media/base/video_util.h" |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +// Empty method used for keeping a reference to the original media::VideoFrame |
| +// in VideoFrameResolutionAdapter::DeliverFrame if cropping is needed. |
| +// The reference to |frame| is kept in the closure that calls this method. |
| +void ReleaseOriginalFrame( |
| + const scoped_refptr<media::VideoFrame>& frame) { |
| +} |
| + |
| +void ResetCallbackOnMainRenderThread( |
| + scoped_ptr<VideoCaptureDeliverFrameCB> callback) { |
| + // |callback| will be deleted when this exits. |
| +} |
| + |
| +} // anonymous namespace |
| + |
| +// VideoFrameResolutionAdapter is created on and lives on |
| +// on the IO-thread. It does the resolution adaptation and delivers frames to |
| +// all registered tracks on the IO-thread. |
| +// All method calls must be on the IO-thread. |
| +class VideoTrackAdapter::VideoFrameResolutionAdapter |
| + : public base::RefCountedThreadSafe<VideoFrameResolutionAdapter> { |
| + public: |
| + VideoFrameResolutionAdapter( |
| + scoped_refptr<base::SingleThreadTaskRunner> render_message_loop, |
| + int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio); |
| + |
| + // Add |callback| to receive video frames on the IO-thread. |
| + // |callback| will however be released on thee main render thread. |
|
mcasas
2014/05/20 07:19:53
s/thee/the/
perkj_chrome
2014/05/22 07:06:05
Done.
|
| + void AddCallback(const MediaStreamVideoTrack* track, |
| + const VideoCaptureDeliverFrameCB& callback); |
| + |
| + // Removes |callback| associated with |track| from receiving video frames if |
| + // |track| has been added. It is ok to call RemoveCallback even if the |track| |
| + // has not been added. The |callback| is released on the main render thread. |
| + void RemoveCallback(const MediaStreamVideoTrack* track); |
| + |
| + void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame, |
| + const media::VideoCaptureFormat& format); |
| + |
| + // Returns true if all arguments match with the output of this adapter. |
| + bool ConstraintsMatch(int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio) const; |
| + |
| + bool IsEmpty() const; |
| + |
| + private: |
| + virtual ~VideoFrameResolutionAdapter(); |
| + friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>; |
| + |
| + virtual void DoDeliverFrame( |
| + const scoped_refptr<media::VideoFrame>& frame, |
| + const media::VideoCaptureFormat& format); |
| + |
| + // Bound to the IO-thread. |
| + base::ThreadChecker io_thread_checker_; |
| + |
| + // The task runner where we will release VideoCaptureDeliverFrameCB |
| + // registered in AddCallback. |
| + scoped_refptr<base::SingleThreadTaskRunner> renderer_task_runner_; |
| + |
| + gfx::Size max_frame_size_; |
| + double min_aspect_ratio_; |
| + double max_aspect_ratio_; |
| + |
| + typedef std::pair<const void*, VideoCaptureDeliverFrameCB> |
| + VideoIdCallbackPair; |
| + std::vector<VideoIdCallbackPair> callbacks_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter); |
| +}; |
| + |
| +VideoTrackAdapter:: |
| +VideoFrameResolutionAdapter::VideoFrameResolutionAdapter( |
| + scoped_refptr<base::SingleThreadTaskRunner> render_message_loop, |
| + int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio) |
| + : renderer_task_runner_(render_message_loop), |
| + max_frame_size_(max_width, max_height), |
| + min_aspect_ratio_(min_aspect_ratio), |
| + max_aspect_ratio_(max_aspect_ratio) { |
| + DCHECK(renderer_task_runner_); |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_); |
| + CHECK_NE(0, max_aspect_ratio_); |
| + DVLOG(3) << "VideoFrameResolutionAdapter(" |
| + << "{ max_width =" << max_width << "}, " |
| + << "{ max_height =" << max_height << "}, " |
| + << "{ min_aspect_ratio =" << min_aspect_ratio << "}, " |
| + << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}) "; |
| +} |
| + |
| +VideoTrackAdapter:: |
| +VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + DCHECK(callbacks_.empty()); |
| +} |
| + |
| +void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame( |
| + const scoped_refptr<media::VideoFrame>& frame, |
| + const media::VideoCaptureFormat& format) { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + // TODO(perkj): Allow cropping / scaling of textures once |
| + // http://crbug/362521 is fixed. |
| + if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { |
| + DoDeliverFrame(frame, format); |
| + return; |
| + } |
| + scoped_refptr<media::VideoFrame> video_frame(frame); |
| + double input_ratio = |
| + static_cast<double>(frame->natural_size().width()) / |
| + frame->natural_size().height(); |
| + |
| + // If |frame| has larger width or height than requested, or the aspect ratio |
| + // does not match the requested, we want to create a wrapped version of this |
| + // frame with a size that fulfills the constraints. |
| + if (frame->natural_size().width() > max_frame_size_.width() || |
| + frame->natural_size().height() > max_frame_size_.height() || |
| + input_ratio > max_aspect_ratio_ || |
| + input_ratio < min_aspect_ratio_) { |
| + int desired_width = std::min(max_frame_size_.width(), |
| + frame->natural_size().width()); |
| + int desired_height = std::min(max_frame_size_.height(), |
| + frame->natural_size().height()); |
| + |
| + double resulting_ratio = |
| + static_cast<double>(desired_width) / desired_height; |
| + double requested_ratio = resulting_ratio; |
| + |
| + if (requested_ratio > max_aspect_ratio_) |
| + requested_ratio = max_aspect_ratio_; |
| + else if (requested_ratio < min_aspect_ratio_) |
| + requested_ratio = min_aspect_ratio_; |
| + |
| + if (resulting_ratio < requested_ratio) { |
| + desired_height = static_cast<int>((desired_height * resulting_ratio) / |
| + requested_ratio); |
| + // Make sure we scale to an even height to avoid rounding errors |
| + desired_height = (desired_height + 1) & ~1; |
| + } else if (resulting_ratio > requested_ratio) { |
| + desired_width = static_cast<int>((desired_width * requested_ratio) / |
| + resulting_ratio); |
| + // Make sure we scale to an even width to avoid rounding errors. |
| + desired_width = (desired_width + 1) & ~1; |
| + } |
| + |
| + gfx::Size desired_size(desired_width, desired_height); |
| + |
| + // Get the largest centered rectangle with the same aspect ratio of |
| + // |desired_size| that fits entirely inside of |frame->visible_rect()|. |
| + // This will be the rect we need to crop the original frame to. |
| + // From this rect, the original frame can be scaled down to |desired_size|. |
| + gfx::Rect region_in_frame = |
| + media::ComputeLetterboxRegion(frame->visible_rect(), desired_size); |
| + |
| + video_frame = media::VideoFrame::WrapVideoFrame( |
| + frame, |
| + region_in_frame, |
| + desired_size, |
| + base::Bind(&ReleaseOriginalFrame, frame)); |
| + |
| + DVLOG(3) << "desired size " << desired_size.ToString() |
| + << " output natural size " |
| + << video_frame->natural_size().ToString() |
| + << " output visible rect " |
| + << video_frame->visible_rect().ToString(); |
| + } |
| + DoDeliverFrame(video_frame, format); |
| +} |
| + |
| +void VideoTrackAdapter:: |
| +VideoFrameResolutionAdapter::DoDeliverFrame( |
| + const scoped_refptr<media::VideoFrame>& frame, |
| + const media::VideoCaptureFormat& format) { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin(); |
| + it != callbacks_.end(); ++it) { |
| + it->second.Run(frame, format); |
| + } |
| +} |
| + |
| +void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback( |
| + const MediaStreamVideoTrack* track, |
| + const VideoCaptureDeliverFrameCB& callback) { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + callbacks_.push_back(std::make_pair(track, callback)); |
| +} |
| + |
| +void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback( |
| + const MediaStreamVideoTrack* track) { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin(); |
| + for (; it != callbacks_.end(); ++it) { |
| + if (it->first == track) { |
| + // Make sure the VideoCaptureDeliverFrameCB is released on the main |
| + // render thread since it was added on the main render thread in |
| + // VideoTrackAdapter::AddTrack. |
| + scoped_ptr<VideoCaptureDeliverFrameCB> callback( |
| + new VideoCaptureDeliverFrameCB(it->second)); |
| + callbacks_.erase(it); |
| + renderer_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(&ResetCallbackOnMainRenderThread, |
| + base::Passed(&callback))); |
|
mcasas
2014/05/20 07:36:09
PS-nit: consider "RelaseSoon()"
https://code.goog
perkj_chrome
2014/05/22 07:06:05
It should be DeleteSoon since this is not referenc
|
| + |
| + return; |
| + } |
| + } |
| +} |
| + |
| +bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch( |
| + int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio) const { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + return max_frame_size_.width() == max_width && |
| + max_frame_size_.height() == max_height && |
| + min_aspect_ratio_ == min_aspect_ratio && |
| + max_aspect_ratio_ == max_aspect_ratio; |
| +} |
| + |
| +bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const { |
| + DCHECK(io_thread_checker_.CalledOnValidThread()); |
| + return callbacks_.empty(); |
| +} |
| + |
| +VideoTrackAdapter::VideoTrackAdapter( |
| + const scoped_refptr<base::MessageLoopProxy>& io_message_loop) |
| + : io_message_loop_(io_message_loop), |
| + renderer_task_runner_(base::MessageLoopProxy::current()) { |
| + DCHECK(io_message_loop_); |
| +} |
| + |
| +VideoTrackAdapter::~VideoTrackAdapter() { |
| + DCHECK(adapters_.empty()); |
| +} |
| + |
| +void VideoTrackAdapter::AddTrack(const MediaStreamVideoTrack* track, |
| + VideoCaptureDeliverFrameCB frame_callback, |
| + int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + io_message_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&VideoTrackAdapter::AddTrackOnIO, |
| + this, track, frame_callback, max_width, max_height, |
| + min_aspect_ratio, max_aspect_ratio)); |
| +} |
| + |
| +void VideoTrackAdapter::AddTrackOnIO( |
| + const MediaStreamVideoTrack* track, |
| + VideoCaptureDeliverFrameCB frame_callback, |
| + int max_width, |
| + int max_height, |
| + double min_aspect_ratio, |
| + double max_aspect_ratio) { |
| + DCHECK(io_message_loop_->BelongsToCurrentThread()); |
| + scoped_refptr<VideoFrameResolutionAdapter> adapter; |
| + for (FrameAdapters::const_iterator it = adapters_.begin(); |
| + it != adapters_.end(); ++it) { |
| + if ((*it)->ConstraintsMatch(max_width, max_height, min_aspect_ratio, |
| + max_aspect_ratio)) { |
| + adapter = it->get(); |
| + break; |
| + } |
| + } |
| + if (!adapter) { |
| + adapter = new VideoFrameResolutionAdapter(renderer_task_runner_, |
| + max_width, |
| + max_height, |
| + min_aspect_ratio, |
| + max_aspect_ratio); |
| + adapters_.push_back(adapter); |
| + } |
| + |
| + adapter->AddCallback(track, frame_callback); |
| +} |
| + |
| +void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + io_message_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track)); |
| +} |
| + |
| +void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) { |
| + DCHECK(io_message_loop_->BelongsToCurrentThread()); |
| + for (FrameAdapters::iterator it = adapters_.begin(); |
| + it != adapters_.end(); ++it) { |
| + (*it)->RemoveCallback(track); |
| + if ((*it)->IsEmpty()) { |
| + adapters_.erase(it); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +void VideoTrackAdapter::DeliverFrameOnIO( |
| + const scoped_refptr<media::VideoFrame>& frame, |
| + const media::VideoCaptureFormat& format) { |
| + DCHECK(io_message_loop_->BelongsToCurrentThread()); |
| + TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO"); |
| + for (FrameAdapters::iterator it = adapters_.begin(); |
| + it != adapters_.end(); ++it) { |
| + (*it)->DeliverFrame(frame, format); |
| + } |
| +} |
| + |
| +} // namespace content |