| // Copyright 2015 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_capture_from_element/canvas_capture_handler.h" |
| |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/bind_helpers.h" |
| #include "base/macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "content/renderer/media/media_stream_video_capturer_source.h" |
| #include "content/renderer/media/media_stream_video_source.h" |
| #include "content/renderer/media/media_stream_video_track.h" |
| #include "content/renderer/media/webrtc_uma_histograms.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "media/base/limits.h" |
| #include "third_party/WebKit/public/platform/WebMediaStreamSource.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| |
| namespace { |
| |
| using media::VideoFrame; |
| |
| const size_t kArgbBytesPerPixel = 4; |
| |
| } // namespace |
| |
| namespace content { |
| |
| // Implementation VideoCapturerSource that is owned by |
| // MediaStreamVideoCapturerSource and delegates the Start/Stop calls to |
| // CanvasCaptureHandler. |
| // This class is single threaded and pinned to main render thread. |
| class VideoCapturerSource : public media::VideoCapturerSource { |
| public: |
| VideoCapturerSource(base::WeakPtr<CanvasCaptureHandler> canvas_handler, |
| double frame_rate) |
| : frame_rate_(static_cast<float>( |
| std::min(static_cast<double>(media::limits::kMaxFramesPerSecond), |
| frame_rate))), |
| canvas_handler_(canvas_handler) { |
| DCHECK_LE(0, frame_rate_); |
| } |
| |
| protected: |
| void GetCurrentSupportedFormats( |
| int max_requested_width, |
| int max_requested_height, |
| double max_requested_frame_rate, |
| const VideoCaptureDeviceFormatsCB& callback) override { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| const blink::WebSize& size = canvas_handler_->GetSourceSize(); |
| media::VideoCaptureFormats formats; |
| formats.push_back( |
| media::VideoCaptureFormat(gfx::Size(size.width, size.height), |
| frame_rate_, media::PIXEL_FORMAT_I420)); |
| formats.push_back( |
| media::VideoCaptureFormat(gfx::Size(size.width, size.height), |
| frame_rate_, media::PIXEL_FORMAT_YV12A)); |
| callback.Run(formats); |
| } |
| void StartCapture(const media::VideoCaptureParams& params, |
| const VideoCaptureDeliverFrameCB& frame_callback, |
| const RunningCallback& running_callback) override { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| canvas_handler_->StartVideoCapture(params, frame_callback, |
| running_callback); |
| } |
| void RequestRefreshFrame() override { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| canvas_handler_->RequestRefreshFrame(); |
| } |
| void StopCapture() override { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| if (canvas_handler_.get()) |
| canvas_handler_->StopVideoCapture(); |
| } |
| |
| private: |
| const float frame_rate_; |
| // Bound to Main Render thread. |
| base::ThreadChecker main_render_thread_checker_; |
| // CanvasCaptureHandler is owned by CanvasDrawListener in blink and might be |
| // destroyed before StopCapture() call. |
| base::WeakPtr<CanvasCaptureHandler> canvas_handler_; |
| }; |
| |
| class CanvasCaptureHandler::CanvasCaptureHandlerDelegate { |
| public: |
| explicit CanvasCaptureHandlerDelegate( |
| media::VideoCapturerSource::VideoCaptureDeliverFrameCB new_frame_callback) |
| : new_frame_callback_(new_frame_callback), |
| weak_ptr_factory_(this) { |
| io_thread_checker_.DetachFromThread(); |
| } |
| ~CanvasCaptureHandlerDelegate() { |
| DCHECK(io_thread_checker_.CalledOnValidThread()); |
| } |
| |
| void SendNewFrameOnIOThread( |
| const scoped_refptr<media::VideoFrame>& video_frame, |
| const base::TimeTicks& current_time) { |
| DCHECK(io_thread_checker_.CalledOnValidThread()); |
| new_frame_callback_.Run(video_frame, current_time); |
| } |
| |
| base::WeakPtr<CanvasCaptureHandlerDelegate> GetWeakPtrForIOThread() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| const media::VideoCapturerSource::VideoCaptureDeliverFrameCB |
| new_frame_callback_; |
| // Bound to IO thread. |
| base::ThreadChecker io_thread_checker_; |
| base::WeakPtrFactory<CanvasCaptureHandlerDelegate> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CanvasCaptureHandlerDelegate); |
| }; |
| |
| CanvasCaptureHandler::CanvasCaptureHandler( |
| const blink::WebSize& size, |
| double frame_rate, |
| const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, |
| blink::WebMediaStreamTrack* track) |
| : ask_for_new_frame_(false), |
| size_(size), |
| io_task_runner_(io_task_runner), |
| weak_ptr_factory_(this) { |
| std::unique_ptr<media::VideoCapturerSource> video_source( |
| new VideoCapturerSource(weak_ptr_factory_.GetWeakPtr(), frame_rate)); |
| AddVideoCapturerSourceToVideoTrack(std::move(video_source), track); |
| } |
| |
| CanvasCaptureHandler::~CanvasCaptureHandler() { |
| DVLOG(3) << __func__; |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()); |
| } |
| |
| // static |
| CanvasCaptureHandler* CanvasCaptureHandler::CreateCanvasCaptureHandler( |
| const blink::WebSize& size, |
| double frame_rate, |
| const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, |
| blink::WebMediaStreamTrack* track) { |
| // Save histogram data so we can see how much CanvasCapture is used. |
| // The histogram counts the number of calls to the JS API. |
| UpdateWebRTCMethodCount(WEBKIT_CANVAS_CAPTURE_STREAM); |
| |
| return new CanvasCaptureHandler(size, frame_rate, io_task_runner, track); |
| } |
| |
| void CanvasCaptureHandler::sendNewFrame(const SkImage* image) { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| CreateNewFrame(image); |
| } |
| |
| bool CanvasCaptureHandler::needsNewFrame() const { |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| return ask_for_new_frame_; |
| } |
| |
| void CanvasCaptureHandler::StartVideoCapture( |
| const media::VideoCaptureParams& params, |
| const media::VideoCapturerSource::VideoCaptureDeliverFrameCB& |
| new_frame_callback, |
| const media::VideoCapturerSource::RunningCallback& running_callback) { |
| DVLOG(3) << __func__ << " requested " |
| << media::VideoCaptureFormat::ToString(params.requested_format); |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| DCHECK(params.requested_format.IsValid()); |
| |
| // TODO(emircan): Accomodate to the given frame rate constraints here. |
| capture_format_ = params.requested_format; |
| delegate_.reset(new CanvasCaptureHandlerDelegate(new_frame_callback)); |
| DCHECK(delegate_); |
| ask_for_new_frame_ = true; |
| running_callback.Run(true); |
| } |
| |
| void CanvasCaptureHandler::RequestRefreshFrame() { |
| DVLOG(3) << __func__; |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| if (last_frame_ && delegate_) { |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&CanvasCaptureHandler::CanvasCaptureHandlerDelegate:: |
| SendNewFrameOnIOThread, |
| delegate_->GetWeakPtrForIOThread(), last_frame_, |
| base::TimeTicks::Now())); |
| } |
| } |
| |
| void CanvasCaptureHandler::StopVideoCapture() { |
| DVLOG(3) << __func__; |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| ask_for_new_frame_ = false; |
| io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()); |
| } |
| |
| void CanvasCaptureHandler::CreateNewFrame(const SkImage* image) { |
| DVLOG(4) << __func__; |
| DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| DCHECK(image); |
| |
| const gfx::Size size(image->width(), image->height()); |
| if (size != last_size) { |
| temp_data_stride_ = kArgbBytesPerPixel * size.width(); |
| temp_data_.resize(temp_data_stride_ * size.height()); |
| image_info_ = |
| SkImageInfo::Make(size.width(), size.height(), kBGRA_8888_SkColorType, |
| kUnpremul_SkAlphaType); |
| last_size = size; |
| } |
| |
| if (!image->readPixels(image_info_, &temp_data_[0], temp_data_stride_, 0, |
| 0)) { |
| DLOG(ERROR) << "Couldn't read SkImage pixels"; |
| return; |
| } |
| |
| const bool isOpaque = image->isOpaque(); |
| const base::TimeTicks timestamp = base::TimeTicks::Now(); |
| scoped_refptr<media::VideoFrame> video_frame = frame_pool_.CreateFrame( |
| isOpaque ? media::PIXEL_FORMAT_I420 : media::PIXEL_FORMAT_YV12A, size, |
| gfx::Rect(size), size, timestamp - base::TimeTicks()); |
| DCHECK(video_frame); |
| |
| libyuv::ARGBToI420(temp_data_.data(), temp_data_stride_, |
| video_frame->visible_data(media::VideoFrame::kYPlane), |
| video_frame->stride(media::VideoFrame::kYPlane), |
| video_frame->visible_data(media::VideoFrame::kUPlane), |
| video_frame->stride(media::VideoFrame::kUPlane), |
| video_frame->visible_data(media::VideoFrame::kVPlane), |
| video_frame->stride(media::VideoFrame::kVPlane), |
| size.width(), size.height()); |
| if (!isOpaque) { |
| libyuv::ARGBExtractAlpha(temp_data_.data(), temp_data_stride_, |
| video_frame->visible_data(VideoFrame::kAPlane), |
| video_frame->stride(VideoFrame::kAPlane), |
| size.width(), size.height()); |
| } |
| |
| last_frame_ = video_frame; |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&CanvasCaptureHandler::CanvasCaptureHandlerDelegate:: |
| SendNewFrameOnIOThread, |
| delegate_->GetWeakPtrForIOThread(), video_frame, timestamp)); |
| } |
| |
| void CanvasCaptureHandler::AddVideoCapturerSourceToVideoTrack( |
| std::unique_ptr<media::VideoCapturerSource> source, |
| blink::WebMediaStreamTrack* web_track) { |
| std::string str_track_id; |
| base::Base64Encode(base::RandBytesAsString(64), &str_track_id); |
| const blink::WebString track_id = blink::WebString::fromASCII(str_track_id); |
| blink::WebMediaStreamSource webkit_source; |
| std::unique_ptr<MediaStreamVideoSource> media_stream_source( |
| new MediaStreamVideoCapturerSource( |
| MediaStreamSource::SourceStoppedCallback(), std::move(source))); |
| webkit_source.initialize(track_id, blink::WebMediaStreamSource::TypeVideo, |
| track_id, false); |
| webkit_source.setExtraData(media_stream_source.get()); |
| |
| web_track->initialize(webkit_source); |
| blink::WebMediaConstraints constraints; |
| constraints.initialize(); |
| web_track->setTrackData(new MediaStreamVideoTrack( |
| media_stream_source.release(), constraints, |
| MediaStreamVideoSource::ConstraintsCallback(), true)); |
| } |
| |
| } // namespace content |