Skip to content

Commit 3a9ce08

Browse files
committed
obs-ffmpeg: Rework AMF dynamic bitrate change behavior
The `amf_xxx_update()` functions for AVC, HEVC, and AV1 are unconditionally invoking Flush() and ReInit() even though this is not required in all situations. Changing the bitrate at least in CBR mode does not require the flush operation and can result in unaligned IDR frames across members of an encoder group. Do not flush the pipeline for CBR bitrate changes; instead, force an IDR to occur with the bitrate change.
1 parent 8c72f80 commit 3a9ce08

File tree

1 file changed

+84
-27
lines changed

1 file changed

+84
-27
lines changed

plugins/obs-ffmpeg/texture-amf.cpp

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ struct amf_base {
186186
bool bframes_supported = false;
187187
bool first_update = true;
188188
bool roi_supported = false;
189+
bool force_idr;
189190

190191
inline amf_base(bool fallback) : fallback(fallback) {}
191192
virtual ~amf_base() = default;
@@ -714,6 +715,26 @@ static void amf_encode_base(amf_base *enc, AMFSurface *amf_surf, encoder_packet
714715

715716
*received_packet = false;
716717

718+
/* -------------------------------------- */
719+
/* Force an IDR or Key frame if signalled */
720+
721+
if (enc->force_idr) {
722+
enc->force_idr = false;
723+
switch (enc->codec) {
724+
case amf_codec_type::AVC:
725+
amf_surf->SetProperty(AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_IDR);
726+
break;
727+
case amf_codec_type::HEVC:
728+
amf_surf->SetProperty(AMF_VIDEO_ENCODER_HEVC_FORCE_PICTURE_TYPE,
729+
AMF_VIDEO_ENCODER_HEVC_PICTURE_TYPE_IDR);
730+
break;
731+
case amf_codec_type::AV1:
732+
amf_surf->SetProperty(AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE,
733+
AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE_KEY);
734+
break;
735+
}
736+
}
737+
717738
bool waiting = true;
718739
while (waiting) {
719740
/* ----------------------------------- */
@@ -1316,8 +1337,14 @@ static inline int get_avc_profile(obs_data_t *settings)
13161337
return AMF_VIDEO_ENCODER_PROFILE_HIGH;
13171338
}
13181339

1319-
static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp)
1340+
static bool amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp)
13201341
{
1342+
/* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property)
1343+
* is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could
1344+
* cause unaligned IDRs in an encoder group.
1345+
*/
1346+
bool pipeline_flush = true;
1347+
13211348
if (rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP &&
13221349
rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_QUALITY_VBR) {
13231350
set_avc_property(enc, TARGET_BITRATE, bitrate);
@@ -1326,13 +1353,15 @@ static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t
13261353

13271354
if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) {
13281355
set_avc_property(enc, FILLER_DATA_ENABLE, true);
1356+
pipeline_flush = false;
13291357
}
13301358
} else {
13311359
set_avc_property(enc, QP_I, qp);
13321360
set_avc_property(enc, QP_P, qp);
13331361
set_avc_property(enc, QP_B, qp);
13341362
set_avc_property(enc, QVBR_QUALITY_LEVEL, qp);
13351363
}
1364+
return pipeline_flush;
13361365
}
13371366

13381367
static bool amf_avc_update(void *data, obs_data_t *settings)
@@ -1350,15 +1379,19 @@ try {
13501379
int rc = get_avc_rate_control(rc_str);
13511380
AMF_RESULT res = AMF_OK;
13521381

1353-
amf_avc_update_data(enc, rc, bitrate * 1000, qp);
1354-
1355-
res = enc->amf_encoder->Flush();
1356-
if (res != AMF_OK)
1357-
throw amf_error("AMFComponent::Flush failed", res);
1382+
if (amf_avc_update_data(enc, rc, bitrate * 1000, qp)) {
1383+
// Flush the pipeline only when needed
1384+
res = enc->amf_encoder->Flush();
1385+
if (res != AMF_OK)
1386+
throw amf_error("AMFComponent::Flush failed", res);
13581387

1359-
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
1360-
if (res != AMF_OK)
1361-
throw amf_error("AMFComponent::ReInit failed", res);
1388+
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
1389+
if (res != AMF_OK)
1390+
throw amf_error("AMFComponent::ReInit failed", res);
1391+
} else {
1392+
// A pipeline flush was not requested, however force an IDR frame to align with the bitrate change.
1393+
enc->force_idr = true;
1394+
}
13621395

13631396
return true;
13641397

@@ -1738,8 +1771,14 @@ static inline int get_hevc_rate_control(const char *rc_str)
17381771
return AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR;
17391772
}
17401773

