Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 45 additions & 31 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <bitset>
#include <list>
#include <thread>
#include <utility>

// lib includes
#include <boost/pointer_cast.hpp>
Expand Down Expand Up @@ -143,6 +144,18 @@ namespace video {

} // namespace qsv

int select_h264_profile(std::string_view encoder_name, const config_t &config, int amd_coder) {
if (config.chromaSamplingType == 1) {
return AV_PROFILE_H264_HIGH_444_PREDICTIVE;
}

if (encoder_name == "h264_amf"sv && amd_coder == std::to_underlying(amf::coder_e::cavlc)) {
return AV_PROFILE_H264_CONSTRAINED_BASELINE;
}

return AV_PROFILE_H264_HIGH;
}

/**
* @brief Create an FFmpeg hardware device buffer for D3D11VA input.
*
Expand Down Expand Up @@ -207,16 +220,16 @@ namespace video {

// If we require aspect ratio padding, copy the output frame into the final padded frame
if (requires_padding) {
auto fmt_desc = av_pix_fmt_desc_get((AVPixelFormat) sws_output_frame->format);
auto planes = av_pix_fmt_count_planes((AVPixelFormat) sws_output_frame->format);
auto fmt_desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(sws_output_frame->format));
auto planes = av_pix_fmt_count_planes(static_cast<AVPixelFormat>(sws_output_frame->format));
for (int plane = 0; plane < planes; plane++) {
auto shift_h = plane == 0 ? 0 : fmt_desc->log2_chroma_h;
auto shift_w = plane == 0 ? 0 : fmt_desc->log2_chroma_w;
auto offset = ((offsetW >> shift_w) * fmt_desc->comp[plane].step) + (offsetH >> shift_h) * sw_frame->linesize[plane];

// Copy line-by-line to preserve leading padding for each row
for (int line = 0; line < sws_output_frame->height >> shift_h; line++) {
memcpy(sw_frame->data[plane] + offset + (line * sw_frame->linesize[plane]), sws_output_frame->data[plane] + (line * sws_output_frame->linesize[plane]), (size_t) (sws_output_frame->width >> shift_w) * fmt_desc->comp[plane].step);
memcpy(sw_frame->data[plane] + offset + (line * sw_frame->linesize[plane]), sws_output_frame->data[plane] + (line * sws_output_frame->linesize[plane]), static_cast<std::size_t>(sws_output_frame->width >> shift_w) * fmt_desc->comp[plane].step);
}
}
}
Expand Down Expand Up @@ -275,7 +288,7 @@ namespace video {
av_frame_get_buffer(frame, 0);
av_frame_make_writable(frame);
ptrdiff_t linesize[4] = {frame->linesize[0], frame->linesize[1], frame->linesize[2], frame->linesize[3]};
av_image_fill_black(frame->data, linesize, (AVPixelFormat) frame->format, frame->color_range, frame->width, frame->height);
av_image_fill_black(frame->data, linesize, static_cast<AVPixelFormat>(frame->format), frame->color_range, frame->width, frame->height);
}

/**
Expand Down Expand Up @@ -307,7 +320,7 @@ namespace video {
auto out_height = frame->height;

// Ensure aspect ratio is maintained
auto scalar = std::fminf((float) out_width / in_width, (float) out_height / in_height);
auto scalar = std::fminf(static_cast<float>(out_width) / in_width, static_cast<float>(out_height) / in_height);
out_width = in_width * scalar;
out_height = in_height * scalar;

Expand Down Expand Up @@ -777,11 +790,11 @@ namespace video {
},
{
// SDR-specific options
{"profile"s, (int) nv::profile_hevc_e::main},
{"profile"s, std::to_underlying(nv::profile_hevc_e::main)},
},
{
// HDR-specific options
{"profile"s, (int) nv::profile_hevc_e::main_10},
{"profile"s, std::to_underlying(nv::profile_hevc_e::main_10)},
},
{}, // YUV444 SDR-specific options
{}, // YUV444 HDR-specific options
Expand All @@ -804,7 +817,7 @@ namespace video {
},
{
// SDR-specific options
{"profile"s, (int) nv::profile_h264_e::high},
{"profile"s, std::to_underlying(nv::profile_h264_e::high)},
},
{}, // HDR-specific options
{}, // YUV444 SDR-specific options
Expand Down Expand Up @@ -843,19 +856,19 @@ namespace video {
},
{
// SDR-specific options
{"profile"s, (int) qsv::profile_av1_e::main},
{"profile"s, std::to_underlying(qsv::profile_av1_e::main)},
},
{
// HDR-specific options
{"profile"s, (int) qsv::profile_av1_e::main},
{"profile"s, std::to_underlying(qsv::profile_av1_e::main)},
},
{
// YUV444 SDR-specific options
{"profile"s, (int) qsv::profile_av1_e::high},
{"profile"s, std::to_underlying(qsv::profile_av1_e::high)},
},
{
// YUV444 HDR-specific options
{"profile"s, (int) qsv::profile_av1_e::high},
{"profile"s, std::to_underlying(qsv::profile_av1_e::high)},
},
{}, // Fallback options
"av1_qsv"s,
Expand All @@ -873,19 +886,19 @@ namespace video {
},
{
// SDR-specific options
{"profile"s, (int) qsv::profile_hevc_e::main},
{"profile"s, std::to_underlying(qsv::profile_hevc_e::main)},
},
{
// HDR-specific options
{"profile"s, (int) qsv::profile_hevc_e::main_10},
{"profile"s, std::to_underlying(qsv::profile_hevc_e::main_10)},
},
{
// YUV444 SDR-specific options
{"profile"s, (int) qsv::profile_hevc_e::rext},
{"profile"s, std::to_underlying(qsv::profile_hevc_e::rext)},
},
{
// YUV444 HDR-specific options
{"profile"s, (int) qsv::profile_hevc_e::rext},
{"profile"s, std::to_underlying(qsv::profile_hevc_e::rext)},
},
{
// Fallback options
Expand All @@ -911,12 +924,12 @@ namespace video {
},
{
// SDR-specific options
{"profile"s, (int) qsv::profile_h264_e::high},
{"profile"s, std::to_underlying(qsv::profile_h264_e::high)},
},
{}, // HDR-specific options
{
// YUV444 SDR-specific options
{"profile"s, (int) qsv::profile_h264_e::high_444p},
{"profile"s, std::to_underlying(qsv::profile_h264_e::high_444p)},
},
{}, // YUV444 HDR-specific options
{
Expand Down Expand Up @@ -1022,6 +1035,7 @@ namespace video {
{"rc"s, &config::video.amd.amd_rc_h264},
{"usage"s, &config::video.amd.amd_usage_h264},
{"vbaq"s, &config::video.amd.amd_vbaq},
{"coder"s, &config::video.amd.amd_coder},
{"enforce_hrd"s, &config::video.amd.amd_enforce_hrd},
},
{}, // SDR-specific options
Expand Down Expand Up @@ -1719,7 +1733,7 @@ namespace video {

// Process any pending display switch with the new list of displays
if (switch_display_event->peek()) {
display_p = std::clamp(*switch_display_event->pop(), 0, (int) display_names.size() - 1);
display_p = std::clamp(*switch_display_event->pop(), 0, static_cast<int>(display_names.size()) - 1);
}

// reset_display() will sleep between retries
Expand All @@ -1743,7 +1757,7 @@ namespace video {
case platf::capture_e::interrupted:
return;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
return;
}
}
Expand Down Expand Up @@ -1808,16 +1822,16 @@ namespace video {
vps = std::move(hevc.vps);

session.replacements.emplace_back(
std::string_view((char *) std::begin(vps.old), vps.old.size()),
std::string_view((char *) std::begin(vps._new), vps._new.size())
std::string_view(reinterpret_cast<const char *>(std::begin(vps.old)), vps.old.size()),
std::string_view(reinterpret_cast<const char *>(std::begin(vps._new)), vps._new.size())
);
}

session.inject = 0;

session.replacements.emplace_back(
std::string_view((char *) std::begin(sps.old), sps.old.size()),
std::string_view((char *) std::begin(sps._new), sps._new.size())
std::string_view(reinterpret_cast<const char *>(std::begin(sps.old)), sps.old.size()),
std::string_view(reinterpret_cast<const char *>(std::begin(sps._new)), sps._new.size())
);
}

Expand Down Expand Up @@ -1965,7 +1979,7 @@ namespace video {
case 0:
// 10-bit h264 encoding is not supported by our streaming protocol
assert(!config.dynamicRange);
ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_H264_HIGH_444_PREDICTIVE : AV_PROFILE_H264_HIGH;
ctx->profile = select_h264_profile(video_format.name, config, config::video.amd.amd_coder);
break;

case 1:
Expand Down Expand Up @@ -2276,7 +2290,7 @@ namespace video {
std::move(encode_device_final),

// 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc
config.videoFormat <= 1 ? (1 - (int) video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat) : 0
config.videoFormat <= 1 ? (1 - static_cast<int>(video_format[encoder_t::VUI_PARAMETERS])) * (1 + config.videoFormat) : 0
);

return session;
Expand Down Expand Up @@ -2643,7 +2657,7 @@ namespace video {

// Process any pending display switch with the new list of displays
if (switch_display_event->peek()) {
display_p = std::clamp(*switch_display_event->pop(), 0, (int) display_names.size() - 1);
display_p = std::clamp(*switch_display_event->pop(), 0, static_cast<int>(display_names.size()) - 1);
}

// reset_display() will sleep between retries
Expand Down Expand Up @@ -3381,7 +3395,7 @@ namespace video {

BOOST_LOG(debug) << "------ h264 ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
auto flag = (encoder_t::flag_e) x;
auto flag = static_cast<encoder_t::flag_e>(x);
BOOST_LOG(debug) << encoder_t::from_flag(flag) << (encoder.h264[flag] ? ": supported"sv : ": unsupported"sv);
}
BOOST_LOG(debug) << "-------------------"sv;
Expand All @@ -3390,7 +3404,7 @@ namespace video {
if (encoder.hevc[encoder_t::PASSED]) {
BOOST_LOG(debug) << "------ hevc ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
auto flag = (encoder_t::flag_e) x;
auto flag = static_cast<encoder_t::flag_e>(x);
BOOST_LOG(debug) << encoder_t::from_flag(flag) << (encoder.hevc[flag] ? ": supported"sv : ": unsupported"sv);
}
BOOST_LOG(debug) << "-------------------"sv;
Expand All @@ -3401,7 +3415,7 @@ namespace video {
if (encoder.av1[encoder_t::PASSED]) {
BOOST_LOG(debug) << "------ av1 ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
auto flag = (encoder_t::flag_e) x;
auto flag = static_cast<encoder_t::flag_e>(x);
BOOST_LOG(debug) << encoder_t::from_flag(flag) << (encoder.av1[flag] ? ": supported"sv : ": unsupported"sv);
}
BOOST_LOG(debug) << "-------------------"sv;
Expand Down Expand Up @@ -3566,7 +3580,7 @@ namespace video {

std::fill_n((std::uint8_t *) ctx, sizeof(AVD3D11VADeviceContext), 0);

auto device = (ID3D11Device *) encode_device->data;
auto device = static_cast<ID3D11Device *>(encode_device->data);

device->AddRef();
ctx->device = device;
Expand Down
24 changes: 24 additions & 0 deletions src/video.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

// standard includes
#include <chrono>
#include <string_view>

// local includes
#include "input.h"
Expand Down Expand Up @@ -40,6 +41,29 @@ namespace video {
int enableIntraRefresh; ///< Intra refresh setting: 0 = disabled, 1 = enabled.
};

namespace amf {

/**
* @brief Enumerates supported coder options for the AMF encoder.
*/
enum class coder_e : int {
auto_ = 0, ///< Select the coder based on the H.264 profile.
cabac = 1, ///< CABAC entropy coding.
cavlc = 2, ///< CAVLC entropy coding.
};

} // namespace amf

