Index: content/renderer/media/video_track_adapter.cc |
diff --git a/content/renderer/media/video_track_adapter.cc b/content/renderer/media/video_track_adapter.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d9fa99a3a02b38e4d11171b22e1a2e5c5a9053ce |
--- /dev/null |
+++ b/content/renderer/media/video_track_adapter.cc |
@@ -0,0 +1,292 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/renderer/media/video_track_adapter.h" |
+ |
+#include <algorithm> |
+#include <limits> |
+#include <utility> |
+ |
+#include "base/bind.h" |
+#include "base/debug/trace_event.h" |
+#include "base/location.h" |
+#include "media/base/video_util.h" |
+ |
+namespace { |
+ |
+// Empty method used for keeping a reference to the original media::VideoFrame |
+// in VideoFrameResolutionAdapter::DeliverFrame if cropping is needed. |
+// The reference to |frame| is kept in the closure that calls this method. |
+void ReleaseOriginalFrame( |
+ const scoped_refptr<media::VideoFrame>& frame) { |
+} |
+ |
+} // anonymous namespace |
+ |
+namespace content { |
+ |
+// VideoFrameResolutionAdapter is created on the main |
+// render thread but operates on the IO-thread. It does the resolution |
+// adaptation and delivers frames to all registered tracks on the IO-thread. |
mcasas
2014/05/07 14:10:43
This threading detail should go in the header file
perkj_chrome
2014/05/08 11:29:47
This class only exist as an internal class. So doc
mcasas
2014/05/09 07:46:24
After offline discussion, I buy the classes' divis
|
+// All method calls except creation, must be on the IO-thread. |
mcasas
2014/05/07 14:10:43
nit: double space after "must be"
perkj_chrome
2014/05/08 11:29:47
Done.
|
+class VideoTrackAdapter::VideoFrameResolutionAdapter |
+ : public base::RefCountedThreadSafe<VideoFrameResolutionAdapter> { |
+ public: |
+ VideoFrameResolutionAdapter( |
+ int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio); |
+ |
+ // Add |callback| to receive video frames on the IO-thread. |
+ void AddCallback(MediaStreamVideoTrack* track, |
+ const VideoCaptureDeliverFrameCB& callback); |
+ |
+ // Removes |callback| associated with |id| from receiving video frames if |
+ // |track| has been added. It is ok to call RemoveCallback even if the |id| |
+ // has not been added. |
+ void RemoveCallback(MediaStreamVideoTrack* track); |
+ |
+ virtual void DeliverFrame( |
+ const scoped_refptr<media::VideoFrame>& frame, |
+ const media::VideoCaptureFormat& format); |
+ |
+ // Returns true if all arguments match with the output of this adapter. |
+ bool ConstraintsMatch(int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio) const; |
+ |
+ bool IsEmpty() const { return callbacks_.empty(); } |
+ |
+ private: |
+ virtual ~VideoFrameResolutionAdapter(); |
+ friend class base::RefCountedThreadSafe<VideoFrameResolutionAdapter>; |
+ |
+ virtual void DoDeliverFrame( |
+ const scoped_refptr<media::VideoFrame>& frame, |
+ const media::VideoCaptureFormat& format); |
+ |
+ // Bound to the IO-thread. |
+ base::ThreadChecker io_thread_checker_; |
+ |
+ gfx::Size max_frame_size_; |
+ double min_aspect_ratio_; |
+ double max_aspect_ratio_; |
+ |
+ typedef std::pair<void*, VideoCaptureDeliverFrameCB> VideoIdCallbackPair; |
+ std::vector<VideoIdCallbackPair> callbacks_; |
mcasas
2014/05/07 14:10:43
std::list? Only used for push_back() and iterator-
perkj_chrome
2014/05/08 11:29:47
dito.
|
+ |
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameResolutionAdapter); |
+}; |
+ |
+VideoTrackAdapter:: |
+VideoFrameResolutionAdapter::VideoFrameResolutionAdapter( |
+ int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio) |
+ : max_frame_size_(max_width, max_height), |
+ min_aspect_ratio_(min_aspect_ratio), |
+ max_aspect_ratio_(max_aspect_ratio) { |
+ |
+ DVLOG(3) << "VideoFrameResolutionAdapter(" |
+ << "{ max_width =" << max_width << "}, " |
+ << "{ max_height =" << max_height << "}, " |
+ << "{ min_aspect_ratio =" << min_aspect_ratio << "}, " |
+ << "{ max_aspect_ratio_ =" << max_aspect_ratio_ << "}) "; |
+} |
+ |
+VideoTrackAdapter:: |
+VideoFrameResolutionAdapter::~VideoFrameResolutionAdapter() { |
+ DCHECK(callbacks_.empty()); |
+} |
+ |
+void VideoTrackAdapter::VideoFrameResolutionAdapter::DeliverFrame( |
+ const scoped_refptr<media::VideoFrame>& frame, |
+ const media::VideoCaptureFormat& format) { |
+ DCHECK(io_thread_checker_.CalledOnValidThread()); |
+ // TODO(perkj): Allow cropping / scaling of textures once |
+ // http://crbug/362521 is fixed. |
+ if (frame->format() == media::VideoFrame::NATIVE_TEXTURE) { |
+ DoDeliverFrame(frame, format); |
+ return; |
+ } |
+ scoped_refptr<media::VideoFrame> video_frame(frame); |
mcasas
2014/05/07 14:10:43
Move to l.152?
perkj_chrome
2014/05/08 11:29:47
line 169 DoDeliverFrame(video_frame, format) shou
|
+ double input_ratio = |
+ static_cast<double>(frame->natural_size().width()) / |
+ frame->natural_size().height(); |
+ |
+ if (frame->natural_size().width() > max_frame_size_.width() || |
mcasas
2014/05/07 14:10:43
Perhaps add comment here? Like: "If the input |fra
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ frame->natural_size().height() > max_frame_size_.height() || |
+ input_ratio > max_aspect_ratio_ || |
+ input_ratio < min_aspect_ratio_) { |
+ int desired_width = std::min(max_frame_size_.width(), |
mcasas
2014/05/07 14:10:43
I'd love to see some human-parseable comment on th
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ frame->natural_size().width()); |
+ int desired_height = std::min(max_frame_size_.height(), |
+ frame->natural_size().height()); |
+ |
+ double resulting_ratio = |
+ static_cast<double>(desired_width) / desired_height; |
+ double requested_ratio = resulting_ratio; |
+ |
+ if (requested_ratio > max_aspect_ratio_) |
+ requested_ratio = max_aspect_ratio_; |
+ |
+ if (requested_ratio < min_aspect_ratio_) |
mcasas
2014/05/07 14:10:43
else if? |requested_ratio_| should not be larger t
perkj_chrome
2014/05/08 11:29:47
done in ctor
|
+ requested_ratio = min_aspect_ratio_; |
+ |
+ if (resulting_ratio < requested_ratio) { |
+ desired_height = static_cast<int>((desired_height * resulting_ratio) / |
+ requested_ratio) + 1 & ~1; |
+ } else if (resulting_ratio > requested_ratio) { |
+ desired_width = static_cast<int>((desired_width * requested_ratio) / |
+ resulting_ratio) + 1 & ~1; |
+ } |
+ |
+ gfx::Size desired_size(desired_width, desired_height); |
+ gfx::Rect region_in_frame = |
+ media::ComputeLetterboxRegion(frame->visible_rect(), desired_size); |
+ |
+ video_frame = media::VideoFrame::WrapVideoFrame( |
+ frame, |
+ region_in_frame, |
+ desired_size, |
+ base::Bind(&ReleaseOriginalFrame, frame)); |
+ |
+ DVLOG(3) << "desired width " << desired_width |
+ << " desired height " << desired_height |
+ << " output natural width " |
+ << video_frame->natural_size().width() |
mcasas
2014/05/07 14:10:43
nit: video_frame->natural_size() has a ToString()
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ << " output natural height " |
+ << video_frame->natural_size().height() |
+ << " output visible rect width " |
+ << video_frame->visible_rect().width() |
+ << " output visible rect height " |
+ << video_frame->visible_rect().height(); |
+ } |
+ DoDeliverFrame(video_frame, format); |
+} |
+ |
+void VideoTrackAdapter:: |
+VideoFrameResolutionAdapter::DoDeliverFrame( |
+ const scoped_refptr<media::VideoFrame>& frame, |
+ const media::VideoCaptureFormat& format) { |
+ for (std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin(); |
mcasas
2014/05/07 14:10:43
const_iterator?
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ it != callbacks_.end(); ++it) { |
+ it->second.Run(frame, format); |
+ } |
+} |
+ |
+void VideoTrackAdapter::VideoFrameResolutionAdapter::AddCallback( |
+ MediaStreamVideoTrack* track, |
+ const VideoCaptureDeliverFrameCB& callback) { |
+ DCHECK(io_thread_checker_.CalledOnValidThread()); |
+ callbacks_.push_back(std::make_pair(track, callback)); |
+} |
+ |
+void VideoTrackAdapter::VideoFrameResolutionAdapter::RemoveCallback( |
+ MediaStreamVideoTrack* track) { |
+ DCHECK(io_thread_checker_.CalledOnValidThread()); |
+ std::vector<VideoIdCallbackPair>::iterator it = callbacks_.begin(); |
+ for (; it != callbacks_.end(); ++it) { |
+ if (it->first == track) { |
+ callbacks_.erase(it); |
+ return; |
+ } |
+ } |
+} |
+ |
+bool VideoTrackAdapter::VideoFrameResolutionAdapter::ConstraintsMatch( |
+ int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio) const { |
+ return max_frame_size_.width() == max_width && |
+ max_frame_size_.height() == max_height && |
+ min_aspect_ratio_ == min_aspect_ratio && |
+ max_aspect_ratio_ == max_aspect_ratio; |
+} |
+ |
+VideoTrackAdapter::VideoTrackAdapter( |
+ const scoped_refptr<base::MessageLoopProxy>& io_message_loop) |
+ : io_message_loop_(io_message_loop) { |
+} |
+ |
+VideoTrackAdapter::~VideoTrackAdapter() { |
+ DCHECK(adapters_.empty()); |
+} |
+ |
+void VideoTrackAdapter::AddTrack(MediaStreamVideoTrack* track, |
mcasas
2014/05/07 14:10:43
The naked pointer stands out. Could we at least qu
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ VideoCaptureDeliverFrameCB frame_callback, |
+ int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ io_message_loop_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&VideoTrackAdapter::AddTrackOnIO, |
+ this, track, frame_callback, max_width, max_height, |
+ min_aspect_ratio, max_aspect_ratio)); |
+} |
+ |
+void VideoTrackAdapter::AddTrackOnIO(MediaStreamVideoTrack* track, |
+ VideoCaptureDeliverFrameCB frame_callback, |
+ int max_width, |
+ int max_height, |
+ double min_aspect_ratio, |
+ double max_aspect_ratio) { |
+ DCHECK(io_message_loop_->BelongsToCurrentThread()); |
+ scoped_refptr<VideoFrameResolutionAdapter> adapter; |
+ for (FrameAdapters::iterator it = adapters_.begin(); it != adapters_.end(); |
mcasas
2014/05/07 14:10:43
const_iterator?
perkj_chrome
2014/05/08 11:29:47
Done.
perkj_chrome
2014/05/08 11:29:47
Done.
|
+ ++it) { |
+ if ((*it)->ConstraintsMatch(max_width, max_height, min_aspect_ratio, |
+ max_aspect_ratio)) { |
+ adapter = it->get(); |
+ break; |
+ } |
+ } |
+ if (!adapter) { |
+ adapter = new VideoFrameResolutionAdapter(max_width, |
+ max_height, |
+ min_aspect_ratio, |
+ max_aspect_ratio); |
+ adapters_.push_back(adapter); |
+ } |
+ |
+ adapter->AddCallback(track, frame_callback); |
+} |
+ |
+void VideoTrackAdapter::RemoveTrack(MediaStreamVideoTrack* track) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ io_message_loop_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&VideoTrackAdapter::RemoveTrackOnIO, this, track)); |
+} |
+ |
+void VideoTrackAdapter::RemoveTrackOnIO(MediaStreamVideoTrack* track) { |
+ DCHECK(io_message_loop_->BelongsToCurrentThread()); |
+ for (FrameAdapters::iterator it = adapters_.begin(); |
+ it != adapters_.end(); ++it) { |
+ (*it)->RemoveCallback(track); |
+ if ((*it)->IsEmpty()) { |
+ adapters_.erase(it); |
+ break; |
+ } |
+ } |
+} |
+ |
+void VideoTrackAdapter::DeliverFrameOnIO( |
+ const scoped_refptr<media::VideoFrame>& frame, |
+ const media::VideoCaptureFormat& format) { |
+ DCHECK(io_message_loop_->BelongsToCurrentThread()); |
+ TRACE_EVENT0("video", "VideoTrackAdapter::DeliverFrameOnIO"); |
+ for (FrameAdapters::iterator it = adapters_.begin(); |
+ it != adapters_.end(); ++it) { |
+ (*it)->DeliverFrame(frame, format); |
+ } |
+} |
+ |
+} // namespace content |