Skip to content

Commit

Permalink
feat(vaapi): add option to enable strict enforcement of frame size
Browse files Browse the repository at this point in the history
  • Loading branch information
cgutman committed Nov 1, 2024
1 parent f418566 commit 2dd5027
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 24 deletions.
27 changes: 27 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,33 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

## VA-API Encoder

### vaapi_strict_rc_buffer

<table>
<tr>
<td>Description</td>
<td colspan="2">
Enabling this option can avoid dropped frames over the network during scene changes, but video quality may
be reduced during motion.
@note{This option only applies for H.264 and HEVC when using VA-API [encoder](#encoder) on AMD GPUs.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
disabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
vaapi_strict_rc_buffer = enabled
@endcode</td>
</tr>
</table>

## Software Encoder

### sw_preset
Expand Down
6 changes: 6 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ namespace config {
-1,
}, // vt

{
false, // strict_rc_buffer
}, // vaapi

{}, // capture
{}, // encoder
{}, // adapter_name
Expand Down Expand Up @@ -1014,6 +1018,8 @@ namespace config {
int_f(vars, "vt_software", video.vt.vt_require_sw, vt::force_software_from_view);
int_f(vars, "vt_realtime", video.vt.vt_realtime, vt::rt_from_view);

bool_f(vars, "vaapi_strict_rc_buffer", video.vaapi.strict_rc_buffer);

string_f(vars, "capture", video.capture);
string_f(vars, "encoder", video.encoder);
string_f(vars, "adapter_name", video.adapter_name);
Expand Down
4 changes: 4 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ namespace config {
int vt_coder;
} vt;

struct {
bool strict_rc_buffer;
} vaapi;

std::string capture;
std::string encoder;
std::string adapter_name;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ namespace platf {
* @note Implementations may set or modify codec options prior to codec initialization.
*/
virtual void
init_codec_options(AVCodecContext *ctx, AVDictionary *options) {};
init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};

/**
* @brief Prepare to derive a context.
Expand Down
164 changes: 160 additions & 4 deletions src/platform/linux/vaapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h>
#include <va/va.h>
#include <va/va_drm.h>
#if !VA_CHECK_VERSION(1, 9, 0)
Expand Down Expand Up @@ -129,13 +130,168 @@ namespace va {
return 0;
}

/**
* @brief Finds a supported VA entrypoint for the given VA profile.
* @param profile The profile to match.
* @return A valid encoding entrypoint or 0 on failure.
*/
VAEntrypoint
select_va_entrypoint(VAProfile profile) {
std::vector<VAEntrypoint> entrypoints(vaMaxNumEntrypoints(va_display));
int num_eps;

Check warning on line 141 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L139-L141

Added lines #L139 - L141 were not covered by tests
auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps);
if (status != VA_STATUS_SUCCESS) {
BOOST_LOG(error) << "Failed to query VA entrypoints: "sv << vaErrorStr(status);
return (VAEntrypoint) 0;

Check warning on line 145 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L145

Added line #L145 was not covered by tests
}
entrypoints.resize(num_eps);

// Sorted in order of descending preference
VAEntrypoint ep_preferences[] = {

Check warning on line 150 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L150

Added line #L150 was not covered by tests
VAEntrypointEncSliceLP,
VAEntrypointEncSlice,
VAEntrypointEncPicture
};
for (auto ep_pref : ep_preferences) {
if (std::find(entrypoints.begin(), entrypoints.end(), ep_pref) != entrypoints.end()) {
return ep_pref;

Check warning on line 157 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L157

Added line #L157 was not covered by tests
}
}

return (VAEntrypoint) 0;

Check warning on line 161 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L161

Added line #L161 was not covered by tests
}

/**
* @brief Determines if a given VA profile is supported.
* @param profile The profile to match.
* @return Boolean value indicating if the profile is supported.
*/
bool
is_va_profile_supported(VAProfile profile) {
std::vector<VAProfile> profiles(vaMaxNumProfiles(va_display));
int num_profs;

Check warning on line 172 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L170-L172

Added lines #L170 - L172 were not covered by tests
auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs);
if (status != VA_STATUS_SUCCESS) {
BOOST_LOG(error) << "Failed to query VA profiles: "sv << vaErrorStr(status);
return false;

Check warning on line 176 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L176

Added line #L176 was not covered by tests
}
profiles.resize(num_profs);

return std::find(profiles.begin(), profiles.end(), profile) != profiles.end();

Check warning on line 180 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L180

Added line #L180 was not covered by tests
}

/**
* @brief Determines the matching VA profile for the codec configuration.
* @param ctx The FFmpeg codec context.
* @return The matching VA profile or `VAProfileNone` on failure.
*/
VAProfile
get_va_profile(AVCodecContext *ctx) {

Check warning on line 189 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L189

Added line #L189 was not covered by tests
if (ctx->codec_id == AV_CODEC_ID_H264) {
// There's no VAAPI profile for H.264 4:4:4
return VAProfileH264High;
}
else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
switch (ctx->profile) {
case FF_PROFILE_HEVC_REXT:

Check warning on line 196 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L196

Added line #L196 was not covered by tests
switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) {
case 10:
return VAProfileHEVCMain444_10;
case 8:
return VAProfileHEVCMain444;

Check warning on line 201 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L200-L201

Added lines #L200 - L201 were not covered by tests
}
break;
case FF_PROFILE_HEVC_MAIN_10:
return VAProfileHEVCMain10;
case FF_PROFILE_HEVC_MAIN:
return VAProfileHEVCMain;

Check warning on line 207 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L206-L207

Added lines #L206 - L207 were not covered by tests
}
}
else if (ctx->codec_id == AV_CODEC_ID_AV1) {
switch (ctx->profile) {
case FF_PROFILE_AV1_HIGH:
return VAProfileAV1Profile1;
case FF_PROFILE_AV1_MAIN:
return VAProfileAV1Profile0;

Check warning on line 215 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L214-L215

Added lines #L214 - L215 were not covered by tests
}
}

