| 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..46223f8cfad2cb89211e484e9532fb7da4e1156a
|
| --- /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 the main render thread.
|
| + 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)));
|
| +
|
| + 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
|
|
|