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. 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:: 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:: 120 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() { 121 DCHECK(io_thread_checker_.CalledOnValidThread()); 122 DCHECK(callbacks_.empty()); 123 } 124 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:: 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 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 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 238 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 250 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const { 251 DCHECK(io_thread_checker_.CalledOnValidThread()); 252 return callbacks_.empty(); 253 } 254 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 262 VideoTrackAdapter::~VideoTrackAdapter() { 263 DCHECK(adapters_.empty()); 264 } 265 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 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 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 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 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