/**
* @brief Select the effective FFmpeg H.264 profile for an encoder configuration.
*
* @param encoder_name FFmpeg encoder name selected for the stream.
* @param config Encoding configuration requested by the remote client.
* @param amd_coder Configured AMF entropy-coder value.
* @return FFmpeg H.264 profile applied to the codec context.
*/
int select_h264_profile(std::string_view encoder_name, const config_t &config, int amd_coder);

/**
* @brief Map an FFmpeg hardware device type to Sunshine's memory type.
*
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@
* @file tests/unit/test_video.cpp
* @brief Test src/video.*.
*/
// test includes
#include "../tests_common.h"

// standard includes
#include <algorithm>
#include <tuple>
#include <utility>

// local includes
#include <src/config.h>
#include <src/video.h>

using namespace std::literals;

struct EncoderTest: PlatformTestSuite, testing::WithParamInterface<video::encoder_t *> {
void SetUp() override {
BaseTest::SetUp();
Expand Down Expand Up @@ -50,6 +60,41 @@ TEST_P(EncoderTest, ValidateEncoder) {
// todo:: test something besides fixture setup
}

/**
* @brief Parameterized coverage for effective H.264 profile selection.
*/
struct H264ProfileTest: testing::TestWithParam<std::tuple<std::string_view, video::amf::coder_e, int, int>> {};

TEST_P(H264ProfileTest, SelectProfile) {
const auto &[encoder_name, coder, chroma_sampling_type, expected_profile] = GetParam();
video::config_t config {};
config.chromaSamplingType = chroma_sampling_type;

EXPECT_EQ(expected_profile, video::select_h264_profile(encoder_name, config, std::to_underlying(coder)));
}

INSTANTIATE_TEST_SUITE_P(
H264ProfileTests,
H264ProfileTest,
testing::Values(
std::make_tuple("h264_amf"sv, video::amf::coder_e::auto_, 0, AV_PROFILE_H264_HIGH),
std::make_tuple("h264_amf"sv, video::amf::coder_e::cabac, 0, AV_PROFILE_H264_HIGH),
std::make_tuple("h264_amf"sv, video::amf::coder_e::cavlc, 0, AV_PROFILE_H264_CONSTRAINED_BASELINE),
std::make_tuple("h264_amf"sv, video::amf::coder_e::cavlc, 1, AV_PROFILE_H264_HIGH_444_PREDICTIVE),
std::make_tuple("h264_nvenc"sv, video::amf::coder_e::cavlc, 0, AV_PROFILE_H264_HIGH)
)
);

#ifdef _WIN32
TEST(AmfH264OptionsTest, CoderUsesConfiguredValue) {
const auto coder_option = std::ranges::find(video::amdvce.h264.common_options, "coder"sv, &video::encoder_t::option_t::name);

ASSERT_NE(video::amdvce.h264.common_options.end(), coder_option);
ASSERT_TRUE(std::holds_alternative<int *>(coder_option->value));
EXPECT_EQ(&config::video.amd.amd_coder, std::get<int *>(coder_option->value));
}
#endif

struct FramerateX100Test: BaseTest, testing::WithParamInterface<std::tuple<std::int32_t, AVRational>> {};

TEST_P(FramerateX100Test, Run) {
Expand Down
Loading