chromium/chromecast/cast_core/runtime/browser/core_streaming_config_manager.cc

// Copyright 2022 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/cast_core/runtime/browser/core_streaming_config_manager.h"

#include <string_view>
#include <utility>

#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/not_fatal_until.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "chromecast/shared/platform_info_serializer.h"
#include "components/cast/message_port/platform_message_port.h"
#include "components/cast_receiver/browser/public/message_port_service.h"
#include "components/cast_receiver/common/public/status.h"
#include "media/base/audio_codecs.h"
#include "media/base/channel_layout.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"

namespace chromecast {
namespace {

constexpr char kMediaCapabilitiesBindingName[] =
    "cast.__platform__.canDisplayType";

::media::VideoCodec ToChromiumCodec(media::VideoCodec codec) {
  switch (codec) {
    case media::VideoCodec::kCodecH264:
      return ::media::VideoCodec::kH264;
    case media::VideoCodec::kCodecVP8:
      return ::media::VideoCodec::kVP8;
    case media::VideoCodec::kCodecHEVC:
      return ::media::VideoCodec::kHEVC;
    case media::VideoCodec::kCodecVP9:
      return ::media::VideoCodec::kVP9;
    case media::VideoCodec::kCodecAV1:
      return ::media::VideoCodec::kAV1;
    default:
      break;
  }

  return ::media::VideoCodec::kUnknown;
}

::media::AudioCodec ToChromiumCodec(media::AudioCodec codec) {
  switch (codec) {
    case media::AudioCodec::kCodecAAC:
      return ::media::AudioCodec::kAAC;
    case media::AudioCodec::kCodecOpus:
      return ::media::AudioCodec::kOpus;
    default:
      break;
  }

  return ::media::AudioCodec::kUnknown;
}

cast_streaming::ReceiverConfig CreateConfig(
    const PlatformInfoSerializer& deserializer) {
  cast_streaming::ReceiverConfig constraints;

  const std::optional<int> width = deserializer.MaxWidth();
  const std::optional<int> height = deserializer.MaxHeight();
  const std::optional<int> frame_rate = deserializer.MaxFrameRate();
  if (width && *width && height && *height && frame_rate && *frame_rate) {
    cast_streaming::ReceiverConfig::Display display;
    display.dimensions = gfx::Rect{*width, *height};
    display.max_frame_rate = *frame_rate;
    constraints.display_description = std::move(display);
  } else {
    DLOG(WARNING)
        << "Some Display properties missing. Using default values for "
        << "MaxWidth, MaxHeight, and MaxFrameRate";

#if DCHECK_IS_ON()
    if (!height) {
      LOG(INFO) << "MaxHeight value not present in received AV Settings.";
    } else if (!*height) {
      LOG(WARNING) << "Invalid MaxHeight of 0 parsed from AV Settings.";
    } else if (height) {
      LOG(WARNING)
          << "MaxHeight value ignored due to missing display properties.";
    }

    if (!width) {
      LOG(INFO) << "MaxWidth value not present in received AV Settings.";
    } else if (!*width) {
      LOG(WARNING) << "Invalid MaxWidth of 0 parsed from AV Settings.";
    } else if (width) {
      LOG(WARNING)
          << "MaxWidth value ignored due to missing display properties.";
    }

    if (!frame_rate) {
      LOG(INFO) << "MaxFrameRate value not present in received AV Settings.";
    } else if (!*frame_rate) {
      LOG(WARNING) << "Invalid MaxFrameRate of 0 parsed from AV Settings.";
    } else if (frame_rate) {
      LOG(WARNING)
          << "MaxFrameRate value ignored due to missing display properties.";
    }
#endif  // DCHECK_IS_ON()
  }

  auto audio_codec_infos = deserializer.SupportedAudioCodecs();
  std::vector<::media::AudioCodec> audio_codecs;
  std::vector<cast_streaming::ReceiverConfig::AudioLimits> audio_limits;
  if (!audio_codec_infos || (*audio_codec_infos).empty()) {
    DLOG(WARNING) << "No AudioCodecInfos in received AV Settings.";
  } else {
    for (auto& info : *audio_codec_infos) {
      const auto converted_codec = ToChromiumCodec(info.codec);
      if (converted_codec == ::media::AudioCodec::kUnknown) {
        DLOG(INFO) << "Skipping processing of unknown audio codec...";
        continue;
      }

      if (!base::Contains(audio_codecs, converted_codec)) {
        audio_codecs.push_back(converted_codec);

        audio_limits.emplace_back();
        auto& limit = audio_limits.back();
        limit.codec = converted_codec;
        limit.max_sample_rate = info.max_samples_per_second;
        limit.channel_layout =
            ::media::GuessChannelLayout(info.max_audio_channels);
        continue;
      }

      auto it = base::ranges::find(
          audio_limits, converted_codec,
          &cast_streaming::ReceiverConfig::AudioLimits::codec);
      CHECK(it != audio_limits.end(), base::NotFatalUntil::M130);
      if (it->max_sample_rate) {
        it->max_sample_rate =
            std::max(it->max_sample_rate.value(), info.max_samples_per_second);
      } else {
        it->max_sample_rate = info.max_samples_per_second;
      }
      it->channel_layout = ::media::GuessChannelLayout(info.max_audio_channels);
    }
  }

  if (!audio_codecs.empty()) {
    DCHECK(!audio_limits.empty());
    constraints.audio_codecs = std::move(audio_codecs);
    constraints.audio_limits = std::move(audio_limits);
  } else {
    auto max_channels = deserializer.MaxChannels();
    if (max_channels && *max_channels) {
      constraints.audio_limits.emplace_back();
      constraints.audio_limits.back().channel_layout =
          ::media::GuessChannelLayout(*max_channels);
    }
  }

  auto video_codec_infos = deserializer.SupportedVideoCodecs();
  if (!video_codec_infos || (*video_codec_infos).empty()) {
    DLOG(WARNING) << "No VideoCodecInfos in received AV Settings.";
  } else {
    std::vector<::media::VideoCodec> video_codecs;
    for (auto& info : *video_codec_infos) {
      const auto converted_codec = ToChromiumCodec(info.codec);
      if (converted_codec == ::media::VideoCodec::kUnknown) {
        DLOG(INFO) << "Skipping processing of unknown video codec...";
        continue;
      }

      if (!base::Contains(video_codecs, converted_codec)) {
        video_codecs.push_back(converted_codec);
      }
    }

    if (!video_codecs.empty()) {
      constraints.video_codecs = std::move(video_codecs);
    }
  }

  return constraints;
}

}  // namespace

CoreStreamingConfigManager::CoreStreamingConfigManager(
    cast_receiver::MessagePortService& message_port_service,
    cast_receiver::RuntimeApplication::StatusCallback error_cb)
    : CoreStreamingConfigManager(std::move(error_cb)) {
  std::unique_ptr<cast_api_bindings::MessagePort> server;
  cast_api_bindings::CreatePlatformMessagePortPair(&message_port_, &server);
  message_port_->SetReceiver(this);

  message_port_service.ConnectToPortAsync(kMediaCapabilitiesBindingName,
                                          std::move(server));
}

CoreStreamingConfigManager::CoreStreamingConfigManager(
    cast_receiver::RuntimeApplication::StatusCallback error_cb)
    : error_callback_(std::move(error_cb)) {}

CoreStreamingConfigManager::~CoreStreamingConfigManager() = default;

bool CoreStreamingConfigManager::OnMessage(
    std::string_view message,
    std::vector<std::unique_ptr<cast_api_bindings::MessagePort>> ports) {
  DLOG(INFO) << "AV Settings Response Received: " << message;

  DCHECK(ports.empty());

  std::optional<PlatformInfoSerializer> deserializer =
      PlatformInfoSerializer::Deserialize(message);
  if (!deserializer) {
    LOG(ERROR) << "AV Settings with invalid protobuf received: " << message;
    if (error_callback_) {
      std::move(error_callback_)
          .Run(cast_receiver::Status(
              cast_receiver::StatusCode::kInvalidArgument,
              "AV Settings with invalid protobuf received"));
    }
    return false;
  }

  OnStreamingConfigSet(CreateConfig(*deserializer));
  return true;
}

void CoreStreamingConfigManager::OnPipeError() {
  DLOG(WARNING) << "Pipe disconnected.";
  if (error_callback_) {
    std::move(error_callback_)
        .Run(cast_receiver::Status(cast_receiver::StatusCode::kInternal,
                                   "MessagePort pipe disconnected"));
  }
}

}  // namespace chromecast