chromium/chromecast/media/audio/audio_output_service/receiver/cma_backend_shim.cc

// 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 "chromecast/media/audio/audio_output_service/receiver/cma_backend_shim.h"

#include <algorithm>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chromecast/media/api/cma_backend_factory.h"
#include "chromecast/media/api/decoder_buffer_base.h"
#include "chromecast/media/audio/net/conversions.h"
#include "chromecast/media/base/audio_device_ids.h"
#include "chromecast/media/base/cast_decoder_buffer_impl.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/public/media/decoder_config.h"
#include "chromecast/public/media/media_pipeline_device_params.h"
#include "chromecast/public/media/stream_id.h"
#include "chromecast/public/volume_control.h"

#define POST_DELEGATE_TASK(callback, ...) \
  delegate_task_runner_->PostTask(        \
      FROM_HERE, base::BindOnce(callback, delegate_, ##__VA_ARGS__));

#define POST_MEDIA_TASK(callback, ...) \
  media_task_runner_->PostTask(        \
      FROM_HERE,                       \
      base::BindOnce(callback, base::Unretained(this), ##__VA_ARGS__))

namespace chromecast {
namespace media {

namespace audio_output_service {

CmaBackendShim::CmaBackendShim(
    base::WeakPtr<Delegate> delegate,
    scoped_refptr<base::SequencedTaskRunner> delegate_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
    const audio_output_service::CmaBackendParams& params,
    CmaBackendFactory* cma_backend_factory)
    : delegate_(std::move(delegate)),
      delegate_task_runner_(std::move(delegate_task_runner)),
      cma_backend_factory_(cma_backend_factory),
      media_task_runner_(std::move(media_task_runner)),
      backend_task_runner_(media_task_runner_),
      backend_params_(params),
      audio_decoder_(nullptr) {
  DCHECK(delegate_task_runner_);
  DCHECK(cma_backend_factory_);
  DCHECK(media_task_runner_);

  POST_MEDIA_TASK(&CmaBackendShim::InitializeOnMediaThread);
}

CmaBackendShim::~CmaBackendShim() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
  if (backend_state_ != BackendState::kStopped) {
    cma_backend_->Stop();
  }
}

void CmaBackendShim::Remove() {
  POST_MEDIA_TASK(&CmaBackendShim::DestroyOnMediaThread);
}

void CmaBackendShim::InitializeOnMediaThread() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  MediaPipelineDeviceParams device_params(
      MediaPipelineDeviceParams::kModeSyncPts, &backend_task_runner_,
      AudioContentType::kMedia, kDefaultDeviceId);
  device_params.session_id =
      backend_params_.application_media_info().application_session_id();
  cma_backend_ = cma_backend_factory_->CreateBackend(device_params);

  audio_decoder_ = cma_backend_->CreateAudioDecoder();
  if (!audio_decoder_) {
    LOG(ERROR) << "Failed to create CMA audio decoder";
    POST_DELEGATE_TASK(&Delegate::OnBackendInitialized, /*success=*/false);
    return;
  }
  audio_decoder_->SetDelegate(this);

  bool success = SetAudioConfig();
  if (!success) {
    LOG(ERROR) << "Failed to set audio decoder config.";
    POST_DELEGATE_TASK(&Delegate::OnBackendInitialized, success);
    return;
  }
  audio_decoder_->SetVolume(1.0f);

  success = cma_backend_->Initialize();
  if (!success) {
    LOG(ERROR) << "Failed to initialize CMA backend";
  }
  POST_DELEGATE_TASK(&Delegate::OnBackendInitialized, success);
}

void CmaBackendShim::DestroyOnMediaThread() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
  delete this;
}

void CmaBackendShim::AddData(char* data, int size, int64_t timestamp) {
  if (size == 0) {
    POST_MEDIA_TASK(&CmaBackendShim::AddEosDataOnMediaThread);
    return;
  }
  auto buffer = base::MakeRefCounted<CastDecoderBufferImpl>(size);
  memcpy(buffer->writable_data(), reinterpret_cast<const uint8_t*>(data), size);
  buffer->set_timestamp(base::Microseconds(timestamp));
  POST_MEDIA_TASK(&CmaBackendShim::AddDataOnMediaThread, std::move(buffer));
}

void CmaBackendShim::AddEosDataOnMediaThread() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  scoped_refptr<CastDecoderBufferImpl> buffer =
      CastDecoderBufferImpl::CreateEOSBuffer();
  AddDataOnMediaThread(std::move(buffer));
}

void CmaBackendShim::AddDataOnMediaThread(
    scoped_refptr<DecoderBufferBase> buffer) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
  DCHECK_NE(backend_state_, BackendState::kStopped);

  if (!audio_decoder_) {
    return;
  }
  pushed_buffer_ = std::move(buffer);
  BufferStatus status = audio_decoder_->PushBuffer(pushed_buffer_);
  if (status != MediaPipelineBackend::kBufferPending) {
    OnPushBufferComplete(status);
  }
}

void CmaBackendShim::SetVolumeMultiplier(float multiplier) {
  multiplier = std::max(0.0f, std::min(multiplier, 1.0f));
  POST_MEDIA_TASK(&CmaBackendShim::SetVolumeMultiplierOnMediaThread,
                  multiplier);
}

void CmaBackendShim::SetPlaybackRate(float playback_rate) {
  POST_MEDIA_TASK(&CmaBackendShim::SetPlaybackRateOnMediaThread, playback_rate);
}

void CmaBackendShim::StartPlayingFrom(int64_t start_pts) {
  POST_MEDIA_TASK(&CmaBackendShim::StartPlayingFromOnMediaThread, start_pts);
}

void CmaBackendShim::Stop() {
  POST_MEDIA_TASK(&CmaBackendShim::StopOnMediaThread);
}

void CmaBackendShim::UpdateAudioConfig(
    const audio_output_service::CmaBackendParams& params) {
  POST_MEDIA_TASK(&CmaBackendShim::UpdateAudioConfigOnMediaThread, params);
}

void CmaBackendShim::SetVolumeMultiplierOnMediaThread(float multiplier) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  if (!audio_decoder_) {
    return;
  }
  audio_decoder_->SetVolume(multiplier);
}

void CmaBackendShim::SetPlaybackRateOnMediaThread(float playback_rate) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  if (!cma_backend_ || playback_rate_ == playback_rate) {
    return;
  }
  float old_playback_rate = playback_rate_;
  playback_rate_ = playback_rate;
  if (backend_state_ == BackendState::kPlaying && playback_rate_ == 0.0f) {
    if (!cma_backend_->Pause()) {
      LOG(ERROR) << "Failed to pause CMA backend.";
      POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    }
    backend_state_ = BackendState::kPaused;
  }

  if (backend_state_ == BackendState::kPaused && old_playback_rate == 0.0f) {
    if (!cma_backend_->Resume()) {
      LOG(ERROR) << "Failed to resume CMA backend.";
      POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
      return;
    }
    backend_state_ = BackendState::kPlaying;
  }

  if (backend_state_ != BackendState::kStopped) {
    UpdateMediaTimeAndRenderingDelay();
  }

  if (playback_rate_ == 0.0f) {
    return;
  }

  if (!cma_backend_->SetPlaybackRate(playback_rate_)) {
    LOG(ERROR) << "Failed to set playback rate for CMA backend.";
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
}

void CmaBackendShim::StartPlayingFromOnMediaThread(int64_t start_pts) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  if (!cma_backend_) {
    return;
  }
  if (!cma_backend_->Start(start_pts)) {
    LOG(ERROR) << "Failed to start CMA backend";
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
  if (playback_rate_ == 0.0f) {
    if (!cma_backend_->Pause()) {
      LOG(ERROR) << "Failed to pause CMA backend";
      POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
      return;
    }
    backend_state_ = BackendState::kPaused;
  } else {
    backend_state_ = BackendState::kPlaying;
  }
}

void CmaBackendShim::StopOnMediaThread() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  if (!cma_backend_ || backend_state_ == BackendState::kStopped) {
    return;
  }
  cma_backend_->Stop();
  backend_state_ = BackendState::kStopped;
}

