| // Copyright 2020 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/audio_decoder.h" | 
 |  | 
 | #include <memory> | 
 | #include <vector> | 
 |  | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/types/to_address.h" | 
 | #include "media/base/audio_codecs.h" | 
 | #include "media/base/audio_decoder.h" | 
 | #include "media/base/audio_decoder_config.h" | 
 | #include "media/base/channel_layout.h" | 
 | #include "media/base/encryption_scheme.h" | 
 | #include "media/base/media_util.h" | 
 | #include "media/base/mime_util.h" | 
 | #include "media/base/supported_types.h" | 
 | #include "media/base/waiting.h" | 
 | #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-blink.h" | 
 | #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" | 
 | #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.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_support.h" | 
 | #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk.h" | 
 | #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h" | 
 | #include "third_party/blink/renderer/modules/webcodecs/array_buffer_util.h" | 
 | #include "third_party/blink/renderer/modules/webcodecs/audio_data.h" | 
 | #include "third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.h" | 
 | #include "third_party/blink/renderer/modules/webcodecs/decrypt_config_util.h" | 
 | #include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk.h" | 
 | #include "third_party/blink/renderer/platform/audio/audio_utilities.h" | 
 | #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" | 
 |  | 
 | namespace blink { | 
 |  | 
 | bool VerifyDescription(const AudioDecoderConfig& config, | 
 |                        String* js_error_message) { | 
 |   // https://www.w3.org/TR/webcodecs-flac-codec-registration | 
 |   // https://www.w3.org/TR/webcodecs-vorbis-codec-registration | 
 |   bool description_required = false; | 
 |   if (config.codec() == "flac" || config.codec() == "vorbis") { | 
 |     description_required = true; | 
 |   } | 
 |  | 
 |   if (description_required && !config.hasDescription()) { | 
 |     *js_error_message = "Invalid config; description is required."; | 
 |     return false; | 
 |   } | 
 |  | 
 |   // For Opus with more than 2 channels, we need a description. While we can | 
 |   // guess a channel mapping for up to 8 channels, we don't know whether the | 
 |   // encoded Opus streams will be mono or stereo streams. | 
 |   if (config.codec() == "opus" && config.numberOfChannels() > 2 && | 
 |       !config.hasDescription()) { | 
 |     *js_error_message = | 
 |         "Invalid config; description is required for multi-channel Opus."; | 
 |     return false; | 
 |   } | 
 |  | 
 |   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 false; | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | AudioDecoderConfig* CopyConfig(const AudioDecoderConfig& config) { | 
 |   AudioDecoderConfig* copy = AudioDecoderConfig::Create(); | 
 |   copy->setCodec(config.codec()); | 
 |   copy->setSampleRate(config.sampleRate()); | 
 |   copy->setNumberOfChannels(config.numberOfChannels()); | 
 |   if (config.hasDescription()) { | 
 |     auto desc_wrapper = AsSpan<const uint8_t>(config.description()); | 
 |     if (!desc_wrapper.empty()) { | 
 |       DOMArrayBuffer* buffer_copy = DOMArrayBuffer::Create(desc_wrapper); | 
 |       copy->setDescription( | 
 |           MakeGarbageCollected<AllowSharedBufferSource>(buffer_copy)); | 
 |     } | 
 |   } | 
 |   return copy; | 
 | } | 
 |  | 
 | std::optional<media::AudioCodec> TryGetPcmCodec(const String& codec) { | 
 |   String codecs_str = codec.LowerASCII(); | 
 |   if (codecs_str == "ulaw") { | 
 |     return media::AudioCodec::kPCM_MULAW; | 
 |   } | 
 |  | 
 |   if (codecs_str == "alaw") { | 
 |     return media::AudioCodec::kPCM_ALAW; | 
 |   } | 
 |  | 
 |   if (codecs_str == "pcm-u8" || codecs_str == "pcm-s16" || | 
 |       codecs_str == "pcm-s24" || codecs_str == "pcm-s32" || | 
 |       codecs_str == "pcm-f32") { | 
 |     return media::AudioCodec::kPCM; | 
 |   } | 
 |  | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | media::SampleFormat PcmCodecToSampleFormat(const String& codec) { | 
 |   String codecs_str = codec.LowerASCII(); | 
 |  | 
 |   if (codecs_str == "pcm-u8") { | 
 |     return media::SampleFormat::kSampleFormatU8; | 
 |   } | 
 |  | 
 |   if (codecs_str == "pcm-s16") { | 
 |     return media::SampleFormat::kSampleFormatS16; | 
 |   } | 
 |  | 
 |   if (codecs_str == "pcm-s24") { | 
 |     return media::SampleFormat::kSampleFormatS24; | 
 |   } | 
 |  | 
 |   if (codecs_str == "pcm-s32") { | 
 |     return media::SampleFormat::kSampleFormatS32; | 
 |   } | 
 |  | 
 |   if (codecs_str == "pcm-f32") { | 
 |     return media::SampleFormat::kSampleFormatF32; | 
 |   } | 
 |  | 
 |   return media::SampleFormat::kSampleFormatPlanarF32; | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<AudioDecoderTraits::MediaDecoderType> | 
 | AudioDecoderTraits::CreateDecoder( | 
 |     ExecutionContext& execution_context, | 
 |     media::GpuVideoAcceleratorFactories* gpu_factories, | 
 |     media::MediaLog* media_log) { | 
 |   return std::make_unique<AudioDecoderBroker>(media_log, execution_context); | 
 | } | 
 |  | 
 | // static | 
 | void AudioDecoderTraits::UpdateDecoderLog(const MediaDecoderType& decoder, | 
 |                                           const MediaConfigType& media_config, | 
 |                                           media::MediaLog* media_log) { | 
 |   media_log->SetProperty<media::MediaLogProperty::kAudioDecoderName>( | 
 |       decoder.GetDecoderType()); | 
 |   media_log->SetProperty<media::MediaLogProperty::kIsPlatformAudioDecoder>( | 
 |       decoder.IsPlatformDecoder()); | 
 |   media_log->SetProperty<media::MediaLogProperty::kAudioTracks>( | 
 |       std::vector<MediaConfigType>{media_config}); | 
 |   MEDIA_LOG(INFO, media_log) | 
 |       << "Initialized AudioDecoder: " << media_config.AsHumanReadableString(); | 
 |   base::UmaHistogramEnumeration("Blink.WebCodecs.AudioDecoder.Codec", | 
 |                                 media_config.codec()); | 
 | } | 
 |  | 
 | // static | 
 | void AudioDecoderTraits::InitializeDecoder( | 
 |     MediaDecoderType& decoder, | 
 |     bool /*low_delay*/, | 
 |     const MediaConfigType& media_config, | 
 |     MediaDecoderType::InitCB init_cb, | 
 |     MediaDecoderType::OutputCB output_cb) { | 
 |   decoder.Initialize(media_config, nullptr /* cdm_context */, | 
 |                      std::move(init_cb), output_cb, media::WaitingCB()); | 
 | } | 
 |  | 
 | // static | 
 | int AudioDecoderTraits::GetMaxDecodeRequests(const MediaDecoderType& decoder) { | 
 |   return 1; | 
 | } | 
 |  | 
 | // static | 
 | const char* AudioDecoderTraits::GetName() { | 
 |   return "AudioDecoder"; | 
 | } | 
 |  | 
 | // static | 
 | AudioDecoder* AudioDecoder::Create(ScriptState* script_state, | 
 |                                    const AudioDecoderInit* init, | 
 |                                    ExceptionState& exception_state) { | 
 |   auto* result = | 
 |       MakeGarbageCollected<AudioDecoder>(script_state, init, exception_state); | 
 |   return exception_state.HadException() ? nullptr : result; | 
 | } | 
 |  | 
 | // static | 
 | ScriptPromise<AudioDecoderSupport> AudioDecoder::isConfigSupported( | 
 |     ScriptState* script_state, | 
 |     const AudioDecoderConfig* config, | 
 |     ExceptionState& exception_state) { | 
 |   String js_error_message; | 
 |   std::optional<media::AudioType> audio_type = | 
 |       IsValidAudioDecoderConfig(*config, &js_error_message); | 
 |  | 
 |   if (!audio_type) { | 
 |     exception_state.ThrowTypeError(js_error_message); | 
 |     return EmptyPromise(); | 
 |   } | 
 |  | 
 |   AudioDecoderSupport* support = AudioDecoderSupport::Create(); | 
 |   support->setSupported(media::IsDecoderSupportedAudioType(*audio_type)); | 
 |   support->setConfig(CopyConfig(*config)); | 
 |   return ToResolvedPromise<AudioDecoderSupport>(script_state, support); | 
 | } | 
 |  | 
 | // static | 
 | std::optional<media::AudioType> AudioDecoder::IsValidAudioDecoderConfig( | 
 |     const AudioDecoderConfig& config, | 
 |     String* js_error_message) { | 
 |   if (config.numberOfChannels() == 0) { | 
 |     *js_error_message = String::Format( | 
 |         "Invalid channel count; channel count must be non-zero, received %d.", | 
 |         config.numberOfChannels()); | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   if (config.sampleRate() == 0) { | 
 |     *js_error_message = String::Format( | 
 |         "Invalid sample rate; sample rate must be non-zero, received %d.", | 
 |         config.sampleRate()); | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   if (config.codec().LengthWithStrippedWhiteSpace() == 0) { | 
 |     *js_error_message = "Invalid codec; codec is required."; | 
 |     return std::nullopt; | 
 |   } | 
 |   // Match codec strings from the codec registry: | 
 |   // https://www.w3.org/TR/webcodecs-codec-registry/#audio-codec-registry | 
 |   std::optional<media::AudioCodec> pcm_type = TryGetPcmCodec(config.codec()); | 
 |   if (pcm_type.has_value()) { | 
 |     return media::AudioType{.codec = *pcm_type}; | 
 |   } | 
 |  | 
 |   if (!VerifyDescription(config, js_error_message)) { | 
 |     CHECK(!js_error_message->empty()); | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   std::optional<media::AudioType> audio_type = | 
 |       media::ParseAudioCodecString("", config.codec().Utf8()); | 
 |   if (!audio_type) { | 
 |     *js_error_message = "Unknown or ambiguous codec name."; | 
 |     return media::AudioType{.codec = media::AudioCodec::kUnknown}; | 
 |   } | 
 |  | 
 |   return audio_type; | 
 | } | 
 |  | 
 | // static | 
 | std::optional<media::AudioDecoderConfig> | 
 | AudioDecoder::MakeMediaAudioDecoderConfig(const ConfigType& config, | 
 |                                           String* js_error_message) { | 
 |   std::optional<media::AudioType> audio_type = | 
 |       IsValidAudioDecoderConfig(config, js_error_message); | 
 |   if (!audio_type) { | 
 |     // Checked by IsValidConfig(). | 
 |     NOTREACHED(); | 
 |   } | 
 |   if (audio_type->codec == media::AudioCodec::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()) { | 
 |       // We should never get here, since this should be caught in | 
 |       // IsValidAudioDecoderConfig(). | 
 |       *js_error_message = "Invalid config; description is detached."; | 
 |       return std::nullopt; | 
 |     } | 
 |  | 
 |     if (!desc_wrapper.empty()) { | 
 |       extra_data.assign(base::to_address(desc_wrapper.begin()), | 
 |                         base::to_address(desc_wrapper.end())); | 
 |     } | 
 |   } | 
 |  | 
 |   media::ChannelLayout channel_layout = | 
 |       config.numberOfChannels() > 8 | 
 |           // GuesschannelLayout() doesn't know how to guess above 8 channels. | 
 |           ? media::CHANNEL_LAYOUT_DISCRETE | 
 |           : media::GuessChannelLayout(config.numberOfChannels()); | 
 |  | 
 |   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(); | 
 |   } | 
 |  | 
 |   // TODO(chcunningham): Add sample format to IDL. | 
 |   media::AudioDecoderConfig media_config; | 
 |  | 
 |   media::SampleFormat format = media::kSampleFormatPlanarF32; | 
 |   if (audio_type->codec == media::AudioCodec::kPCM) { | 
 |     // There is a case of the codec being "1", which is a valid PCM codec for | 
 |     // WAV in media/base/mime_util_internal.cc. We should reject this case for | 
 |     // webcodecs. | 
 |     if (config.codec() == "1") { | 
 |       return std::nullopt; | 
 |     } | 
 |     format = PcmCodecToSampleFormat(config.codec()); | 
 |   } | 
 |  | 
 |   media_config.Initialize(audio_type->codec, format, channel_layout, | 
 |                           config.sampleRate(), extra_data, encryption_scheme, | 
 |                           base::TimeDelta() /* seek preroll */, | 
 |                           0 /* codec delay */); | 
 |   if (!media_config.IsValidConfig()) { | 
 |     *js_error_message = "Unsupported config."; | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   return media_config; | 
 | } | 
 |  | 
 | AudioDecoder::AudioDecoder(ScriptState* script_state, | 
 |                            const AudioDecoderInit* init, | 
 |                            ExceptionState& exception_state) | 
 |     : DecoderTemplate<AudioDecoderTraits>(script_state, init, exception_state) { | 
 |   UseCounter::Count(ExecutionContext::From(script_state), | 
 |                     WebFeature::kWebCodecs); | 
 | } | 
 |  | 
 | bool AudioDecoder::IsValidConfig(const ConfigType& config, | 
 |                                  String* js_error_message) { | 
 |   return IsValidAudioDecoderConfig(config, js_error_message /* out */) | 
 |       .has_value(); | 
 | } | 
 |  | 
 | std::optional<media::AudioDecoderConfig> AudioDecoder::MakeMediaConfig( | 
 |     const ConfigType& config, | 
 |     String* js_error_message) { | 
 |   DCHECK(js_error_message); | 
 |   return MakeMediaAudioDecoderConfig(config, js_error_message /* out */); | 
 | } | 
 |  | 
 | media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>> | 
 | AudioDecoder::MakeInput(const InputType& chunk, bool verify_key_frame) { | 
 |   if (verify_key_frame && !chunk.buffer()->is_key_frame()) | 
 |     return media::DecoderStatus::Codes::kKeyFrameRequired; | 
 |   return chunk.buffer(); | 
 | } | 
 |  | 
 | media::DecoderStatus::Or<AudioDecoder::OutputType*> AudioDecoder::MakeOutput( | 
 |     scoped_refptr<MediaOutputType> output, | 
 |     ExecutionContext* context) { | 
 |   if (!blink::audio_utilities::IsValidAudioBufferSampleRate( | 
 |           output->sample_rate())) { | 
 |     return media::DecoderStatus( | 
 |         media::DecoderStatus::Codes::kInvalidArgument, | 
 |         String::Format("Invalid decoded audio output sample rate. Got %u, " | 
 |                        "which is outside [%f, %f]", | 
 |                        output->sample_rate(), | 
 |                        blink::audio_utilities::MinAudioBufferSampleRate(), | 
 |                        blink::audio_utilities::MaxAudioBufferSampleRate()) | 
 |             .Ascii()); | 
 |   } | 
 |  | 
 |   if (static_cast<uint32_t>(output->channel_count()) > | 
 |       BaseAudioContext::MaxNumberOfChannels()) { | 
 |     return media::DecoderStatus( | 
 |         media::DecoderStatus::Codes::kInvalidArgument, | 
 |         String::Format("Invalid decoded audio output channel " | 
 |                        "count. Got %u, which exceeds %u", | 
 |                        output->channel_count(), | 
 |                        BaseAudioContext::MaxNumberOfChannels()) | 
 |             .Ascii()); | 
 |   } | 
 |  | 
 |   return MakeGarbageCollected<AudioDecoderTraits::OutputType>( | 
 |       std::move(output)); | 
 | } | 
 |  | 
 | const AtomicString& AudioDecoder::InterfaceName() const { | 
 |   return event_target_names::kAudioDecoder; | 
 | } | 
 |  | 
 | }  // namespace blink |