| // Copyright 2021 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_encoder.h" |
| |
| #include <string> |
| |
| #include "base/run_loop.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "media/base/media_switches.h" |
| #include "media/media_buildflags.h" |
| #include "media/mojo/buildflags.h" |
| #include "media/mojo/mojom/audio_encoder.mojom.h" |
| #include "media/mojo/mojom/interface_factory.mojom.h" |
| #include "media/mojo/services/mojo_audio_encoder_service.h" |
| #include "testing/libfuzzer/proto/lpm_interface.h" |
| #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_config.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_encoder_init.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk_output_callback.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" |
| #include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk.h" |
| #include "third_party/blink/renderer/modules/webcodecs/fuzzer_inputs.pb.h" |
| #include "third_party/blink/renderer/modules/webcodecs/fuzzer_utils.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/persistent.h" |
| #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h" |
| #include "third_party/blink/renderer/platform/testing/task_environment.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "third_party/protobuf/src/google/protobuf/text_format.h" |
| |
| #if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_PROPRIETARY_CODECS) |
| #include "base/win/scoped_com_initializer.h" |
| #include "media/gpu/windows/mf_audio_encoder.h" |
| #define HAS_AAC_ENCODER 1 |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_PROPRIETARY_CODECS) |
| #include "media/filters/mac/audio_toolbox_audio_encoder.h" |
| #define HAS_AAC_ENCODER 1 |
| #endif |
| |
| #if HAS_AAC_ENCODER |
| namespace { |
| |
| // Other end of remote InterfaceFactory requested by AudioEncoder. Used |
| // to create real media::mojom::AudioEncoders. |
| class TestInterfaceFactory : public media::mojom::InterfaceFactory { |
| public: |
| TestInterfaceFactory() = default; |
| ~TestInterfaceFactory() override = default; |
| |
| void BindRequest(mojo::ScopedMessagePipeHandle handle) { |
| receiver_.Bind(mojo::PendingReceiver<media::mojom::InterfaceFactory>( |
| std::move(handle))); |
| |
| // Each AudioEncoder instance will try to open a connection to this |
| // factory, so we must clean up after each one is destroyed. |
| receiver_.set_disconnect_handler(BindOnce( |
| &TestInterfaceFactory::OnConnectionError, blink::Unretained(this))); |
| } |
| |
| void OnConnectionError() { receiver_.reset(); } |
| |
| // Implement this one interface from mojom::InterfaceFactory. |
| void CreateAudioEncoder( |
| mojo::PendingReceiver<media::mojom::AudioEncoder> receiver) override { |
| // While we'd like to use the real GpuMojoMediaFactory here, it requires |
| // quite a bit more of scaffolding to setup and isn't really needed. |
| #if BUILDFLAG(IS_MAC) |
| auto platform_audio_encoder = |
| std::make_unique<media::AudioToolboxAudioEncoder>(); |
| #elif BUILDFLAG(IS_WIN) |
| CHECK(com_initializer_.Succeeded()); |
| auto platform_audio_encoder = std::make_unique<media::MFAudioEncoder>( |
| blink::scheduler::GetSequencedTaskRunnerForTesting()); |
| #else |
| #error "Unknown platform encoder." |
| #endif |
| audio_encoder_receivers_.Add( |
| std::make_unique<media::MojoAudioEncoderService>( |
| std::move(platform_audio_encoder)), |
| std::move(receiver)); |
| } |
| |
| // Stub out other mojom::InterfaceFactory interfaces. |
| void CreateVideoDecoder( |
| mojo::PendingReceiver<media::mojom::VideoDecoder> receiver, |
| mojo::PendingRemote<media::mojom::VideoDecoder> dst_video_decoder) |
| override {} |
| void CreateAudioDecoder( |
| mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) override {} |
| void CreateDefaultRenderer( |
| const std::string& audio_device_id, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #if BUILDFLAG(ENABLE_CAST_RENDERER) |
| void CreateCastRenderer( |
| const base::UnguessableToken& overlay_plane_id, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #endif |
| #if BUILDFLAG(IS_ANDROID) |
| void CreateFlingingRenderer( |
| const std::string& presentation_id, |
| mojo::PendingRemote<media::mojom::FlingingRendererClientExtension> |
| client_extension, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #endif // BUILDFLAG(IS_ANDROID) |
| void CreateCdm(const media::CdmConfig& cdm_config, |
| CreateCdmCallback callback) override { |
| std::move(callback).Run(mojo::NullRemote(), nullptr, |
| media::CreateCdmStatus::kCdmNotSupported); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void CreateMediaFoundationRenderer( |
| mojo::PendingRemote<media::mojom::MediaLog> media_log_remote, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver, |
| mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension> |
| renderer_extension_receiver, |
| mojo::PendingRemote< |
| ::media::mojom::MediaFoundationRendererClientExtension> |
| client_extension_remote) override {} |
| #endif // BUILDFLAG(IS_WIN) |
| private: |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedCOMInitializer com_initializer_; |
| #endif // BUILDFLAG(IS_WIN) |
| // media::MojoCdmServiceContext cdm_service_context_; |
| mojo::Receiver<media::mojom::InterfaceFactory> receiver_{this}; |
| mojo::UniqueReceiverSet<media::mojom::AudioEncoder> audio_encoder_receivers_; |
| }; |
| |
| } // namespace |
| #endif // HAS_AAC_ENCODER |
| |
| namespace blink { |
| |
| DEFINE_TEXT_PROTO_FUZZER( |
| const wc_fuzzer::AudioEncoderApiInvocationSequence& proto) { |
| static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport(); |
| test::TaskEnvironment task_environment; |
| auto page_holder = std::make_unique<DummyPageHolder>(); |
| page_holder->GetFrame().GetSettings()->SetScriptEnabled(true); |
| |
| #if HAS_AAC_ENCODER |
| base::test::ScopedFeatureList platform_aac(media::kPlatformAudioEncoder); |
| static const bool kSetTestBinder = []() { |
| auto interface_factory = std::make_unique<TestInterfaceFactory>(); |
| return Platform::Current() |
| ->GetBrowserInterfaceBroker() |
| ->SetBinderForTesting( |
| media::mojom::InterfaceFactory::Name_, |
| BindRepeating(&TestInterfaceFactory::BindRequest, |
| base::Owned(std::move(interface_factory)))); |
| }(); |
| CHECK(kSetTestBinder) << "Failed to register media interface binder."; |
| #endif |
| |
| // |
| // NOTE: GC objects that need to survive iterations of the loop below |
| // must be Persistent<>! |
| // |
| // GC may be triggered by the RunLoop().RunUntilIdle() below, which will GC |
| // raw pointers on the stack. This is not required in production code because |
| // GC typically runs at the top of the stack, or is conservative enough to |
| // keep stack pointers alive. |
| // |
| |
| // Scoping Persistent<> refs so GC can collect these at the end. |
| Persistent<ScriptState> script_state = |
| ToScriptStateForMainWorld(&page_holder->GetFrame()); |
| ScriptState::Scope scope(script_state); |
| |
| Persistent<V8WebCodecsErrorCallback> error_callback = |
| V8WebCodecsErrorCallback::Create( |
| MakeGarbageCollected<FakeFunction>("error")->ToV8Function( |
| script_state)); |
| Persistent<V8EncodedAudioChunkOutputCallback> output_callback = |
| V8EncodedAudioChunkOutputCallback::Create( |
| MakeGarbageCollected<FakeFunction>("output")->ToV8Function( |
| script_state)); |
| |
| Persistent<AudioEncoderInit> audio_encoder_init = |
| MakeGarbageCollected<AudioEncoderInit>(); |
| audio_encoder_init->setError(error_callback); |
| audio_encoder_init->setOutput(output_callback); |
| |
| Persistent<AudioEncoder> audio_encoder = AudioEncoder::Create( |
| script_state, audio_encoder_init, IGNORE_EXCEPTION_FOR_TESTING); |
| |
| if (audio_encoder) { |
| for (auto& invocation : proto.invocations()) { |
| switch (invocation.Api_case()) { |
| case wc_fuzzer::AudioEncoderApiInvocation::kConfigure: { |
| AudioEncoderConfig* config = |
| MakeAudioEncoderConfig(invocation.configure()); |
| |
| // Use the same config to fuzz isConfigSupported(). |
| AudioEncoder::isConfigSupported(script_state, config, |
| IGNORE_EXCEPTION_FOR_TESTING); |
| |
| audio_encoder->configure(config, IGNORE_EXCEPTION_FOR_TESTING); |
| break; |
| } |
| case wc_fuzzer::AudioEncoderApiInvocation::kEncode: { |
| AudioData* data = |
| MakeAudioData(script_state, invocation.encode().data()); |
| if (!data) { |
| return; |
| } |
| |
| audio_encoder->encode(data, IGNORE_EXCEPTION_FOR_TESTING); |
| break; |
| } |
| case wc_fuzzer::AudioEncoderApiInvocation::kFlush: { |
| // TODO(https://crbug.com/1119253): Fuzz whether to await resolution |
| // of the flush promise. |
| audio_encoder->flush(IGNORE_EXCEPTION_FOR_TESTING); |
| break; |
| } |
| case wc_fuzzer::AudioEncoderApiInvocation::kReset: |
| audio_encoder->reset(IGNORE_EXCEPTION_FOR_TESTING); |
| break; |
| case wc_fuzzer::AudioEncoderApiInvocation::kClose: |
| audio_encoder->close(IGNORE_EXCEPTION_FOR_TESTING); |
| break; |
| case wc_fuzzer::AudioEncoderApiInvocation::API_NOT_SET: |
| break; |
| } |
| |
| // Give other tasks a chance to run (e.g. calling our output callback). |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| } |
| |
| } // namespace blink |