// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/audio/cast_audio_device_factory.h"
#include <string>
#include "base/android/bundle_utils.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/task/bind_post_task.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/media/audio/cast_audio_output_device.h"
#include "content/public/renderer/render_frame.h"
#include "media/audio/audio_output_device.h"
#include "media/base/audio_capturer_source.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_renderer_sink.h"
#include "media/base/output_device_info.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/web/modules/media/audio/audio_output_ipc_factory.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace chromecast {
namespace media {
namespace {
constexpr base::TimeDelta kAuthorizationTimeout = base::Seconds(100);
content::RenderFrame* GetRenderFrameForToken(
const blink::LocalFrameToken& frame_token) {
auto* web_frame = blink::WebLocalFrame::FromFrameToken(frame_token);
DCHECK(web_frame);
return content::RenderFrame::FromWebFrame(web_frame);
}
scoped_refptr<::media::AudioOutputDevice> NewOutputDevice(
const blink::LocalFrameToken& frame_token,
const ::media::AudioSinkParameters& params,
base::TimeDelta auth_timeout) {
auto device = base::MakeRefCounted<::media::AudioOutputDevice>(
blink::AudioOutputIPCFactory::GetInstance().CreateAudioOutputIPC(
frame_token),
blink::AudioOutputIPCFactory::GetInstance().io_task_runner(), params,
auth_timeout);
device->RequestDeviceAuthorization();
return device;
}
} // namespace
class NonSwitchableAudioRendererSink
: public ::media::SwitchableAudioRendererSink {
public:
explicit NonSwitchableAudioRendererSink(
const blink::LocalFrameToken& frame_token,
const ::media::AudioSinkParameters& params)
: frame_token_(frame_token), sink_params_(params) {
auto* render_frame = GetRenderFrameForToken(frame_token);
DCHECK(render_frame);
render_frame->GetBrowserInterfaceBroker().GetInterface(
pending_audio_socket_broker_.InitWithNewPipeAndPassReceiver());
render_frame->GetBrowserInterfaceBroker().GetInterface(
pending_app_media_info_manager_.InitWithNewPipeAndPassReceiver());
}
void Initialize(const ::media::AudioParameters& params,
RenderCallback* callback) override {
// NonSwitchableAudioRendererSink derives from RestartableRenderSink which
// does allow calling Initialize and Play again after stopping.
if (is_initialized_)
return;
is_initialized_ = true;
if (!(base::android::BundleUtils::IsBundle() ||
base::FeatureList::IsEnabled(kEnableCastAudioOutputDevice)) ||
params.IsBitstreamFormat()) {
output_device_ =
NewOutputDevice(frame_token_, sink_params_, kAuthorizationTimeout);
output_device_->Initialize(params, callback);
return;
}
app_media_info_manager_.Bind(std::move(pending_app_media_info_manager_));
app_media_info_manager_->GetCastApplicationMediaInfo(base::BindOnce(
&NonSwitchableAudioRendererSink::OnApplicationMediaInfoReceived, this,
params, callback));
}
void Start() override {
if (output_device_) {
output_device_->Start();
} else {
pending_start_ = true;
}
}
void Stop() override {
pending_start_ = false;
if (output_device_) {
output_device_->Stop();
}
}
void Pause() override {
if (output_device_) {
output_device_->Pause();
} else {
pending_pause_ = true;
}
}
void Play() override {
pending_pause_ = false;
if (output_device_) {
output_device_->Play();
}
}
bool SetVolume(double volume) override {
if (output_device_) {
return output_device_->SetVolume(volume);
}
pending_volume_ = volume;
return true;
}
::media::OutputDeviceInfo GetOutputDeviceInfo() override {
if (output_device_) {
return output_device_->GetOutputDeviceInfo();
}
// GetOutputDeviceInfo() may be called when the underlying `output_device_`
// hasn't been constructed. Return the default set of parameters in this
// case.
return ::media::OutputDeviceInfo(
std::string(), ::media::OUTPUT_DEVICE_STATUS_OK,
::media::AudioParameters(
::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
::media::ChannelLayoutConfig::Stereo(), 48000, 480));
}
void GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) override {
if (output_device_) {
output_device_->GetOutputDeviceInfoAsync(std::move(info_cb));
return;
}
// Always post to avoid the caller being reentrant.
base::BindPostTaskToCurrentDefault(
base::BindOnce(std::move(info_cb), GetOutputDeviceInfo()))
.Run();
}
bool IsOptimizedForHardwareParameters() override {
if (output_device_) {
return output_device_->IsOptimizedForHardwareParameters();
}
return false;
}
bool CurrentThreadIsRenderingThread() override {
return output_device_ && output_device_->CurrentThreadIsRenderingThread();
}
void SwitchOutputDevice(const std::string& device_id,
::media::OutputDeviceStatusCB callback) override {
LOG(ERROR) << __func__ << " is not suported.";
std::move(callback).Run(::media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
}
void Flush() override {
if (output_device_) {
output_device_->Flush();
}
}
protected:
~NonSwitchableAudioRendererSink() override {
if (output_device_)
output_device_->Stop();
}
private:
void OnApplicationMediaInfoReceived(
const ::media::AudioParameters& params,
RenderCallback* callback,
::media::mojom::CastApplicationMediaInfoPtr application_media_info) {
// Use CastAudioOutputDevice if either:
// 1. the playback only has audio stream.
// 2. the app is an audio only app.
// Otherwise create Chromium's audio output for better av sync quality.
if (params.effects() & ::media::AudioParameters::AUDIO_PREFETCH ||
application_media_info->is_audio_only_session) {
LOG(INFO) << "Use cast audio output device.";
output_device_ = base::MakeRefCounted<CastAudioOutputDevice>(
std::move(pending_audio_socket_broker_),
application_media_info->application_session_id);
} else {
output_device_ =
NewOutputDevice(frame_token_, sink_params_, kAuthorizationTimeout);
}
// The media info manager is only needed to query whether this is an
// audio-only session and session id; after this, the binding can be reset.
//
// If this is not done on the thread on which the binding was created (in
// Initialize()), then the destructor can run on a different thread and
// violate a mojo sequence checker assertion.
app_media_info_manager_.reset();
output_device_->Initialize(params, callback);
if (pending_start_) {
output_device_->Start();
pending_start_ = false;
}
if (pending_pause_) {
output_device_->Pause();
pending_pause_ = false;
}
if (pending_volume_) {
output_device_->SetVolume(pending_volume_.value());
pending_volume_ = std::nullopt;
}
}
const blink::LocalFrameToken frame_token_;
const ::media::AudioSinkParameters sink_params_;
mojo::PendingRemote<mojom::AudioSocketBroker> pending_audio_socket_broker_;
mojo::PendingRemote<::media::mojom::CastApplicationMediaInfoManager>
pending_app_media_info_manager_;
mojo::Remote<::media::mojom::CastApplicationMediaInfoManager>
app_media_info_manager_;
scoped_refptr<::media::AudioRendererSink> output_device_;
bool is_initialized_ = false;
bool pending_start_ = false;
bool pending_pause_ = false;
std::optional<double> pending_volume_;
};
CastAudioDeviceFactory::CastAudioDeviceFactory() {
DVLOG(1) << "Register CastAudioDeviceFactory";
}
CastAudioDeviceFactory::~CastAudioDeviceFactory() {
DVLOG(1) << "Unregister CastAudioDeviceFactory";
}
scoped_refptr<::media::SwitchableAudioRendererSink>
CastAudioDeviceFactory::NewMixableSink(
blink::WebAudioDeviceSourceType source_type,
const blink::LocalFrameToken& frame_token,
const blink::FrameToken& main_frame_token,
const ::media::AudioSinkParameters& params) {
return base::MakeRefCounted<NonSwitchableAudioRendererSink>(frame_token,
params);
}
} // namespace media
} // namespace chromecast