| // 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/html_video_element_capturer_source.h" |
| |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/single_thread_task_runner.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/public/renderer/render_thread.h" |
| #include "content/renderer/media/media_stream_video_source.h" |
| #include "content/renderer/media/webrtc_uma_histograms.h" |
| #include "media/base/limits.h" |
| #include "media/blink/webmediaplayer_impl.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/WebKit/public/platform/WebMediaPlayer.h" |
| #include "third_party/WebKit/public/platform/WebRect.h" |
| #include "third_party/WebKit/public/platform/WebSize.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| |
| namespace { |
| const float kMinFramesPerSecond = 1.0; |
| } // anonymous namespace |
| |
| namespace content { |
| |
| //static |
| std::unique_ptr<HtmlVideoElementCapturerSource> |
| HtmlVideoElementCapturerSource::CreateFromWebMediaPlayerImpl( |
| blink::WebMediaPlayer* player, |
| const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) { |
| // Save histogram data so we can see how much HTML Video capture is used. |
| // The histogram counts the number of calls to the JS API. |
| UpdateWebRTCMethodCount(WEBKIT_VIDEO_CAPTURE_STREAM); |
| |
| return base::WrapUnique(new HtmlVideoElementCapturerSource( |
| static_cast<media::WebMediaPlayerImpl*>(player)->AsWeakPtr(), |
| io_task_runner)); |
| } |
| |
| HtmlVideoElementCapturerSource::HtmlVideoElementCapturerSource( |
| const base::WeakPtr<blink::WebMediaPlayer>& player, |
| const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) |
| : web_media_player_(player), |
| io_task_runner_(io_task_runner), |
| capture_frame_rate_(0.0), |
| weak_factory_(this) { |
| DCHECK(web_media_player_); |
| } |
| |
| HtmlVideoElementCapturerSource::~HtmlVideoElementCapturerSource() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| } |
| |
| media::VideoCaptureFormats |
| HtmlVideoElementCapturerSource::GetPreferredFormats() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // WebMediaPlayer has a setRate() but can't be read back. |
| // TODO(mcasas): Add getRate() to WMPlayer and/or fix the spec to allow users |
| // to specify it. |
| const media::VideoCaptureFormat format( |
| web_media_player_->NaturalSize(), |
| MediaStreamVideoSource::kDefaultFrameRate, media::PIXEL_FORMAT_I420); |
| media::VideoCaptureFormats formats; |
| formats.push_back(format); |
| return formats; |
| } |
| |
| void HtmlVideoElementCapturerSource::StartCapture( |
| const media::VideoCaptureParams& params, |
| const VideoCaptureDeliverFrameCB& new_frame_callback, |
| const RunningCallback& running_callback) { |
| DVLOG(2) << __func__ << " requested " |
| << media::VideoCaptureFormat::ToString(params.requested_format); |
| DCHECK(params.requested_format.IsValid()); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| running_callback_ = running_callback; |
| if (!web_media_player_ || !web_media_player_->HasVideo()) { |
| running_callback_.Run(false); |
| return; |
| } |
| const blink::WebSize resolution = web_media_player_->NaturalSize(); |
| if (!bitmap_.tryAllocPixels( |
| SkImageInfo::MakeN32Premul(resolution.width, resolution.height))) { |
| running_callback_.Run(false); |
| return; |
| } |
| canvas_ = base::MakeUnique<cc::SkiaPaintCanvas>(bitmap_); |
| |
| new_frame_callback_ = new_frame_callback; |
| // Force |capture_frame_rate_| to be in between k{Min,Max}FramesPerSecond. |
| capture_frame_rate_ = |
| std::max(kMinFramesPerSecond, |
| std::min(static_cast<float>(media::limits::kMaxFramesPerSecond), |
| params.requested_format.frame_rate)); |
| |
| running_callback_.Run(true); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&HtmlVideoElementCapturerSource::sendNewFrame, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void HtmlVideoElementCapturerSource::StopCapture() { |
| DVLOG(2) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| running_callback_.Reset(); |
| new_frame_callback_.Reset(); |
| next_capture_time_ = base::TimeTicks(); |
| } |
| |
| void HtmlVideoElementCapturerSource::sendNewFrame() { |
| DVLOG(3) << __func__; |
| TRACE_EVENT0("video", "HtmlVideoElementCapturerSource::sendNewFrame"); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!web_media_player_ || new_frame_callback_.is_null()) |
| return; |
| |
| const base::TimeTicks current_time = base::TimeTicks::Now(); |
| const blink::WebSize resolution = web_media_player_->NaturalSize(); |
| |
| cc::PaintFlags flags; |
| flags.setBlendMode(SkBlendMode::kSrc); |
| flags.setFilterQuality(kLow_SkFilterQuality); |
| web_media_player_->Paint( |
| canvas_.get(), blink::WebRect(0, 0, resolution.width, resolution.height), |
| flags); |
| DCHECK_NE(kUnknown_SkColorType, canvas_->imageInfo().colorType()); |
| DCHECK_EQ(canvas_->imageInfo().width(), resolution.width); |
| DCHECK_EQ(canvas_->imageInfo().height(), resolution.height); |
| |
| DCHECK_NE(kUnknown_SkColorType, bitmap_.colorType()); |
| DCHECK(!bitmap_.drawsNothing()); |
| DCHECK(bitmap_.getPixels()); |
| if (bitmap_.colorType() != kN32_SkColorType) { |
| DLOG(ERROR) << "Only supported color type is kN32_SkColorType (ARGB/ABGR)"; |
| return; |
| } |
| |
| scoped_refptr<media::VideoFrame> frame = frame_pool_.CreateFrame( |
| media::PIXEL_FORMAT_I420, resolution, gfx::Rect(resolution), resolution, |
| base::TimeTicks::Now() - base::TimeTicks()); |
| DCHECK(frame); |
| |
| const uint32 source_pixel_format = |
| (kN32_SkColorType == kRGBA_8888_SkColorType) ? libyuv::FOURCC_ABGR |
| : libyuv::FOURCC_ARGB; |
| |
| if (libyuv::ConvertToI420( |
| static_cast<uint8*>(bitmap_.getPixels()), bitmap_.getSize(), |
| frame->data(media::VideoFrame::kYPlane), |
| frame->stride(media::VideoFrame::kYPlane), |
| frame->data(media::VideoFrame::kUPlane), |
| frame->stride(media::VideoFrame::kUPlane), |
| frame->data(media::VideoFrame::kVPlane), |
| frame->stride(media::VideoFrame::kVPlane), 0 /* crop_x */, |
| 0 /* crop_y */, bitmap_.info().width(), bitmap_.info().height(), |
| frame->coded_size().width(), frame->coded_size().height(), |
| libyuv::kRotate0, source_pixel_format) == 0) { |
| // Success! |
| io_task_runner_->PostTask( |
| FROM_HERE, base::Bind(new_frame_callback_, frame, current_time)); |
| } |
| |
| // Calculate the time in the future where the next frame should be created. |
| const base::TimeDelta frame_interval = |
| base::TimeDelta::FromMicroseconds(1E6 / capture_frame_rate_); |
| if (next_capture_time_.is_null()) { |
| next_capture_time_ = current_time + frame_interval; |
| } else { |
| next_capture_time_ += frame_interval; |
| // Don't accumulate any debt if we are lagging behind - just post next frame |
| // immediately and continue as normal. |
| if (next_capture_time_ < current_time) |
| next_capture_time_ = current_time; |
| } |
| // Schedule next capture. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::Bind(&HtmlVideoElementCapturerSource::sendNewFrame, |
| weak_factory_.GetWeakPtr()), |
| next_capture_time_ - current_time); |
| } |
| |
| } // namespace content |