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

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

#include <algorithm>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "chromecast/media/audio/net/common.pb.h"
#include "chromecast/media/audio/net/conversions.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/media/common/media_pipeline_backend_manager.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"
#include "media/base/decoder_buffer.h"
#include "media/base/timestamp_constants.h"

#define POST_DELEGATE_TASK(callback, ...)                               \
  if (delegate_task_runner_) {                                          \
    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 {

MediaPipelineDeviceParams::AudioStreamType ConvertStreamType(
    mixer_service::OutputStreamParams::StreamType type) {
  if (type == mixer_service::OutputStreamParams::STREAM_TYPE_SFX) {
    return MediaPipelineDeviceParams::kAudioStreamSoundEffects;
  }
  return MediaPipelineDeviceParams::kAudioStreamNormal;
}

AudioChannel ConvertChannelSelection(int channel_selection) {
  switch (channel_selection) {
    case -1:
      return AudioChannel::kAll;
    case 0:
      return AudioChannel::kLeft;
    case 1:
      return AudioChannel::kRight;
    default:
      NOTREACHED_IN_MIGRATION();
      return AudioChannel::kAll;
  }
}

}  // namespace

namespace mixer_service {

CmaBackendShim::CmaBackendShim(
    base::WeakPtr<Delegate> delegate,
    scoped_refptr<base::SequencedTaskRunner> delegate_task_runner,
    const mixer_service::OutputStreamParams& params,
    MediaPipelineBackendManager* backend_manager)
    : delegate_(std::move(delegate)),
      delegate_task_runner_(std::move(delegate_task_runner)),
      params_(params),
      backend_manager_(backend_manager),
      media_task_runner_(backend_manager_->GetMediaTaskRunner()),
      backend_task_runner_(backend_manager_->task_runner()),
      audio_decoder_(nullptr) {
  DETACH_FROM_SEQUENCE(media_sequence_checker_);
  POST_MEDIA_TASK(&CmaBackendShim::InitializeOnMediaThread);
}

CmaBackendShim::~CmaBackendShim() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  cma_backend_->Stop();
}

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

void CmaBackendShim::InitializeOnMediaThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  MediaPipelineDeviceParams device_params(
      MediaPipelineDeviceParams::kModeIgnorePts,
      ConvertStreamType(params_.stream_type()), &backend_task_runner_,
      audio_service::ConvertContentType(params_.content_type()),
      params_.device_id());
  device_params.audio_channel =
      ConvertChannelSelection(params_.channel_selection());
  cma_backend_ = backend_manager_->CreateBackend(device_params);

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

  AudioConfig audio_config;
  audio_config.id = kPrimary;
  audio_config.codec = kCodecPCM;
  audio_config.channel_layout =
      audio_service::ConvertChannelLayout(params_.channel_layout());
  if (audio_config.channel_layout == media::ChannelLayout::UNSUPPORTED ||
      audio_config.channel_layout == media::ChannelLayout::BITSTREAM) {
    audio_config.channel_layout =
        ChannelLayoutFromChannelNumber(params_.num_channels());
  }
  audio_config.sample_format =
      audio_service::ConvertSampleFormat(params_.sample_format());
  audio_config.bytes_per_channel =
      audio_service::GetSampleSizeBytes(params_.sample_format());
  audio_config.channel_number = params_.num_channels();
  audio_config.samples_per_second = params_.sample_rate();
  if (!audio_decoder_->SetConfig(audio_config)) {
    LOG(ERROR) << "Failed to set CMA audio config";
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
  audio_decoder_->SetVolume(1.0f);

  if (!cma_backend_->Initialize() || !cma_backend_->Start(0)) {
    LOG(ERROR) << "Failed to start CMA backend";
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
}

void CmaBackendShim::DestroyOnMediaThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  delete this;
}

void CmaBackendShim::AddData(char* data, int size) {
  scoped_refptr<::media::DecoderBuffer> buffer;
  if (size == 0) {
    buffer = ::media::DecoderBuffer::CreateEOSBuffer();
  } else {
    // TODO(crbug.com/40284755): These functions should use span and size_t.
    buffer = ::media::DecoderBuffer::CopyFrom(base::as_bytes(
        UNSAFE_TODO(base::span(data, base::checked_cast<size_t>(size)))));
    buffer->set_timestamp(::media::kNoTimestamp);
  }
  POST_MEDIA_TASK(&CmaBackendShim::AddDataOnMediaThread, std::move(buffer));
}

void CmaBackendShim::AddDataOnMediaThread(
    scoped_refptr<::media::DecoderBuffer> buffer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  if (!audio_decoder_) {
    return;
  }
  pushed_buffer_ =
      base::MakeRefCounted<DecoderBufferAdapter>(std::move(buffer));
  BufferStatus status = audio_decoder_->PushBuffer(pushed_buffer_);

  if (status != MediaPipelineBackend::kBufferPending) {
    OnPushBufferComplete(status);
  }
}

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

void CmaBackendShim::SetVolumeMultiplierOnMediaThread(float multiplier) {
  if (!audio_decoder_) {
    return;
  }
  audio_decoder_->SetVolume(multiplier);
}

void CmaBackendShim::OnPushBufferComplete(BufferStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  pushed_buffer_.reset();
  if (status != MediaPipelineBackend::kBufferSuccess) {
    POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
    return;
  }
  if (!audio_decoder_) {
    return;
  }

  RenderingDelay delay = audio_decoder_->GetRenderingDelay();
  POST_DELEGATE_TASK(&Delegate::OnBufferPushed, delay);
}

void CmaBackendShim::OnEndOfStream() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  POST_DELEGATE_TASK(&Delegate::PlayedEos);
}

void CmaBackendShim::OnDecoderError() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
  POST_DELEGATE_TASK(&Delegate::OnAudioPlaybackError);
}

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

void CmaBackendShim::OnVideoResolutionChanged(const Size& /* size */) {
  // Ignored.
}

}  // namespace mixer_service
}  // namespace media
}  // namespace chromecast