blob: 269400018b1fbcf1d86071048ae9bb35c80f2e73 [file] [log] [blame]
// 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/bind.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 "media/base/limits.h"
#include "media/blink/webmediaplayer_impl.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/blink/public/platform/modules/mediastream/webrtc_uma_histograms.h"
#include "third_party/blink/public/platform/web_media_player.h"
#include "third_party/blink/public/platform/web_rect.h"
#include "third_party/blink/public/platform/web_size.h"
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.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,
scoped_refptr<base::SingleThreadTaskRunner> 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(blink::WebRTCAPIName::kVideoCaptureStream);
return base::WrapUnique(new HtmlVideoElementCapturerSource(
static_cast<media::WebMediaPlayerImpl*>(player)->AsWeakPtr(),
io_task_runner, task_runner));
}
HtmlVideoElementCapturerSource::HtmlVideoElementCapturerSource(
const base::WeakPtr<blink::WebMediaPlayer>& player,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: web_media_player_(player),
io_task_runner_(io_task_runner),
task_runner_(task_runner),
is_opaque_(player->IsOpaque()),
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(),
blink::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;
}
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::BindOnce(&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("media", "HtmlVideoElementCapturerSource::sendNewFrame");
DCHECK(thread_checker_.CalledOnValidThread());
if (!web_media_player_ || new_frame_callback_.is_null())
return;
const base::TimeTicks current_time = base::TimeTicks::Now();
if (start_capture_time_.is_null())
start_capture_time_ = current_time;
const blink::WebSize resolution = web_media_player_->NaturalSize();
if (!canvas_ || is_opaque_ != web_media_player_->IsOpaque()) {
LOG(ERROR) << " Change in opacity !!!";
is_opaque_ = web_media_player_->IsOpaque();
if (!bitmap_.tryAllocPixels(SkImageInfo::MakeN32(
resolution.width, resolution.height,
is_opaque_ ? kOpaque_SkAlphaType : kPremul_SkAlphaType))) {
running_callback_.Run(false);
return;
}
canvas_ = std::make_unique<cc::SkiaPaintCanvas>(bitmap_);
}
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(
is_opaque_ ? media::PIXEL_FORMAT_I420 : media::PIXEL_FORMAT_I420A,
resolution, gfx::Rect(resolution), resolution,
current_time - start_capture_time_);
const uint32_t source_pixel_format =
(kN32_SkColorType == kRGBA_8888_SkColorType) ? libyuv::FOURCC_ABGR
: libyuv::FOURCC_ARGB;
if (frame &&
libyuv::ConvertToI420(
static_cast<uint8_t*>(bitmap_.getPixels()), bitmap_.computeByteSize(),
frame->visible_data(media::VideoFrame::kYPlane),
frame->stride(media::VideoFrame::kYPlane),
frame->visible_data(media::VideoFrame::kUPlane),
frame->stride(media::VideoFrame::kUPlane),
frame->visible_data(media::VideoFrame::kVPlane),
frame->stride(media::VideoFrame::kVPlane), 0 /* crop_x */,
0 /* crop_y */, frame->visible_rect().size().width(),
frame->visible_rect().size().height(), bitmap_.info().width(),
bitmap_.info().height(), libyuv::kRotate0,
source_pixel_format) == 0) {
if (!is_opaque_) {
// OK to use ARGB...() because alpha has the same alignment for both ABGR
// and ARGB.
libyuv::ARGBExtractAlpha(static_cast<uint8_t*>(bitmap_.getPixels()),
bitmap_.rowBytes() /* stride */,
frame->visible_data(media::VideoFrame::kAPlane),
frame->stride(media::VideoFrame::kAPlane),
bitmap_.info().width(), bitmap_.info().height());
} // Success!
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(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.
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&HtmlVideoElementCapturerSource::sendNewFrame,
weak_factory_.GetWeakPtr()),
next_capture_time_ - current_time);
}
} // namespace content