void CmaBackendShim::UpdateAudioConfigOnMediaThread(
    const audio_output_service::CmaBackendParams& params) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  backend_params_.MergeFrom(params);
  if (!audio_decoder_) {
    return;
  }
  if (!SetAudioConfig()) {
    LOG(ERROR) << "Failed to set CMA audio config";
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
  }
}

bool CmaBackendShim::SetAudioConfig() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(audio_decoder_);

  const auto& audio_decoder_config = backend_params_.audio_decoder_config();

  AudioConfig audio_config;
  audio_config.id = kPrimary;
  audio_config.codec =
      audio_service::ConvertAudioCodec(audio_decoder_config.audio_codec());
  audio_config.channel_layout =
      ConvertChannelLayout(audio_decoder_config.channel_layout());
  if (audio_config.channel_layout == media::ChannelLayout::UNSUPPORTED) {
    audio_config.channel_layout =
        ChannelLayoutFromChannelNumber(audio_decoder_config.num_channels());
  }
  audio_config.sample_format =
      audio_service::ConvertSampleFormat(audio_decoder_config.sample_format());
  audio_config.bytes_per_channel =
      audio_service::GetSampleSizeBytes(audio_decoder_config.sample_format());
  audio_config.channel_number = audio_decoder_config.num_channels();
  audio_config.samples_per_second = audio_decoder_config.sample_rate();
  audio_config.extra_data =
      std::vector<uint8_t>(audio_decoder_config.extra_data().begin(),
                           audio_decoder_config.extra_data().end());
  return audio_decoder_->SetConfig(audio_config);
}

void CmaBackendShim::UpdateMediaTimeAndRenderingDelay() {
  if (!cma_backend_ || !audio_decoder_) {
    return;
  }
  auto rendering_delay = audio_decoder_->GetRenderingDelay();
  POST_DELEGATE_TASK(&Delegate::UpdateMediaTimeAndRenderingDelay,
                     cma_backend_->GetCurrentPts(),
                     base::TimeTicks::Now().since_origin().InMicroseconds(),
                     rendering_delay.delay_microseconds,
                     rendering_delay.timestamp_microseconds);
}

void CmaBackendShim::OnPushBufferComplete(BufferStatus status) {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  pushed_buffer_.reset();
  if (status != MediaPipelineBackend::kBufferSuccess) {
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
  POST_DELEGATE_TASK(&Delegate::OnBufferPushed);
  UpdateMediaTimeAndRenderingDelay();
}

void CmaBackendShim::OnEndOfStream() {
  NOTIMPLEMENTED_LOG_ONCE();
}

void CmaBackendShim::OnDecoderError() {
  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());

  POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
}

void CmaBackendShim::OnKeyStatusChanged(const std::string& /* key_id */,
                                        CastKeyStatus /* key_status */,
                                        uint32_t /* system_code */) {
  NOTIMPLEMENTED_LOG_ONCE();
}

void CmaBackendShim::OnVideoResolutionChanged(const Size& /* size */) {
  NOTIMPLEMENTED_LOG_ONCE();
}

}  // namespace audio_output_service
}  // namespace media
}  // namespace chromecast