chromium/chromecast/starboard/media/media/starboard_video_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_video_decoder.h"

#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "chromecast/public/graphics_types.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "chromecast/starboard/media/media/drm_util.h"
#include "chromecast/starboard/media/media/mime_utils.h"
#include "chromecast/starboard/media/media/starboard_api_wrapper.h"

namespace chromecast {
namespace media {

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

static StarboardVideoCodec VideoCodecToStarboardCodec(VideoCodec codec) {
  switch (codec) {
    case kCodecH264:
      return kStarboardVideoCodecH264;
    case kCodecVC1:
      return kStarboardVideoCodecVc1;
    case kCodecMPEG2:
      return kStarboardVideoCodecMpeg2;
    case kCodecTheora:
      return kStarboardVideoCodecTheora;
    case kCodecVP8:
      return kStarboardVideoCodecVp8;
    case kCodecVP9:
      return kStarboardVideoCodecVp9;
    case kCodecHEVC:
    case kCodecDolbyVisionHEVC:
      return kStarboardVideoCodecH265;
    case kCodecAV1:
      return kStarboardVideoCodecAv1;
    case kCodecDolbyVisionH264:
    case kCodecMPEG4:
    case kVideoCodecUnknown:
    default:
      LOG(ERROR) << "Unsupported video codec: " << codec;
      return kStarboardVideoCodecNone;
  }
}

// Converts a cast VideoConfig to a StarboardVideoSampleInfo. MIME type is not
// properly set, since it stores a c string pointing to data that could go out
// of scope. Instead, it is hardcoded to the empty string.
static StarboardVideoSampleInfo ToVideoSampleInfo(const VideoConfig& config) {
  StarboardVideoSampleInfo sample_info = {};

  sample_info.codec = VideoCodecToStarboardCodec(config.codec);
  sample_info.mime = "";
  // Unknown maximum capabilities. For adaptive playback, I don't think we can
  // guarantee the max video quality that the MSP will send.
  sample_info.max_video_capabilities = "";
  // We do not have info on whether a frame is actually a keyframe, so this is
  // hardcoded to true for now. We should confirm that starboard implementations
  // do not rely on this field, or submit a CL like crrev.com/c/4348024 to
  // properly populate the field.
  sample_info.is_key_frame = true;
  sample_info.frame_width = config.width;
  sample_info.frame_height = config.height;

  StarboardColorMetadata& color_metadata = sample_info.color_metadata;
  // bits_per_channel and the chroma_*/cb_* fields below need to be derived from
  // the MIME string. See b/230915942 for more info.
  // Unfortunately, it doesn't look like MIME type is exposed to cast. Note that
  // in Cobalt, these fields are all currently hard-coded to zero (in
  // third_party/chromium/media/base/starboard_utils.cc). I don't think they're
  // necessary for cast either, since cast doesn't seem to populate this info
  // anywhere.

  // 0 translates to "unknown".
  color_metadata.bits_per_channel = 0;
  color_metadata.chroma_subsampling_horizontal = 0;
  color_metadata.chroma_subsampling_vertical = 0;
  color_metadata.cb_subsampling_horizontal = 0;
  color_metadata.cb_subsampling_vertical = 0;
  color_metadata.chroma_siting_horizontal = 0;
  color_metadata.chroma_siting_vertical = 0;

  if (config.have_hdr_metadata) {
    LOG(INFO) << "Video config has HDR metadata.";

    color_metadata.max_cll = config.hdr_metadata.max_content_light_level;
    color_metadata.max_fall = config.hdr_metadata.max_frame_average_light_level;

    const auto& color_volume_metadata =
        config.hdr_metadata.color_volume_metadata;
    StarboardMediaMasteringMetadata& mastering_metadata =
        color_metadata.mastering_metadata;

    mastering_metadata.primary_r_chromaticity_x =
        color_volume_metadata.primary_r_chromaticity_x;
    mastering_metadata.primary_r_chromaticity_y =
        color_volume_metadata.primary_r_chromaticity_y;
    mastering_metadata.primary_g_chromaticity_x =
        color_volume_metadata.primary_g_chromaticity_x;
    mastering_metadata.primary_g_chromaticity_y =
        color_volume_metadata.primary_g_chromaticity_y;
    mastering_metadata.primary_b_chromaticity_x =
        color_volume_metadata.primary_b_chromaticity_x;
    mastering_metadata.primary_b_chromaticity_y =
        color_volume_metadata.primary_b_chromaticity_y;
    mastering_metadata.white_point_chromaticity_x =
        color_volume_metadata.white_point_chromaticity_x;
    mastering_metadata.white_point_chromaticity_y =
        color_volume_metadata.white_point_chromaticity_y;
    mastering_metadata.luminance_max = color_volume_metadata.luminance_max;
    mastering_metadata.luminance_min = color_volume_metadata.luminance_min;
  } else {
    color_metadata.max_cll = 0;
    color_metadata.max_fall = 0;
  }

  // config.primaries (::chromecast::media::PrimaryID) does not support any
  // value equivalent to kSbMediaPrimaryIdCustom. Thus, we don't need to
  // populate custom_primary_matrix.
  color_metadata.primaries = static_cast<int>(config.primaries);
  color_metadata.transfer = static_cast<int>(config.transfer);
  color_metadata.matrix = static_cast<int>(config.matrix);
  color_metadata.range = static_cast<int>(config.range);

  return sample_info;
}

StarboardVideoDecoder::StarboardVideoDecoder(StarboardApiWrapper* starboard)
    : StarboardDecoder(starboard, kStarboardMediaTypeVideo) {}

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

void StarboardVideoDecoder::InitializeInternal() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

const std::optional<StarboardVideoSampleInfo>&
StarboardVideoDecoder::GetVideoSampleInfo() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return video_sample_info_;
}

bool StarboardVideoDecoder::SetConfig(const VideoConfig& config) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  resolution_changed_ =
      config.width != config_.width || config.height != config_.height;
  if (resolution_changed_) {
    LOG(INFO) << "Video resolution changed from (" << config_.width << ", "
              << config_.height << ") to (" << config.width << ", "
              << config.height << ")";
  }

