blob: 43f873be55b5b87f5ed23d4538c6c5abaf3c009c [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/modules/webcodecs/video_decoder.h"
#include <utility>
#include <vector>
#include "base/time/time.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_util.h"
#include "media/base/mime_util.h"
#include "media/base/supported_types.h"
#include "media/base/video_decoder.h"
#include "media/media_buildflags.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_support.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_config_eval.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/to_v8.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/h264_to_annex_b_bitstream_converter.h"
#include "media/formats/mp4/box_definitions.h"
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
namespace blink {
namespace {
media::GpuVideoAcceleratorFactories* GetGpuFactoriesOnMainThread() {
DCHECK(IsMainThread());
return Platform::Current()->GetGpuFactories();
}
void DecoderSupport_OnKnown(
VideoDecoderSupport* support,
std::unique_ptr<VideoDecoder::MediaConfigType> media_config,
ScriptPromiseResolver* resolver,
media::GpuVideoAcceleratorFactories* gpu_factories) {
DCHECK(gpu_factories->IsDecoderSupportKnown());
support->setSupported(
gpu_factories->IsDecoderConfigSupported(*media_config) ==
media::GpuVideoAcceleratorFactories::Supported::kTrue);
resolver->Resolve(support);
}
void DecoderSupport_OnGpuFactories(
VideoDecoderSupport* support,
std::unique_ptr<VideoDecoder::MediaConfigType> media_config,
ScriptPromiseResolver* resolver,
media::GpuVideoAcceleratorFactories* gpu_factories) {
if (!gpu_factories || !gpu_factories->IsGpuVideoAcceleratorEnabled()) {
support->setSupported(false);
resolver->Resolve(support);
return;
}
if (gpu_factories->IsDecoderSupportKnown()) {
DecoderSupport_OnKnown(support, std::move(media_config), resolver,
gpu_factories);
return;
}
gpu_factories->NotifyDecoderSupportKnown(
ConvertToBaseOnceCallback(CrossThreadBindOnce(
&DecoderSupport_OnKnown, WrapCrossThreadPersistent(support),
std::move(media_config), WrapCrossThreadPersistent(resolver),
CrossThreadUnretained(gpu_factories))));
}
} // namespace
bool ParseCodecString(const String& codec_string,
media::VideoType& out_video_type,
String& out_console_message) {
bool is_codec_ambiguous = true;
media::VideoCodec codec = media::kUnknownVideoCodec;
media::VideoCodecProfile profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
media::VideoColorSpace color_space = media::VideoColorSpace::REC709();
uint8_t level = 0;
bool parse_succeeded =
media::ParseVideoCodecString("", codec_string.Utf8(), &is_codec_ambiguous,
&codec, &profile, &level, &color_space);
if (!parse_succeeded) {
out_console_message = "Failed to parse codec string.";
return false;
}
if (is_codec_ambiguous) {
out_console_message = "Codec string is ambiguous.";
return false;
}
out_video_type = {codec, profile, level, color_space};
return true;
}
// TODO(crbug.com/1179970): rename out_console_message.
// TODO(crbug.com/1181443): Make this a pure virtual in DecoderTemplate, and
// refactor its uses.
bool IsValidConfig(const VideoDecoderConfig& config,
media::VideoType& out_video_type,
String& out_console_message) {
if (!ParseCodecString(config.codec(), out_video_type, out_console_message))
return false;
if (config.hasCodedWidth()) {
if (config.codedWidth() == 0) {
out_console_message =
"Invalid codedWidth. Value must be greater than zero.";
return false;
}
uint32_t crop_left = config.hasCropLeft() ? config.cropLeft() : 0;
uint32_t crop_width =
config.hasCropWidth() ? config.cropWidth() : config.codedWidth();
if (crop_width == 0) {
out_console_message =
"Invalid cropWidth. Value must be greater than zero.";
return false;
}
if (crop_left + crop_width > config.codedWidth()) {
out_console_message =
"Invalid cropLeft + cropWidth. Sum must not exceed codedWidth.";
return false;
}
} else { // !config.hasCodedWidth()
if (config.hasCropLeft()) {
out_console_message =
"Invalid config. cropLeft specified without codedWidth.";
return false;
}
if (config.hasCropWidth()) {
out_console_message =
"Invalid config. cropWidth specified without codedWidth.";
return false;
}
}
if (config.hasCodedHeight()) {
if (config.codedHeight() == 0) {
out_console_message =
"Invalid codedHeight. Value must be greater than zero.";
return false;
}
uint32_t crop_top = config.hasCropTop() ? config.cropTop() : 0;
uint32_t crop_height =
config.hasCropHeight() ? config.cropHeight() : config.codedHeight();
if (crop_height == 0) {
out_console_message =
"Invalid cropHeight. Value must be greater than zero.";
return false;
}
if (crop_top + crop_height > config.codedHeight()) {
out_console_message =
"Invalid cropTop + cropHeight. Sum must not exceed codedHeight.";
return false;
}
} else { // !config.hasCodedHeight()
if (config.hasCropTop()) {
out_console_message =
"Invalid config. cropTop specified without codedHeight.";
return false;
}
if (config.hasCropHeight()) {
out_console_message =
"Invalid config. cropHeight specified without codedHeight.";
return false;
}
}
if (config.hasDisplayWidth() && config.displayWidth() == 0) {
out_console_message =
"Invalid displayWidth. Value must be greater than zero.";
return false;
}
if (config.hasDisplayHeight() && config.displayHeight() == 0) {
out_console_message =
"Invalid displayHeight. Value must be greater than zero.";
return false;
}
return true;
}
VideoDecoderConfig* CopyConfig(const VideoDecoderConfig& config) {
VideoDecoderConfig* copy = VideoDecoderConfig::Create();
copy->setCodec(config.codec());
if (config.hasDescription()) {
DOMArrayPiece buffer(config.description());
DOMArrayBuffer* buffer_copy =
DOMArrayBuffer::Create(buffer.Data(), buffer.ByteLength());
copy->setDescription(
ArrayBufferOrArrayBufferView::FromArrayBuffer(buffer_copy));
}
if (config.hasCodedWidth())
copy->setCodedWidth(config.codedWidth());
if (config.hasCodedHeight())
copy->setCodedHeight(config.codedHeight());
if (config.hasCropLeft())
copy->setCropLeft(config.cropLeft());
if (config.hasCropTop())
copy->setCropTop(config.cropTop());
if (config.hasCropWidth())
copy->setCropWidth(config.cropWidth());
if (config.hasCropHeight())
copy->setCropHeight(config.cropHeight());
if (config.hasDisplayWidth())
copy->setDisplayWidth(config.displayWidth());
if (config.hasDisplayHeight())
copy->setDisplayHeight(config.displayHeight());
if (config.hasHardwareAcceleration())
copy->setHardwareAcceleration(config.hardwareAcceleration());
return copy;
}
// static
std::unique_ptr<VideoDecoderTraits::MediaDecoderType>
VideoDecoderTraits::CreateDecoder(
ExecutionContext& execution_context,
media::GpuVideoAcceleratorFactories* gpu_factories,
media::MediaLog* media_log) {
return std::make_unique<VideoDecoderBroker>(execution_context, gpu_factories,
media_log);
}
// static
HardwarePreference VideoDecoder::GetHardwareAccelerationPreference(
const ConfigType& config) {
// The IDL defines a default value of "allow".
DCHECK(config.hasHardwareAcceleration());
return StringToHardwarePreference(
IDLEnumAsString(config.hardwareAcceleration()));
}
// static
void VideoDecoderTraits::InitializeDecoder(
MediaDecoderType& decoder,
const MediaConfigType& media_config,
MediaDecoderType::InitCB init_cb,
MediaDecoderType::OutputCB output_cb) {
decoder.Initialize(media_config, false /* low_delay */,
nullptr /* cdm_context */, std::move(init_cb), output_cb,
media::WaitingCB());
}
// static
void VideoDecoderTraits::UpdateDecoderLog(const MediaDecoderType& decoder,
const MediaConfigType& media_config,
media::MediaLog* media_log) {
media_log->SetProperty<media::MediaLogProperty::kFrameTitle>(
std::string("VideoDecoder(WebCodecs)"));
media_log->SetProperty<media::MediaLogProperty::kVideoDecoderName>(
decoder.GetDecoderType());
media_log->SetProperty<media::MediaLogProperty::kIsPlatformVideoDecoder>(
decoder.IsPlatformDecoder());
media_log->SetProperty<media::MediaLogProperty::kVideoTracks>(
std::vector<MediaConfigType>{media_config});
}
// static
VideoDecoderTraits::OutputType* VideoDecoderTraits::MakeOutput(
scoped_refptr<MediaOutputType> output,
ExecutionContext* context) {
return MakeGarbageCollected<VideoDecoderTraits::OutputType>(std::move(output),
context);
}
// static
int VideoDecoderTraits::GetMaxDecodeRequests(const MediaDecoderType& decoder) {
return decoder.GetMaxDecodeRequests();
}
// static
VideoDecoder* VideoDecoder::Create(ScriptState* script_state,
const VideoDecoderInit* init,
ExceptionState& exception_state) {
auto* result =
MakeGarbageCollected<VideoDecoder>(script_state, init, exception_state);
return exception_state.HadException() ? nullptr : result;
}
// static
ScriptPromise VideoDecoder::isConfigSupported(ScriptState* script_state,
const VideoDecoderConfig* config,
ExceptionState& exception_state) {
HardwarePreference hw_pref = GetHardwareAccelerationPreference(*config);
if (hw_pref == HardwarePreference::kRequire)
return IsAcceleratedConfigSupported(script_state, config, exception_state);
media::VideoType video_type;
String console_message;
if (!IsValidConfig(*config, video_type, console_message)) {
exception_state.ThrowTypeError(console_message);
return ScriptPromise();
}
// Accept all supported configs if we are not requiring hardware only.
VideoDecoderSupport* support = VideoDecoderSupport::Create();
support->setSupported(media::IsSupportedVideoType(video_type));
support->setConfig(CopyConfig(*config));
return ScriptPromise::Cast(script_state, ToV8(support, script_state));
}
ScriptPromise VideoDecoder::IsAcceleratedConfigSupported(
ScriptState* script_state,
const VideoDecoderConfig* config,
ExceptionState& exception_state) {
String console_message;
auto media_config = std::make_unique<MediaConfigType>();
CodecConfigEval config_eval;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
std::unique_ptr<media::H264ToAnnexBBitstreamConverter> h264_converter;
std::unique_ptr<media::mp4::AVCDecoderConfigurationRecord> h264_avcc;
config_eval = MakeMediaVideoDecoderConfig(
*config, *media_config, h264_converter, h264_avcc, console_message);
#else
config_eval =
MakeMediaVideoDecoderConfig(*config, *media_config, console_message);
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
if (config_eval != CodecConfigEval::kSupported) {
exception_state.ThrowTypeError(console_message);
return ScriptPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
VideoDecoderSupport* support = VideoDecoderSupport::Create();
support->setConfig(CopyConfig(*config));
if (IsMainThread()) {
media::GpuVideoAcceleratorFactories* gpu_factories =
Platform::Current()->GetGpuFactories();
DecoderSupport_OnGpuFactories(support, std::move(media_config), resolver,
gpu_factories);
} else {
auto on_gpu_factories_cb = CrossThreadBindOnce(
&DecoderSupport_OnGpuFactories, WrapCrossThreadPersistent(support),
std::move(media_config), WrapCrossThreadPersistent(resolver));
Thread::MainThread()->GetTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE,
ConvertToBaseOnceCallback(
CrossThreadBindOnce(&GetGpuFactoriesOnMainThread)),
ConvertToBaseOnceCallback(std::move(on_gpu_factories_cb)));
}
return promise;
}
HardwarePreference VideoDecoder::GetHardwarePreference(
const ConfigType& config) {
return GetHardwareAccelerationPreference(config);
}
void VideoDecoder::SetHardwarePreference(HardwarePreference preference) {
static_cast<VideoDecoderBroker*>(decoder())->SetHardwarePreference(
preference);
}
// static
// TODO(crbug.com/1179970): rename out_console_message.
CodecConfigEval VideoDecoder::MakeMediaVideoDecoderConfig(
const ConfigType& config,
MediaConfigType& out_media_config,
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
std::unique_ptr<media::H264ToAnnexBBitstreamConverter>& out_h264_converter,
std::unique_ptr<media::mp4::AVCDecoderConfigurationRecord>& out_h264_avcc,
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
String& out_console_message) {
media::VideoType video_type;
if (!IsValidConfig(config, video_type, out_console_message))
return CodecConfigEval::kInvalid;
// TODO(sandersd): Can we allow shared ArrayBuffers?
std::vector<uint8_t> extra_data;
if (config.hasDescription()) {
DOMArrayPiece buffer(config.description());
uint8_t* start = static_cast<uint8_t*>(buffer.Data());
size_t size = buffer.ByteLength();
extra_data.assign(start, start + size);
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
if (video_type.codec == media::kCodecH264 && !extra_data.empty()) {
out_h264_avcc =
std::make_unique<media::mp4::AVCDecoderConfigurationRecord>();
out_h264_converter =
std::make_unique<media::H264ToAnnexBBitstreamConverter>();
if (!out_h264_converter->ParseConfiguration(
extra_data.data(), static_cast<uint32_t>(extra_data.size()),
out_h264_avcc.get())) {
out_console_message = "Failed to parse avcC.";
return CodecConfigEval::kInvalid;
}
} else {
out_h264_avcc.reset();
out_h264_converter.reset();
}
#else
if (video_type.codec == media::kCodecH264) {
out_console_message = "H.264 decoding is not supported.";
return CodecConfigEval::kUnsupported;
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
// TODO(sandersd): Use size information from the VideoDecoderConfig when it is
// provided, and figure out how to combine it with the avcC. Update fuzzer to
// match.
gfx::Size size = gfx::Size(1280, 720);
out_media_config.Initialize(
video_type.codec, video_type.profile,
media::VideoDecoderConfig::AlphaMode::kIsOpaque, video_type.color_space,
media::kNoTransformation, size, gfx::Rect(gfx::Point(), size), size,
extra_data, media::EncryptionScheme::kUnencrypted);
return CodecConfigEval::kSupported;
}
VideoDecoder::VideoDecoder(ScriptState* script_state,
const VideoDecoderInit* init,
ExceptionState& exception_state)
: DecoderTemplate<VideoDecoderTraits>(script_state, init, exception_state) {
UseCounter::Count(ExecutionContext::From(script_state),
WebFeature::kWebCodecs);
}
CodecConfigEval VideoDecoder::MakeMediaConfig(const ConfigType& config,
MediaConfigType* out_media_config,
String* out_console_message) {
DCHECK(out_media_config);
DCHECK(out_console_message);
return MakeMediaVideoDecoderConfig(config, *out_media_config,
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
h264_converter_ /* out */,
h264_avcc_ /* out */,
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
*out_console_message);
}
media::StatusOr<scoped_refptr<media::DecoderBuffer>>
VideoDecoder::MakeDecoderBuffer(const InputType& chunk) {
uint8_t* src = static_cast<uint8_t*>(chunk.data()->Data());
size_t src_size = chunk.data()->ByteLength();
scoped_refptr<media::DecoderBuffer> decoder_buffer;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
if (h264_converter_) {
// Note: this may not be safe if support for SharedArrayBuffers is added.
uint32_t output_size = h264_converter_->CalculateNeededOutputBufferSize(
src, static_cast<uint32_t>(src_size), h264_avcc_.get());
if (!output_size) {
return media::Status(media::StatusCode::kH264ParsingError,
"Unable to determine size of bitstream buffer.");
}
std::vector<uint8_t> buf(output_size);
if (!h264_converter_->ConvertNalUnitStreamToByteStream(
src, static_cast<uint32_t>(src_size), h264_avcc_.get(), buf.data(),
&output_size)) {
return media::Status(media::StatusCode::kH264ParsingError,
"Unable to convert NALU to byte stream.");
}
decoder_buffer = media::DecoderBuffer::CopyFrom(buf.data(), output_size);
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
if (!decoder_buffer)
decoder_buffer = media::DecoderBuffer::CopyFrom(src, src_size);
decoder_buffer->set_timestamp(
base::TimeDelta::FromMicroseconds(chunk.timestamp()));
// TODO(sandersd): Use kUnknownTimestamp instead of 0?
decoder_buffer->set_duration(
base::TimeDelta::FromMicroseconds(chunk.duration().value_or(0)));
decoder_buffer->set_is_key_frame(chunk.type() == "key");
return decoder_buffer;
}
} // namespace blink