• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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