BOOST_LOG(error) << "Unknown encoder profile: "sv << ctx->profile;
return VAProfileNone;

Check warning on line 220 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L220

Added line #L220 was not covered by tests
}

void
init_codec_options(AVCodecContext *ctx, AVDictionary *options) override {
// Don't set the RC buffer size when using H.264 on Intel GPUs. It causes
// major encoding quality degradation.
init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
auto va_profile = get_va_profile(ctx);

Check warning on line 225 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L224-L225

Added lines #L224 - L225 were not covered by tests
if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
// Don't bother doing anything if the profile isn't supported
return;

Check warning on line 228 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L228

Added line #L228 was not covered by tests
}

auto va_entrypoint = select_va_entrypoint(va_profile);

Check warning on line 231 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L231

Added line #L231 was not covered by tests
if (va_entrypoint == 0) {
// It's possible that only decoding is supported for this profile
return;
}

auto vendor = vaQueryVendorString(va_display);
if (ctx->codec_id != AV_CODEC_ID_H264 || (vendor && !strstr(vendor, "Intel"))) {

if (va_entrypoint == VAEntrypointEncSliceLP) {
BOOST_LOG(info) << "Using LP encoding mode"sv;
av_dict_set_int(options, "low_power", 1, 0);

Check warning on line 241 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L241

Added line #L241 was not covered by tests
}
else {
BOOST_LOG(info) << "Using normal encoding mode"sv;
}

VAConfigAttrib rc_attr = { VAConfigAttribRateControl };
auto status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &rc_attr, 1);

Check warning on line 248 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L247-L248

Added lines #L247 - L248 were not covered by tests
if (status != VA_STATUS_SUCCESS) {
// Stick to the default rate control (CQP)
rc_attr.value = 0;

Check warning on line 251 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L251

Added line #L251 was not covered by tests
}

VAConfigAttrib slice_attr = { VAConfigAttribEncMaxSlices };
status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &slice_attr, 1);

Check warning on line 255 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L254-L255

Added lines #L254 - L255 were not covered by tests
if (status != VA_STATUS_SUCCESS) {
// Assume only a single slice is supported
slice_attr.value = 1;

Check warning on line 258 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L258

Added line #L258 was not covered by tests
}
if (ctx->slices > slice_attr.value) {
BOOST_LOG(info) << "Limiting slice count to encoder maximum: "sv << slice_attr.value;
ctx->slices = slice_attr.value;

Check warning on line 262 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L262

Added line #L262 was not covered by tests
}

// Use VBR with a single frame VBV when the user forces it and for known good cases:
// - Intel GPUs
// - AV1
//
// VBR ensures the bitstream isn't full of filler data for bitrate undershoots and
// single frame VBV ensures that we don't have large bitrate overshoots (at least
// as much as they can be avoided without pre-analysis).
//
// When we have to resort to the default 1 second VBV for encoding quality reasons,
// we stick to CBR in order to avoid encoding huge frames after bitrate undershoots
// leave headroom available in the RC window.
if (config::video.vaapi.strict_rc_buffer ||
(vendor && strstr(vendor, "Intel")) ||
ctx->codec_id == AV_CODEC_ID_AV1) {
ctx->rc_buffer_size = ctx->bit_rate * ctx->framerate.den / ctx->framerate.num;

if (rc_attr.value & VA_RC_VBR) {
BOOST_LOG(info) << "Using VBR with single frame VBV size"sv;
av_dict_set(options, "rc_mode", "VBR", 0);

Check warning on line 283 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L283

Added line #L283 was not covered by tests
}
else if (rc_attr.value & VA_RC_CBR) {
BOOST_LOG(info) << "Using CBR with single frame VBV size"sv;
av_dict_set(options, "rc_mode", "CBR", 0);

Check warning on line 287 in src/platform/linux/vaapi.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/vaapi.cpp#L287

Added line #L287 was not covered by tests
}
else {
BOOST_LOG(info) << "Using default rate control with single frame VBV size"sv;
}
}
else {
BOOST_LOG(info) << "Using default rate control"sv;
}
}