  // Note: we do not call delegate->OnVideoResolutionChanged in this function,
  // because the VideoPipelineImpl (the delegate) may not be in the kPlaying
  // state. In that case, it ignores resolution changes. So we wait until
  // PushBuffer; at that point, it must be in the kPlaying state.

  config_ = config;
  video_sample_info_ = ToVideoSampleInfo(config_);

  codec_mime_ =
      GetMimeType(config.codec, config.profile, config.codec_profile_level);
  video_sample_info_->mime = codec_mime_.c_str();

  return IsValidConfig(config_);
}

BufferStatus StarboardVideoDecoder::PushBuffer(CastDecoderBuffer* buffer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(buffer);

  // At this point the VideoPipelineImpl (the delegate) should be in the
  // kPlaying state, so it is safe to update the resolution.
  MediaPipelineBackend::Decoder::Delegate* const delegate = GetDelegate();
  if (resolution_changed_ && delegate) {
    delegate->OnVideoResolutionChanged(
        chromecast::Size(config_.width, config_.height));
    resolution_changed_ = false;
  }

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

  DCHECK(video_sample_info_);

  const size_t copy_size = buffer->data_size();
  std::unique_ptr<uint8_t[]> data_copy(new uint8_t[copy_size]);
  uint8_t* const copy_addr = data_copy.get();
  memcpy(copy_addr, buffer->data(), copy_size);

  StarboardSampleInfo sample = {};
  sample.type = kStarboardMediaTypeVideo;
  sample.timestamp = buffer->timestamp();
  sample.side_data = nullptr;
  sample.side_data_count = 0;
  sample.video_sample_info = *video_sample_info_;

  decoded_bytes_ += copy_size;

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

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

  StarboardPlayerInfo player_info = {};
  GetStarboardApi().GetPlayerInfo(GetPlayer(), &player_info);

  statistics->decoded_bytes = decoded_bytes_;
  statistics->decoded_frames = player_info.total_video_frames;
  statistics->dropped_frames = player_info.dropped_video_frames;
}

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

}  // namespace media
}  // namespace chromecast