Commit 456bdd17 authored by Chris Cunningham's avatar Chris Cunningham Committed by Chromium LUCI CQ

Implement AudioDecoder.isConfigSupported()

API and motivations described here:
https://github.com/WICG/web-codecs/issues/98

Spec PR here:
https://github.com/WICG/web-codecs/pull/120

Bug: 1141707
Test: new layout tests
Change-Id: I3a0f3cbc2bb64a86dcefdc454d6b03e72d3f36bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2622421Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Chrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842153}
parent 0416d890
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "media/base/supported_types.h" #include "media/base/supported_types.h"
#include "media/base/waiting.h" #include "media/base/waiting.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h" #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_config.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_init.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk.h"
...@@ -27,6 +28,28 @@ ...@@ -27,6 +28,28 @@
namespace blink { namespace blink {
bool IsValidConfig(const AudioDecoderConfig& config,
media::AudioType& out_audio_type,
String& out_console_message) {
media::AudioCodec codec = media::kUnknownAudioCodec;
bool is_codec_ambiguous = true;
bool parse_succeeded = ParseAudioCodecString("", config.codec().Utf8(),
&is_codec_ambiguous, &codec);
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_audio_type = {codec};
return true;
}
// static // static
std::unique_ptr<AudioDecoderTraits::MediaDecoderType> std::unique_ptr<AudioDecoderTraits::MediaDecoderType>
AudioDecoderTraits::CreateDecoder( AudioDecoderTraits::CreateDecoder(
...@@ -81,30 +104,31 @@ AudioDecoder* AudioDecoder::Create(ScriptState* script_state, ...@@ -81,30 +104,31 @@ AudioDecoder* AudioDecoder::Create(ScriptState* script_state,
exception_state); exception_state);
} }
// static
ScriptPromise AudioDecoder::isConfigSupported(ScriptState* script_state,
const AudioDecoderConfig* config,
ExceptionState& exception_state) {
media::AudioType audio_type;
String console_message;
if (!IsValidConfig(*config, audio_type, console_message)) {
exception_state.ThrowTypeError(console_message);
return ScriptPromise();
}
bool is_supported = media::IsSupportedAudioType(audio_type);
return ScriptPromise::Cast(script_state, ToV8(is_supported, script_state));
}
// static // static
CodecConfigEval AudioDecoder::MakeMediaAudioDecoderConfig( CodecConfigEval AudioDecoder::MakeMediaAudioDecoderConfig(
const ConfigType& config, const ConfigType& config,
MediaConfigType& out_media_config, MediaConfigType& out_media_config,
String& out_console_message) { String& out_console_message) {
media::AudioCodec codec = media::kUnknownAudioCodec; media::AudioType audio_type;
bool is_codec_ambiguous = true;
bool parse_succeeded = ParseAudioCodecString("", config.codec().Utf8(),
&is_codec_ambiguous, &codec);
if (!parse_succeeded) { if (!IsValidConfig(config, audio_type, out_console_message))
out_console_message = "Failed to parse codec string.";
return CodecConfigEval::kInvalid; return CodecConfigEval::kInvalid;
}
if (is_codec_ambiguous) {
out_console_message = "Codec string is ambiguous.";
return CodecConfigEval::kInvalid;
}
if (!media::IsSupportedAudioType({codec})) {
out_console_message = "Configuration is not supported.";
return CodecConfigEval::kUnsupported;
}
std::vector<uint8_t> extra_data; std::vector<uint8_t> extra_data;
if (config.hasDescription()) { if (config.hasDescription()) {
...@@ -131,8 +155,8 @@ CodecConfigEval AudioDecoder::MakeMediaAudioDecoderConfig( ...@@ -131,8 +155,8 @@ CodecConfigEval AudioDecoder::MakeMediaAudioDecoderConfig(
// TODO(chcunningham): Add sample format to IDL. // TODO(chcunningham): Add sample format to IDL.
out_media_config.Initialize( out_media_config.Initialize(
codec, media::kSampleFormatPlanarF32, channel_layout, config.sampleRate(), audio_type.codec, media::kSampleFormatPlanarF32, channel_layout,
extra_data, media::EncryptionScheme::kUnencrypted, config.sampleRate(), extra_data, media::EncryptionScheme::kUnencrypted,
base::TimeDelta() /* seek preroll */, 0 /* codec delay */); base::TimeDelta() /* seek preroll */, 0 /* codec delay */);
return CodecConfigEval::kSupported; return CodecConfigEval::kSupported;
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include "media/base/status.h" #include "media/base/status.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.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/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_output_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h"
#include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_config_eval.h" #include "third_party/blink/renderer/modules/webcodecs/codec_config_eval.h"
...@@ -38,6 +37,7 @@ class AudioFrame; ...@@ -38,6 +37,7 @@ class AudioFrame;
class EncodedAudioChunk; class EncodedAudioChunk;
class ExceptionState; class ExceptionState;
class AudioDecoderInit; class AudioDecoderInit;
class ScriptPromise;
class V8AudioFrameOutputCallback; class V8AudioFrameOutputCallback;
class MODULES_EXPORT AudioDecoderTraits { class MODULES_EXPORT AudioDecoderTraits {
...@@ -77,6 +77,10 @@ class MODULES_EXPORT AudioDecoder : public DecoderTemplate<AudioDecoderTraits> { ...@@ -77,6 +77,10 @@ class MODULES_EXPORT AudioDecoder : public DecoderTemplate<AudioDecoderTraits> {
const AudioDecoderInit*, const AudioDecoderInit*,
ExceptionState&); ExceptionState&);
static ScriptPromise isConfigSupported(ScriptState*,
const AudioDecoderConfig*,
ExceptionState&);
// For use by MediaSource and by ::MakeMediaConfig. // For use by MediaSource and by ::MakeMediaConfig.
static CodecConfigEval MakeMediaAudioDecoderConfig( static CodecConfigEval MakeMediaAudioDecoderConfig(
const ConfigType& config, const ConfigType& config,
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
// |decodeQueueSize| is greater than a constant. // |decodeQueueSize| is greater than a constant.
readonly attribute long decodeQueueSize; readonly attribute long decodeQueueSize;
// Which state the decoder is in, indicating which methods can be called.
readonly attribute CodecState state;
// Set the stream configuration for future decode() requests. // Set the stream configuration for future decode() requests.
// //
// The next decode request must be for a keyframe. // The next decode request must be for a keyframe.
...@@ -57,6 +60,7 @@ ...@@ -57,6 +60,7 @@
// Not recoverable: make a new AudioDecoder if needed. // Not recoverable: make a new AudioDecoder if needed.
[RaisesException] void close(); [RaisesException] void close();
// Which state the decoder is in, indicating which methods can be called. // Call prior to configure() to determine whether config will be supported.
readonly attribute CodecState state; [CallWith=ScriptState, RaisesException]
static Promise<boolean> isConfigSupported(AudioDecoderConfig config);
}; };
...@@ -269,9 +269,9 @@ bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) { ...@@ -269,9 +269,9 @@ bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) {
decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_), decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_),
gpu_factories_, logger_->log()); gpu_factories_, logger_->log());
if (!decoder_) { if (!decoder_) {
Shutdown(logger_->MakeException( Shutdown(
"Configuration error: Could not create decoder.", logger_->MakeException("Internal error: Could not create decoder.",
media::StatusCode::kDecoderCreationFailed)); media::StatusCode::kDecoderCreationFailed));
return false; return false;
} }
...@@ -468,7 +468,9 @@ void DecoderTemplate<Traits>::OnConfigureFlushDone(media::Status status) { ...@@ -468,7 +468,9 @@ void DecoderTemplate<Traits>::OnConfigureFlushDone(media::Status status) {
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure); DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
if (!status.is_ok()) { if (!status.is_ok()) {
Shutdown(logger_->MakeException("Configuration error.", status)); Shutdown(logger_->MakeException(
"Internal error: failed to flush out frames from previous config.",
status));
return; return;
} }
...@@ -490,7 +492,13 @@ void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) { ...@@ -490,7 +492,13 @@ void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) {
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure); DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
if (!status.is_ok()) { if (!status.is_ok()) {
Shutdown(logger_->MakeException("Decoder initialization error.", status)); std::string error_message = "Decoder initialization error.";
if (status.code() == media::StatusCode::kDecoderUnsupportedConfig) {
error_message =
"Unsupported configuration. Check isConfigSupported() prior to "
"calling configure().";
}
Shutdown(logger_->MakeException(error_message, status));
return; return;
} }
......
...@@ -15,6 +15,55 @@ function getFakeChunk() { ...@@ -15,6 +15,55 @@ function getFakeChunk() {
}); });
} }
const invalidConfigs = [
{
comment: 'Emtpy codec',
config: {codec: ''},
},
{
comment: 'Unrecognized codec',
config: {codec: 'bogus'},
},
{
comment: 'Video codec',
config: {codec: 'vp8'},
},
{
comment: 'Ambiguous codec',
config: {codec: 'vp9'},
},
{
comment: 'Codec with MIME type',
config: {codec: 'audio/webm; codecs="opus"'},
},
];
invalidConfigs.forEach(entry => {
promise_test(t => {
return promise_rejects_js(t, TypeError, AudioDecoder.isConfigSupported(entry.config));
}, 'Test that AudioDecoder.isConfigSupported() rejects invalid config:' + entry.comment);
});
invalidConfigs.forEach(entry => {
async_test(t => {
let codec = new AudioDecoder(getDefaultCodecInit(t));
assert_throws_js(TypeError, () => { codec.configure(entry.config); });
t.done();
}, 'Test that AudioDecoder.configure() rejects invalid config:' + entry.comment);
});
promise_test(t => {
return AudioDecoder.isConfigSupported({
codec: 'opus',
sampleRate: 48000,
numberOfChannels: 2,
// Opus header extradata.
description: new Uint8Array([0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64,
0x01, 0x02, 0x38, 0x01, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00])
});
}, 'Test AudioDecoder.isConfigSupported() with a valid config');
promise_test(t => { promise_test(t => {
// AudioDecoderInit lacks required fields. // AudioDecoderInit lacks required fields.
assert_throws_js(TypeError, () => { new AudioDecoder({}); }); assert_throws_js(TypeError, () => { new AudioDecoder({}); });
......
...@@ -16,6 +16,7 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -16,6 +16,7 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method constructor [Worker] method constructor
[Worker] setter onabort [Worker] setter onabort
[Worker] interface AudioDecoder [Worker] interface AudioDecoder
[Worker] static method isConfigSupported
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] getter decodeQueueSize [Worker] getter decodeQueueSize
[Worker] getter state [Worker] getter state
......
...@@ -271,6 +271,7 @@ interface AudioContext : BaseAudioContext ...@@ -271,6 +271,7 @@ interface AudioContext : BaseAudioContext
method resume method resume
method suspend method suspend
interface AudioDecoder interface AudioDecoder
static method isConfigSupported
attribute @@toStringTag attribute @@toStringTag
getter decodeQueueSize getter decodeQueueSize
getter state getter state
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment