chromium/chromecast/media/audio/cast_audio_device_factory.cc

// 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