blob: 461b16638f6fe1b2020e6309595303651aa8f446 [file] [log] [blame]
// 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,
const base::TimeTicks& estimated_capture_time);
// 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,
const base::TimeTicks& estimated_capture_time);
// 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,
const base::TimeTicks& estimated_capture_time) {
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, estimated_capture_time);
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, estimated_capture_time);
}
void VideoTrackAdapter::
VideoFrameResolutionAdapter::DoDeliverFrame(
const scoped_refptr<media::VideoFrame>& frame,
const media::VideoCaptureFormat& format,
const base::TimeTicks& estimated_capture_time) {
DCHECK(io_thread_checker_.CalledOnValidThread());
for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin();
it != callbacks_.end(); ++it) {
it->second.Run(frame, format, estimated_capture_time);
}
}
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,
const base::TimeTicks& estimated_capture_time) {
DCHECK(io_message_loop_->BelongsToCurrentThread());
TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO");
for (FrameAdapters::iterator it = adapters_.begin();
it != adapters_.end(); ++it) {
(*it)->DeliverFrame(frame, format, estimated_capture_time);
}
}
} // namespace content