diff --git a/plugins/obs-ffmpeg/texture-amf.cpp b/plugins/obs-ffmpeg/texture-amf.cpp index af9a5a3c5..789bf2fc1 100644 --- a/plugins/obs-ffmpeg/texture-amf.cpp +++ b/plugins/obs-ffmpeg/texture-amf.cpp @@ -186,6 +186,7 @@ struct amf_base { bool bframes_supported = false; bool first_update = true; bool roi_supported = false; + bool force_idr; inline amf_base(bool fallback) : fallback(fallback) {} virtual ~amf_base() = default; @@ -712,6 +713,26 @@ static void amf_encode_base(amf_base *enc, AMFSurface *amf_surf, encoder_packet *received_packet = false; + /* -------------------------------------- */ + /* Force an IDR or Key frame if signalled */ + + if (enc->force_idr) { + enc->force_idr = false; + switch (enc->codec) { + case amf_codec_type::AVC: + amf_surf->SetProperty(AMF_VIDEO_ENCODER_FORCE_PICTURE_TYPE, AMF_VIDEO_ENCODER_PICTURE_TYPE_IDR); + break; + case amf_codec_type::HEVC: + amf_surf->SetProperty(AMF_VIDEO_ENCODER_HEVC_FORCE_PICTURE_TYPE, + AMF_VIDEO_ENCODER_HEVC_PICTURE_TYPE_IDR); + break; + case amf_codec_type::AV1: + amf_surf->SetProperty(AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE, + AMF_VIDEO_ENCODER_AV1_FORCE_FRAME_TYPE_KEY); + break; + } + } + bool waiting = true; while (waiting) { /* ----------------------------------- */ @@ -1319,8 +1340,14 @@ static inline int get_avc_profile(obs_data_t *settings) return AMF_VIDEO_ENCODER_PROFILE_HIGH; } -static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp) +static bool amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp) { + /* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property) + * is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could + * cause unaligned IDRs in an encoder group. + */ + bool pipeline_flush = true; + if (rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP && rc != AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_QUALITY_VBR) { set_avc_property(enc, TARGET_BITRATE, bitrate); @@ -1329,6 +1356,7 @@ static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) { set_avc_property(enc, FILLER_DATA_ENABLE, true); + pipeline_flush = false; } else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { set_avc_property(enc, PEAK_BITRATE, bitrate * 1.5); @@ -1343,6 +1371,7 @@ static void amf_avc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_avc_property(enc, QP_B, qp); set_avc_property(enc, QVBR_QUALITY_LEVEL, qp); } + return pipeline_flush; } static bool amf_avc_update(void *data, obs_data_t *settings) @@ -1360,15 +1389,21 @@ try { int rc = get_avc_rate_control(rc_str); AMF_RESULT res = AMF_OK; - amf_avc_update_data(enc, rc, bitrate * 1000, qp); + if (amf_avc_update_data(enc, rc, bitrate * 1000, qp)) { + // Flush the pipeline only when needed + res = enc->amf_encoder->Flush(); + if (res != AMF_OK) { + throw amf_error("AMFComponent::Flush failed", res); + } - res = enc->amf_encoder->Flush(); - if (res != AMF_OK) - throw amf_error("AMFComponent::Flush failed", res); - - res = enc->amf_encoder->ReInit(enc->cx, enc->cy); - if (res != AMF_OK) - throw amf_error("AMFComponent::ReInit failed", res); + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); + if (res != AMF_OK) { + throw amf_error("AMFComponent::ReInit failed", res); + } + } else { + // A pipeline flush was not requested, however force an IDR frame to align with the bitrate change. + enc->force_idr = true; + } return true; @@ -1777,8 +1812,14 @@ static inline int get_hevc_rate_control(const char *rc_str) return AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR; } -static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp) +static bool amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t qp) { + /* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property) + * is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could + * cause unaligned IDRs in an encoder group. + */ + bool pipeline_flush = true; + if (rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP && rc != AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_QUALITY_VBR) { set_hevc_property(enc, TARGET_BITRATE, bitrate); @@ -1787,6 +1828,7 @@ static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR) { set_hevc_property(enc, FILLER_DATA_ENABLE, true); + pipeline_flush = false; } else if (rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || rc == AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { set_hevc_property(enc, PEAK_BITRATE, bitrate * 1.5); @@ -1800,6 +1842,7 @@ static void amf_hevc_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_hevc_property(enc, QP_P, qp); set_hevc_property(enc, QVBR_QUALITY_LEVEL, qp); } + return pipeline_flush; } static bool amf_hevc_update(void *data, obs_data_t *settings) @@ -1817,15 +1860,21 @@ try { int rc = get_hevc_rate_control(rc_str); AMF_RESULT res = AMF_OK; - amf_hevc_update_data(enc, rc, bitrate * 1000, qp); + if (amf_hevc_update_data(enc, rc, bitrate * 1000, qp)) { + // Flush the pipeline only when needed + res = enc->amf_encoder->Flush(); + if (res != AMF_OK) { + throw amf_error("AMFComponent::Flush failed", res); + } - res = enc->amf_encoder->Flush(); - if (res != AMF_OK) - throw amf_error("AMFComponent::Flush failed", res); - - res = enc->amf_encoder->ReInit(enc->cx, enc->cy); - if (res != AMF_OK) - throw amf_error("AMFComponent::ReInit failed", res); + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); + if (res != AMF_OK) { + throw amf_error("AMFComponent::ReInit failed", res); + } + } else { + // A pipeline flush was not requested, however force an IDR frame to align with the bitrate change. + enc->force_idr = true; + } return true; @@ -2179,8 +2228,14 @@ static inline int get_av1_profile(obs_data_t *settings) return AMF_VIDEO_ENCODER_AV1_PROFILE_MAIN; } -static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t cq_value) +static bool amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t cq_value) { + /* Return a flag indicating if a pipeline flush is needed. Changing the bitrate (or any other dynamic property) + * is updated with the next SubmitInput() call. For CBR mode, flushing the pipeline is not needed and could + * cause unaligned IDRs in an encoder group. + */ + bool pipeline_flush = true; + if (rc != AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP && rc != AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_QUALITY_VBR) { set_av1_property(enc, TARGET_BITRATE, bitrate); @@ -2189,6 +2244,7 @@ static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR) { set_av1_property(enc, FILLER_DATA, true); + pipeline_flush = false; } else if (rc == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR || rc == AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_HIGH_QUALITY_VBR) { set_av1_property(enc, PEAK_BITRATE, bitrate * 1.5); @@ -2204,6 +2260,7 @@ static void amf_av1_update_data(amf_base *enc, int rc, int64_t bitrate, int64_t set_av1_property(enc, Q_INDEX_INTER, qp); set_av1_property(enc, Q_INDEX_INTER_B, qp); } + return pipeline_flush; } static bool amf_av1_update(void *data, obs_data_t *settings) @@ -2221,15 +2278,21 @@ try { int rc = get_av1_rate_control(rc_str); AMF_RESULT res = AMF_OK; - amf_av1_update_data(enc, rc, bitrate * 1000, cq_level); + if (amf_av1_update_data(enc, rc, bitrate * 1000, cq_level)) { + // Flush the pipeline only when needed + res = enc->amf_encoder->Flush(); + if (res != AMF_OK) { + throw amf_error("AMFComponent::Flush failed", res); + } - res = enc->amf_encoder->Flush(); - if (res != AMF_OK) - throw amf_error("AMFComponent::Flush failed", res); - - res = enc->amf_encoder->ReInit(enc->cx, enc->cy); - if (res != AMF_OK) - throw amf_error("AMFComponent::ReInit failed", res); + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); + if (res != AMF_OK) { + throw amf_error("AMFComponent::ReInit failed", res); + } + } else { + // A pipeline flush was not requested, however force an IDR frame to align with the bitrate change. + enc->force_idr = true; + } return true;