| // 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/webrtc/webrtc_video_capturer_adapter.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/paint/skia_paint_canvas.h" |
| #include "content/renderer/media/webrtc/webrtc_video_frame_adapter.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "media/base/timestamp_constants.h" |
| #include "media/base/video_util.h" |
| #include "media/renderers/paint_canvas_video_renderer.h" |
| #include "services/ui/public/cpp/gpu/context_provider_command_buffer.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/libyuv/include/libyuv/convert.h" |
| #include "third_party/libyuv/include/libyuv/convert_from.h" |
| #include "third_party/libyuv/include/libyuv/scale.h" |
| #include "third_party/webrtc/api/video/video_rotation.h" |
| #include "third_party/webrtc/rtc_base/refcountedobject.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Empty method used for keeping a reference to the original media::VideoFrame. |
| // The reference to |frame| is kept in the closure that calls this method. |
| void CapturerReleaseOriginalFrame( |
| const scoped_refptr<media::VideoFrame>& frame) {} |
| |
| // Helper class that signals a WaitableEvent when it goes out of scope. |
| class ScopedWaitableEvent { |
| public: |
| explicit ScopedWaitableEvent(base::WaitableEvent* event) : event_(event) {} |
| ~ScopedWaitableEvent() { |
| if (event_) |
| event_->Signal(); |
| } |
| |
| private: |
| base::WaitableEvent* const event_; |
| }; |
| |
| } // anonymous namespace |
| |
| // Initializes the GL context environment and provides a method for copying |
| // texture backed frames into CPU mappable memory. |
| // The class is created and destroyed on the main render thread. |
| class WebRtcVideoCapturerAdapter::TextureFrameCopier |
| : public base::RefCounted<WebRtcVideoCapturerAdapter::TextureFrameCopier> { |
| public: |
| TextureFrameCopier() |
| : main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| canvas_video_renderer_(new media::PaintCanvasVideoRenderer) { |
| RenderThreadImpl* const main_thread = RenderThreadImpl::current(); |
| if (main_thread) |
| provider_ = main_thread->SharedMainThreadContextProvider(); |
| } |
| |
| // Synchronous call to copy a texture backed |frame| into a CPU mappable |
| // |new_frame|. If it is not called on the main render thread, this call posts |
| // a task on main thread by calling CopyTextureFrameOnMainThread() and blocks |
| // until it is completed. |
| void CopyTextureFrame(const scoped_refptr<media::VideoFrame>& frame, |
| scoped_refptr<media::VideoFrame>* new_frame) { |
| if (main_thread_task_runner_->BelongsToCurrentThread()) { |
| CopyTextureFrameOnMainThread(frame, new_frame, nullptr); |
| return; |
| } |
| |
| base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| main_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TextureFrameCopier::CopyTextureFrameOnMainThread, this, |
| frame, new_frame, &waiter)); |
| waiter.Wait(); |
| } |
| |
| private: |
| friend class base::RefCounted<TextureFrameCopier>; |
| ~TextureFrameCopier() { |
| // |canvas_video_renderer_| should be deleted on the thread it was created. |
| if (!main_thread_task_runner_->BelongsToCurrentThread()) { |
| main_thread_task_runner_->DeleteSoon(FROM_HERE, |
| canvas_video_renderer_.release()); |
| } |
| } |
| |
| void CopyTextureFrameOnMainThread( |
| const scoped_refptr<media::VideoFrame>& frame, |
| scoped_refptr<media::VideoFrame>* new_frame, |
| base::WaitableEvent* waiter) { |
| DCHECK(main_thread_task_runner_->BelongsToCurrentThread()); |
| DCHECK(frame->format() == media::PIXEL_FORMAT_ARGB || |
| frame->format() == media::PIXEL_FORMAT_XRGB || |
| frame->format() == media::PIXEL_FORMAT_I420 || |
| frame->format() == media::PIXEL_FORMAT_UYVY || |
| frame->format() == media::PIXEL_FORMAT_NV12); |
| ScopedWaitableEvent event(waiter); |
| |
| if (!provider_) { |
| // Return a black frame (yuv = {0, 0x80, 0x80}). |
| *new_frame = media::VideoFrame::CreateColorFrame( |
| frame->visible_rect().size(), 0u, 0x80, 0x80, frame->timestamp()); |
| return; |
| } |
| |
| SkBitmap bitmap; |
| bitmap.allocPixels(SkImageInfo::MakeN32Premul( |
| frame->visible_rect().width(), frame->visible_rect().height())); |
| cc::SkiaPaintCanvas paint_canvas(bitmap); |
| |
| *new_frame = media::VideoFrame::CreateFrame( |
| media::PIXEL_FORMAT_I420, frame->coded_size(), frame->visible_rect(), |
| frame->natural_size(), frame->timestamp()); |
| DCHECK(provider_->ContextGL()); |
| canvas_video_renderer_->Copy( |
| frame.get(), &paint_canvas, |
| media::Context3D(provider_->ContextGL(), provider_->GrContext())); |
| |
| SkPixmap pixmap; |
| const bool result = bitmap.peekPixels(&pixmap); |
| DCHECK(result) << "Error trying to access SkBitmap's pixels"; |
| const uint32 source_pixel_format = |
| (kN32_SkColorType == kRGBA_8888_SkColorType) ? cricket::FOURCC_ABGR |
| : cricket::FOURCC_ARGB; |
| libyuv::ConvertToI420( |
| static_cast<const uint8*>(pixmap.addr(0, 0)), pixmap.computeByteSize(), |
| (*new_frame)->visible_data(media::VideoFrame::kYPlane), |
| (*new_frame)->stride(media::VideoFrame::kYPlane), |
| (*new_frame)->visible_data(media::VideoFrame::kUPlane), |
| (*new_frame)->stride(media::VideoFrame::kUPlane), |
| (*new_frame)->visible_data(media::VideoFrame::kVPlane), |
| (*new_frame)->stride(media::VideoFrame::kVPlane), 0 /* crop_x */, |
| 0 /* crop_y */, pixmap.width(), pixmap.height(), |
| (*new_frame)->visible_rect().width(), |
| (*new_frame)->visible_rect().height(), libyuv::kRotate0, |
| source_pixel_format); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; |
| scoped_refptr<ui::ContextProviderCommandBuffer> provider_; |
| std::unique_ptr<media::PaintCanvasVideoRenderer> canvas_video_renderer_; |
| }; |
| |
| WebRtcVideoCapturerAdapter::WebRtcVideoCapturerAdapter( |
| bool is_screencast, |
| blink::WebMediaStreamTrack::ContentHintType content_hint) |
| : texture_copier_(new WebRtcVideoCapturerAdapter::TextureFrameCopier()), |
| is_screencast_(is_screencast), |
| content_hint_(content_hint), |
| running_(false) { |
| thread_checker_.DetachFromThread(); |
| } |
| |
| WebRtcVideoCapturerAdapter::~WebRtcVideoCapturerAdapter() { |
| DVLOG(3) << __func__; |
| } |
| |
| void WebRtcVideoCapturerAdapter::OnFrameCaptured( |
| const scoped_refptr<media::VideoFrame>& input_frame) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| TRACE_EVENT0("video", "WebRtcVideoCapturerAdapter::OnFrameCaptured"); |
| if (!(input_frame->IsMappable() && |
| (input_frame->format() == media::PIXEL_FORMAT_I420 || |
| input_frame->format() == media::PIXEL_FORMAT_I420A)) && |
| !input_frame->HasTextures()) { |
| // Since connecting sources and sinks do not check the format, we need to |
| // just ignore formats that we can not handle. |
| LOG(ERROR) << "We cannot send frame with storage type: " |
| << input_frame->AsHumanReadableString(); |
| NOTREACHED(); |
| return; |
| } |
| scoped_refptr<media::VideoFrame> frame = input_frame; |
| const int orig_width = frame->natural_size().width(); |
| const int orig_height = frame->natural_size().height(); |
| int adapted_width; |
| int adapted_height; |
| // The VideoAdapter is only used for cpu-adaptation downscaling, no |
| // aspect changes. So we ignore these crop-related outputs. |
| int crop_width; |
| int crop_height; |
| int crop_x; |
| int crop_y; |
| int64_t translated_camera_time_us; |
| |
| if (!AdaptFrame(orig_width, orig_height, |
| frame->timestamp().InMicroseconds(), |
| rtc::TimeMicros(), |
| &adapted_width, &adapted_height, |
| &crop_width, &crop_height, &crop_x, &crop_y, |
| &translated_camera_time_us)) { |
| return; |
| } |
| |
| // Return |frame| directly if it is texture backed, because there is no |
| // cropping support for texture yet. See http://crbug/503653. |
| if (frame->HasTextures()) { |
| OnFrame(webrtc::VideoFrame( |
| new rtc::RefCountedObject<WebRtcVideoFrameAdapter>( |
| frame, base::Bind(&TextureFrameCopier::CopyTextureFrame, |
| texture_copier_)), |
| webrtc::kVideoRotation_0, translated_camera_time_us), |
| orig_width, orig_height); |
| return; |
| } |
| |
| // Translate crop rectangle from natural size to visible size. |
| gfx::Rect cropped_visible_rect( |
| frame->visible_rect().x() + |
| crop_x * frame->visible_rect().width() / orig_width, |
| frame->visible_rect().y() + |
| crop_y * frame->visible_rect().height() / orig_height, |
| crop_width * frame->visible_rect().width() / orig_width, |
| crop_height * frame->visible_rect().height() / orig_height); |
| |
| const gfx::Size adapted_size(adapted_width, adapted_height); |
| scoped_refptr<media::VideoFrame> video_frame = |
| media::VideoFrame::WrapVideoFrame(frame, frame->format(), |
| cropped_visible_rect, adapted_size); |
| if (!video_frame) |
| return; |
| |
| video_frame->AddDestructionObserver( |
| base::BindOnce(&CapturerReleaseOriginalFrame, frame)); |
| |
| // If no scaling is needed, return a wrapped version of |frame| directly. |
| if (video_frame->natural_size() == video_frame->visible_rect().size()) { |
| OnFrame(webrtc::VideoFrame( |
| new rtc::RefCountedObject<WebRtcVideoFrameAdapter>( |
| video_frame, |
| WebRtcVideoFrameAdapter::CopyTextureFrameCallback()), |
| webrtc::kVideoRotation_0, translated_camera_time_us), |
| orig_width, orig_height); |
| return; |
| } |
| |
| // We need to scale the frame before we hand it over to webrtc. |
| const bool has_alpha = video_frame->format() == media::PIXEL_FORMAT_I420A; |
| scoped_refptr<media::VideoFrame> scaled_frame = |
| scaled_frame_pool_.CreateFrame( |
| has_alpha ? media::PIXEL_FORMAT_I420A : media::PIXEL_FORMAT_I420, |
| adapted_size, gfx::Rect(adapted_size), adapted_size, |
| frame->timestamp()); |
| libyuv::I420Scale(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), |
| video_frame->visible_rect().width(), |
| video_frame->visible_rect().height(), |
| scaled_frame->data(media::VideoFrame::kYPlane), |
| scaled_frame->stride(media::VideoFrame::kYPlane), |
| scaled_frame->data(media::VideoFrame::kUPlane), |
| scaled_frame->stride(media::VideoFrame::kUPlane), |
| scaled_frame->data(media::VideoFrame::kVPlane), |
| scaled_frame->stride(media::VideoFrame::kVPlane), |
| adapted_width, adapted_height, libyuv::kFilterBilinear); |
| if (has_alpha) { |
| libyuv::ScalePlane(video_frame->visible_data(media::VideoFrame::kAPlane), |
| video_frame->stride(media::VideoFrame::kAPlane), |
| video_frame->visible_rect().width(), |
| video_frame->visible_rect().height(), |
| scaled_frame->data(media::VideoFrame::kAPlane), |
| scaled_frame->stride(media::VideoFrame::kAPlane), |
| adapted_width, adapted_height, libyuv::kFilterBilinear); |
| } |
| |
| OnFrame(webrtc::VideoFrame( |
| new rtc::RefCountedObject<WebRtcVideoFrameAdapter>( |
| scaled_frame, |
| WebRtcVideoFrameAdapter::CopyTextureFrameCallback()), |
| webrtc::kVideoRotation_0, translated_camera_time_us), |
| orig_width, orig_height); |
| } |
| |
| cricket::CaptureState WebRtcVideoCapturerAdapter::Start( |
| const cricket::VideoFormat& capture_format) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!running_); |
| DVLOG(3) << __func__ << " capture format: " << capture_format.ToString(); |
| |
| running_ = true; |
| return cricket::CS_RUNNING; |
| } |
| |
| void WebRtcVideoCapturerAdapter::Stop() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DVLOG(3) << __func__; |
| DCHECK(running_); |
| running_ = false; |
| SetCaptureFormat(nullptr); |
| SignalStateChange(this, cricket::CS_STOPPED); |
| } |
| |
| bool WebRtcVideoCapturerAdapter::IsRunning() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return running_; |
| } |
| |
| bool WebRtcVideoCapturerAdapter::GetPreferredFourccs( |
| std::vector<uint32_t>* fourccs) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!fourccs) |
| return false; |
| DCHECK(fourccs->empty()); |
| fourccs->push_back(cricket::FOURCC_I420); |
| return true; |
| } |
| |
| void WebRtcVideoCapturerAdapter::SetContentHint( |
| blink::WebMediaStreamTrack::ContentHintType content_hint) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| content_hint_ = content_hint; |
| } |
| |
| bool WebRtcVideoCapturerAdapter::IsScreencast() const { |
| // IsScreencast() is misleading since content hints were added to |
| // MediaStreamTracks. What IsScreencast() really signals is whether or not |
| // video frames should ever be scaled before being handed over to WebRTC. |
| // TODO(pbos): Remove the need for IsScreencast() -> ShouldAdaptResolution() |
| // by inlining VideoCapturer::AdaptFrame() and removing it from VideoCapturer. |
| return !ShouldAdaptResolution(); |
| } |
| |
| bool WebRtcVideoCapturerAdapter::ShouldAdaptResolution() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (content_hint_ == |
| blink::WebMediaStreamTrack::ContentHintType::kVideoMotion) { |
| return true; |
| } |
| if (content_hint_ == |
| blink::WebMediaStreamTrack::ContentHintType::kVideoDetail) { |
| return false; |
| } |
| // Screencast does not adapt by default. |
| return !is_screencast_; |
| } |
| |
| bool WebRtcVideoCapturerAdapter::GetBestCaptureFormat( |
| const cricket::VideoFormat& desired, |
| cricket::VideoFormat* best_format) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DVLOG(3) << __func__ << " desired: " << desired.ToString(); |
| |
| // Capability enumeration is done in MediaStreamVideoSource. The adapter can |
| // just use what is provided. |
| // Use the desired format as the best format. |
| best_format->width = desired.width; |
| best_format->height = desired.height; |
| best_format->fourcc = cricket::FOURCC_I420; |
| best_format->interval = desired.interval; |
| return true; |
| } |
| |
| } // namespace content |