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 "base/metrics/histogram.h" 15 #include "media/base/bind_to_current_loop.h" 16 #include "media/base/video_util.h" 17 18 namespace content { 19 20 namespace { 21 22 // Amount of frame intervals to wait before considering the source as muted, for 23 // the first frame and under normal conditions, respectively. First frame might 24 // take longer to arrive due to source startup. 25 const float kFirstFrameTimeoutInFrameIntervals = 100.0f; 26 const float kNormalFrameTimeoutInFrameIntervals = 25.0f; 27 28 // Min delta time between two frames allowed without being dropped if a max 29 // frame rate is specified. 30 const int kMinTimeInMsBetweenFrames = 5; 31 32 // Empty method used for keeping a reference to the original media::VideoFrame 33 // in VideoFrameResolutionAdapter::DeliverFrame if cropping is needed. 34 // The reference to |frame| is kept in the closure that calls this method. 35 void ReleaseOriginalFrame( 36 const scoped_refptr<media::VideoFrame>& frame) { 37 } 38 39 void ResetCallbackOnMainRenderThread( 40 scoped_ptr<VideoCaptureDeliverFrameCB> callback) { 41 // |callback| will be deleted when this exits. 42 } 43 44 } // anonymous namespace 45 46 // VideoFrameResolutionAdapter is created on and lives on 47 // on the IO-thread. It does the resolution adaptation and delivers frames to 48 // all registered tracks on the IO-thread. 49 // All method calls must be on the IO-thread. 50 class VideoTrackAdapter::VideoFrameResolutionAdapter 51 : public base::RefCountedThreadSafe<VideoFrameResolutionAdapter> { 52 public: 53 VideoFrameResolutionAdapter( 54 scoped_refptr<base::SingleThreadTaskRunner> render_message_loop, 55 const gfx::Size& max_size, 56 double min_aspect_ratio, 57 double max_aspect_ratio, 58 double max_frame_rate); 59 60 // Add |callback| to receive video frames on the IO-thread. 61 // |callback| will however be released on the main render thread. 62 void AddCallback(const MediaStreamVideoTrack* track, 63 const VideoCaptureDeliverFrameCB& callback); 64 65 // Removes |callback| associated with |track| from receiving video frames if 66 // |track| has been added. It is ok to call RemoveCallback even if the |track| 67 // has not been added. The |callback| is released on the main render thread. 68 void RemoveCallback(const MediaStreamVideoTrack* track); 69 70 void DeliverFrame(const scoped_refptr<media::VideoFrame>& frame, 71 const media::VideoCaptureFormat& format, 72 const base::TimeTicks& estimated_capture_time); 73 74 // Returns true if all arguments match with the output of this adapter. 75 bool ConstraintsMatch(const gfx::Size& max_size, 76 double min_aspect_ratio, 77 double max_aspect_ratio, 78 double max_frame_rate) const; 79 80 bool IsEmpty() const; 81 82 private: 83 virtual ~VideoFrameResolutionAdapter(); 84 friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>; 85 86 virtual void DoDeliverFrame( 87 const scoped_refptr<media::VideoFrame>& frame, 88 const media::VideoCaptureFormat& format, 89 const base::TimeTicks& estimated_capture_time); 90 91 // Returns |true| if the input frame rate is higher that the requested max 92 // frame rate and |frame| should be dropped. 93 bool MaybeDropFrame(const scoped_refptr<media::VideoFrame>& frame, 94 float source_frame_rate); 95 96 // Bound to the IO-thread. 97 base::ThreadChecker io_thread_checker_; 98 99 // The task runner where we will release VideoCaptureDeliverFrameCB 100 // registered in AddCallback. 101 scoped_refptr<base::SingleThreadTaskRunner> renderer_task_runner_; 102 103 gfx::Size max_frame_size_; 104 double min_aspect_ratio_; 105 double max_aspect_ratio_; 106 107 double frame_rate_; 108 base::TimeDelta last_time_stamp_; 109 double max_frame_rate_; 110 double keep_frame_counter_; 111 112 typedef std::pair<const void*, VideoCaptureDeliverFrameCB> 113 VideoIdCallbackPair; 114 std::vector<VideoIdCallbackPair> callbacks_; 115 116 DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter); 117 }; 118 119 VideoTrackAdapter:: 120 VideoFrameResolutionAdapter::VideoFrameResolutionAdapter( 121 scoped_refptr<base::SingleThreadTaskRunner> render_message_loop, 122 const gfx::Size& max_size, 123 double min_aspect_ratio, 124 double max_aspect_ratio, 125 double max_frame_rate) 126 : renderer_task_runner_(render_message_loop), 127 max_frame_size_(max_size), 128 min_aspect_ratio_(min_aspect_ratio), 129 max_aspect_ratio_(max_aspect_ratio), 130 frame_rate_(MediaStreamVideoSource::kDefaultFrameRate), 131 max_frame_rate_(max_frame_rate), 132 keep_frame_counter_(0.0f) { 133 DCHECK(renderer_task_runner_.get()); 134 DCHECK(io_thread_checker_.CalledOnValidThread()); 135 DCHECK_GE(max_aspect_ratio_, min_aspect_ratio_); 136 CHECK_NE(0, max_aspect_ratio_); 137 DVLOG(3) << "VideoFrameResolutionAdapter(" 138 << "{ max_width =" << max_frame_size_.width() << "}, " 139 << "{ max_height =" << max_frame_size_.height() << "}, " 140 << "{ min_aspect_ratio =" << min_aspect_ratio << "}, " 141 << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}" 142 << "{ max_frame_rate_ =" << max_frame_rate_ << "}) "; 143 } 144 145 VideoTrackAdapter:: 146 VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() { 147 DCHECK(io_thread_checker_.CalledOnValidThread()); 148 DCHECK(callbacks_.empty()); 149 } 150 151 void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame( 152 const scoped_refptr<media::VideoFrame>& frame, 153 const media::VideoCaptureFormat& format, 154 const base::TimeTicks& estimated_capture_time) { 155 DCHECK(io_thread_checker_.CalledOnValidThread()); 156 157 if (MaybeDropFrame(frame, format.frame_rate)) 158 return; 159 160 // TODO(perkj): Allow cropping / scaling of textures once 161 // http://crbug/362521 is fixed. 162 if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { 163 DoDeliverFrame(frame, format, estimated_capture_time); 164 return; 165 } 166 scoped_refptr<media::VideoFrame> video_frame(frame); 167 double input_ratio = 168 static_cast<double>(frame->natural_size().width()) / 169 frame->natural_size().height(); 170 171 // If |frame| has larger width or height than requested, or the aspect ratio 172 // does not match the requested, we want to create a wrapped version of this 173 // frame with a size that fulfills the constraints. 174 if (frame->natural_size().width() > max_frame_size_.width() || 175 frame->natural_size().height() > max_frame_size_.height() || 176 input_ratio > max_aspect_ratio_ || 177 input_ratio < min_aspect_ratio_) { 178 int desired_width = std::min(max_frame_size_.width(), 179 frame->natural_size().width()); 180 int desired_height = std::min(max_frame_size_.height(), 181 frame->natural_size().height()); 182 183 double resulting_ratio = 184 static_cast<double>(desired_width) / desired_height; 185 double requested_ratio = resulting_ratio; 186 187 if (requested_ratio > max_aspect_ratio_) 188 requested_ratio = max_aspect_ratio_; 189 else if (requested_ratio < min_aspect_ratio_) 190 requested_ratio = min_aspect_ratio_; 191 192 if (resulting_ratio < requested_ratio) { 193 desired_height = static_cast<int>((desired_height * resulting_ratio) / 194 requested_ratio); 195 // Make sure we scale to an even height to avoid rounding errors 196 desired_height = (desired_height + 1) & ~1; 197 } else if (resulting_ratio > requested_ratio) { 198 desired_width = static_cast<int>((desired_width * requested_ratio) / 199 resulting_ratio); 200 // Make sure we scale to an even width to avoid rounding errors. 201 desired_width = (desired_width + 1) & ~1; 202 } 203 204 gfx::Size desired_size(desired_width, desired_height); 205 206 // Get the largest centered rectangle with the same aspect ratio of 207 // |desired_size| that fits entirely inside of |frame->visible_rect()|. 208 // This will be the rect we need to crop the original frame to. 209 // From this rect, the original frame can be scaled down to |desired_size|. 210 gfx::Rect region_in_frame = 211 media::ComputeLetterboxRegion(frame->visible_rect(), desired_size); 212 213 video_frame = media::VideoFrame::WrapVideoFrame( 214 frame, 215 region_in_frame, 216 desired_size, 217 base::Bind(&ReleaseOriginalFrame, frame)); 218 219 DVLOG(3) << "desired size " << desired_size.ToString() 220 << " output natural size " 221 << video_frame->natural_size().ToString() 222 << " output visible rect " 223 << video_frame->visible_rect().ToString(); 224 } 225 DoDeliverFrame(video_frame, format, estimated_capture_time); 226 } 227 228 bool VideoTrackAdapter::VideoFrameResolutionAdapter::MaybeDropFrame( 229 const scoped_refptr<media::VideoFrame>& frame, 230 float source_frame_rate) { 231 DCHECK(io_thread_checker_.CalledOnValidThread()); 232 233 // Do not drop frames if max frame rate hasn't been specified or the source 234 // frame rate is known and is lower than max. 235 if (max_frame_rate_ == 0.0f || 236 (source_frame_rate > 0 && 237 source_frame_rate <= max_frame_rate_)) { 238 return false; 239 } 240 241 base::TimeDelta delta = frame->timestamp() - last_time_stamp_; 242 if (delta.InMilliseconds() < kMinTimeInMsBetweenFrames) { 243 // We have seen video frames being delivered from camera devices back to 244 // back. The simple AR filter for frame rate calculation is too short to 245 // handle that. http://crbug/394315 246 // TODO(perkj): Can we come up with a way to fix the times stamps and the 247 // timing when frames are delivered so all frames can be used? 248 // The time stamps are generated by Chrome and not the actual device. 249 // Most likely the back to back problem is caused by software and not the 250 // actual camera. 251 DVLOG(3) << "Drop frame since delta time since previous frame is " 252 << delta.InMilliseconds() << "ms."; 253 return true; 254 } 255 last_time_stamp_ = frame->timestamp(); 256 if (delta == last_time_stamp_) // First received frame. 257 return false; 258 // Calculate the frame rate using a simple AR filter. 259 // Use a simple filter with 0.1 weight of the current sample. 260 frame_rate_ = 100 / delta.InMillisecondsF() + 0.9 * frame_rate_; 261 262 // Prefer to not drop frames. 263 if (max_frame_rate_ + 0.5f > frame_rate_) 264 return false; // Keep this frame. 265 266 // The input frame rate is higher than requested. 267 // Decide if we should keep this frame or drop it. 268 keep_frame_counter_ += max_frame_rate_ / frame_rate_; 269 if (keep_frame_counter_ >= 1) { 270 keep_frame_counter_ -= 1; 271 // Keep the frame. 272 return false; 273 } 274 DVLOG(3) << "Drop frame. Input frame_rate_ " << frame_rate_ << "."; 275 return true; 276 } 277 278 void VideoTrackAdapter:: 279 VideoFrameResolutionAdapter::DoDeliverFrame( 280 const scoped_refptr<media::VideoFrame>& frame, 281 const media::VideoCaptureFormat& format, 282 const base::TimeTicks& estimated_capture_time) { 283 DCHECK(io_thread_checker_.CalledOnValidThread()); 284 for (std::vector<VideoIdCallbackPair>::const_iterator it = callbacks_.begin(); 285 it != callbacks_.end(); ++it) { 286 it->second.Run(frame, format, estimated_capture_time); 287 } 288 } 289 290 void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback( 291 const MediaStreamVideoTrack* track, 292 const VideoCaptureDeliverFrameCB& callback) { 293 DCHECK(io_thread_checker_.CalledOnValidThread()); 294 callbacks_.push_back(std::make_pair(track, callback)); 295 } 296 297 void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback( 298 const MediaStreamVideoTrack* track) { 299 DCHECK(io_thread_checker_.CalledOnValidThread()); 300 std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin(); 301 for (; it != callbacks_.end(); ++it) { 302 if (it->first == track) { 303 // Make sure the VideoCaptureDeliverFrameCB is released on the main 304 // render thread since it was added on the main render thread in 305 // VideoTrackAdapter::AddTrack. 306 scoped_ptr<VideoCaptureDeliverFrameCB> callback( 307 new VideoCaptureDeliverFrameCB(it->second)); 308 callbacks_.erase(it); 309 renderer_task_runner_->PostTask( 310 FROM_HERE, base::Bind(&ResetCallbackOnMainRenderThread, 311 base::Passed(&callback))); 312 313 return; 314 } 315 } 316 } 317 318 bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch( 319 const gfx::Size& max_size, 320 double min_aspect_ratio, 321 double max_aspect_ratio, 322 double max_frame_rate) const { 323 DCHECK(io_thread_checker_.CalledOnValidThread()); 324 return max_frame_size_ == max_size && 325 min_aspect_ratio_ == min_aspect_ratio && 326 max_aspect_ratio_ == max_aspect_ratio && 327 max_frame_rate_ == max_frame_rate; 328 } 329 330 bool VideoTrackAdapter::VideoFrameResolutionAdapter::IsEmpty() const { 331 DCHECK(io_thread_checker_.CalledOnValidThread()); 332 return callbacks_.empty(); 333 } 334 335 VideoTrackAdapter::VideoTrackAdapter( 336 const scoped_refptr<base::MessageLoopProxy>& io_message_loop) 337 : io_message_loop_(io_message_loop), 338 renderer_task_runner_(base::MessageLoopProxy::current()), 339 monitoring_frame_rate_(false), 340 muted_state_(false), 341 frame_counter_(0), 342 source_frame_rate_(0.0f) { 343 DCHECK(io_message_loop_.get()); 344 } 345 346 VideoTrackAdapter::~VideoTrackAdapter() { 347 DCHECK(adapters_.empty()); 348 } 349 350 void VideoTrackAdapter::AddTrack( 351 const MediaStreamVideoTrack* track, 352 VideoCaptureDeliverFrameCB frame_callback, 353 int max_width, 354 int max_height, 355 double min_aspect_ratio, 356 double max_aspect_ratio, 357 double max_frame_rate) { 358 DCHECK(thread_checker_.CalledOnValidThread()); 359 360 io_message_loop_->PostTask( 361 FROM_HERE, 362 base::Bind(&VideoTrackAdapter::AddTrackOnIO, 363 this, track, frame_callback, gfx::Size(max_width, max_height), 364 min_aspect_ratio, max_aspect_ratio, max_frame_rate)); 365 } 366 367 void VideoTrackAdapter::AddTrackOnIO( 368 const MediaStreamVideoTrack* track, 369 VideoCaptureDeliverFrameCB frame_callback, 370 const gfx::Size& max_frame_size, 371 double min_aspect_ratio, 372 double max_aspect_ratio, 373 double max_frame_rate) { 374 DCHECK(io_message_loop_->BelongsToCurrentThread()); 375 scoped_refptr<VideoFrameResolutionAdapter> adapter; 376 for (FrameAdapters::const_iterator it = adapters_.begin(); 377 it != adapters_.end(); ++it) { 378 if ((*it)->ConstraintsMatch(max_frame_size, min_aspect_ratio, 379 max_aspect_ratio, max_frame_rate)) { 380 adapter = it->get(); 381 break; 382 } 383 } 384 if (!adapter.get()) { 385 adapter = new VideoFrameResolutionAdapter(renderer_task_runner_, 386 max_frame_size, 387 min_aspect_ratio, 388 max_aspect_ratio, 389 max_frame_rate); 390 adapters_.push_back(adapter); 391 } 392 393 adapter->AddCallback(track, frame_callback); 394 } 395 396 void VideoTrackAdapter::RemoveTrack(const MediaStreamVideoTrack* track) { 397 DCHECK(thread_checker_.CalledOnValidThread()); 398 io_message_loop_->PostTask( 399 FROM_HERE, 400 base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track)); 401 } 402 403 void VideoTrackAdapter::StartFrameMonitoring( 404 double source_frame_rate, 405 const OnMutedCallback& on_muted_callback) { 406 DCHECK(thread_checker_.CalledOnValidThread()); 407 408 VideoTrackAdapter::OnMutedCallback bound_on_muted_callback = 409 media::BindToCurrentLoop(on_muted_callback); 410 411 io_message_loop_->PostTask( 412 FROM_HERE, 413 base::Bind(&VideoTrackAdapter::StartFrameMonitoringOnIO, 414 this, bound_on_muted_callback, source_frame_rate)); 415 } 416 417 void VideoTrackAdapter::StartFrameMonitoringOnIO( 418 const OnMutedCallback& on_muted_callback, 419 double source_frame_rate) { 420 DCHECK(io_message_loop_->BelongsToCurrentThread()); 421 DCHECK(!monitoring_frame_rate_); 422 423 monitoring_frame_rate_ = true; 424 425 // If the source does not know the frame rate, set one by default. 426 if (source_frame_rate == 0.0f) 427 source_frame_rate = MediaStreamVideoSource::kDefaultFrameRate; 428 source_frame_rate_ = source_frame_rate; 429 DVLOG(1) << "Monitoring frame creation, first (large) delay: " 430 << (kFirstFrameTimeoutInFrameIntervals / source_frame_rate_) << "s"; 431 io_message_loop_->PostDelayedTask(FROM_HERE, 432 base::Bind(&VideoTrackAdapter::CheckFramesReceivedOnIO, this, 433 on_muted_callback, frame_counter_), 434 base::TimeDelta::FromSecondsD(kFirstFrameTimeoutInFrameIntervals / 435 source_frame_rate_)); 436 } 437 438 void VideoTrackAdapter::StopFrameMonitoring() { 439 DCHECK(thread_checker_.CalledOnValidThread()); 440 io_message_loop_->PostTask( 441 FROM_HERE, 442 base::Bind(&VideoTrackAdapter::StopFrameMonitoringOnIO, this)); 443 } 444 445 void VideoTrackAdapter::StopFrameMonitoringOnIO() { 446 DCHECK(io_message_loop_->BelongsToCurrentThread()); 447 monitoring_frame_rate_ = false; 448 } 449 450 void VideoTrackAdapter::RemoveTrackOnIO(const MediaStreamVideoTrack* track) { 451 DCHECK(io_message_loop_->BelongsToCurrentThread()); 452 for (FrameAdapters::iterator it = adapters_.begin(); 453 it != adapters_.end(); ++it) { 454 (*it)->RemoveCallback(track); 455 if ((*it)->IsEmpty()) { 456 adapters_.erase(it); 457 break; 458 } 459 } 460 } 461 462 void VideoTrackAdapter::DeliverFrameOnIO( 463 const scoped_refptr<media::VideoFrame>& frame, 464 const media::VideoCaptureFormat& format, 465 const base::TimeTicks& estimated_capture_time) { 466 DCHECK(io_message_loop_->BelongsToCurrentThread()); 467 TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO"); 468 ++frame_counter_; 469 for (FrameAdapters::iterator it = adapters_.begin(); 470 it != adapters_.end(); ++it) { 471 (*it)->DeliverFrame(frame, format, estimated_capture_time); 472 } 473 } 474 475 void VideoTrackAdapter::CheckFramesReceivedOnIO( 476 const OnMutedCallback& set_muted_state_callback, 477 uint64 old_frame_counter_snapshot) { 478 DCHECK(io_message_loop_->BelongsToCurrentThread()); 479 480 if (!monitoring_frame_rate_) 481 return; 482 483 DVLOG_IF(1, old_frame_counter_snapshot == frame_counter_) 484 << "No frames have passed, setting source as Muted."; 485 486 bool muted_state = old_frame_counter_snapshot == frame_counter_; 487 if (muted_state_ != muted_state) { 488 set_muted_state_callback.Run(muted_state); 489 muted_state_ = muted_state; 490 } 491 492 io_message_loop_->PostDelayedTask(FROM_HERE, 493 base::Bind(&VideoTrackAdapter::CheckFramesReceivedOnIO, this, 494 set_muted_state_callback, frame_counter_), 495 base::TimeDelta::FromSecondsD(kNormalFrameTimeoutInFrameIntervals / 496 source_frame_rate_)); 497 } 498 499 } // namespace content 500