Expand Down
22 changes: 5 additions & 17 deletions src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,25 +857,20 @@ namespace video {
{
// Common options
{
{ "low_power"s, 1 },
{ "async_depth"s, 1 },
{ "idr_interval"s, std::numeric_limits<int>::max() },
},
{}, // SDR-specific options
{}, // HDR-specific options
{}, // YUV444 SDR-specific options
{}, // YUV444 HDR-specific options
{
// Fallback options
{ "low_power"s, 0 }, // Not all VAAPI drivers expose LP entrypoints
},
{}, // Fallback options
std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
"av1_vaapi"s,
},
{
// Common options
{
{ "low_power"s, 1 },
{ "async_depth"s, 1 },
{ "sei"s, 0 },
{ "idr_interval"s, std::numeric_limits<int>::max() },
Expand All @@ -884,17 +879,13 @@ namespace video {
{}, // HDR-specific options
{}, // YUV444 SDR-specific options
{}, // YUV444 HDR-specific options
{
// Fallback options
{ "low_power"s, 0 }, // Not all VAAPI drivers expose LP entrypoints
},
{}, // Fallback options
std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
"hevc_vaapi"s,
},
{
// Common options
{
{ "low_power"s, 1 },
{ "async_depth"s, 1 },
{ "sei"s, 0 },
{ "idr_interval"s, std::numeric_limits<int>::max() },
Expand All @@ -903,15 +894,12 @@ namespace video {
{}, // HDR-specific options
{}, // YUV444 SDR-specific options
{}, // YUV444 HDR-specific options
{
// Fallback options
{ "low_power"s, 0 }, // Not all VAAPI drivers expose LP entrypoints
},
{}, // Fallback options
std::make_optional<encoder_t::option_t>("qp"s, &config::video.qp),
"h264_vaapi"s,
},
// RC buffer size will be set in platform code if supported
LIMITED_GOP_SIZE | PARALLEL_ENCODING | SINGLE_SLICE_ONLY | NO_RC_BUF_LIMIT
LIMITED_GOP_SIZE | PARALLEL_ENCODING | NO_RC_BUF_LIMIT
};
#endif

Expand Down Expand Up @@ -1696,7 +1684,7 @@ namespace video {
}

// Allow the encoding device a final opportunity to set/unset or override any options
encode_device->init_codec_options(ctx.get(), options);
encode_device->init_codec_options(ctx.get(), &options);

if (auto status = avcodec_open2(ctx.get(), codec, &options)) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
Expand Down
11 changes: 9 additions & 2 deletions src_assets/common/assets/web/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
"vt_realtime": "enabled",
},
},
{
id: "vaapi",
name: "VA-API Encoder",
options: {
"vaapi_strict_rc_buffer": "disabled",
},
},
{
id: "sw",
name: "Software Encoder",
Expand All @@ -283,7 +290,7 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
var app = document.getElementById("app");
if (this.platform === "windows") {
this.tabs = this.tabs.filter((el) => {
return el.id !== "vt";
return el.id !== "vt" && el.id !== "vaapi";
});
}
if (this.platform === "linux") {
Expand All @@ -293,7 +300,7 @@ <h1 class="my-4">{{ $t('config.configuration') }}</h1>
}
if (this.platform === "macos") {
this.tabs = this.tabs.filter((el) => {
return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv";
return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv" && el.id !== "vaapi";
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import IntelQuickSyncEncoder from './encoders/IntelQuickSyncEncoder.vue'
import AmdAmfEncoder from './encoders/AmdAmfEncoder.vue'
import VideotoolboxEncoder from './encoders/VideotoolboxEncoder.vue'
import SoftwareEncoder from './encoders/SoftwareEncoder.vue'
import VAAPIEncoder from './encoders/VAAPIEncoder.vue'
const props = defineProps([
'platform',
Expand Down Expand Up @@ -45,6 +46,13 @@ const config = ref(props.config)
:config="config"
/>

<!-- VAAPI Encoder Tab -->
<VAAPIEncoder
v-if="currentTab === 'vaapi'"
:platform="platform"
:config="config"
/>

<!-- Software Encoder Tab -->
<SoftwareEncoder
v-if="currentTab === 'sw'"
Expand Down
Loading

0 comments on commit 2dd5027

Please sign in to comment.