// 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 "content/public/browser/audio_service.h" #include "base/command_line.h" #include "base/metrics/field_trial_params.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/task/deferred_sequenced_task_runner.h" #include "base/threading/sequence_local_storage_slot.h" #include "base/time/time.h" #include "build/build_config.h" #include "content/browser/browser_main_loop.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/service_process_host.h" #include "content/public/common/content_client.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "media/audio/audio_manager.h" #include "media/base/media_switches.h" #include "mojo/public/cpp/bindings/remote.h" #include "services/audio/public/cpp/audio_system_to_service_adapter.h" #include "services/audio/public/mojom/audio_service.mojom.h" #include "services/audio/service.h" #include "services/audio/service_factory.h" #include "third_party/abseil-cpp/absl/types/optional.h" #if BUILDFLAG(IS_WIN) && BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) #include "media/audio/win/audio_edid_scan_win.h" #include "media/audio/win/audio_manager_win.h" #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) namespace content { namespace { absl::optional GetFieldTrialIdleTimeout() { std::string timeout_str = base::GetFieldTrialParamValue("AudioService", "teardown_timeout_s"); int timeout_s = 0; if (!base::StringToInt(timeout_str, &timeout_s)) return absl::nullopt; return base::Seconds(timeout_s); } absl::optional GetCommandLineIdleTimeout() { const base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); std::string timeout_str = command_line.GetSwitchValueASCII(switches::kAudioServiceQuitTimeoutMs); int timeout_ms = 0; if (!base::StringToInt(timeout_str, &timeout_ms)) return absl::nullopt; return base::Milliseconds(timeout_ms); } absl::optional GetAudioServiceProcessIdleTimeout() { absl::optional timeout = GetCommandLineIdleTimeout(); if (!timeout) timeout = GetFieldTrialIdleTimeout(); if (timeout && timeout->is_negative()) return absl::nullopt; return timeout; } bool IsAudioServiceOutOfProcess() { return !base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kSingleProcess) && base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess) && !GetContentClient()->browser()->OverridesAudioManager(); } void BindSystemInfoFromAnySequence( mojo::PendingReceiver receiver) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&BindSystemInfoFromAnySequence, std::move(receiver))); return; } GetAudioService().BindSystemInfo(std::move(receiver)); } void BindStreamFactoryFromAnySequence( mojo::PendingReceiver receiver) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&BindStreamFactoryFromAnySequence, std::move(receiver))); return; } GetAudioService().BindStreamFactory(std::move(receiver)); } void LaunchAudioServiceInProcess( mojo::PendingReceiver receiver, uint32_t codec_bitmask) { // NOTE: If BrowserMainLoop is uninitialized, we have no AudioManager. In // this case we discard the receiver. The remote will always discard // messages. This is to work around unit testing environments where no // BrowserMainLoop is initialized. if (!BrowserMainLoop::GetInstance()) return; #if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) && BUILDFLAG(IS_WIN) media::AudioManagerWin::SetBitstreamPassthroughBitmask(codec_bitmask); #endif // TODO(https://crbug.com/853254): Remove // BrowserMainLoop::GetAudioManager(). audio::Service::GetInProcessTaskRunner()->PostTask( FROM_HERE, base::BindOnce( [](media::AudioManager* audio_manager, mojo::PendingReceiver receiver) { static base::SequenceLocalStorageSlot< std::unique_ptr> service; service.GetOrCreateValue() = audio::CreateEmbeddedService( audio_manager, std::move(receiver)); }, BrowserMainLoop::GetAudioManager(), std::move(receiver))); } void LaunchAudioServiceOutOfProcess( mojo::PendingReceiver receiver, uint32_t codec_bitmask) { std::vector switches; #if BUILDFLAG(IS_MAC) // On Mac, the audio service requires a CFRunLoop provided by a // UI MessageLoop type, to run AVFoundation and CoreAudio code. // See https://crbug.com/834581. switches.push_back(switches::kMessageLoopTypeUi); #elif BUILDFLAG(IS_WIN) if (GetContentClient()->browser()->ShouldEnableAudioProcessHighPriority()) switches.push_back(switches::kAudioProcessHighPriority); #if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) switches.push_back(base::StrCat({switches::kAudioCodecsFromEDID, "=", base::NumberToString(codec_bitmask)})); #endif // BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) #endif // BUILDFLAG(IS_WIN) ServiceProcessHost::Launch( std::move(receiver), ServiceProcessHost::Options() .WithDisplayName("Audio Service") .WithExtraCommandLineSwitches(std::move(switches)) .Pass()); } void LaunchAudioService( mojo::PendingReceiver receiver, uint32_t codec_bitmask) { // The static storage slot in GetAudioService() prevents LaunchAudioService // from being called more than once. if (IsAudioServiceOutOfProcess()) { LaunchAudioServiceOutOfProcess(std::move(receiver), codec_bitmask); } else { LaunchAudioServiceInProcess(std::move(receiver), codec_bitmask); } } } // namespace audio::mojom::AudioService& GetAudioService() { DCHECK_CURRENTLY_ON(BrowserThread::UI); // NOTE: We use sequence-local storage slot not because we support access from // any sequence, but to limit the lifetime of this Remote to the lifetime of // UI-thread sequence. This is to support re-creation after task environment // shutdown and reinitialization e.g. between unit tests. static base::SequenceLocalStorageSlot< mojo::Remote> remote_slot; auto& remote = remote_slot.GetOrCreateValue(); if (!remote) { auto receiver = remote.BindNewPipeAndPassReceiver(); #if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) && BUILDFLAG(IS_WIN) // The EDID scan is done in a COM STA thread and the result // passed to the audio service launcher. base::ThreadPool::CreateCOMSTATaskRunner( {base::MayBlock(), base::TaskPriority::USER_BLOCKING, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}) ->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&media::ScanEdidBitstreams), base::BindOnce(&LaunchAudioService, std::move(receiver))); #else LaunchAudioService(std::move(receiver), 0); #endif // BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) && BUILDFLAG(IS_WIN) if (IsAudioServiceOutOfProcess()) { auto idle_timeout = GetAudioServiceProcessIdleTimeout(); if (idle_timeout) remote.reset_on_idle_timeout(*idle_timeout); } remote.reset_on_disconnect(); } return *remote.get(); } std::unique_ptr CreateAudioSystemForAudioService() { constexpr auto kServiceDisconnectTimeout = base::Seconds(1); return std::make_unique( base::BindRepeating(&BindSystemInfoFromAnySequence), kServiceDisconnectTimeout); } AudioServiceStreamFactoryBinder GetAudioServiceStreamFactoryBinder() { return base::BindRepeating(&BindStreamFactoryFromAnySequence); } } // namespace content