| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/mediacapturefromelement/canvas_capture_handler.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/rand_util.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "build/build_config.h" |
| #include "gpu/command_buffer/client/raster_interface.h" |
| #include "media/base/limits.h" |
| #include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h" |
| #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.h" |
| #include "third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h" |
| #include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h" |
| #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.h" |
| #include "third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h" |
| #include "third_party/blink/renderer/platform/mediastream/media_stream_source.h" |
| #include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/text/base64.h" |
| #include "ui/gfx/color_space.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace blink { |
| |
| // 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 CanvasVideoCapturerSource : public VideoCapturerSource { |
| public: |
| CanvasVideoCapturerSource(base::WeakPtr<CanvasCaptureHandler> canvas_handler, |
| const gfx::Size& size, |
| double frame_rate) |
| : size_(size), |
| frame_rate_(static_cast<float>( |
| std::min(static_cast<double>(media::limits::kMaxFramesPerSecond), |
| frame_rate))), |
| canvas_handler_(std::move(canvas_handler)) { |
| DCHECK_LE(0, frame_rate_); |
| } |
| |
| protected: |
| media::VideoCaptureFormats GetPreferredFormats() override { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| media::VideoCaptureFormats formats; |
| formats.push_back(media::VideoCaptureFormat(gfx::Size(size_), frame_rate_, |
| media::PIXEL_FORMAT_I420)); |
| formats.push_back(media::VideoCaptureFormat(gfx::Size(size_), frame_rate_, |
| media::PIXEL_FORMAT_I420A)); |
| return formats; |
| } |
| void StartCapture(const media::VideoCaptureParams& params, |
| VideoCaptureCallbacks video_capture_callbacks, |
| VideoCapturerSource::VideoCaptureRunningCallbackCB |
| running_callback) override { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| |
| if (canvas_handler_.get()) { |
| canvas_handler_->StartVideoCapture( |
| params, std::move(video_capture_callbacks.deliver_frame_cb), |
| std::move(running_callback)); |
| } |
| } |
| void RequestRefreshFrame() override { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| if (canvas_handler_.get()) |
| canvas_handler_->RequestRefreshFrame(); |
| } |
| void StopCapture() override { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| if (canvas_handler_.get()) |
| canvas_handler_->StopVideoCapture(); |
| } |
| void SetCanDiscardAlpha(bool can_discard_alpha) override { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| if (canvas_handler_.get()) |
| canvas_handler_->SetCanDiscardAlpha(can_discard_alpha); |
| } |
| |
| private: |
| const gfx::Size size_; |
| const float frame_rate_; |
| // Bound to Main Render thread. |
| THREAD_CHECKER(main_render_thread_checker_); |
| // CanvasCaptureHandler is owned by CanvasDrawListener in blink. It is |
| // guaranteed to be destroyed on Main Render thread and it would happen |
| // independently of this class. Therefore, WeakPtr should always be checked |
| // before use. |
| base::WeakPtr<CanvasCaptureHandler> canvas_handler_; |
| }; |
| |
| class CanvasCaptureHandler::CanvasCaptureHandlerDelegate { |
| public: |
| explicit CanvasCaptureHandlerDelegate( |
| VideoCaptureDeliverFrameCB new_frame_callback) |
| : new_frame_callback_(new_frame_callback) { |
| DETACH_FROM_THREAD(io_thread_checker_); |
| } |
| |
| CanvasCaptureHandlerDelegate(const CanvasCaptureHandlerDelegate&) = delete; |
| CanvasCaptureHandlerDelegate& operator=(const CanvasCaptureHandlerDelegate&) = |
| delete; |
| |
| ~CanvasCaptureHandlerDelegate() { |
| DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_); |
| } |
| |
| void SendNewFrameOnIOThread(scoped_refptr<media::VideoFrame> video_frame, |
| base::TimeTicks current_time) { |
| DCHECK_CALLED_ON_VALID_THREAD(io_thread_checker_); |
| new_frame_callback_.Run(std::move(video_frame), current_time); |
| } |
| |
| base::WeakPtr<CanvasCaptureHandlerDelegate> GetWeakPtrForIOThread() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| const VideoCaptureDeliverFrameCB new_frame_callback_; |
| // Bound to IO thread. |
| THREAD_CHECKER(io_thread_checker_); |
| base::WeakPtrFactory<CanvasCaptureHandlerDelegate> weak_ptr_factory_{this}; |
| }; |
| |
| CanvasCaptureHandler::CanvasCaptureHandler( |
| LocalFrame* frame, |
| const gfx::Size& size, |
| double frame_rate, |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| MediaStreamComponent** component) |
| : io_task_runner_(std::move(io_task_runner)) { |
| std::unique_ptr<VideoCapturerSource> video_source( |
| new CanvasVideoCapturerSource(weak_ptr_factory_.GetWeakPtr(), size, |
| frame_rate)); |
| AddVideoCapturerSourceToVideoTrack(std::move(main_task_runner), frame, |
| std::move(video_source), component); |
| } |
| |
| CanvasCaptureHandler::~CanvasCaptureHandler() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()); |
| } |
| |
| // static |
| std::unique_ptr<CanvasCaptureHandler> |
| CanvasCaptureHandler::CreateCanvasCaptureHandler( |
| LocalFrame* frame, |
| const gfx::Size& size, |
| double frame_rate, |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| MediaStreamComponent** component) { |
| // Save histogram data so we can see how much CanvasCapture is used. |
| // The histogram counts the number of calls to the JS API. |
| UpdateWebRTCMethodCount(RTCAPIName::kCanvasCaptureStream); |
| |
| return std::unique_ptr<CanvasCaptureHandler>(new CanvasCaptureHandler( |
| frame, size, frame_rate, std::move(main_task_runner), |
| std::move(io_task_runner), component)); |
| } |
| |
| CanvasCaptureHandler::NewFrameCallback |
| CanvasCaptureHandler::GetNewFrameCallback() { |
| // Increment the number of pending calls, and create a ScopedClosureRunner |
| // to ensure that it be decremented even if the returned callback is dropped |
| // instead of being run. |
| pending_send_new_frame_calls_ += 1; |
| auto decrement_closure = blink::BindOnce( |
| [](base::WeakPtr<CanvasCaptureHandler> handler) { |
| if (handler) |
| handler->pending_send_new_frame_calls_ -= 1; |
| }, |
| weak_ptr_factory_.GetWeakPtr()); |
| |
| return blink::BindOnce( |
| &CanvasCaptureHandler::OnNewFrameCallback, weak_ptr_factory_.GetWeakPtr(), |
| base::ScopedClosureRunner(std::move(decrement_closure)), |
| base::TimeTicks::Now(), gfx::ColorSpace()); |
| } |
| |
| void CanvasCaptureHandler::OnNewFrameCallback( |
| base::ScopedClosureRunner decrement_runner, |
| base::TimeTicks this_frame_ticks, |
| const gfx::ColorSpace& color_space, |
| scoped_refptr<media::VideoFrame> video_frame) { |
| DCHECK_GT(pending_send_new_frame_calls_, 0u); |
| decrement_runner.RunAndReset(); |
| |
| if (video_frame) |
| SendFrame(this_frame_ticks, color_space, video_frame); |
| |
| if (!pending_send_new_frame_calls_ && deferred_request_refresh_frame_) |
| SendRefreshFrame(); |
| } |
| |
| bool CanvasCaptureHandler::NeedsNewFrame() const { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| return ask_for_new_frame_; |
| } |
| |
| void CanvasCaptureHandler::StartVideoCapture( |
| const media::VideoCaptureParams& params, |
| VideoCaptureDeliverFrameCB new_frame_callback, |
| VideoCapturerSource::VideoCaptureRunningCallbackCB running_callback) { |
| DVLOG(3) << __func__ << " requested " |
| << media::VideoCaptureFormat::ToString(params.requested_format); |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| DCHECK(params.requested_format.IsValid()); |
| capture_format_ = params.requested_format; |
| delegate_ = std::make_unique<CanvasCaptureHandlerDelegate>( |
| std::move(new_frame_callback)); |
| DCHECK(delegate_); |
| ask_for_new_frame_ = true; |
| running_callback.Run(VideoCaptureRunState::kRunning); |
| } |
| |
| void CanvasCaptureHandler::RequestRefreshFrame() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| if (last_frame_ && delegate_) { |
| // If we're currently reading out pixels from GL memory, we risk |
| // emitting frames with non-incrementally increasing timestamps. |
| // Defer sending the refresh frame until we have completed those async |
| // reads. |
| if (pending_send_new_frame_calls_) { |
| deferred_request_refresh_frame_ = true; |
| return; |
| } |
| SendRefreshFrame(); |
| } |
| } |
| |
| void CanvasCaptureHandler::StopVideoCapture() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| ask_for_new_frame_ = false; |
| io_task_runner_->DeleteSoon(FROM_HERE, delegate_.release()); |
| } |
| |
| void CanvasCaptureHandler::SendFrame( |
| base::TimeTicks this_frame_ticks, |
| const gfx::ColorSpace& color_space, |
| scoped_refptr<media::VideoFrame> video_frame) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| |
| // If this function is called asynchronously, |delegate_| might have been |
| // released already in StopVideoCapture(). |
| if (!delegate_ || !video_frame) |
| return; |
| |
| if (!first_frame_ticks_) |
| first_frame_ticks_ = this_frame_ticks; |
| video_frame->set_timestamp(this_frame_ticks - *first_frame_ticks_); |
| if (color_space.IsValid()) |
| video_frame->set_color_space(color_space); |
| |
| last_frame_ = video_frame; |
| PostCrossThreadTask( |
| *io_task_runner_, FROM_HERE, |
| CrossThreadBindOnce(&CanvasCaptureHandler::CanvasCaptureHandlerDelegate:: |
| SendNewFrameOnIOThread, |
| delegate_->GetWeakPtrForIOThread(), |
| std::move(video_frame), this_frame_ticks)); |
| } |
| |
| void CanvasCaptureHandler::AddVideoCapturerSourceToVideoTrack( |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| LocalFrame* frame, |
| std::unique_ptr<VideoCapturerSource> source, |
| MediaStreamComponent** component) { |
| uint8_t track_id_bytes[64]; |
| base::RandBytes(track_id_bytes); |
| String track_id = Base64Encode(track_id_bytes); |
| media::VideoCaptureFormats preferred_formats = source->GetPreferredFormats(); |
| auto stream_video_source = std::make_unique<MediaStreamVideoCapturerSource>( |
| main_task_runner, frame, |
| WebPlatformMediaStreamSource::SourceStoppedCallback(), std::move(source)); |
| auto* stream_video_source_ptr = stream_video_source.get(); |
| auto* stream_source = MakeGarbageCollected<MediaStreamSource>( |
| track_id, MediaStreamSource::kTypeVideo, track_id, false, |
| std::move(stream_video_source)); |
| stream_source->SetCapabilities(ComputeCapabilitiesForVideoSource( |
| track_id, preferred_formats, mojom::blink::FacingMode::kNone, |
| false /* is_device_capture */)); |
| |
| *component = MakeGarbageCollected<MediaStreamComponentImpl>( |
| stream_source, |
| std::make_unique<MediaStreamVideoTrack>( |
| stream_video_source_ptr, |
| MediaStreamVideoSource::ConstraintsOnceCallback(), true)); |
| } |
| |
| void CanvasCaptureHandler::SendRefreshFrame() { |
| DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_); |
| DCHECK_EQ(pending_send_new_frame_calls_, 0u); |
| if (last_frame_ && delegate_) { |
| PostCrossThreadTask( |
| *io_task_runner_, FROM_HERE, |
| CrossThreadBindOnce( |
| &CanvasCaptureHandler::CanvasCaptureHandlerDelegate:: |
| SendNewFrameOnIOThread, |
| delegate_->GetWeakPtrForIOThread(), last_frame_, |
| base::TimeTicks::Now())); |
| } |
| deferred_request_refresh_frame_ = false; |
| } |
| |
| } // namespace blink |