1741-
static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp)
1774+
static bool amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp)
17421775
{
1776+
/* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property)
1777+
* is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could
1778+
* cause unaligned IDRs in an encoder group.
1779+
*/
1780+
bool pipeline_flush = true;
1781+
17431782
if (rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP &&
17441783
rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_QUALITY_VBR) {
17451784
set_hevc_property(enc, TARGET_BITRATE, bitrate);
@@ -1748,12 +1787,14 @@ static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t
17481787

17491788
if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR) {
17501789
set_hevc_property(enc, FILLER_DATA_ENABLE, true);
1790+
pipeline_flush = false;
17511791
}
17521792
} else {
17531793
set_hevc_property(enc, QP_I, qp);
17541794
set_hevc_property(enc, QP_P, qp);
17551795
set_hevc_property(enc, QVBR_QUALITY_LEVEL, qp);
17561796
}
1797+
return pipeline_flush;
17571798
}
17581799

17591800
static bool amf_hevc_update(void *data, obs_data_t *settings)
@@ -1771,15 +1812,19 @@ try {
17711812
int rc = get_hevc_rate_control(rc_str);
17721813
AMF_RESULT res = AMF_OK;
17731814

1774-
amf_hevc_update_data(enc, rc, bitrate * 1000, qp);
1775-
1776-
res = enc->amf_encoder->Flush();
1777-
if (res != AMF_OK)
1778-
throw amf_error("AMFComponent::Flush failed", res);
1815+
if (amf_hevc_update_data(enc, rc, bitrate * 1000, qp)) {
1816+
// Flush the pipeline only when needed
1817+
res = enc->amf_encoder->Flush();
1818+
if (res != AMF_OK)
1819+
throw amf_error("AMFComponent::Flush failed", res);
17791820

1780-
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
1781-
if (res != AMF_OK)
1782-
throw amf_error("AMFComponent::ReInit failed", res);
1821+
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
1822+
if (res != AMF_OK)
1823+
throw amf_error("AMFComponent::ReInit failed", res);
1824+
} else {
1825+
// A pipeline flush was not requested, however force an IDR frame to align with the bitrate change.
1826+
enc->force_idr = true;
1827+
}
17831828

17841829
return true;
17851830

@@ -2095,8 +2140,14 @@ static inline int get_av1_profile(obs_data_t *settings)
20952140
return AMF_VIDEO_ENCODER_AV1_PROFILE_MAIN;
20962141
}
20972142

2098-
static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t cq_value)
2143+
static bool amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t cq_value)
20992144
{
2145+
/* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property)
2146+
* is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could
2147+
* cause unaligned IDRs in an encoder group.
2148+
*/
2149+
bool pipeline_flush = true;
2150+
21002151
if (rc != AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP &&
21012152
rc != AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_QUALITY_VBR) {
21022153
set_av1_property(enc, TARGET_BITRATE, bitrate);
@@ -2105,6 +2156,7 @@ static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t
21052156

21062157
if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) {
21072158
set_av1_property(enc, FILLER_DATA, true);
2159+
pipeline_flush = false;
21082160
} else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ||
21092161
rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) {
21102162
set_av1_property(enc, PEAK_BITRATE, bitrate * 1.5);
@@ -2115,6 +2167,7 @@ static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t
21152167
set_av1_property(enc, Q_INDEX_INTRA, qp);
21162168
set_av1_property(enc, Q_INDEX_INTER, qp);
21172169
}
2170+
return pipeline_flush;
21182171
}
21192172

21202173
static bool amf_av1_update(void *data, obs_data_t *settings)
@@ -2132,15 +2185,19 @@ try {
21322185
int rc = get_av1_rate_control(rc_str);
21332186
AMF_RESULT res = AMF_OK;
21342187

2135-
amf_av1_update_data(enc, rc, bitrate * 1000, cq_level);
2136-
2137-
res = enc->amf_encoder->Flush();
2138-
if (res != AMF_OK)
2139-
throw amf_error("AMFComponent::Flush failed", res);
2188+
if (amf_av1_update_data(enc, rc, bitrate * 1000, cq_level)) {
2189+
// Flush the pipeline only when needed
2190+
res = enc->amf_encoder->Flush();
2191+
if (res != AMF_OK)
2192+
throw amf_error("AMFComponent::Flush failed", res);
21402193

2141-
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
2142-
if (res != AMF_OK)
2143-
throw amf_error("AMFComponent::ReInit failed", res);
2194+
res = enc->amf_encoder->ReInit(enc->cx, enc->cy);
2195+
if (res != AMF_OK)
2196+
throw amf_error("AMFComponent::ReInit failed", res);
2197+
} else {
2198+
// A pipeline flush was not requested, however force an IDR frame to align with the bitrate change.
2199+
enc->force_idr = true;
2200+
}
21442201

21452202
return true;
21462203

0 commit comments

Comments
 (0)