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

#include "media/gpu/android/video_accelerator_util.h"

#include "base/android/build_info.h"
#include "base/android/jni_string.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "media/base/android/media_jni_headers/VideoAcceleratorUtil_jni.h"

namespace media {

const std::vector<MediaCodecEncoderInfo>& GetEncoderInfoCache() {
  static const base::NoDestructor<std::vector<MediaCodecEncoderInfo>> infos([] {
    // Sadly the NDK doesn't provide a mechanism for accessing the equivalent of
    // the SDK's MediaCodecList, so we must call into Java to enumerate support.
    JNIEnv* env = base::android::AttachCurrentThread();
    CHECK(env);
    auto java_profiles =
        Java_VideoAcceleratorUtil_getSupportedEncoderProfiles(env);

    constexpr char kHasMediaCodecEncoderInfo[] =
        "Media.Android.MediaCodecInfo.HasEncoderInfo";
    std::vector<MediaCodecEncoderInfo> cpp_infos;
    if (!java_profiles) {
      base::UmaHistogramBoolean(kHasMediaCodecEncoderInfo, false);
      return cpp_infos;
    }

    for (auto java_profile : java_profiles.ReadElements<jobject>()) {
      MediaCodecEncoderInfo info;
      info.profile.profile = static_cast<VideoCodecProfile>(
          Java_SupportedProfileAdapter_getProfile(env, java_profile));
      info.profile.min_resolution = gfx::Size(
          Java_SupportedProfileAdapter_getMinWidth(env, java_profile),
          Java_SupportedProfileAdapter_getMinHeight(env, java_profile));
      info.profile.max_resolution = gfx::Size(
          Java_SupportedProfileAdapter_getMaxWidth(env, java_profile),
          Java_SupportedProfileAdapter_getMaxHeight(env, java_profile));
      info.profile.max_framerate_numerator =
          Java_SupportedProfileAdapter_getMaxFramerateNumerator(env,
                                                                java_profile);
      info.profile.max_framerate_denominator =
          Java_SupportedProfileAdapter_getMaxFramerateDenominator(env,
                                                                  java_profile);
      if (Java_SupportedProfileAdapter_supportsCbr(env, java_profile)) {
        info.profile.rate_control_modes |=
            VideoEncodeAccelerator::kConstantMode;
      }
      if (Java_SupportedProfileAdapter_supportsVbr(env, java_profile)) {
        info.profile.rate_control_modes |=
            VideoEncodeAccelerator::kVariableMode;
      }
      info.profile.is_software_codec =
          Java_SupportedProfileAdapter_isSoftwareCodec(env, java_profile);

      info.name = base::android::ConvertJavaStringToUTF8(
          Java_SupportedProfileAdapter_getName(env, java_profile));
      cpp_infos.push_back(info);
    }
    std::sort(
        cpp_infos.begin(), cpp_infos.end(),
        [](const MediaCodecEncoderInfo& a, const MediaCodecEncoderInfo& b) {
          return a.profile.profile < b.profile.profile;
        });
    base::UmaHistogramBoolean(kHasMediaCodecEncoderInfo, !cpp_infos.empty());
    return cpp_infos;
  }());
  return *infos;
}

const std::vector<MediaCodecDecoderInfo>& GetDecoderInfoCache() {
  static const base::NoDestructor<std::vector<MediaCodecDecoderInfo>> infos([] {
    JNIEnv* env = base::android::AttachCurrentThread();
    CHECK(env);
    auto java_profiles =
        Java_VideoAcceleratorUtil_getSupportedDecoderProfiles(env);
    constexpr char kHasMediaCodecDecoderInfo[] =
        "Media.Android.MediaCodecInfo.HasDecoderInfo";
    if (!java_profiles) {
      // TODO(crbug.com/1413887): Can we remove default profiles?
      base::UmaHistogramBoolean(kHasMediaCodecDecoderInfo, false);

      LOG(ERROR)
          << "Unable to retreive MediaCodecInfo, assuming default support.";

      // Since we don't bundle a software decoder for H.264, H.265, to avoid
      // breaking all video unnecessarily, inject what is likely supported.
      constexpr auto kDefaultSize = gfx::Size(4096, 4096);
      constexpr auto kSoftwareCodec = true;
      constexpr auto kHardwareCodec = false;
      constexpr char kUnknownDecoderName[] = "";
      return std::vector<MediaCodecDecoderInfo>({
          {H264PROFILE_BASELINE, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kHardwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},
          {H264PROFILE_MAIN, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kHardwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},
          {H264PROFILE_HIGH, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kHardwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},
          {HEVCPROFILE_MAIN, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kHardwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},

          // Report codecs as software where we have a bundled decoder.
          {VP8PROFILE_ANY, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kSoftwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},
          {VP9PROFILE_PROFILE0, kNoVideoCodecLevel, gfx::Size(), kDefaultSize,
           kSoftwareCodec, SecureCodecCapability::kClear, kUnknownDecoderName},
      });
    }

    std::vector<MediaCodecDecoderInfo> cpp_infos;
    for (auto java_profile : java_profiles.ReadElements<jobject>()) {
      MediaCodecDecoderInfo info;
      info.profile = static_cast<VideoCodecProfile>(
          Java_SupportedProfileAdapter_getProfile(env, java_profile));
      info.level = Java_SupportedProfileAdapter_getLevel(env, java_profile);
      info.coded_size_min = gfx::Size(
          Java_SupportedProfileAdapter_getMinWidth(env, java_profile),
          Java_SupportedProfileAdapter_getMinHeight(env, java_profile));
      info.coded_size_max = gfx::Size(
          Java_SupportedProfileAdapter_getMaxWidth(env, java_profile),
          Java_SupportedProfileAdapter_getMaxHeight(env, java_profile));
      info.is_software_codec =
          Java_SupportedProfileAdapter_isSoftwareCodec(env, java_profile);
      bool supports_secure_playback =
          Java_SupportedProfileAdapter_supportsSecurePlayback(env,
                                                              java_profile);
      bool requires_secure_playback =
          Java_SupportedProfileAdapter_requiresSecurePlayback(env,
                                                              java_profile);
      // If the decoder requires secure playback, it must support secure
      // playback.
      DCHECK(!requires_secure_playback || supports_secure_playback);
      info.secure_codec_capability =
          requires_secure_playback
              ? SecureCodecCapability::kEncrypted
              : (supports_secure_playback ? SecureCodecCapability::kAny
                                          : SecureCodecCapability::kClear);
      info.name = base::android::ConvertJavaStringToUTF8(
          Java_SupportedProfileAdapter_getName(env, java_profile));
      cpp_infos.push_back(info);
    }
    std::sort(
        cpp_infos.begin(), cpp_infos.end(),
        [](const MediaCodecDecoderInfo& a, const MediaCodecDecoderInfo& b) {
          return a.profile < b.profile;
        });

    base::UmaHistogramBoolean(kHasMediaCodecDecoderInfo, !cpp_infos.empty());
    return cpp_infos;
  }());
  return *infos;
}

}  // namespace media