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 "media/base/video_util.h"
15
16 namespace content {
17
18 namespace {
19
20 // Empty method used for keeping a reference to the original media::VideoFrame
21 // in VideoFrameResolutionAdapter::DeliverFrame if cropping is needed.
22 // The reference to |frame| is kept in the closure that calls this method.
ReleaseOriginalFrame(const scoped_refptr<media::VideoFrame> & frame)23 void ReleaseOriginalFrame(
24 const scoped_refptr<media::VideoFrame>& frame) {
25 }
26
ResetCallbackOnMainRenderThread(scoped_ptr<VideoCaptureDeliverFrameCB> callback)27 void ResetCallbackOnMainRenderThread(
28 scoped_ptr<VideoCaptureDeliverFrameCB> callback) {
29 // |callback| will be deleted when this exits.
30 }
31
32 } // anonymous namespace
33
34 // VideoFrameResolutionAdapter is created on and lives on
35 // on the IO-thread. It does the resolution adaptation and delivers frames to
36 // all registered tracks on the IO-thread.
37 // All method calls must be on the IO-thread.
38 class VideoTrackAdapter::VideoFrameResolutionAdapter
39 : public base::RefCountedThreadSafe<VideoFrameResolutionAdapter> {
40 public:
41 VideoFrameResolutionAdapter(
42 scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
43 int max_width,
44 int max_height,
45 double min_aspect_ratio,
46 double max_aspect_ratio);
47
48 // Add |callback| to receive video frames on the IO-thread.
49 // |callback| will however be released on the main render thread.
50 void AddCallback(const MediaStreamVideoTrack* track,
51 const VideoCaptureDeliverFrameCB& callback);
52
53 // Removes |callback| associated with |track| from receiving video frames if
54 // |track| has been added. It is ok to call RemoveCallback even if the |track|
55 // has not been added. The |callback| is released on the main render thread.
56 void RemoveCallback(const MediaStreamVideoTrack* track);
57
58 void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame,
59 const media::VideoCaptureFormat& format,
60 const base::TimeTicks& estimated_capture_time);
61
62 // Returns true if all arguments match with the output of this adapter.
63 bool ConstraintsMatch(int max_width,
64 int max_height,
65 double min_aspect_ratio,
66 double max_aspect_ratio) const;
67
68 bool IsEmpty() const;
69
70 private:
71 virtual ~VideoFrameResolutionAdapter();
72 friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>;
73
74 virtual void DoDeliverFrame(
75 const scoped_refptr<media::VideoFrame>& frame,
76 const media::VideoCaptureFormat& format,
77 const base::TimeTicks& estimated_capture_time);
78
79 // Bound to the IO-thread.
80 base::ThreadChecker io_thread_checker_;
81
82 // The task runner where we will release VideoCaptureDeliverFrameCB
83 // registered in AddCallback.
84 scoped_refptr<base::SingleThreadTaskRunner> renderer_task_runner_;
85
86 gfx::Size max_frame_size_;
87 double min_aspect_ratio_;
88 double max_aspect_ratio_;
89
90 typedef std::pair<const void*, VideoCaptureDeliverFrameCB>
91 VideoIdCallbackPair;
92 std::vector<VideoIdCallbackPair> callbacks_;
93
94 DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter);
95 };
96
97 VideoTrackAdapter::
VideoFrameResolutionAdapter(scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,int max_width,int max_height,double min_aspect_ratio,double max_aspect_ratio)98 VideoFrameResolutionAdapter::VideoFrameResolutionAdapter(
99 scoped_refptr<base::SingleThreadTaskRunner> render_message_loop,
100 int max_width,
101 int max_height,
102 double min_aspect_ratio,
103 double max_aspect_ratio)
104 : renderer_task_runner_(render_message_loop),
105 max_frame_size_(max_width, max_height),
106 min_aspect_ratio_(min_aspect_ratio),
107 max_aspect_ratio_(max_aspect_ratio) {
108 DCHECK(renderer_task_runner_);
109 DCHECK(io_thread_checker_.CalledOnValidThread());
110 DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_);
111 CHECK_NE(0, max_aspect_ratio_);
112 DVLOG(3) << "VideoFrameResolutionAdapter("
113 << "{ max_width =" << max_width << "}, "
114 << "{ max_height =" << max_height << "}, "
115 << "{ min_aspect_ratio =" << min_aspect_ratio << "}, "
116 << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}) ";
117 }
118
119 VideoTrackAdapter::
~VideoFrameResolutionAdapter()120 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() {
121 DCHECK(io_thread_checker_.CalledOnValidThread());
122 DCHECK(callbacks_.empty());
123 }
124
DeliverFrame(const scoped_refptr<media::VideoFrame> & frame,const media::VideoCaptureFormat & format,const base::TimeTicks & estimated_capture_time)125 void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame(
126 const scoped_refptr<media::VideoFrame>& frame,
127 const media::VideoCaptureFormat& format,
128 const base::TimeTicks& estimated_capture_time) {
129 DCHECK(io_thread_checker_.CalledOnValidThread());
130 // TODO(perkj): Allow cropping / scaling of textures once
131 // http://crbug/362521 is fixed.
132 if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) {
133 DoDeliverFrame(frame, format, estimated_capture_time);
134 return;
135 }
136 scoped_refptr<media::VideoFrame> video_frame(frame);
137 double input_ratio =
138 static_cast<double>(frame->natural_size().width()) /
139 frame->natural_size().height();
140
141 // If |frame| has larger width or height than requested, or the aspect ratio
142 // does not match the requested, we want to create a wrapped version of this
143 // frame with a size that fulfills the constraints.
144 if (frame->natural_size().width() > max_frame_size_.width() ||
145 frame->natural_size().height() > max_frame_size_.height() ||
146 input_ratio > max_aspect_ratio_ ||
147 input_ratio < min_aspect_ratio_) {
148 int desired_width = std::min(max_frame_size_.width(),
149 frame->natural_size().width());
150 int desired_height = std::min(max_frame_size_.height(),
151 frame->natural_size().height());
152
153 double resulting_ratio =
154 static_cast<double>(desired_width) / desired_height;
155 double requested_ratio = resulting_ratio;
156
157 if (requested_ratio > max_aspect_ratio_)
158 requested_ratio = max_aspect_ratio_;
159 else if (requested_ratio < min_aspect_ratio_)
160 requested_ratio = min_aspect_ratio_;
161
162 if (resulting_ratio < requested_ratio) {
163 desired_height = static_cast<int>((desired_height * resulting_ratio) /
164 requested_ratio);
165 // Make sure we scale to an even height to avoid rounding errors
166 desired_height = (desired_height + 1) & ~1;
167 } else if (resulting_ratio > requested_ratio) {
168 desired_width = static_cast<int>((desired_width * requested_ratio) /
169 resulting_ratio);
170 // Make sure we scale to an even width to avoid rounding errors.
171 desired_width = (desired_width + 1) & ~1;
172 }
173
174 gfx::Size desired_size(desired_width, desired_height);
175
176 // Get the largest centered rectangle with the same aspect ratio of
177 // |desired_size| that fits entirely inside of |frame->visible_rect()|.
178 // This will be the rect we need to crop the original frame to.
179 // From this rect, the original frame can be scaled down to |desired_size|.
180 gfx::Rect region_in_frame =
181 media::ComputeLetterboxRegion(frame->visible_rect(), desired_size);
182
183 video_frame = media::VideoFrame::WrapVideoFrame(
184 frame,
185 region_in_frame,
186 desired_size,
187 base::Bind(&ReleaseOriginalFrame, frame));
188
189 DVLOG(3) << "desired size " << desired_size.ToString()
190 << " output natural size "
191 << video_frame->natural_size().ToString()
192 << " output visible rect "
193 << video_frame->visible_rect().ToString();
194 }
195 DoDeliverFrame(video_frame, format, estimated_capture_time);
196 }
197
198 void VideoTrackAdapter::
DoDeliverFrame(const scoped_refptr<media::VideoFrame> & frame,const media::VideoCaptureFormat & format,const base::TimeTicks & estimated_capture_time)199 VideoFrameResolutionAdapter::DoDeliverFrame(
200 const scoped_refptr<media::VideoFrame>& frame,
201 const media::VideoCaptureFormat& format,
202 const base::TimeTicks& estimated_capture_time) {
203 DCHECK(io_thread_checker_.CalledOnValidThread());
204 for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin();
205 it != callbacks_.end(); ++it) {
206 it->second.Run(frame, format, estimated_capture_time);
207 }
208 }
209
AddCallback(const MediaStreamVideoTrack * track,const VideoCaptureDeliverFrameCB & callback)210 void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback(
211 const MediaStreamVideoTrack* track,
212 const VideoCaptureDeliverFrameCB& callback) {
213 DCHECK(io_thread_checker_.CalledOnValidThread());
214 callbacks_.push_back(std::make_pair(track, callback));
215 }
216
RemoveCallback(const MediaStreamVideoTrack * track)217 void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback(
218 const MediaStreamVideoTrack* track) {
219 DCHECK(io_thread_checker_.CalledOnValidThread());
220 std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin();
221 for (; it != callbacks_.end(); ++it) {
222 if (it->first == track) {
223 // Make sure the VideoCaptureDeliverFrameCB is released on the main
224 // render thread since it was added on the main render thread in
225 // VideoTrackAdapter::AddTrack.
226 scoped_ptr<VideoCaptureDeliverFrameCB> callback(
227 new VideoCaptureDeliverFrameCB(it->second));
228 callbacks_.erase(it);
229 renderer_task_runner_->PostTask(
230 FROM_HERE, base::Bind(&ResetCallbackOnMainRenderThread,
231 base::Passed(&callback)));
232
233 return;
234 }
235 }
236 }
237
ConstraintsMatch(int max_width,int max_height,double min_aspect_ratio,double max_aspect_ratio) const238 bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch(
239 int max_width,
240 int max_height,
241 double min_aspect_ratio,
242 double max_aspect_ratio) const {
243 DCHECK(io_thread_checker_.CalledOnValidThread());
244 return max_frame_size_.width() == max_width &&
245 max_frame_size_.height() == max_height &&
246 min_aspect_ratio_ == min_aspect_ratio &&
247 max_aspect_ratio_ == max_aspect_ratio;
248 }
249
IsEmpty() const250 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const {
251 DCHECK(io_thread_checker_.CalledOnValidThread());
252 return callbacks_.empty();
253 }
254
VideoTrackAdapter(const scoped_refptr<base::MessageLoopProxy> & io_message_loop)255 VideoTrackAdapter::VideoTrackAdapter(
256 const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
257 : io_message_loop_(io_message_loop),
258 renderer_task_runner_(base::MessageLoopProxy::current()) {
259 DCHECK(io_message_loop_);
260 }
261
~VideoTrackAdapter()262 VideoTrackAdapter::~VideoTrackAdapter() {
263 DCHECK(adapters_.empty());
264 }
265
AddTrack(const MediaStreamVideoTrack * track,VideoCaptureDeliverFrameCB frame_callback,int max_width,int max_height,double min_aspect_ratio,double max_aspect_ratio)266 void VideoTrackAdapter::AddTrack(const MediaStreamVideoTrack* track,
267 VideoCaptureDeliverFrameCB frame_callback,
268 int max_width,
269 int max_height,
270 double min_aspect_ratio,
271 double max_aspect_ratio) {
272 DCHECK(thread_checker_.CalledOnValidThread());
273 io_message_loop_->PostTask(
274 FROM_HERE,
275 base::Bind(&VideoTrackAdapter::AddTrackOnIO,
276 this, track, frame_callback, max_width, max_height,
277 min_aspect_ratio, max_aspect_ratio));
278 }
279
AddTrackOnIO(const MediaStreamVideoTrack * track,VideoCaptureDeliverFrameCB frame_callback,int max_width,int max_height,double min_aspect_ratio,double max_aspect_ratio)280 void VideoTrackAdapter::AddTrackOnIO(
281 const MediaStreamVideoTrack* track,
282 VideoCaptureDeliverFrameCB frame_callback,
283 int max_width,
284 int max_height,
285 double min_aspect_ratio,
286 double max_aspect_ratio) {
287 DCHECK(io_message_loop_->BelongsToCurrentThread());
288 scoped_refptr<VideoFrameResolutionAdapter> adapter;
289 for (FrameAdapters::const_iterator it = adapters_.begin();
290 it != adapters_.end(); ++it) {
291 if ((*it)->ConstraintsMatch(max_width, max_height, min_aspect_ratio,
292 max_aspect_ratio)) {
293 adapter = it->get();
294 break;
295 }
296 }
297 if (!adapter) {
298 adapter = new VideoFrameResolutionAdapter(renderer_task_runner_,
299 max_width,
300 max_height,
301 min_aspect_ratio,
302 max_aspect_ratio);
303 adapters_.push_back(adapter);
304 }
305
306 adapter->AddCallback(track, frame_callback);
307 }
308
RemoveTrack(const MediaStreamVideoTrack * track)309 void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) {
310 DCHECK(thread_checker_.CalledOnValidThread());
311 io_message_loop_->PostTask(
312 FROM_HERE,
313 base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track));
314 }
315
RemoveTrackOnIO(const MediaStreamVideoTrack * track)316 void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) {
317 DCHECK(io_message_loop_->BelongsToCurrentThread());
318 for (FrameAdapters::iterator it = adapters_.begin();
319 it != adapters_.end(); ++it) {
320 (*it)->RemoveCallback(track);
321 if ((*it)->IsEmpty()) {
322 adapters_.erase(it);
323 break;
324 }
325 }
326 }
327
DeliverFrameOnIO(const scoped_refptr<media::VideoFrame> & frame,const media::VideoCaptureFormat & format,const base::TimeTicks & estimated_capture_time)328 void VideoTrackAdapter::DeliverFrameOnIO(
329 const scoped_refptr<media::VideoFrame>& frame,
330 const media::VideoCaptureFormat& format,
331 const base::TimeTicks& estimated_capture_time) {
332 DCHECK(io_message_loop_->BelongsToCurrentThread());
333 TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO");
334 for (FrameAdapters::iterator it = adapters_.begin();
335 it != adapters_.end(); ++it) {
336 (*it)->DeliverFrame(frame, format, estimated_capture_time);
337 }
338 }
339
340 } // namespace content
341