blob: 8aab712dd15b85533cb76e6ec76d1d40af8eaf81 [file]
// 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 "media/cast/encoding/encoding_support.h"
#include <algorithm>
#include <bitset>
#include <optional>
#include <string>
#include <string_view>
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "media/media_buildflags.h"
#include "ui/gfx/geometry/size.h"
namespace media::cast::encoding_support {
namespace {
using VideoCodecBitset =
std::bitset<static_cast<size_t>(VideoCodec::kMaxValue) + 1>;
static VideoCodecBitset& GetHardwareCodecDenyList() {
static VideoCodecBitset kInstance;
return kInstance;
}
// Configuration parameters used for determining if hardware encoding is enabled
// for a codec.
struct HardwareCodecParameters {
// Minimum and maximum supported profiles for the codec.
VideoCodecProfile min_profile;
VideoCodecProfile max_profile;
// Command line switches to force enable/disable hardware encoding.
const char* force_enable_switch;
const char* force_disable_switch;
// Platform-specific feature flag to control hardware encoding. Can be null if
// no feature flag is used.
raw_ptr<const base::Feature> feature;
};
std::optional<HardwareCodecParameters> GetHardwareCodecParameters(
VideoCodec codec) {
switch (codec) {
case VideoCodec::kH264:
return HardwareCodecParameters{
H264PROFILE_MIN, H264PROFILE_MAX,
switches::kCastStreamingForceEnableHardwareH264,
switches::kCastStreamingForceDisableHardwareH264,
#if BUILDFLAG(IS_MAC)
&kCastStreamingMacHardwareH264
#elif BUILDFLAG(IS_WIN)
&kCastStreamingWinHardwareH264
#else
nullptr
#endif
};
case VideoCodec::kHEVC:
return HardwareCodecParameters{
HEVCPROFILE_MIN, HEVCPROFILE_MAX,
switches::kCastStreamingForceEnableHardwareHevc,
switches::kCastStreamingForceDisableHardwareHevc,
&media::kCastStreamingHardwareHevc};
case VideoCodec::kVP8:
return HardwareCodecParameters{
VP8PROFILE_MIN, VP8PROFILE_MAX,
switches::kCastStreamingForceEnableHardwareVp8,
switches::kCastStreamingForceDisableHardwareVp8, &kCastStreamingVp8};
case VideoCodec::kVP9:
return HardwareCodecParameters{
VP9PROFILE_MIN, VP9PROFILE_MAX,
switches::kCastStreamingForceEnableHardwareVp9,
switches::kCastStreamingForceDisableHardwareVp9, &kCastStreamingVp9};
case VideoCodec::kAV1:
return HardwareCodecParameters{
AV1PROFILE_MIN, AV1PROFILE_MAX,
switches::kCastStreamingForceEnableHardwareAv1,
switches::kCastStreamingForceDisableHardwareAv1, &kCastStreamingAv1};
default:
return std::nullopt;
}
}
bool IsCastStreamingAv1Enabled() {
#if BUILDFLAG(ENABLE_LIBAOM)
return base::FeatureList::IsEnabled(kCastStreamingAv1);
#else
return false;
#endif
}
bool IsHardwareEncodingEnabled(
const std::vector<VideoEncodeAccelerator::SupportedProfile>& profiles,
VideoCodecProfile min_profile,
VideoCodecProfile max_profile,
gfx::Size requested_resolution,
double requested_frame_rate) {
return std::ranges::any_of(profiles, [&](const auto& vea_profile) {
if (vea_profile.profile < min_profile ||
vea_profile.profile > max_profile) {
return false;
}
if (requested_resolution.width() < vea_profile.min_resolution.width() ||
requested_resolution.height() < vea_profile.min_resolution.height() ||
requested_resolution.width() > vea_profile.max_resolution.width() ||
requested_resolution.height() > vea_profile.max_resolution.height()) {
return false;
}
if (vea_profile.max_framerate_denominator == 0 ||
vea_profile.max_framerate_numerator == 0) {
return true;
}
const double max_fps =
static_cast<double>(vea_profile.max_framerate_numerator) /
vea_profile.max_framerate_denominator;
return requested_frame_rate <= max_fps;
});
}
constexpr int k1080pPixels = 1920 * 1080;
template <typename T>
T GetCodecLevel(gfx::Size resolution,
double frame_rate,
T level_low,
T level_high) {
if (resolution.Area64() > static_cast<uint64_t>(k1080pPixels) ||
frame_rate > 30.0) {
return level_high;
}
return level_low;
}
// Configuration parameters for generating codec-specific MIME parameter
// strings.
struct CodecParameterConfig {
VideoCodec codec;
// Prepending string for the codec parameter.
std::string_view prefix;
// Appending string for the codec parameter.
std::string_view suffix;
// Level representing <= 1080p30.
std::string_view level_low;
// Level representing > 1080p30.
std::string_view level_high;
};
constexpr CodecParameterConfig kCodecParameterConfigs[] = {
{
VideoCodec::kH264,
"avc1", // Prefix for H.264 (AVC)
"", // Suffix is generated by BuildH264MimeSuffix
"40", // Level 4.0 (decimal 40) supports up to 1080p30 (H.264 Table
// A-1).
"42", // Level 4.2 (decimal 42) supports up to 1080p60 (H.264 Table
// A-1).
},
{
VideoCodec::kVP9,
"vp09.00.", // Profile 0
".08", // 8-bit depth
"40", // Level 4.0 supports up to 1080p30 (VP9 Spec Annex A).
"41", // Level 4.1 supports up to 1080p60 (VP9 Spec Annex A).
},
{
VideoCodec::kHEVC,
"hev1.1.6.L", // Profile 1 (Main), compatibility flags, Tier L (Main)
".B0", // Constraint flags
"120", // Level 4.0 (level * 30 = 120) supports up to 1080p30 (H.265
// Table A.6).
"123", // Level 4.1 (level * 30 = 123) supports up to 1080p60 (H.265
// Table A.6).
},
{
VideoCodec::kAV1,
"av01.0.", // Profile 0 (Main)
".08", // 8-bit depth
"08M", // Level 4.0 (08), Tier M (Main) supports up to 1080p30 (AV1
// Spec Annex A).
"09M", // Level 4.1 (09), Tier M (Main) supports up to 1080p60 (AV1
// Spec Annex A).
},
};
} // namespace
bool IsSoftwareEnabled(VideoCodec codec) {
switch (codec) {
case VideoCodec::kVP8:
return base::FeatureList::IsEnabled(kCastStreamingVp8);
case VideoCodec::kVP9:
return base::FeatureList::IsEnabled(kCastStreamingVp9);
case VideoCodec::kAV1:
return IsCastStreamingAv1Enabled();
// The test infrastructure is responsible for ensuring the fake codec is
// used properly.
case VideoCodec::kUnknown:
return true;
default:
return false;
}
}
bool IsHardwareEnabled(
VideoCodec codec,
const std::vector<VideoEncodeAccelerator::SupportedProfile>& profiles,
gfx::Size requested_resolution,
double requested_frame_rate) {
if (IsHardwareDenyListed(codec)) {
return false;
}
auto config = GetHardwareCodecParameters(codec);
if (!config) {
return false;
}
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(config->force_disable_switch)) {
return false;
}
bool feature_enabled = true;
if (config->feature) {
feature_enabled = base::FeatureList::IsEnabled(*config->feature);
}
if (!feature_enabled &&
!command_line.HasSwitch(config->force_enable_switch)) {
return false;
}
return IsHardwareEncodingEnabled(profiles, config->min_profile,
config->max_profile, requested_resolution,
requested_frame_rate);
}
bool IsHardwareDenyListed(VideoCodec codec) {
return GetHardwareCodecDenyList().test(static_cast<size_t>(codec));
}
void DenyListHardwareCodec(VideoCodec codec) {
// Codecs should not be disabled multiple times. This likely means that we
// offered it again when we shouldn't have, somehow.
CHECK(!IsHardwareDenyListed(codec));
GetHardwareCodecDenyList().set(static_cast<size_t>(codec));
}
void ClearHardwareCodecDenyListForTesting() {
GetHardwareCodecDenyList().reset();
}
VideoCodecProfile ToProfile(VideoCodec codec) {
switch (codec) {
case VideoCodec::kH264:
#if BUILDFLAG(IS_MAC)
if (base::FeatureList::IsEnabled(media::kCastMacForceBaselineProfile)) {
return H264PROFILE_BASELINE;
}
#endif
return H264PROFILE_MAIN;
case VideoCodec::kHEVC:
return HEVCPROFILE_MAIN;
case VideoCodec::kVP8:
return VP8PROFILE_ANY;
case VideoCodec::kVP9:
return VP9PROFILE_PROFILE0;
case VideoCodec::kAV1:
return AV1PROFILE_PROFILE_MAIN;
default:
NOTREACHED() << "Unhandled codec. value=" << static_cast<int>(codec);
}
}
std::string GetCodecParameterString(VideoCodec codec,
gfx::Size resolution,
double frame_rate) {
const auto* it =
std::ranges::find_if(kCodecParameterConfigs,
[codec](const auto& c) { return c.codec == codec; });
if (it == std::ranges::end(kCodecParameterConfigs)) {
return std::string();
}
const auto& config = *it;
std::string_view level_str = GetCodecLevel(
resolution, frame_rate, config.level_low, config.level_high);
// H264 is a special case, since we need to call into BuildH264MimeSuffix to
// get the correct profile information.
if (codec == VideoCodec::kH264) {
const VideoCodecProfile profile = ToProfile(codec);
unsigned int level_int = 0;
CHECK(base::StringToUint(level_str, &level_int));
std::string suffix =
media::BuildH264MimeSuffix(profile, static_cast<uint8_t>(level_int));
return base::StrCat({config.prefix, suffix});
}
return base::StrCat({config.prefix, level_str, config.suffix});
}
} // namespace media::cast::encoding_support