chromium/chromecast/starboard/media/media/starboard_audio_decoder.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "starboard_audio_decoder.h"

#include <cstdint>
#include <limits>
#include <memory>
#include <optional>

#include "base/check.h"
#include "base/logging.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "chromecast/starboard/chromecast/starboard_cast_api/cast_starboard_api_types.h"
#include "chromecast/starboard/media/media/drm_util.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"

namespace chromecast {
namespace media {

using BufferStatus = ::chromecast::media::MediaPipelineBackend::BufferStatus;
using RenderingDelay =
    ::chromecast::media::MediaPipelineBackend::AudioDecoder::RenderingDelay;

static StarboardAudioCodec AudioCodecToStarboardCodec(AudioCodec codec) {
  switch (codec) {
    case kCodecAAC:
      return kStarboardAudioCodecAac;
    case kCodecMP3:
      return kStarboardAudioCodecMp3;
    case kCodecPCM_S16BE:
    case kCodecPCM:
      return kStarboardAudioCodecPcm;
    case kCodecVorbis:
      return kStarboardAudioCodecVorbis;
    case kCodecOpus:
      return kStarboardAudioCodecOpus;
    case kCodecEAC3:
      return kStarboardAudioCodecEac3;
    case kCodecAC3:
      return kStarboardAudioCodecAc3;
    case kCodecFLAC:
      return kStarboardAudioCodecFlac;
    case kCodecMpegHAudio:
    case kCodecDTS:
    case kAudioCodecUnknown:
    default:
      LOG(ERROR) << "Unsupported audio codec: " << codec;
      return kStarboardAudioCodecNone;
  }
}

static StarboardAudioSampleInfo ToAudioSampleInfo(const AudioConfig& config) {
  StarboardAudioSampleInfo sample_info = {};

  sample_info.codec = AudioCodecToStarboardCodec(config.codec);
  sample_info.mime = "";
  sample_info.format_tag = 0;
  sample_info.number_of_channels = config.channel_number;
  sample_info.samples_per_second = config.samples_per_second;
  // Based on starboard_utils.cc (MediaAudioConfigToSbMediaAudioSampleInfo) in
  // the cobalt codebase, this value does not take into account the number of
  // channels.
  // TODO(b/334907387): Add logic to change audio_sample_info_.bits_per_sample
  // depending on our desired output. For now it's just 16 because we only
  // need signed 16 as our desired out.
  if (sample_info.codec == kStarboardAudioCodecPcm) {
    sample_info.bits_per_sample = 16;
  } else {
    sample_info.bits_per_sample = config.bytes_per_channel * 8;
  }
  sample_info.block_alignment = 4;
  sample_info.average_bytes_per_second = sample_info.number_of_channels *
                                         sample_info.samples_per_second *
                                         sample_info.bits_per_sample / 8;
  // Since extra_data is a vector of uint8_t, size() gives us the size in bytes.
  sample_info.audio_specific_config_size = config.extra_data.size();
  sample_info.audio_specific_config =
      sample_info.audio_specific_config_size > 0
          ? static_cast<const void*>(config.extra_data.data())
          : nullptr;

  return sample_info;
}

StarboardAudioDecoder::StarboardAudioDecoder(StarboardApiWrapper* starboard)
    : StarboardDecoder(starboard, kStarboardMediaTypeAudio) {}

StarboardAudioDecoder::~StarboardAudioDecoder() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void StarboardAudioDecoder::InitializeInternal() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (volume_) {
    LOG(INFO) << "Setting starboard's volume to " << *volume_;
    GetStarboardApi().SetVolume(GetPlayer(), *volume_);
    volume_ = std::nullopt;
  }

  // This might not be the correct location for this, so this is subject to
  // change. We may eventually want to get this value from
  // CastStarboardApiAdapter::GetInstance() (e.g. by adding a
  // CastStarboardApiAdapterGetStarboardPcmSampleFormat() function to the
  // adapter).
  format_to_decode_to_ = StarboardPcmSampleFormat::kStarboardPcmSampleFormatS16;
}

const std::optional<StarboardAudioSampleInfo>&
StarboardAudioDecoder::GetAudioSampleInfo() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return audio_sample_info_;
}

bool StarboardAudioDecoder::SetConfig(const AudioConfig& config) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if ((config.codec == kCodecPCM || config.codec == kCodecPCM_S16BE) &&
      config.channel_number > 8) {
    LOG(ERROR) << "Config channels exceeds 8, which is not supported.";
    return false;
  }

  config_ = config;
  audio_sample_info_.emplace(ToAudioSampleInfo(config_));
  return IsValidConfig(config_);
}

bool StarboardAudioDecoder::SetVolume(float multiplier) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (multiplier < 0.0 || multiplier > 1.0) {
    LOG(ERROR) << "Invalid volume multiplier: " << multiplier;
    return false;
  }

  void* const player = GetPlayer();
  if (player) {
    LOG(INFO) << "Setting starboard's volume to " << multiplier;
    GetStarboardApi().SetVolume(player, multiplier);
  } else {
    LOG(INFO) << "Delaying setting volume until SbPlayer is created.";
    volume_ = multiplier;
  }

  return true;
}

RenderingDelay StarboardAudioDecoder::GetRenderingDelay() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  RenderingDelay delay = {};
  // Signifies that the latency is not available.
  delay.timestamp_microseconds = std::numeric_limits<int64_t>::min();
  delay.delay_microseconds = 0;
  return delay;
}

BufferStatus StarboardAudioDecoder::PushBuffer(CastDecoderBuffer* buffer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(buffer);

  if (buffer->end_of_stream()) {
    return PushEndOfStream();
  }

  DCHECK(audio_sample_info_);
  size_t size_of_buffer = buffer->data_size();
  std::unique_ptr<uint8_t[]> data_copy;

  if (audio_sample_info_->codec == kStarboardAudioCodecPcm) {
    // This call will also set the value for `size_of_buffer`.
    data_copy = ResamplePCMAudioDataForStarboard(
        format_to_decode_to_, config_.sample_format, config_.codec,
        audio_sample_info_->number_of_channels, *buffer, size_of_buffer);
  } else {
    // Need to do this when not resampling to ensure that the input data is not
    // freed until Starboard is done using it.
    data_copy = std::make_unique<uint8_t[]>(size_of_buffer);
    memcpy(data_copy.get(), buffer->data(), size_of_buffer);
  }

  StarboardSampleInfo sample = {};
  sample.type = kStarboardMediaTypeAudio;
  sample.timestamp = buffer->timestamp();
  sample.side_data = nullptr;
  sample.side_data_count = 0;
  sample.audio_sample_info = *audio_sample_info_;

  decoded_bytes_ += size_of_buffer;

  return PushBufferInternal(std::move(sample), GetDrmInfo(*buffer),
                            std::move(data_copy), size_of_buffer);
}

void StarboardAudioDecoder::GetStatistics(Statistics* statistics) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!statistics) {
    return;
  }

  statistics->decoded_bytes = decoded_bytes_;
}

void StarboardAudioDecoder::SetDelegate(Delegate* delegate) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetDecoderDelegate(delegate);
}

StarboardAudioDecoder::AudioTrackTimestamp
StarboardAudioDecoder::GetAudioTrackTimestamp() {
  return AudioTrackTimestamp();
}

int StarboardAudioDecoder::GetStartThresholdInFrames() {
  return 0;
}

}  // namespace media
}  // namespace chromecast