OLD | NEW |
---|---|
(Empty) | |
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. | |
23 void ReleaseOriginalFrame( | |
24 const scoped_refptr<media::VideoFrame>& frame) { | |
25 } | |
26 | |
27 void ResetCallbackOnMainRenderThread( | |
28 scoped_ptr<VideoCaptureDeliverFrameCB> callback) { | |
29 // |callback| will be deleted when this exits. | |
tommi (sloooow) - chröme
2014/05/14 09:43:38
indent
perkj_chrome
2014/05/14 13:29:44
Done.
| |
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::MessageLoopProxy> 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 thee 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 | |
61 // Returns true if all arguments match with the output of this adapter. | |
62 bool ConstraintsMatch(int max_width, | |
63 int max_height, | |
64 double min_aspect_ratio, | |
65 double max_aspect_ratio) const; | |
66 | |
67 bool IsEmpty() const; | |
68 | |
69 private: | |
70 virtual ~VideoFrameResolutionAdapter(); | |
71 friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>; | |
72 | |
73 virtual void DoDeliverFrame( | |
74 const scoped_refptr<media::VideoFrame>& frame, | |
75 const media::VideoCaptureFormat& format); | |
76 | |
77 // Bound to the IO-thread. | |
78 base::ThreadChecker io_thread_checker_; | |
79 | |
80 // The message loop where we will release VideoCaptureDeliverFrameCB | |
81 // registered in AddCallback. | |
82 scoped_refptr<base::MessageLoopProxy> render_message_loop_; | |
83 | |
84 gfx::Size max_frame_size_; | |
85 double min_aspect_ratio_; | |
86 double max_aspect_ratio_; | |
87 | |
88 typedef std::pair<const void*, VideoCaptureDeliverFrameCB> | |
89 VideoIdCallbackPair; | |
90 std::vector<VideoIdCallbackPair> callbacks_; | |
91 | |
92 DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter); | |
93 }; | |
94 | |
95 VideoTrackAdapter:: | |
96 VideoFrameResolutionAdapter::VideoFrameResolutionAdapter( | |
97 scoped_refptr<base::MessageLoopProxy> render_message_loop, | |
98 int max_width, | |
99 int max_height, | |
100 double min_aspect_ratio, | |
101 double max_aspect_ratio) | |
102 : render_message_loop_(render_message_loop), | |
103 max_frame_size_(max_width, max_height), | |
104 min_aspect_ratio_(min_aspect_ratio), | |
105 max_aspect_ratio_(max_aspect_ratio) { | |
106 DCHECK(render_message_loop_); | |
tommi (sloooow) - chröme
2014/05/14 09:43:38
w00t! dcheck party!
(it's a good thing)
perkj_chrome
2014/05/14 13:29:44
Done.
| |
107 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
108 DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_); | |
109 CHECK_NE(0, max_aspect_ratio_); | |
110 DVLOG(3) << "VideoFrameResolutionAdapter(" | |
111 << "{ max_width =" << max_width << "}, " | |
112 << "{ max_height =" << max_height << "}, " | |
113 << "{ min_aspect_ratio =" << min_aspect_ratio << "}, " | |
114 << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}) "; | |
115 } | |
116 | |
117 VideoTrackAdapter:: | |
118 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() { | |
119 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
120 DCHECK(callbacks_.empty()); | |
121 } | |
122 | |
123 void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame( | |
124 const scoped_refptr<media::VideoFrame>& frame, | |
125 const media::VideoCaptureFormat& format) { | |
126 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
127 // TODO(perkj): Allow cropping / scaling of textures once | |
128 // http://crbug/362521 is fixed. | |
129 if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { | |
130 DoDeliverFrame(frame, format); | |
131 return; | |
132 } | |
133 scoped_refptr<media::VideoFrame> video_frame(frame); | |
134 double input_ratio = | |
135 static_cast<double>(frame->natural_size().width()) / | |
136 frame->natural_size().height(); | |
137 | |
138 // If |frame| has larger width or height than requested, or the aspect ratio | |
139 // does not match the requested, we want to create a wrapped version of this | |
140 // frame with a size that fulfills the constraints. | |
141 if (frame->natural_size().width() > max_frame_size_.width() || | |
142 frame->natural_size().height() > max_frame_size_.height() || | |
143 input_ratio > max_aspect_ratio_ || | |
144 input_ratio < min_aspect_ratio_) { | |
145 int desired_width = std::min(max_frame_size_.width(), | |
146 frame->natural_size().width()); | |
147 int desired_height = std::min(max_frame_size_.height(), | |
148 frame->natural_size().height()); | |
149 | |
150 double resulting_ratio = | |
151 static_cast<double>(desired_width) / desired_height; | |
152 double requested_ratio = resulting_ratio; | |
153 | |
154 if (requested_ratio > max_aspect_ratio_) | |
155 requested_ratio = max_aspect_ratio_; | |
156 else if (requested_ratio < min_aspect_ratio_) | |
157 requested_ratio = min_aspect_ratio_; | |
158 | |
159 if (resulting_ratio < requested_ratio) { | |
160 // Make sure we scale to an even height to avoid rounding errors | |
161 // (+ 1 & ~1). | |
162 desired_height = static_cast<int>((desired_height * resulting_ratio) / | |
163 requested_ratio) + 1 & ~1; | |
164 } else if (resulting_ratio > requested_ratio) { | |
165 // Make sure we scale to an even width to avoid rounding errors. | |
166 desired_width = static_cast<int>((desired_width * requested_ratio) / | |
167 resulting_ratio) + 1 & ~1; | |
168 } | |
169 | |
170 gfx::Size desired_size(desired_width, desired_height); | |
171 | |
172 // Get the largest centered rectangle with the same aspect ratio of | |
173 // |desired_size| that fits entirely inside of |frame->visible_rect()|. | |
174 // This will be the rect we need to crop the original frame to. | |
175 // From this rect, the original frame can be scaled down to |desired_size|. | |
176 gfx::Rect region_in_frame = | |
177 media::ComputeLetterboxRegion(frame->visible_rect(), desired_size); | |
178 | |
179 video_frame = media::VideoFrame::WrapVideoFrame( | |
180 frame, | |
181 region_in_frame, | |
182 desired_size, | |
183 base::Bind(&ReleaseOriginalFrame, frame)); | |
184 | |
185 DVLOG(3) << "desired size " << desired_size.ToString() | |
186 << " output natural size " | |
187 << video_frame->natural_size().ToString() | |
188 << " output visible rect " | |
189 << video_frame->visible_rect().ToString(); | |
190 } | |
191 DoDeliverFrame(video_frame, format); | |
192 } | |
193 | |
194 void VideoTrackAdapter:: | |
195 VideoFrameResolutionAdapter::DoDeliverFrame( | |
196 const scoped_refptr<media::VideoFrame>& frame, | |
197 const media::VideoCaptureFormat& format) { | |
198 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
199 for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin(); | |
200 it != callbacks_.end(); ++it) { | |
201 it->second.Run(frame, format); | |
202 } | |
203 } | |
204 | |
205 void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback( | |
206 const MediaStreamVideoTrack* track, | |
207 const VideoCaptureDeliverFrameCB& callback) { | |
208 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
209 callbacks_.push_back(std::make_pair(track, callback)); | |
210 } | |
211 | |
212 void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback( | |
213 const MediaStreamVideoTrack* track) { | |
214 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
215 std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin(); | |
216 for (; it != callbacks_.end(); ++it) { | |
217 if (it->first == track) { | |
218 // Callback is copied to heap and then deleted on the target thread. | |
219 // The following code ensures that the callback is not referenced on | |
220 // the stack. | |
221 scoped_ptr<VideoCaptureDeliverFrameCB> callback; | |
222 { | |
223 callback.reset(new VideoCaptureDeliverFrameCB(it->second)); | |
tommi (sloooow) - chröme
2014/05/14 09:43:38
add a comment for why this is done inside a scope.
perkj_chrome
2014/05/14 13:29:44
You are right. Since the object is on the heap
| |
224 callbacks_.erase(it); | |
225 } | |
226 render_message_loop_->PostTask( | |
227 FROM_HERE, base::Bind(&ResetCallbackOnMainRenderThread, | |
228 base::Passed(&callback))); | |
229 | |
230 return; | |
231 } | |
232 } | |
233 } | |
234 | |
235 bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch( | |
236 int max_width, | |
237 int max_height, | |
238 double min_aspect_ratio, | |
239 double max_aspect_ratio) const { | |
240 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
241 return max_frame_size_.width() == max_width && | |
242 max_frame_size_.height() == max_height && | |
243 min_aspect_ratio_ == min_aspect_ratio && | |
244 max_aspect_ratio_ == max_aspect_ratio; | |
245 } | |
246 | |
247 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const { | |
248 DCHECK(io_thread_checker_.CalledOnValidThread()); | |
249 return callbacks_.empty(); | |
250 } | |
251 | |
252 VideoTrackAdapter::VideoTrackAdapter( | |
253 const scoped_refptr<base::MessageLoopProxy>& io_message_loop) | |
254 : io_message_loop_(io_message_loop) { | |
255 DCHECK(io_message_loop_); | |
256 io_message_loop_->PostTask( | |
257 FROM_HERE, | |
258 base::Bind(&VideoTrackAdapter::SetRenderMessageLoopOnIo, | |
259 this, base::MessageLoopProxy::current())); | |
260 } | |
261 | |
262 VideoTrackAdapter::~VideoTrackAdapter() { | |
263 DCHECK(adapters_.empty()); | |
264 } | |
265 | |
266 void VideoTrackAdapter::SetRenderMessageLoopOnIo( | |
267 const scoped_refptr<base::MessageLoopProxy>& renderer_message_loop) { | |
268 DCHECK(io_message_loop_->BelongsToCurrentThread()); | |
269 DCHECK(renderer_message_loop); | |
270 renderer_message_loop_ = renderer_message_loop; | |
271 } | |
272 | |
273 void VideoTrackAdapter::AddTrack(const MediaStreamVideoTrack* track, | |
274 VideoCaptureDeliverFrameCB frame_callback, | |
275 int max_width, | |
276 int max_height, | |
277 double min_aspect_ratio, | |
278 double max_aspect_ratio) { | |
279 DCHECK(thread_checker_.CalledOnValidThread()); | |
280 io_message_loop_->PostTask( | |
281 FROM_HERE, | |
282 base::Bind(&VideoTrackAdapter::AddTrackOnIO, | |
283 this, track, frame_callback, max_width, max_height, | |
284 min_aspect_ratio, max_aspect_ratio)); | |
285 } | |
286 | |
287 void VideoTrackAdapter::AddTrackOnIO( | |
288 const MediaStreamVideoTrack* track, | |
289 VideoCaptureDeliverFrameCB frame_callback, | |
290 int max_width, | |
291 int max_height, | |
292 double min_aspect_ratio, | |
293 double max_aspect_ratio) { | |
294 DCHECK(io_message_loop_->BelongsToCurrentThread()); | |
295 scoped_refptr<VideoFrameResolutionAdapter> adapter; | |
296 for (FrameAdapters::const_iterator it = adapters_.begin(); | |
297 it != adapters_.end(); ++it) { | |
298 if ((*it)->ConstraintsMatch(max_width, max_height, min_aspect_ratio, | |
299 max_aspect_ratio)) { | |
300 adapter = it->get(); | |
301 break; | |
302 } | |
303 } | |
304 if (!adapter) { | |
305 adapter = new VideoFrameResolutionAdapter(renderer_message_loop_, | |
306 max_width, | |
307 max_height, | |
308 min_aspect_ratio, | |
309 max_aspect_ratio); | |
310 adapters_.push_back(adapter); | |
311 } | |
312 | |
313 adapter->AddCallback(track, frame_callback); | |
314 } | |
315 | |
316 void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) { | |
317 DCHECK(thread_checker_.CalledOnValidThread()); | |
318 io_message_loop_->PostTask( | |
319 FROM_HERE, | |
320 base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track)); | |
321 } | |
322 | |
323 void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) { | |
324 DCHECK(io_message_loop_->BelongsToCurrentThread()); | |
325 for (FrameAdapters::iterator it = adapters_.begin(); | |
326 it != adapters_.end(); ++it) { | |
327 (*it)->RemoveCallback(track); | |
328 if ((*it)->IsEmpty()) { | |
329 adapters_.erase(it); | |
330 break; | |
331 } | |
332 } | |
333 } | |
334 | |
335 void VideoTrackAdapter::DeliverFrameOnIO( | |
336 const scoped_refptr<media::VideoFrame>& frame, | |
337 const media::VideoCaptureFormat& format) { | |
338 DCHECK(io_message_loop_->BelongsToCurrentThread()); | |
339 TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO"); | |
340 for (FrameAdapters::iterator it = adapters_.begin(); | |
341 it != adapters_.end(); ++it) { | |
342 (*it)->DeliverFrame(frame, format); | |
343 } | |
344 } | |
345 | |
346 } // namespace content | |
OLD | NEW |