blob: bf591ae1f35be1935d5f5180844fffc6c0c7865a [file] [log] [blame]
// Copyright 2019 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/webcodecs/video_decoder.h"
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "media/base/decoder_buffer.h"
#include "media/base/limits.h"
#include "media/base/media_util.h"
#include "media/base/mime_util.h"
#include "media/base/supported_types.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_aspect_ratio.h"
#include "media/base/video_decoder.h"
#include "media/base/video_frame.h"
#include "media/media_buildflags.h"
#include "third_party/blink/public/mojom/use_counter/metrics/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/core/v8/to_v8_traits.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk_type.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_color_space_init.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/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/webcodecs/array_buffer_util.h"
#include "third_party/blink/renderer/modules/webcodecs/decrypt_config_util.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/gpu_factories_retriever.h"
#include "third_party/blink/renderer/modules/webcodecs/video_color_space.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/heap/cross_thread_handle.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_copier_std.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/libgav1/src/src/buffer_pool.h"
#include "third_party/libgav1/src/src/decoder_state.h"
#include "third_party/libgav1/src/src/gav1/status_code.h"
#include "third_party/libgav1/src/src/obu_parser.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(ENABLE_LIBVPX)
#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" // nogncheck
#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" // nogncheck
#endif
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/h264_to_annex_b_bitstream_converter.h" // nogncheck
#include "media/formats/mp4/box_definitions.h" // nogncheck
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
#include "media/filters/h265_to_annex_b_bitstream_converter.h" // nogncheck
#include "media/formats/mp4/hevc.h" // nogncheck
#endif
#endif
namespace blink {
namespace {
void DecoderSupport_OnKnown(
VideoDecoderSupport* support,
std::unique_ptr<VideoDecoder::MediaConfigType> media_config,
ScriptPromiseResolver<VideoDecoderSupport>* resolver,
media::GpuVideoAcceleratorFactories* gpu_factories) {
if (!gpu_factories) {
support->setSupported(false);
resolver->Resolve(support);
return;
}
DCHECK(gpu_factories->IsDecoderSupportKnown());
support->setSupported(
gpu_factories->IsDecoderConfigSupportedOrUnknown(*media_config) ==
media::GpuVideoAcceleratorFactories::Supported::kTrue);
resolver->Resolve(support);
}
bool ParseCodecString(const String& codec_string,
media::VideoType& out_video_type,
String& js_error_message) {
if (codec_string.LengthWithStrippedWhiteSpace() == 0) {
js_error_message = "Invalid codec; codec is required.";
return false;
}
auto result = media::ParseVideoCodecString("", codec_string.Utf8(),
/*allow_ambiguous_matches=*/false);
if (!result) {
js_error_message = "Unknown or ambiguous codec name.";
out_video_type = {media::VideoCodec::kUnknown,
media::VIDEO_CODEC_PROFILE_UNKNOWN,
media::kNoVideoCodecLevel, media::VideoColorSpace()};
return true;
}
out_video_type = {result->codec, result->profile, result->level,
result->color_space};
return true;
}
VideoDecoderConfig* CopyConfig(const VideoDecoderConfig& config) {
VideoDecoderConfig* copy = VideoDecoderConfig::Create();
copy->setCodec(config.codec());
if (config.hasDescription()) {
auto desc_wrapper = AsSpan<const uint8_t>(config.description());
if (!desc_wrapper.data()) {
// Checked by IsValidVideoDecoderConfig.
NOTREACHED();
}
DOMArrayBuffer* buffer_copy = DOMArrayBuffer::Create(desc_wrapper);
copy->setDescription(
MakeGarbageCollected<AllowSharedBufferSource>(buffer_copy));
}
if (config.hasCodedWidth())
copy->setCodedWidth(config.codedWidth());
if (config.hasCodedHeight())
copy->setCodedHeight(config.codedHeight());
if (config.hasDisplayAspectWidth())
copy->setDisplayAspectWidth(config.displayAspectWidth());
if (config.hasDisplayAspectHeight())
copy->setDisplayAspectHeight(config.displayAspectHeight());
if (config.hasColorSpace()) {
VideoColorSpace* color_space =
MakeGarbageCollected<VideoColorSpace>(config.colorSpace());
copy->setColorSpace(color_space->toJSON());
}
if (config.hasHardwareAcceleration())
copy->setHardwareAcceleration(config.hardwareAcceleration());
if (config.hasOptimizeForLatency())
copy->setOptimizeForLatency(config.optimizeForLatency());
if (config.hasFlip()) {
copy->setFlip(config.flip());
}
if (config.hasRotation()) {
copy->setRotation(config.rotation());
}
return copy;
}
void ParseAv1KeyFrame(const media::DecoderBuffer& buffer,
libgav1::BufferPool* buffer_pool,
bool* is_key_frame) {
libgav1::DecoderState decoder_state;
auto buffer_span = base::span(buffer);
libgav1::ObuParser parser(buffer_span.data(), buffer_span.size(),
/*operating_point=*/0, buffer_pool, &decoder_state);
libgav1::RefCountedBufferPtr frame;
libgav1::StatusCode status_code = parser.ParseOneFrame(&frame);
*is_key_frame = status_code == libgav1::kStatusOk &&
parser.frame_header().frame_type == libgav1::kFrameKey;
}
void ParseVpxKeyFrame(const media::DecoderBuffer& buffer,
media::VideoCodec codec,
bool* is_key_frame) {
#if BUILDFLAG(ENABLE_LIBVPX)
auto buffer_span = base::span(buffer);
vpx_codec_stream_info_t stream_info = {0};
stream_info.sz = sizeof(vpx_codec_stream_info_t);
auto status = vpx_codec_peek_stream_info(
codec == media::VideoCodec::kVP8 ? vpx_codec_vp8_dx()
: vpx_codec_vp9_dx(),
buffer_span.data(), static_cast<uint32_t>(buffer_span.size()),
&stream_info);
*is_key_frame = (status == VPX_CODEC_OK) && stream_info.is_kf;
#endif
}
void ParseH264KeyFrame(const media::DecoderBuffer& buffer, bool* is_key_frame) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
auto buffer_span = base::span(buffer);
auto result =
media::mp4::AVC::AnalyzeAnnexB(buffer_span.data(), buffer_span.size(),
std::vector<media::SubsampleEntry>());
*is_key_frame = result.is_keyframe.value_or(false);
#endif
}
void ParseH265KeyFrame(const media::DecoderBuffer& buffer, bool* is_key_frame) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
auto buffer_span = base::span(buffer);
auto result =
media::mp4::HEVC::AnalyzeAnnexB(buffer_span.data(), buffer_span.size(),
std::vector<media::SubsampleEntry>());
*is_key_frame = result.is_keyframe.value_or(false);
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
}
} // namespace
// 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 IdlEnumToHardwarePreference(config.hardwareAcceleration());
}
// static
void VideoDecoderTraits::InitializeDecoder(
MediaDecoderType& decoder,
bool low_delay,
const MediaConfigType& media_config,
MediaDecoderType::InitCB init_cb,
MediaDecoderType::OutputCB output_cb) {
decoder.Initialize(media_config, 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::kVideoDecoderName>(
decoder.GetDecoderType());
media_log->SetProperty<media::MediaLogProperty::kIsPlatformVideoDecoder>(
decoder.IsPlatformDecoder());
media_log->SetProperty<media::MediaLogProperty::kVideoTracks>(
std::vector<MediaConfigType>{media_config});
MEDIA_LOG(INFO, media_log)
<< "Initialized VideoDecoder: " << media_config.AsHumanReadableString();
base::UmaHistogramEnumeration("Blink.WebCodecs.VideoDecoder.Codec",
media_config.codec());
}
// static
int VideoDecoderTraits::GetMaxDecodeRequests(const MediaDecoderType& decoder) {
return decoder.GetMaxDecodeRequests();
}
// static
const char* VideoDecoderTraits::GetName() {
return "VideoDecoder";
}
// 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<VideoDecoderSupport> VideoDecoder::isConfigSupported(
ScriptState* script_state,
const VideoDecoderConfig* config,
ExceptionState& exception_state) {
// Run the "check if a config is a valid VideoDecoderConfig" algorithm.
String js_error_message;
std::optional<media::VideoType> video_type =
IsValidVideoDecoderConfig(*config, &js_error_message /* out */);
if (!video_type) {
exception_state.ThrowTypeError(js_error_message);
return EmptyPromise();
}
// Run the "Clone Configuration" algorithm.
auto* config_copy = CopyConfig(*config);
// Run the "Check Configuration Support" algorithm.
HardwarePreference hw_pref = GetHardwareAccelerationPreference(*config_copy);
VideoDecoderSupport* support = VideoDecoderSupport::Create();
support->setConfig(config_copy);
if ((hw_pref == HardwarePreference::kPreferSoftware &&
!media::IsDecoderBuiltInVideoCodec(video_type->codec)) ||
!media::IsDecoderSupportedVideoType(*video_type)) {
support->setSupported(false);
return ToResolvedPromise<VideoDecoderSupport>(script_state, support);
}
// Check that we can make a media::VideoDecoderConfig. The |js_error_message|
// is ignored, we report only via |support.supported|.
std::optional<MediaConfigType> media_config;
media_config = MakeMediaVideoDecoderConfig(*config_copy, &js_error_message);
if (!media_config) {
support->setSupported(false);
return ToResolvedPromise<VideoDecoderSupport>(script_state, support);
}
// If hardware is preferred, asynchronously check for a hardware decoder.
if (hw_pref == HardwarePreference::kPreferHardware) {
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolver<VideoDecoderSupport>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
RetrieveGpuFactoriesWithKnownDecoderSupport(CrossThreadBindOnce(
&DecoderSupport_OnKnown, MakeUnwrappingCrossThreadHandle(support),
std::make_unique<MediaConfigType>(*media_config),
MakeUnwrappingCrossThreadHandle(resolver)));
return promise;
}
// Otherwise, the config is supported.
support->setSupported(true);
return ToResolvedPromise<VideoDecoderSupport>(script_state, support);
}
HardwarePreference VideoDecoder::GetHardwarePreference(
const ConfigType& config) {
return GetHardwareAccelerationPreference(config);
}
bool VideoDecoder::GetLowDelayPreference(const ConfigType& config) {
return config.hasOptimizeForLatency() && config.optimizeForLatency();
}
void VideoDecoder::SetHardwarePreference(HardwarePreference preference) {
static_cast<VideoDecoderBroker*>(decoder())->SetHardwarePreference(
preference);
}
// static
// TODO(crbug.com/1198324): Merge shared logic with VideoFramePlaneInit.
std::optional<media::VideoType> VideoDecoder::IsValidVideoDecoderConfig(
const VideoDecoderConfig& config,
String* js_error_message) {
media::VideoType video_type;
if (!ParseCodecString(config.codec(), video_type, *js_error_message))
return std::nullopt;
if (config.hasDescription()) {
auto desc_wrapper = AsSpan<const uint8_t>(config.description());
if (!desc_wrapper.data()) {
*js_error_message = "Invalid config, description is detached.";
return std::nullopt;
}
}
if (config.hasCodedWidth() || config.hasCodedHeight()) {
if (!config.hasCodedWidth()) {
*js_error_message =
"Invalid config, codedHeight specified without codedWidth.";
return std::nullopt;
}
if (!config.hasCodedHeight()) {
*js_error_message =
"Invalid config, codedWidth specified without codedHeight.";
return std::nullopt;
}
const uint32_t coded_width = config.codedWidth();
const uint32_t coded_height = config.codedHeight();
if (!coded_width || !coded_height) {
*js_error_message = String::Format("Invalid coded size (%u, %u).",
coded_width, coded_height);
return std::nullopt;
}
}
if (config.hasDisplayAspectWidth() || config.hasDisplayAspectHeight()) {
if (!config.hasDisplayAspectWidth()) {
*js_error_message =
"Invalid config, displayAspectHeight specified without "
"displayAspectWidth.";
return std::nullopt;
}
if (!config.hasDisplayAspectHeight()) {
*js_error_message =
"Invalid config, displayAspectWidth specified without "
"displayAspectHeight.";
return std::nullopt;
}
uint32_t display_aspect_width = config.displayAspectWidth();
uint32_t display_aspect_height = config.displayAspectHeight();
if (display_aspect_width == 0 || display_aspect_height == 0) {
*js_error_message =
String::Format("Invalid display aspect (%u, %u).",
display_aspect_width, display_aspect_height);
return std::nullopt;
}
}
return video_type;
}
// static
std::optional<media::VideoDecoderConfig>
VideoDecoder::MakeMediaVideoDecoderConfig(const ConfigType& config,
String* js_error_message,
bool* needs_converter_out) {
std::unique_ptr<VideoDecoderHelper> decoder_helper;
VideoDecoder::DecoderSpecificData decoder_specific_data;
return MakeMediaVideoDecoderConfigInternal(
config, decoder_specific_data, js_error_message, needs_converter_out);
}
// static
std::optional<media::VideoDecoderConfig>
VideoDecoder::MakeMediaVideoDecoderConfigInternal(
const ConfigType& config,
DecoderSpecificData& decoder_specific_data,
String* js_error_message,
bool* needs_converter_out) {
media::VideoType video_type;
if (!ParseCodecString(config.codec(), video_type, *js_error_message)) {
// Checked by IsValidVideoDecoderConfig().
NOTREACHED();
}
if (video_type.codec == media::VideoCodec::kUnknown) {
return std::nullopt;
}
std::vector<uint8_t> extra_data;
if (config.hasDescription()) {
auto desc_wrapper = AsSpan<const uint8_t>(config.description());
if (!desc_wrapper.data()) {
// Checked by IsValidVideoDecoderConfig().
NOTREACHED();
}
if (!desc_wrapper.empty()) {
extra_data.assign(base::to_address(desc_wrapper.begin()),
base::to_address(desc_wrapper.end()));
}
}
if (needs_converter_out) {
*needs_converter_out = (extra_data.size() > 0);
}
if ((extra_data.size() > 0) &&
(video_type.codec == media::VideoCodec::kH264 ||
video_type.codec == media::VideoCodec::kHEVC)) {
VideoDecoderHelper::Status status;
decoder_specific_data.decoder_helper =
VideoDecoderHelper::Create(video_type, extra_data, &status);
if (status != VideoDecoderHelper::Status::kSucceed) {
if (video_type.codec == media::VideoCodec::kH264) {
if (status == VideoDecoderHelper::Status::kDescriptionParseFailed) {
*js_error_message = "Failed to parse avcC.";
} else if (status == VideoDecoderHelper::Status::kUnsupportedCodec) {
*js_error_message = "H.264 decoding is not supported.";
}
} else if (video_type.codec == media::VideoCodec::kHEVC) {
if (status == VideoDecoderHelper::Status::kDescriptionParseFailed) {
*js_error_message = "Failed to parse hvcC.";
} else if (status == VideoDecoderHelper::Status::kUnsupportedCodec) {
*js_error_message = "HEVC decoding is not supported.";
}
}
return std::nullopt;
}
// The description should not be provided to the decoder because the stream
// will be converted to Annex B format.
extra_data.clear();
} else {
decoder_specific_data.decoder_helper.reset();
}
if (video_type.codec == media::VideoCodec::kAV1 &&
!decoder_specific_data.av1_buffer_pool) {
decoder_specific_data.av1_buffer_pool =
std::make_unique<libgav1::BufferPool>(
/*on_frame_buffer_size_changed=*/nullptr,
/*get_frame_buffer=*/nullptr,
/*release_frame_buffer=*/nullptr,
/*callback_private_data=*/nullptr);
}
// Guess 720p if no coded size hint is provided. This choice should result in
// a preference for hardware decode.
gfx::Size coded_size = gfx::Size(1280, 720);
if (config.hasCodedWidth() && config.hasCodedHeight())
coded_size = gfx::Size(config.codedWidth(), config.codedHeight());
// These are meaningless.
// TODO(crbug.com/1214061): Remove.
gfx::Rect visible_rect(gfx::Point(), coded_size);
gfx::Size natural_size = coded_size;
// Note: Using a default-constructed VideoAspectRatio allows decoders to
// override using in-band metadata.
media::VideoAspectRatio aspect_ratio;
if (config.hasDisplayAspectWidth() && config.hasDisplayAspectHeight()) {
aspect_ratio = media::VideoAspectRatio::DAR(config.displayAspectWidth(),
config.displayAspectHeight());
}
// TODO(crbug.com/1138680): Ensure that this default value is acceptable
// under the WebCodecs spec. Should be BT.709 for YUV, sRGB for RGB, or
// whatever was explicitly set for codec strings that include a color space.
media::VideoColorSpace media_color_space = video_type.color_space;
if (config.hasColorSpace()) {
VideoColorSpace* color_space =
MakeGarbageCollected<VideoColorSpace>(config.colorSpace());
media_color_space = color_space->ToMediaColorSpace();
}
auto encryption_scheme = media::EncryptionScheme::kUnencrypted;
if (config.hasEncryptionScheme()) {
auto scheme = ToMediaEncryptionScheme(config.encryptionScheme());
if (!scheme) {
*js_error_message = "Unsupported encryption scheme";
return std::nullopt;
}
encryption_scheme = scheme.value();
}
auto transformation =
media::VideoTransformation(config.rotation(), config.flip());
media::VideoDecoderConfig media_config;
media_config.Initialize(video_type.codec, video_type.profile,
media::VideoDecoderConfig::AlphaMode::kIsOpaque,
media_color_space, transformation, coded_size,
visible_rect, natural_size, extra_data,
encryption_scheme);
media_config.set_aspect_ratio(aspect_ratio);
if (!media_config.IsValidConfig()) {
*js_error_message = "Unsupported config.";
return std::nullopt;
}
return media_config;
}
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);
}
VideoDecoder::~VideoDecoder() = default;
bool VideoDecoder::IsValidConfig(const ConfigType& config,
String* js_error_message) {
return IsValidVideoDecoderConfig(config, js_error_message /* out */)
.has_value();
}
std::optional<media::VideoDecoderConfig> VideoDecoder::MakeMediaConfig(
const ConfigType& config,
String* js_error_message) {
DCHECK(js_error_message);
auto media_config = MakeMediaVideoDecoderConfigInternal(
config, decoder_specific_data_ /* out */, js_error_message /* out */);
pending_codec_ =
media_config ? media_config->codec() : media::VideoCodec::kUnknown;
return media_config;
}
media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
VideoDecoder::MakeInput(const InputType& chunk, bool verify_key_frame) {
scoped_refptr<media::DecoderBuffer> decoder_buffer = chunk.buffer();
if (decoder_specific_data_.decoder_helper) {
auto decoder_buffer_span = base::span(*chunk.buffer());
// Note: this may not be safe if support for SharedArrayBuffers is added.
uint32_t output_size =
decoder_specific_data_.decoder_helper->CalculateNeededOutputBufferSize(
decoder_buffer_span, verify_key_frame);
if (!output_size) {
return media::DecoderStatus(
media::DecoderStatus::Codes::kMalformedBitstream,
"Unable to determine size of bitstream buffer.");
}
std::vector<uint8_t> buf(output_size);
if (decoder_specific_data_.decoder_helper->ConvertNalUnitStreamToByteStream(
decoder_buffer_span, buf, &output_size, verify_key_frame) !=
VideoDecoderHelper::Status::kSucceed) {
return media::DecoderStatus(
media::DecoderStatus::Codes::kMalformedBitstream,
"Unable to convert NALU to byte stream.");
}
decoder_buffer =
media::DecoderBuffer::CopyFrom(base::span(buf).first(output_size));
decoder_buffer->set_timestamp(chunk.buffer()->timestamp());
decoder_buffer->set_duration(chunk.buffer()->duration());
}
bool is_key_frame = chunk.type() == V8EncodedVideoChunkType::Enum::kKey;
if (verify_key_frame) {
if (pending_codec_ == media::VideoCodec::kVP9 ||
pending_codec_ == media::VideoCodec::kVP8) {
ParseVpxKeyFrame(*decoder_buffer, pending_codec_, &is_key_frame);
} else if (pending_codec_ == media::VideoCodec::kAV1 &&
decoder_specific_data_.av1_buffer_pool) {
ParseAv1KeyFrame(*decoder_buffer,
decoder_specific_data_.av1_buffer_pool.get(),
&is_key_frame);
} else if (pending_codec_ == media::VideoCodec::kH264) {
ParseH264KeyFrame(*decoder_buffer, &is_key_frame);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// Use a more helpful error message if we think the user may have forgot
// to provide a description for AVC H.264. We could try to guess at the
// NAL unit size and see if a NAL unit parses out, but this seems fine.
if (!is_key_frame && !decoder_specific_data_.decoder_helper) {
return media::DecoderStatus(
media::DecoderStatus::Codes::kKeyFrameRequired,
"A key frame is required after configure() or flush(). If you're "
"using AVC formatted H.264 you must fill out the description field "
"in the VideoDecoderConfig.");
}
#endif
} else if (pending_codec_ == media::VideoCodec::kHEVC) {
ParseH265KeyFrame(*decoder_buffer, &is_key_frame);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
if (!is_key_frame && !decoder_specific_data_.decoder_helper) {
return media::DecoderStatus(
media::DecoderStatus::Codes::kKeyFrameRequired,
"A key frame is required after configure() or flush(). If you're "
"using HEVC formatted H.265 you must fill out the description "
"field in the VideoDecoderConfig.");
}
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
}
if (!is_key_frame) {
return media::DecoderStatus(
media::DecoderStatus::Codes::kKeyFrameRequired,
"A key frame is required after configure() or flush().");
}
}
chunk_metadata_[chunk.buffer()->timestamp()] =
ChunkMetadata{chunk.buffer()->duration()};
return decoder_buffer;
}
media::DecoderStatus::Or<VideoDecoder::OutputType*> VideoDecoder::MakeOutput(
scoped_refptr<MediaOutputType> output,
ExecutionContext* context) {
if (output) {
output->metadata().transformation = active_transform_;
}
const auto it = chunk_metadata_.find(output->timestamp());
if (it != chunk_metadata_.end()) {
const auto duration = it->second.duration;
if (!duration.is_zero() && duration != media::kNoTimestamp) {
auto wrapped_output = media::VideoFrame::WrapVideoFrame(
output, output->format(), output->visible_rect(),
output->natural_size());
wrapped_output->set_color_space(output->ColorSpace());
wrapped_output->metadata().frame_duration = duration;
output = wrapped_output;
}
// We erase from the beginning onward to our target frame since frames
// should be returned in presentation order.
chunk_metadata_.erase(chunk_metadata_.begin(), it + 1);
}
return MakeGarbageCollected<OutputType>(std::move(output), context);
}
void VideoDecoder::OnActiveConfigChanged(const MediaConfigType& config) {
active_transform_ = config.video_transformation();
}
const AtomicString& VideoDecoder::InterfaceName() const {
return event_target_names::kVideoDecoder;
}
} // namespace blink