diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp b/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp index 9f1bc53aed..b31d3cb15c 100644 --- a/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp +++ b/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp @@ -455,7 +455,6 @@ gst_qsv_encoder_reset (GstQsvEncoder * self) g_array_set_size (priv->task_pool, 0); g_queue_clear (&priv->free_tasks); g_queue_clear (&priv->pending_tasks); - g_clear_pointer (&priv->input_state, gst_video_codec_state_unref); return TRUE; } @@ -464,8 +463,12 @@ static gboolean gst_qsv_encoder_stop (GstVideoEncoder * encoder) { GstQsvEncoder *self = GST_QSV_ENCODER (encoder); + GstQsvEncoderPrivate *priv = self->priv; - return gst_qsv_encoder_reset (self); + gst_qsv_encoder_reset (self); + g_clear_pointer (&priv->input_state, gst_video_codec_state_unref); + + return TRUE; } static gboolean @@ -932,13 +935,12 @@ gst_qsv_encoder_prepare_pool (GstQsvEncoder * self, GstCaps * caps, } static gboolean -gst_qsv_encoder_set_format (GstVideoEncoder * encoder, - GstVideoCodecState * state) +gst_qsv_encoder_init_encode_session (GstQsvEncoder * self) { - GstQsvEncoder *self = GST_QSV_ENCODER (encoder); GstQsvEncoderPrivate *priv = self->priv; GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self); - GstVideoInfo *info; + GstVideoInfo *info = &priv->input_state->info; + GstCaps *caps = priv->input_state->caps; mfxVideoParam param; mfxFrameInfo *frame_info; mfxFrameAllocRequest alloc_request; @@ -952,10 +954,6 @@ gst_qsv_encoder_set_format (GstVideoEncoder * encoder, gst_qsv_encoder_drain (self, FALSE); gst_qsv_encoder_reset (self); - priv->input_state = gst_video_codec_state_ref (state); - - info = &priv->input_state->info; - encoder_handle = new MFXVideoENCODE (priv->session); memset (¶m, 0, sizeof (mfxVideoParam)); @@ -982,7 +980,7 @@ gst_qsv_encoder_set_format (GstVideoEncoder * encoder, GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info), frame_info->Width, frame_info->Height); - if (!gst_qsv_encoder_prepare_pool (self, state->caps, &priv->aligned_info, + if (!gst_qsv_encoder_prepare_pool (self, caps, &priv->aligned_info, ¶m.IOPattern)) { GST_ERROR_OBJECT (self, "Failed to prepare pool"); goto error; @@ -1053,7 +1051,8 @@ gst_qsv_encoder_set_format (GstVideoEncoder * encoder, param.mfx.FrameInfo.FrameRateExtD, param.mfx.FrameInfo.FrameRateExtN); max_latency = gst_util_uint64_scale (max_delay_frames * GST_SECOND, param.mfx.FrameInfo.FrameRateExtD, param.mfx.FrameInfo.FrameRateExtN); - gst_video_encoder_set_latency (encoder, min_latency, max_latency); + gst_video_encoder_set_latency (GST_VIDEO_ENCODER (self), + min_latency, max_latency); priv->video_param = param; priv->encoder = encoder_handle; @@ -1069,6 +1068,57 @@ error: return FALSE; } +static gboolean +gst_qsv_encoder_reset_encode_session (GstQsvEncoder * self) +{ + GstQsvEncoderPrivate *priv = self->priv; + GPtrArray *extra_params = priv->extra_params; + mfxStatus status; + mfxExtEncoderResetOption reset_opt; + + if (!priv->encoder) { + GST_WARNING_OBJECT (self, "Encoder was not configured"); + return gst_qsv_encoder_init_encode_session (self); + } + + reset_opt.Header.BufferId = MFX_EXTBUFF_ENCODER_RESET_OPTION; + reset_opt.Header.BufferSz = sizeof (mfxExtEncoderResetOption); + reset_opt.StartNewSequence = MFX_CODINGOPTION_OFF; + + gst_qsv_encoder_drain (self, FALSE); + + g_ptr_array_add (extra_params, &reset_opt); + priv->video_param.ExtParam = (mfxExtBuffer **) extra_params->pdata; + priv->video_param.NumExtParam = extra_params->len; + + status = priv->encoder->Reset (&priv->video_param); + g_ptr_array_remove_index (extra_params, extra_params->len - 1); + priv->video_param.NumExtParam = extra_params->len; + + if (status != MFX_ERR_NONE) { + GST_WARNING_OBJECT (self, "MFXVideoENCODE_Reset returned %d (%s)", + QSV_STATUS_ARGS (status)); + return gst_qsv_encoder_init_encode_session (self); + } + + GST_DEBUG_OBJECT (self, "Encode session reset done"); + + return TRUE; +} + +static gboolean +gst_qsv_encoder_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state) +{ + GstQsvEncoder *self = GST_QSV_ENCODER (encoder); + GstQsvEncoderPrivate *priv = self->priv; + + g_clear_pointer (&priv->input_state, gst_video_codec_state_unref); + priv->input_state = gst_video_codec_state_ref (state); + + return gst_qsv_encoder_init_encode_session (self); +} + static mfxU16 gst_qsv_encoder_get_pic_struct (GstQsvEncoder * self, GstVideoCodecFrame * frame) @@ -1125,32 +1175,29 @@ gst_qsv_encoder_handle_frame (GstVideoEncoder * encoder, mfxU64 timestamp; mfxStatus status; - if (klass->check_reconfigure) { + if (klass->check_reconfigure && priv->encoder) { GstQsvEncoderReconfigure reconfigure; - reconfigure = klass->check_reconfigure (self, &priv->video_param); + reconfigure = klass->check_reconfigure (self, priv->session, + &priv->video_param, priv->extra_params); + switch (reconfigure) { case GST_QSV_ENCODER_RECONFIGURE_BITRATE: - /* TODO: In case of bitrate change, we can query whether we need to - * start from a new sequence or soft-reset is possible - * via MFXVideoENCODE_Query() with mfxExtEncoderResetOption struct, - * and then if soft-reset is allowed, we can avoid inefficient full-reset - * (including IDR insertion) by using MFXVideoENCODE_Reset() */ - /* fallthrough */ - case GST_QSV_ENCODER_RECONFIGURE_FULL: - { - GstVideoCodecState *state = - gst_video_codec_state_ref (priv->input_state); - gboolean rst; + if (!gst_qsv_encoder_reset_encode_session (self)) { + GST_ERROR_OBJECT (self, "Failed to reset session"); + gst_video_encoder_finish_frame (encoder, frame); - GST_INFO_OBJECT (self, "Configure encoder again"); - rst = gst_qsv_encoder_set_format (encoder, state); - gst_video_codec_state_unref (state); - - if (!rst) - return GST_FLOW_NOT_NEGOTIATED; + return GST_FLOW_ERROR; + } + break; + case GST_QSV_ENCODER_RECONFIGURE_FULL: + if (!gst_qsv_encoder_init_encode_session (self)) { + GST_ERROR_OBJECT (self, "Failed to init session"); + gst_video_encoder_finish_frame (encoder, frame); + + return GST_FLOW_ERROR; + } break; - } default: break; } @@ -1158,6 +1205,8 @@ gst_qsv_encoder_handle_frame (GstVideoEncoder * encoder, if (!priv->encoder) { GST_ERROR_OBJECT (self, "Encoder object was not configured"); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_NOT_NEGOTIATED; } diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.h b/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.h index 2f301bac8a..079db648ec 100644 --- a/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.h +++ b/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.h @@ -85,7 +85,9 @@ struct _GstQsvEncoderClass mfxBitstream * bitstream); GstQsvEncoderReconfigure (*check_reconfigure) (GstQsvEncoder * encoder, - mfxVideoParam * param); + mfxSession session, + mfxVideoParam * param, + GPtrArray * extra_params); }; GType gst_qsv_encoder_get_type (void); diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvh264enc.cpp b/subprojects/gst-plugins-bad/sys/qsv/gstqsvh264enc.cpp index bd2843fabc..1a6b3af194 100644 --- a/subprojects/gst-plugins-bad/sys/qsv/gstqsvh264enc.cpp +++ b/subprojects/gst-plugins-bad/sys/qsv/gstqsvh264enc.cpp @@ -77,7 +77,7 @@ gst_qsv_h264_enc_rate_control_get_type (void) {MFX_RATECONTROL_CBR, "Constant Bitrate", "cbr"}, {MFX_RATECONTROL_VBR, "Variable Bitrate", "vbr"}, {MFX_RATECONTROL_CQP, "Constant Quantizer", "cqp"}, - {MFX_RATECONTROL_AVBR, "Average Bitrate", "avbr"}, + {MFX_RATECONTROL_AVBR, "Average Variable Bitrate", "avbr"}, {MFX_RATECONTROL_LA, "VBR with look ahead (Non HRD compliant)", "la_vbr"}, {MFX_RATECONTROL_ICQ, "Intelligent CQP", "icq"}, {MFX_RATECONTROL_VCM, "Video Conferencing Mode (Non HRD compliant)", "vcm"}, @@ -149,6 +149,7 @@ enum PROP_AVBR_CONVERGENCE, PROP_ICQ_QUALITY, PROP_QVBR_QUALITY, + PROP_DISABLE_HRD_CONFORMANCE, PROP_CC_INSERT, }; @@ -167,6 +168,7 @@ enum #define DEFAULT_AVBR_CONVERGENCE 0 #define DEFAULT_IQC_QUALITY 0 #define DEFAULT_QVBR_QUALITY 0 +#define DEFAULT_DISABLE_HRD_CONFORMANCE FALSE #define DEFAULT_CC_INSERT GST_QSV_H264_ENC_SEI_INSERT typedef struct _GstQsvH264EncClassData @@ -221,6 +223,7 @@ typedef struct _GstQsvH264Enc guint avbr_convergence; guint icq_quality; guint qvbr_quality; + gboolean disable_hrd_conformance; GstQsvH264EncSeiInsertMode cc_insert; } GstQsvH264Enc; @@ -257,8 +260,8 @@ static gboolean gst_qsv_h264_enc_attach_payload (GstQsvEncoder * encoder, static GstBuffer *gst_qsv_h264_enc_create_output_buffer (GstQsvEncoder * encoder, mfxBitstream * bitstream); static GstQsvEncoderReconfigure -gst_qsv_h264_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param); +gst_qsv_h264_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params); static void gst_qsv_h264_enc_class_init (GstQsvH264EncClass * klass, gpointer data) @@ -404,7 +407,7 @@ gst_qsv_h264_enc_class_init (GstQsvH264EncClass * klass, gpointer data) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_ICQ_QUALITY, g_param_spec_uint ("icq-quality", "ICQ Quality", - "Intelligent Constant Quality (0: default)", + "Intelligent Constant Quality for \"icq\" rate-control (0: default)", 0, 51, DEFAULT_IQC_QUALITY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_QVBR_QUALITY, @@ -412,9 +415,13 @@ gst_qsv_h264_enc_class_init (GstQsvH264EncClass * klass, gpointer data) "Quality level used for \"qvbr\" rate-control mode (0: default)", 0, 51, DEFAULT_QVBR_QUALITY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_DISABLE_HRD_CONFORMANCE, + g_param_spec_boolean ("disable-hrd-conformance", + "Disable HRD Conformance", "Allow NAL HRD non-conformant stream", + DEFAULT_DISABLE_HRD_CONFORMANCE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_CC_INSERT, - g_param_spec_enum ("cc-insert", - "Closed Caption Insert", + g_param_spec_enum ("cc-insert", "Closed Caption Insert", "Closed Caption Insert mode. " "Only CEA-708 RAW format is supported for now", GST_TYPE_QSV_H264_ENC_SEI_INSERT_MODE, DEFAULT_CC_INSERT, @@ -480,6 +487,7 @@ gst_qsv_h264_enc_init (GstQsvH264Enc * self) self->avbr_convergence = DEFAULT_AVBR_CONVERGENCE; self->icq_quality = DEFAULT_IQC_QUALITY; self->qvbr_quality = DEFAULT_QVBR_QUALITY; + self->disable_hrd_conformance = DEFAULT_DISABLE_HRD_CONFORMANCE; self->cc_insert = DEFAULT_CC_INSERT; g_mutex_init (&self->prop_lock); @@ -527,6 +535,19 @@ gst_qsv_h264_enc_check_update_enum (GstQsvH264Enc * self, mfxU16 * old_val, g_mutex_unlock (&self->prop_lock); } +static void +gst_qsv_h264_enc_check_update_boolean (GstQsvH264Enc * self, gboolean * old_val, + gboolean new_val) +{ + if (*old_val == new_val) + return; + + g_mutex_lock (&self->prop_lock); + *old_val = new_val; + self->property_updated = TRUE; + g_mutex_unlock (&self->prop_lock); +} + static void gst_qsv_h264_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -627,6 +648,10 @@ gst_qsv_h264_enc_set_property (GObject * object, guint prop_id, gst_qsv_h264_enc_check_update_uint (self, &self->qvbr_quality, g_value_get_uint (value), FALSE); break; + case PROP_DISABLE_HRD_CONFORMANCE: + gst_qsv_h264_enc_check_update_boolean (self, + &self->disable_hrd_conformance, g_value_get_boolean (value)); + break; case PROP_CC_INSERT: /* This property is unrelated to encoder-reset */ self->cc_insert = (GstQsvH264EncSeiInsertMode) g_value_get_enum (value); @@ -723,6 +748,9 @@ gst_qsv_h264_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_CC_INSERT: g_value_set_enum (value, self->cc_insert); break; + case PROP_DISABLE_HRD_CONFORMANCE: + g_value_set_boolean (value, self->disable_hrd_conformance); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -922,6 +950,62 @@ gst_qsv_h264_enc_init_extra_params (GstQsvH264Enc * self) self->option3.Header.BufferSz = sizeof (mfxExtCodingOption3); } +static void +gst_qsv_h264_enc_set_bitrate (GstQsvH264Enc * self, mfxVideoParam * param) +{ + guint max_val; + guint multiplier; + + switch (param->mfx.RateControlMethod) { + case MFX_RATECONTROL_CBR: + multiplier = (self->bitrate + 0x10000) / 0x10000; + param->mfx.TargetKbps = param->mfx.MaxKbps = self->bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_VBR: + case MFX_RATECONTROL_VCM: + case MFX_RATECONTROL_QVBR: + max_val = MAX (self->bitrate, self->max_bitrate); + multiplier = (max_val + 0x10000) / 0x10000; + param->mfx.TargetKbps = self->bitrate / multiplier; + param->mfx.MaxKbps = self->max_bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_CQP: + param->mfx.QPI = self->qp_i; + param->mfx.QPP = self->qp_p; + param->mfx.QPB = self->qp_b; + break; + case MFX_RATECONTROL_AVBR: + multiplier = (self->bitrate + 0x10000) / 0x10000; + param->mfx.TargetKbps = self->bitrate / multiplier; + param->mfx.Accuracy = self->avbr_accuracy; + param->mfx.Convergence = self->avbr_convergence; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_LA: + multiplier = (self->bitrate + 0x10000) / 0x10000; + param->mfx.TargetKbps = self->bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_LA_HRD: + max_val = MAX (self->bitrate, self->max_bitrate); + multiplier = (max_val + 0x10000) / 0x10000; + param->mfx.TargetKbps = self->bitrate / multiplier; + param->mfx.MaxKbps = self->max_bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_ICQ: + case MFX_RATECONTROL_LA_ICQ: + param->mfx.ICQQuality = self->icq_quality; + break; + default: + GST_WARNING_OBJECT (self, + "Unhandled rate-control method %d", self->rate_control); + break; + } +} + static gboolean gst_qsv_h264_enc_set_format (GstQsvEncoder * encoder, GstVideoCodecState * state, mfxVideoParam * param, GPtrArray * extra_params) @@ -1147,48 +1231,7 @@ gst_qsv_h264_enc_set_format (GstQsvEncoder * encoder, param->mfx.RateControlMethod = self->rate_control; param->mfx.NumRefFrame = self->ref_frames; - /* Calculate multiplier to avoid uint16 overflow */ - guint max_val = MAX (self->bitrate, self->max_bitrate); - guint multiplier = (max_val + 0x10000) / 0x10000; - - switch (param->mfx.RateControlMethod) { - case MFX_RATECONTROL_CBR: - case MFX_RATECONTROL_VBR: - case MFX_RATECONTROL_VCM: - case MFX_RATECONTROL_QVBR: - param->mfx.TargetKbps = self->bitrate / multiplier; - param->mfx.MaxKbps = self->max_bitrate / multiplier; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - case MFX_RATECONTROL_CQP: - param->mfx.QPI = self->qp_i; - param->mfx.QPP = self->qp_p; - param->mfx.QPB = self->qp_b; - break; - case MFX_RATECONTROL_AVBR: - param->mfx.TargetKbps = self->bitrate; - param->mfx.Accuracy = self->avbr_accuracy; - param->mfx.Convergence = self->avbr_convergence; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - case MFX_RATECONTROL_LA: - param->mfx.TargetKbps = self->bitrate; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - case MFX_RATECONTROL_LA_HRD: - param->mfx.TargetKbps = self->bitrate; - param->mfx.MaxKbps = self->max_bitrate; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - case MFX_RATECONTROL_ICQ: - case MFX_RATECONTROL_LA_ICQ: - param->mfx.ICQQuality = self->icq_quality; - break; - default: - GST_WARNING_OBJECT (self, - "Unhandled rate-control method %d", self->rate_control); - break; - } + gst_qsv_h264_enc_set_bitrate (self, param); /* Write signal info only when upstream caps contains valid colorimetry, * because derived default colorimetry in gst_video_info_from_caps() tends to @@ -1226,6 +1269,11 @@ gst_qsv_h264_enc_set_format (GstQsvEncoder * encoder, /* TODO: property ? */ option->AUDelimiter = MFX_CODINGOPTION_ON; + if (self->disable_hrd_conformance) { + option->NalHrdConformance = MFX_CODINGOPTION_OFF; + option->VuiVclHrdParameters = MFX_CODINGOPTION_OFF; + } + /* Enables PicTiming SEI by default */ option->PicTimingSEI = MFX_CODINGOPTION_ON; @@ -1593,34 +1641,56 @@ gst_qsv_h264_enc_create_output_buffer (GstQsvEncoder * encoder, } static GstQsvEncoderReconfigure -gst_qsv_h264_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param) +gst_qsv_h264_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params) { GstQsvH264Enc *self = GST_QSV_H264_ENC (encoder); + GstQsvEncoderReconfigure ret = GST_QSV_ENCODER_RECONFIGURE_NONE; g_mutex_lock (&self->prop_lock); - if (self->property_updated) { - g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_FULL; + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + goto done; } if (self->bitrate_updated) { - /* Update @param with updated bitrate values so that baseclass can - * call MFXVideoENCODE_Query() with updated values */ - param->mfx.TargetKbps = self->bitrate; - param->mfx.MaxKbps = self->max_bitrate; - param->mfx.QPI = self->qp_i; - param->mfx.QPP = self->qp_p; - param->mfx.QPB = self->qp_b; - g_mutex_unlock (&self->prop_lock); + mfxStatus status; + mfxExtEncoderResetOption reset_opt; + reset_opt.Header.BufferId = MFX_EXTBUFF_ENCODER_RESET_OPTION; + reset_opt.Header.BufferSz = sizeof (mfxExtEncoderResetOption); + reset_opt.StartNewSequence = MFX_CODINGOPTION_UNKNOWN; - return GST_QSV_ENCODER_RECONFIGURE_BITRATE; + gst_qsv_h264_enc_set_bitrate (self, param); + + g_ptr_array_add (extra_params, &reset_opt); + param->ExtParam = (mfxExtBuffer **) extra_params->pdata; + param->NumExtParam = extra_params->len; + + status = MFXVideoENCODE_Query (session, param, param); + g_ptr_array_remove_index (extra_params, extra_params->len - 1); + param->NumExtParam = extra_params->len; + + if (status != MFX_ERR_NONE) { + GST_WARNING_OBJECT (self, "MFXVideoENCODE_Query returned %d (%s)", + QSV_STATUS_ARGS (status)); + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + } else { + if (reset_opt.StartNewSequence == MFX_CODINGOPTION_OFF) { + GST_DEBUG_OBJECT (self, "Can update without new sequence"); + ret = GST_QSV_ENCODER_RECONFIGURE_BITRATE; + } else { + GST_DEBUG_OBJECT (self, "Need new sequence"); + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + } + } } +done: + self->property_updated = FALSE; + self->bitrate_updated = FALSE; g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_NONE; + return ret; } typedef struct diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvh265enc.cpp b/subprojects/gst-plugins-bad/sys/qsv/gstqsvh265enc.cpp index 98987e8cfd..7324952c7a 100644 --- a/subprojects/gst-plugins-bad/sys/qsv/gstqsvh265enc.cpp +++ b/subprojects/gst-plugins-bad/sys/qsv/gstqsvh265enc.cpp @@ -75,6 +75,10 @@ gst_qsv_h265_enc_rate_control_get_type (void) static const GEnumValue rate_controls[] = { {MFX_RATECONTROL_CBR, "Constant Bitrate", "cbr"}, {MFX_RATECONTROL_VBR, "Variable Bitrate", "vbr"}, + {MFX_RATECONTROL_CQP, "Constant Quantizer", "cqp"}, + {MFX_RATECONTROL_ICQ, "Intelligent CQP", "icq"}, + {MFX_RATECONTROL_VCM, "Video Conferencing Mode (Non HRD compliant)", "vcm"}, + {MFX_RATECONTROL_QVBR, "VBR with CQP", "qvbr"}, {0, nullptr, nullptr} }; @@ -92,21 +96,39 @@ enum PROP_0, PROP_ADAPTER_LUID, PROP_DEVICE_PATH, + PROP_MIN_QP_I, + PROP_MIN_QP_P, + PROP_MIN_QP_B, + PROP_MAX_QP_I, + PROP_MAX_QP_P, + PROP_MAX_QP_B, + PROP_QP_I, + PROP_QP_P, + PROP_QP_B, PROP_GOP_SIZE, + PROP_I_FRAMES, PROP_B_FRAMES, PROP_REF_FRAMES, PROP_BITRATE, PROP_MAX_BITRATE, PROP_RATE_CONTROL, + PROP_ICQ_QUALITY, + PROP_QVBR_QUALITY, + PROP_DISABLE_HRD_CONFORMANCE, PROP_CC_INSERT, }; +#define DEFAULT_QP 0 #define DEFAULT_GOP_SIZE 0 +#define DEFAULT_I_FRAMES 0 #define DEFAULT_B_FRAMES 0 #define DEFAULT_REF_FRAMES 2 #define DEFAULT_BITRATE 2000 #define DEFAULT_MAX_BITRATE 0 #define DEFAULT_RATE_CONTROL MFX_RATECONTROL_CBR +#define DEFAULT_IQC_QUALITY 0 +#define DEFAULT_QVBR_QUALITY 0 +#define DEFAULT_DISABLE_HRD_CONFORMANCE FALSE #define DEFAULT_CC_INSERT GST_QSV_H265_ENC_SEI_INSERT typedef struct _GstQsvH265EncClassData @@ -135,12 +157,25 @@ typedef struct _GstQsvH265Enc gboolean property_updated; /* properties */ + guint min_qp_i; + guint min_qp_p; + guint min_qp_b; + guint max_qp_i; + guint max_qp_p; + guint max_qp_b; + guint qp_i; + guint qp_p; + guint qp_b; guint gop_size; + guint iframes; guint bframes; guint ref_frames; guint bitrate; guint max_bitrate; mfxU16 rate_control; + guint icq_quality; + guint qvbr_quality; + gboolean disable_hrd_conformance; GstQsvH265EncSeiInsertMode cc_insert; } GstQsvH265Enc; @@ -177,8 +212,8 @@ static gboolean gst_qsv_h265_enc_attach_payload (GstQsvEncoder * encoder, static GstBuffer *gst_qsv_h265_enc_create_output_buffer (GstQsvEncoder * encoder, mfxBitstream * bitstream); static GstQsvEncoderReconfigure -gst_qsv_h265_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param); +gst_qsv_h265_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params); static void gst_qsv_h265_enc_class_init (GstQsvH265EncClass * klass, gpointer data) @@ -213,11 +248,62 @@ gst_qsv_h265_enc_class_init (GstQsvH265EncClass * klass, gpointer data) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); #endif + g_object_class_install_property (object_class, PROP_MIN_QP_I, + g_param_spec_uint ("min-qpi", "Min QP I", + "Minimum allowed QP value for I-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_MIN_QP_P, + g_param_spec_uint ("min-qpp", "Min QP P", + "Minimum allowed QP value for P-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_MIN_QP_B, + g_param_spec_uint ("min-qpb", "Min QP B", + "Minimum allowed QP value for B-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_MAX_QP_I, + g_param_spec_uint ("max-qpi", "Max QP I", + "Maximum allowed QP value for I-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_MAX_QP_P, + g_param_spec_uint ("max-qpp", "Max QP P", + "Maximum allowed QP value for P-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_MAX_QP_B, + g_param_spec_uint ("max-qpb", "Max QP B", + "Maximum allowed QP value for B-frame types (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_QP_I, + g_param_spec_uint ("qpi", "QP I", + "Constant quantizer for I frames (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_QP_P, + g_param_spec_uint ("qpp", "QP P", + "Constant quantizer for P frames (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_QP_B, + g_param_spec_uint ("qpb", "QP B", + "Constant quantizer for B frames (0: no limitations)", + 0, 51, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_GOP_SIZE, g_param_spec_uint ("gop-size", "GOP Size", "Number of pictures within a GOP (0: unspecified)", 0, G_MAXINT, DEFAULT_GOP_SIZE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_I_FRAMES, + g_param_spec_uint ("i-frames", "I Frames", + "Number of I frames between IDR frames" + "(0: every I frame is an IDR frame)", + 0, G_MAXINT, DEFAULT_I_FRAMES, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_B_FRAMES, g_param_spec_uint ("b-frames", "B Frames", "Number of B frames between I and P frames", @@ -231,13 +317,13 @@ gst_qsv_h265_enc_class_init (GstQsvH265EncClass * klass, gpointer data) g_object_class_install_property (object_class, PROP_BITRATE, g_param_spec_uint ("bitrate", "Bitrate", "Target bitrate in kbit/sec, Ignored when selected rate-control mode " - "is constant QP variants (i.e., \"cqp\", \"icq\", and \"la_icq\")", + "is constant QP variants (i.e., \"cqp\" and \"icq\")", 0, G_MAXINT, DEFAULT_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_MAX_BITRATE, g_param_spec_uint ("max-bitrate", "Max Bitrate", "Maximum bitrate in kbit/sec, Ignored when selected rate-control mode " - "is constant QP variants (i.e., \"cqp\", \"icq\", and \"la_icq\")", + "is constant QP variants (i.e., \"cqp\" and \"icq\")", 0, G_MAXINT, DEFAULT_MAX_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_RATE_CONTROL, @@ -245,9 +331,23 @@ gst_qsv_h265_enc_class_init (GstQsvH265EncClass * klass, gpointer data) "Rate Control Method", GST_TYPE_QSV_H265_ENC_RATE_CONTROL, DEFAULT_RATE_CONTROL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_ICQ_QUALITY, + g_param_spec_uint ("icq-quality", "ICQ Quality", + "Intelligent Constant Quality for \"icq\" rate-control (0: default)", + 0, 51, DEFAULT_IQC_QUALITY, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_QVBR_QUALITY, + g_param_spec_uint ("qvbr-quality", "QVBR Quality", + "Quality level used for \"qvbr\" rate-control mode (0: default)", + 0, 51, DEFAULT_QVBR_QUALITY, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_DISABLE_HRD_CONFORMANCE, + g_param_spec_boolean ("disable-hrd-conformance", + "Disable HRD Conformance", "Allow NAL HRD non-conformant stream", + DEFAULT_DISABLE_HRD_CONFORMANCE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_CC_INSERT, - g_param_spec_enum ("cc-insert", - "Closed Caption Insert", + g_param_spec_enum ("cc-insert", "Closed Caption Insert", "Closed Caption Insert mode. " "Only CEA-708 RAW format is supported for now", GST_TYPE_QSV_H265_ENC_SEI_INSERT_MODE, DEFAULT_CC_INSERT, @@ -290,12 +390,25 @@ gst_qsv_h265_enc_class_init (GstQsvH265EncClass * klass, gpointer data) static void gst_qsv_h265_enc_init (GstQsvH265Enc * self) { + self->min_qp_i = DEFAULT_QP; + self->min_qp_p = DEFAULT_QP; + self->min_qp_b = DEFAULT_QP; + self->max_qp_i = DEFAULT_QP; + self->max_qp_p = DEFAULT_QP; + self->max_qp_p = DEFAULT_QP; + self->qp_i = DEFAULT_QP; + self->qp_p = DEFAULT_QP; + self->qp_b = DEFAULT_QP; self->gop_size = DEFAULT_GOP_SIZE; + self->iframes = DEFAULT_I_FRAMES; self->bframes = DEFAULT_B_FRAMES; self->ref_frames = DEFAULT_REF_FRAMES; self->bitrate = DEFAULT_BITRATE; self->max_bitrate = DEFAULT_MAX_BITRATE; self->rate_control = DEFAULT_RATE_CONTROL; + self->icq_quality = DEFAULT_IQC_QUALITY; + self->qvbr_quality = DEFAULT_QVBR_QUALITY; + self->disable_hrd_conformance = DEFAULT_DISABLE_HRD_CONFORMANCE; self->cc_insert = DEFAULT_CC_INSERT; g_mutex_init (&self->prop_lock); @@ -340,6 +453,19 @@ gst_qsv_h265_enc_check_update_enum (GstQsvH265Enc * self, mfxU16 * old_val, g_mutex_unlock (&self->prop_lock); } +static void +gst_qsv_h265_enc_check_update_boolean (GstQsvH265Enc * self, gboolean * old_val, + gboolean new_val) +{ + if (*old_val == new_val) + return; + + g_mutex_lock (&self->prop_lock); + *old_val = new_val; + self->property_updated = TRUE; + g_mutex_unlock (&self->prop_lock); +} + static void gst_qsv_h265_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -347,10 +473,50 @@ gst_qsv_h265_enc_set_property (GObject * object, guint prop_id, GstQsvH265Enc *self = GST_QSV_H265_ENC (object); switch (prop_id) { + case PROP_MIN_QP_I: + gst_qsv_h265_enc_check_update_uint (self, &self->min_qp_i, + g_value_get_uint (value), FALSE); + break; + case PROP_MIN_QP_P: + gst_qsv_h265_enc_check_update_uint (self, &self->min_qp_p, + g_value_get_uint (value), FALSE); + break; + case PROP_MIN_QP_B: + gst_qsv_h265_enc_check_update_uint (self, &self->min_qp_b, + g_value_get_uint (value), FALSE); + break; + case PROP_MAX_QP_I: + gst_qsv_h265_enc_check_update_uint (self, &self->max_qp_i, + g_value_get_uint (value), FALSE); + break; + case PROP_MAX_QP_P: + gst_qsv_h265_enc_check_update_uint (self, &self->max_qp_p, + g_value_get_uint (value), FALSE); + break; + case PROP_MAX_QP_B: + gst_qsv_h265_enc_check_update_uint (self, &self->max_qp_b, + g_value_get_uint (value), FALSE); + break; + case PROP_QP_I: + gst_qsv_h265_enc_check_update_uint (self, &self->qp_i, + g_value_get_uint (value), TRUE); + break; + case PROP_QP_P: + gst_qsv_h265_enc_check_update_uint (self, &self->qp_p, + g_value_get_uint (value), TRUE); + break; + case PROP_QP_B: + gst_qsv_h265_enc_check_update_uint (self, &self->qp_b, + g_value_get_uint (value), TRUE); + break; case PROP_GOP_SIZE: gst_qsv_h265_enc_check_update_uint (self, &self->gop_size, g_value_get_uint (value), FALSE); break; + case PROP_I_FRAMES: + gst_qsv_h265_enc_check_update_uint (self, &self->iframes, + g_value_get_uint (value), FALSE); + break; case PROP_B_FRAMES: gst_qsv_h265_enc_check_update_uint (self, &self->bframes, g_value_get_uint (value), FALSE); @@ -371,6 +537,18 @@ gst_qsv_h265_enc_set_property (GObject * object, guint prop_id, gst_qsv_h265_enc_check_update_enum (self, &self->rate_control, g_value_get_enum (value)); break; + case PROP_ICQ_QUALITY: + gst_qsv_h265_enc_check_update_uint (self, &self->icq_quality, + g_value_get_uint (value), FALSE); + break; + case PROP_QVBR_QUALITY: + gst_qsv_h265_enc_check_update_uint (self, &self->qvbr_quality, + g_value_get_uint (value), FALSE); + break; + case PROP_DISABLE_HRD_CONFORMANCE: + gst_qsv_h265_enc_check_update_boolean (self, + &self->disable_hrd_conformance, g_value_get_boolean (value)); + break; case PROP_CC_INSERT: /* This property is unrelated to encoder-reset */ self->cc_insert = (GstQsvH265EncSeiInsertMode) g_value_get_enum (value); @@ -395,9 +573,39 @@ gst_qsv_h265_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_DEVICE_PATH: g_value_set_string (value, klass->display_path); break; + case PROP_MIN_QP_I: + g_value_set_uint (value, self->min_qp_i); + break; + case PROP_MIN_QP_P: + g_value_set_uint (value, self->min_qp_p); + break; + case PROP_MIN_QP_B: + g_value_set_uint (value, self->min_qp_b); + break; + case PROP_MAX_QP_I: + g_value_set_uint (value, self->max_qp_i); + break; + case PROP_MAX_QP_P: + g_value_set_uint (value, self->max_qp_p); + break; + case PROP_MAX_QP_B: + g_value_set_uint (value, self->max_qp_b); + break; + case PROP_QP_I: + g_value_set_uint (value, self->qp_i); + break; + case PROP_QP_P: + g_value_set_uint (value, self->qp_p); + break; + case PROP_QP_B: + g_value_set_uint (value, self->qp_b); + break; case PROP_GOP_SIZE: g_value_set_uint (value, self->gop_size); break; + case PROP_I_FRAMES: + g_value_set_uint (value, self->iframes); + break; case PROP_B_FRAMES: g_value_set_uint (value, self->bframes); break; @@ -413,9 +621,18 @@ gst_qsv_h265_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_RATE_CONTROL: g_value_set_enum (value, self->rate_control); break; + case PROP_ICQ_QUALITY: + g_value_set_uint (value, self->icq_quality); + break; + case PROP_QVBR_QUALITY: + g_value_set_uint (value, self->qvbr_quality); + break; case PROP_CC_INSERT: g_value_set_enum (value, self->cc_insert); break; + case PROP_DISABLE_HRD_CONFORMANCE: + g_value_set_boolean (value, self->disable_hrd_conformance); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -625,6 +842,42 @@ gst_qsv_h265_enc_init_extra_params (GstQsvH265Enc * self) self->option3.Header.BufferSz = sizeof (mfxExtCodingOption3); } +static void +gst_qsv_h265_enc_set_bitrate (GstQsvH265Enc * self, mfxVideoParam * param) +{ + guint max_val; + guint multiplier; + + switch (param->mfx.RateControlMethod) { + case MFX_RATECONTROL_CBR: + multiplier = (self->bitrate + 0x10000) / 0x10000; + param->mfx.TargetKbps = param->mfx.MaxKbps = self->bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_VBR: + case MFX_RATECONTROL_VCM: + case MFX_RATECONTROL_QVBR: + max_val = MAX (self->bitrate, self->max_bitrate); + multiplier = (max_val + 0x10000) / 0x10000; + param->mfx.TargetKbps = self->bitrate / multiplier; + param->mfx.MaxKbps = self->max_bitrate / multiplier; + param->mfx.BRCParamMultiplier = (mfxU16) multiplier; + break; + case MFX_RATECONTROL_CQP: + param->mfx.QPI = self->qp_i; + param->mfx.QPP = self->qp_p; + param->mfx.QPB = self->qp_b; + break; + case MFX_RATECONTROL_ICQ: + param->mfx.ICQQuality = self->icq_quality; + break; + default: + GST_WARNING_OBJECT (self, + "Unhandled rate-control method %d", self->rate_control); + break; + } +} + static gboolean gst_qsv_h265_enc_set_format (GstQsvEncoder * encoder, GstVideoCodecState * state, mfxVideoParam * param, GPtrArray * extra_params) @@ -698,25 +951,11 @@ gst_qsv_h265_enc_set_format (GstQsvEncoder * encoder, param->mfx.CodecProfile = mfx_profile; param->mfx.GopRefDist = self->bframes + 1; param->mfx.GopPicSize = self->gop_size; + param->mfx.IdrInterval = self->iframes; param->mfx.RateControlMethod = self->rate_control; param->mfx.NumRefFrame = self->ref_frames; - /* Calculate multiplier to avoid uint16 overflow */ - guint max_val = MAX (self->bitrate, self->max_bitrate); - guint multiplier = (max_val + 0x10000) / 0x10000; - - switch (param->mfx.RateControlMethod) { - case MFX_RATECONTROL_CBR: - case MFX_RATECONTROL_VBR: - param->mfx.TargetKbps = self->bitrate / multiplier; - param->mfx.MaxKbps = self->max_bitrate / multiplier; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - default: - GST_WARNING_OBJECT (self, - "Unhandled rate-control method %d", self->rate_control); - break; - } + gst_qsv_h265_enc_set_bitrate (self, param); /* Write signal info only when upstream caps contains valid colorimetry, * because derived default colorimetry in gst_video_info_from_caps() tends to @@ -749,6 +988,11 @@ gst_qsv_h265_enc_set_format (GstQsvEncoder * encoder, /* TODO: property ? */ option->AUDelimiter = MFX_CODINGOPTION_ON; + if (self->disable_hrd_conformance) { + option->NalHrdConformance = MFX_CODINGOPTION_OFF; + option->VuiVclHrdParameters = MFX_CODINGOPTION_OFF; + } + /* Enables PicTiming SEI by default */ option->PicTimingSEI = MFX_CODINGOPTION_ON; @@ -758,6 +1002,13 @@ gst_qsv_h265_enc_set_format (GstQsvEncoder * encoder, /* Do not repeat PPS */ option2->RepeatPPS = MFX_CODINGOPTION_OFF; + option2->MinQPI = self->min_qp_i; + option2->MinQPP = self->min_qp_p; + option2->MinQPB = self->min_qp_b; + option2->MaxQPI = self->max_qp_i; + option2->MaxQPP = self->max_qp_p; + option2->MaxQPB = self->max_qp_b; + /* QSV wants MFX_B_REF_PYRAMID when more than 1 b-frame is enabled */ if (param->mfx.GopRefDist > 2) option2->BRefType = MFX_B_REF_PYRAMID; @@ -768,6 +1019,9 @@ gst_qsv_h265_enc_set_format (GstQsvEncoder * encoder, option3->TimingInfoPresent = MFX_CODINGOPTION_ON; } + if (param->mfx.RateControlMethod == MFX_RATECONTROL_QVBR) + option3->QVBRQuality = self->qvbr_quality; + if (signal_info) g_ptr_array_add (extra_params, signal_info); g_ptr_array_add (extra_params, option); @@ -830,7 +1084,6 @@ gst_qsv_h265_enc_set_output_state (GstQsvEncoder * encoder, switch (param.mfx.RateControlMethod) { case MFX_RATECONTROL_CQP: case MFX_RATECONTROL_ICQ: - case MFX_RATECONTROL_LA_ICQ: /* We don't know target/max bitrate in this case */ break; default: @@ -966,31 +1219,56 @@ gst_qsv_h265_enc_create_output_buffer (GstQsvEncoder * encoder, } static GstQsvEncoderReconfigure -gst_qsv_h265_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param) +gst_qsv_h265_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params) { GstQsvH265Enc *self = GST_QSV_H265_ENC (encoder); + GstQsvEncoderReconfigure ret = GST_QSV_ENCODER_RECONFIGURE_NONE; g_mutex_lock (&self->prop_lock); - if (self->property_updated) { - g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_FULL; + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + goto done; } if (self->bitrate_updated) { - /* Update @param with updated bitrate values so that baseclass can - * call MFXVideoENCODE_Query() with updated values */ - param->mfx.TargetKbps = self->bitrate; - param->mfx.MaxKbps = self->max_bitrate; - g_mutex_unlock (&self->prop_lock); + mfxStatus status; + mfxExtEncoderResetOption reset_opt; + reset_opt.Header.BufferId = MFX_EXTBUFF_ENCODER_RESET_OPTION; + reset_opt.Header.BufferSz = sizeof (mfxExtEncoderResetOption); + reset_opt.StartNewSequence = MFX_CODINGOPTION_UNKNOWN; - return GST_QSV_ENCODER_RECONFIGURE_BITRATE; + gst_qsv_h265_enc_set_bitrate (self, param); + + g_ptr_array_add (extra_params, &reset_opt); + param->ExtParam = (mfxExtBuffer **) extra_params->pdata; + param->NumExtParam = extra_params->len; + + status = MFXVideoENCODE_Query (session, param, param); + g_ptr_array_remove_index (extra_params, extra_params->len - 1); + param->NumExtParam = extra_params->len; + + if (status != MFX_ERR_NONE) { + GST_WARNING_OBJECT (self, "MFXVideoENCODE_Query returned %d (%s)", + QSV_STATUS_ARGS (status)); + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + } else { + if (reset_opt.StartNewSequence == MFX_CODINGOPTION_OFF) { + GST_DEBUG_OBJECT (self, "Can update without new sequence"); + ret = GST_QSV_ENCODER_RECONFIGURE_BITRATE; + } else { + GST_DEBUG_OBJECT (self, "Need new sequence"); + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + } + } } +done: + self->property_updated = FALSE; + self->bitrate_updated = FALSE; g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_NONE; + return ret; } typedef struct diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvvp9enc.cpp b/subprojects/gst-plugins-bad/sys/qsv/gstqsvvp9enc.cpp index 0c893f053d..173f486a54 100644 --- a/subprojects/gst-plugins-bad/sys/qsv/gstqsvvp9enc.cpp +++ b/subprojects/gst-plugins-bad/sys/qsv/gstqsvvp9enc.cpp @@ -44,7 +44,8 @@ gst_qsv_vp9_enc_rate_control_get_type (void) static const GEnumValue rate_controls[] = { {MFX_RATECONTROL_CBR, "Constant Bitrate", "cbr"}, {MFX_RATECONTROL_VBR, "Variable Bitrate", "vbr"}, - /* TODO: Add more rate control modes */ + {MFX_RATECONTROL_CQP, "Constant Quantizer", "cqp"}, + {MFX_RATECONTROL_ICQ, "Intelligent CQP", "icq"}, {0, nullptr, nullptr} }; @@ -62,18 +63,23 @@ enum PROP_0, PROP_ADAPTER_LUID, PROP_DEVICE_PATH, + PROP_QP_I, + PROP_QP_P, PROP_GOP_SIZE, PROP_REF_FRAMES, PROP_BITRATE, PROP_MAX_BITRATE, PROP_RATE_CONTROL, + PROP_ICQ_QUALITY, }; +#define DEFAULT_QP 0 #define DEFAULT_GOP_SIZE 0 -#define DEFAULT_REF_FRAMES 2 +#define DEFAULT_REF_FRAMES 1 #define DEFAULT_BITRATE 2000 #define DEFAULT_MAX_BITRATE 0 #define DEFAULT_RATE_CONTROL MFX_RATECONTROL_CBR +#define DEFAULT_IQC_QUALITY 0 typedef struct _GstQsvVP9EncClassData { @@ -98,11 +104,14 @@ typedef struct _GstQsvVP9Enc gboolean property_updated; /* properties */ + guint qp_i; + guint qp_p; guint gop_size; guint ref_frames; guint bitrate; guint max_bitrate; mfxU16 rate_control; + guint icq_quality; } GstQsvVP9Enc; typedef struct _GstQsvVP9EncClass @@ -131,8 +140,8 @@ static gboolean gst_qsv_vp9_enc_set_format (GstQsvEncoder * encoder, static gboolean gst_qsv_vp9_enc_set_output_state (GstQsvEncoder * encoder, GstVideoCodecState * state, mfxSession session); static GstQsvEncoderReconfigure -gst_qsv_vp9_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param); +gst_qsv_vp9_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params); static void gst_qsv_vp9_enc_class_init (GstQsvVP9EncClass * klass, gpointer data) @@ -167,6 +176,16 @@ gst_qsv_vp9_enc_class_init (GstQsvVP9EncClass * klass, gpointer data) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); #endif + g_object_class_install_property (object_class, PROP_QP_I, + g_param_spec_uint ("qpi", "QP I", + "Constant quantizer for I frames (0: no limitations)", + 0, 255, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_QP_P, + g_param_spec_uint ("qpp", "QP P", + "Constant quantizer for P frames (0: no limitations)", + 0, 255, DEFAULT_QP, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_GOP_SIZE, g_param_spec_uint ("gop-size", "GOP Size", "Number of pictures within a GOP (0: unspecified)", @@ -175,25 +194,30 @@ gst_qsv_vp9_enc_class_init (GstQsvVP9EncClass * klass, gpointer data) g_object_class_install_property (object_class, PROP_REF_FRAMES, g_param_spec_uint ("ref-frames", "Reference Frames", "Number of reference frames (0: unspecified)", - 0, 16, DEFAULT_REF_FRAMES, (GParamFlags) + 0, 3, DEFAULT_REF_FRAMES, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_BITRATE, g_param_spec_uint ("bitrate", "Bitrate", "Target bitrate in kbit/sec, Ignored when selected rate-control mode " - "is constant QP variants (i.e., \"cqp\", \"icq\", and \"la_icq\")", - 0, G_MAXINT, DEFAULT_BITRATE, (GParamFlags) + "is constant QP variants (i.e., \"cqp\" and \"icq\")", + 0, G_MAXUINT16, DEFAULT_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_MAX_BITRATE, g_param_spec_uint ("max-bitrate", "Max Bitrate", "Maximum bitrate in kbit/sec, Ignored when selected rate-control mode " - "is constant QP variants (i.e., \"cqp\", \"icq\", and \"la_icq\")", - 0, G_MAXINT, DEFAULT_MAX_BITRATE, (GParamFlags) + "is constant QP variants (i.e., \"cqp\" and \"icq\")", + 0, G_MAXUINT16, DEFAULT_MAX_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (object_class, PROP_RATE_CONTROL, g_param_spec_enum ("rate-control", "Rate Control", "Rate Control Method", GST_TYPE_QSV_VP9_ENC_RATE_CONTROL, DEFAULT_RATE_CONTROL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_ICQ_QUALITY, + g_param_spec_uint ("icq-quality", "ICQ Quality", + "Intelligent Constant Quality for \"icq\" rate-control (0: default)", + 0, 255, DEFAULT_IQC_QUALITY, (GParamFlags) + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); parent_class = (GstElementClass *) g_type_class_peek_parent (klass); gst_element_class_set_static_metadata (element_class, @@ -225,11 +249,14 @@ gst_qsv_vp9_enc_class_init (GstQsvVP9EncClass * klass, gpointer data) static void gst_qsv_vp9_enc_init (GstQsvVP9Enc * self) { + self->qp_i = DEFAULT_QP; + self->qp_p = DEFAULT_QP; self->gop_size = DEFAULT_GOP_SIZE; self->ref_frames = DEFAULT_REF_FRAMES; self->bitrate = DEFAULT_BITRATE; self->max_bitrate = DEFAULT_MAX_BITRATE; self->rate_control = DEFAULT_RATE_CONTROL; + self->icq_quality = DEFAULT_IQC_QUALITY; g_mutex_init (&self->prop_lock); } @@ -280,6 +307,14 @@ gst_qsv_vp9_enc_set_property (GObject * object, guint prop_id, GstQsvVP9Enc *self = GST_QSV_VP9_ENC (object); switch (prop_id) { + case PROP_QP_I: + gst_qsv_vp9_enc_check_update_uint (self, &self->qp_i, + g_value_get_uint (value), TRUE); + break; + case PROP_QP_P: + gst_qsv_vp9_enc_check_update_uint (self, &self->qp_p, + g_value_get_uint (value), TRUE); + break; case PROP_GOP_SIZE: gst_qsv_vp9_enc_check_update_uint (self, &self->gop_size, g_value_get_uint (value), FALSE); @@ -300,6 +335,10 @@ gst_qsv_vp9_enc_set_property (GObject * object, guint prop_id, gst_qsv_vp9_enc_check_update_enum (self, &self->rate_control, g_value_get_enum (value)); break; + case PROP_ICQ_QUALITY: + gst_qsv_vp9_enc_check_update_uint (self, &self->icq_quality, + g_value_get_uint (value), FALSE); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -320,6 +359,12 @@ gst_qsv_vp9_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_DEVICE_PATH: g_value_set_string (value, klass->display_path); break; + case PROP_QP_I: + g_value_set_uint (value, self->qp_i); + break; + case PROP_QP_P: + g_value_set_uint (value, self->qp_p); + break; case PROP_GOP_SIZE: g_value_set_uint (value, self->gop_size); break; @@ -335,6 +380,9 @@ gst_qsv_vp9_enc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_RATE_CONTROL: g_value_set_enum (value, self->rate_control); break; + case PROP_ICQ_QUALITY: + g_value_set_uint (value, self->icq_quality); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -507,6 +555,33 @@ gst_qsv_vp9_enc_init_vp9_param (mfxExtVP9Param * param) param->Header.BufferSz = sizeof (mfxExtVP9Param); } +static void +gst_qsv_vp9_enc_set_bitrate (GstQsvVP9Enc * self, mfxVideoParam * param) +{ + switch (param->mfx.RateControlMethod) { + case MFX_RATECONTROL_CBR: + param->mfx.TargetKbps = param->mfx.MaxKbps = self->bitrate; + param->mfx.BRCParamMultiplier = 1; + break; + case MFX_RATECONTROL_VBR: + param->mfx.TargetKbps = self->bitrate; + param->mfx.MaxKbps = self->max_bitrate; + param->mfx.BRCParamMultiplier = 1; + break; + case MFX_RATECONTROL_CQP: + param->mfx.QPI = self->qp_i; + param->mfx.QPP = self->qp_p; + break; + case MFX_RATECONTROL_ICQ: + param->mfx.ICQQuality = self->icq_quality; + break; + default: + GST_WARNING_OBJECT (self, + "Unhandled rate-control method %d", self->rate_control); + break; + } +} + static gboolean gst_qsv_vp9_enc_set_format (GstQsvEncoder * encoder, GstVideoCodecState * state, mfxVideoParam * param, GPtrArray * extra_params) @@ -594,22 +669,7 @@ gst_qsv_vp9_enc_set_format (GstQsvEncoder * encoder, param->mfx.RateControlMethod = self->rate_control; param->mfx.NumRefFrame = self->ref_frames; - /* Calculate multiplier to avoid uint16 overflow */ - guint max_val = MAX (self->bitrate, self->max_bitrate); - guint multiplier = (max_val + 0x10000) / 0x10000; - - switch (param->mfx.RateControlMethod) { - case MFX_RATECONTROL_CBR: - case MFX_RATECONTROL_VBR: - param->mfx.TargetKbps = self->bitrate / multiplier; - param->mfx.MaxKbps = self->max_bitrate / multiplier; - param->mfx.BRCParamMultiplier = (mfxU16) multiplier; - break; - default: - GST_WARNING_OBJECT (self, - "Unhandled rate-control method %d", self->rate_control); - break; - } + gst_qsv_vp9_enc_set_bitrate (self, param); g_ptr_array_add (extra_params, vp9_param); @@ -633,7 +693,6 @@ gst_qsv_vp9_enc_set_output_state (GstQsvEncoder * encoder, GstTagList *tags; GstVideoCodecState *out_state; guint bitrate, max_bitrate; - guint multiplier = 1; mfxVideoParam param; const gchar *profile_str; mfxStatus status; @@ -662,19 +721,24 @@ gst_qsv_vp9_enc_set_output_state (GstQsvEncoder * encoder, gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, "qsvvp9enc", nullptr); - if (param.mfx.BRCParamMultiplier > 0) - multiplier = param.mfx.BRCParamMultiplier; + switch (param.mfx.RateControlMethod) { + case MFX_RATECONTROL_CQP: + case MFX_RATECONTROL_ICQ: + /* We don't know target/max bitrate in this case */ + break; + default: + max_bitrate = (guint) param.mfx.MaxKbps; + bitrate = (guint) param.mfx.TargetKbps; + if (bitrate > 0) { + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, + GST_TAG_NOMINAL_BITRATE, bitrate * 1000, nullptr); + } - max_bitrate = (guint) param.mfx.MaxKbps * multiplier; - bitrate = (guint) param.mfx.TargetKbps * multiplier; - if (bitrate > 0) { - gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, - GST_TAG_NOMINAL_BITRATE, bitrate * 1000, nullptr); - } - - if (max_bitrate > 0) { - gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, - GST_TAG_MAXIMUM_BITRATE, max_bitrate * 1000, nullptr); + if (max_bitrate > 0) { + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, max_bitrate * 1000, nullptr); + } + break; } gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (encoder), @@ -685,31 +749,33 @@ gst_qsv_vp9_enc_set_output_state (GstQsvEncoder * encoder, } static GstQsvEncoderReconfigure -gst_qsv_vp9_enc_check_reconfigure (GstQsvEncoder * encoder, - mfxVideoParam * param) +gst_qsv_vp9_enc_check_reconfigure (GstQsvEncoder * encoder, mfxSession session, + mfxVideoParam * param, GPtrArray * extra_params) { GstQsvVP9Enc *self = GST_QSV_VP9_ENC (encoder); + GstQsvEncoderReconfigure ret = GST_QSV_ENCODER_RECONFIGURE_NONE; g_mutex_lock (&self->prop_lock); - if (self->property_updated) { - g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_FULL; + ret = GST_QSV_ENCODER_RECONFIGURE_FULL; + goto done; } if (self->bitrate_updated) { - /* Update @param with updated bitrate values so that baseclass can - * call MFXVideoENCODE_Query() with updated values */ - param->mfx.TargetKbps = self->bitrate; - param->mfx.MaxKbps = self->max_bitrate; - g_mutex_unlock (&self->prop_lock); + /* VP9 does not support query with MFX_EXTBUFF_ENCODER_RESET_OPTION + * Just return GST_QSV_ENCODER_RECONFIGURE_BITRATE here. + * Baseclass will care error */ + gst_qsv_vp9_enc_set_bitrate (self, param); - return GST_QSV_ENCODER_RECONFIGURE_BITRATE; + ret = GST_QSV_ENCODER_RECONFIGURE_BITRATE; } +done: + self->property_updated = FALSE; + self->bitrate_updated = FALSE; g_mutex_unlock (&self->prop_lock); - return GST_QSV_ENCODER_RECONFIGURE_NONE; + return ret; } typedef struct diff --git a/subprojects/gst-plugins-bad/tests/examples/meson.build b/subprojects/gst-plugins-bad/tests/examples/meson.build index 7a2057e7b8..e9deaec983 100644 --- a/subprojects/gst-plugins-bad/tests/examples/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/meson.build @@ -10,6 +10,7 @@ subdir('msdk') subdir('mxf') subdir('nvcodec') subdir('opencv', if_found: opencv_dep) +subdir('qsv') subdir('uvch264') subdir('va') subdir('waylandsink') diff --git a/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.c b/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.c new file mode 100644 index 0000000000..b83bb8213d --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.c @@ -0,0 +1,268 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "key-handler.h" + +#ifdef G_OS_WIN32 +#include + +typedef struct _Win32KeyHandler +{ + GThread *thread; + HANDLE cancellable; + HANDLE console_handle; + GMutex lock; + gboolean closing; + + KeyInputCallback callback; + gpointer user_data; +} Win32KeyHandler; + +typedef struct +{ + KeyInputCallback callback; + gpointer user_data; + gchar value; + gboolean is_ascii; +} KeyInputCallbackData; + +static Win32KeyHandler *_handler = NULL; + +static gboolean +handler_source_func (KeyInputCallbackData * data) +{ + data->callback (data->value, data->is_ascii, data->user_data); + + return G_SOURCE_REMOVE; +} + +static gpointer +handler_thread_func (Win32KeyHandler * handler) +{ + HANDLE handles[2]; + + handles[0] = handler->cancellable; + handles[1] = handler->console_handle; + + while (TRUE) { + DWORD ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE); + INPUT_RECORD buffer; + DWORD num_read = 0; + KeyInputCallbackData *data; + + if (ret == WAIT_FAILED) { + gst_printerrln ("Wait failed"); + return NULL; + } + + g_mutex_lock (&handler->lock); + if (handler->closing) { + g_mutex_unlock (&handler->lock); + + return NULL; + } + g_mutex_unlock (&handler->lock); + + if (!PeekConsoleInput (handler->console_handle, &buffer, 1, &num_read) || + num_read != 1) + continue; + + ReadConsoleInput (handler->console_handle, &buffer, 1, &num_read); + if (buffer.EventType != KEY_EVENT || !buffer.Event.KeyEvent.bKeyDown) + continue; + + data = g_new0 (KeyInputCallbackData, 1); + data->callback = handler->callback; + data->user_data = handler->user_data; + + switch (buffer.Event.KeyEvent.wVirtualKeyCode) { + case VK_UP: + data->value = (gchar) KB_ARROW_UP; + break; + case VK_DOWN: + data->value = (gchar) KB_ARROW_DOWN; + break; + case VK_LEFT: + data->value = (gchar) KB_ARROW_LEFT; + break; + case VK_RIGHT: + data->value = (gchar) KB_ARROW_RIGHT; + break; + default: + data->value = buffer.Event.KeyEvent.uChar.AsciiChar; + data->is_ascii = TRUE; + break; + } + + g_main_context_invoke_full (NULL, + G_PRIORITY_DEFAULT, + (GSourceFunc) handler_source_func, data, (GDestroyNotify) g_free); + } +} + +void +set_key_handler (KeyInputCallback callback, gpointer user_data) +{ + if (_handler || !callback) + return; + + _handler = g_new0 (Win32KeyHandler, 1); + + SECURITY_ATTRIBUTES attr; + attr.nLength = sizeof (SECURITY_ATTRIBUTES); + attr.lpSecurityDescriptor = NULL; + attr.bInheritHandle = FALSE; + + _handler->cancellable = CreateEvent (&attr, TRUE, FALSE, NULL); + _handler->console_handle = GetStdHandle (STD_INPUT_HANDLE); + _handler->callback = callback; + _handler->user_data = user_data; + g_mutex_init (&_handler->lock); + _handler->thread = + g_thread_new ("key-handler", (GThreadFunc) handler_thread_func, _handler); +} + +void +unset_key_handler (void) +{ + if (!_handler) + return; + + g_mutex_lock (&_handler->lock); + _handler->closing = TRUE; + g_mutex_unlock (&_handler->lock); + + SetEvent (_handler->cancellable); + g_thread_join (_handler->thread); + CloseHandle (_handler->cancellable); + g_mutex_clear (&_handler->lock); + + g_clear_pointer (&_handler, g_free); +} +#else /* G_OS_WIN32 */ + +#include +#include +#include + +typedef struct _LinuxKeyHandler +{ + gulong watch_id; + struct termios term_settings; + KeyInputCallback callback; + GSource *source; + gpointer user_data; +} LinuxKeyHandler; + +static LinuxKeyHandler *_handler = NULL; + +static gboolean +_handlerio_func (GIOChannel * channel, + GIOCondition condition, LinuxKeyHandler * handler) +{ + if (condition & G_IO_IN) { + GIOStatus status; + gchar buf[16] = { 0, }; + gsize read; + + status = + g_io_channel_read_chars (channel, buf, sizeof (buf) - 1, &read, NULL); + if (status == G_IO_STATUS_ERROR) { + return G_SOURCE_REMOVE; + } + + if (status == G_IO_STATUS_NORMAL) { + gchar value; + gboolean is_ascii = FALSE; + + if (g_strcmp0 (buf, "\033[A") == 0) { + value = (gchar) KB_ARROW_UP; + } else if (g_strcmp0 (buf, "\033[B") == 0) { + value = (gchar) KB_ARROW_DOWN; + } else if (g_strcmp0 (buf, "\033[D") == 0) { + value = (gchar) KB_ARROW_LEFT; + } else if (g_strcmp0 (buf, "\033[C") == 0) { + value = (gchar) KB_ARROW_RIGHT; + } else { + value = buf[0]; + is_ascii = TRUE; + } + + handler->callback (value, is_ascii, handler->user_data); + } + } + + return G_SOURCE_CONTINUE; +} + +void +set_key_handler (KeyInputCallback callback, gpointer user_data) +{ + struct termios new_settings; + struct termios old_settings; + GIOChannel *io_channel; + + if (_handler || !callback) + return; + + if (tcgetattr (STDIN_FILENO, &old_settings) != 0) + return; + + new_settings = old_settings; + new_settings.c_lflag &= ~(ECHO | ICANON | IEXTEN); + new_settings.c_cc[VMIN] = 0; + new_settings.c_cc[VTIME] = 0; + + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new_settings) != 0) + return; + + setvbuf (stdin, NULL, _IONBF, 0); + + _handler = g_new0 (LinuxKeyHandler, 1); + _handler->term_settings = old_settings; + _handler->callback = callback; + _handler->user_data = user_data; + + io_channel = g_io_channel_unix_new (STDIN_FILENO); + + _handler->source = g_io_create_watch (io_channel, G_IO_IN); + g_io_channel_unref (io_channel); + + g_source_set_callback (_handler->source, (GSourceFunc) _handlerio_func, + _handler, NULL); + g_source_attach (_handler->source, NULL); +} + +void +unset_key_handler (void) +{ + if (!_handler) + return; + + if (_handler->source) { + g_source_destroy (_handler->source); + g_source_unref (_handler->source); + } + + tcsetattr (STDIN_FILENO, TCSAFLUSH, &_handler->term_settings); + setvbuf (stdin, NULL, _IOLBF, 0); + + g_clear_pointer (&_handler, g_free); +} +#endif diff --git a/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.h b/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.h new file mode 100644 index 0000000000..0effdb1ca5 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.h @@ -0,0 +1,40 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef enum +{ + KB_ARROW_UP = 0, + KB_ARROW_DOWN, + KB_ARROW_LEFT, + KB_ARROW_RIGHT, +} VirtualKeyValue; + +typedef void (*KeyInputCallback) (gchar input, gboolean is_ascii, gpointer user_data); + +void set_key_handler (KeyInputCallback callback, gpointer user_data); + +void unset_key_handler (void); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/tests/examples/qsv/meson.build b/subprojects/gst-plugins-bad/tests/examples/qsv/meson.build new file mode 100644 index 0000000000..5203e623b0 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qsv/meson.build @@ -0,0 +1,10 @@ +if host_system not in ['windows', 'linux'] + subdir_done() +endif + +executable('qsvenc-dynamic-reconfigure', + ['qsvenc-dynamic-reconfigure.c', 'key-handler.c'], + include_directories : [configinc], + dependencies: [gst_dep, gstbase_dep, gstvideo_dep], + c_args : gst_plugins_bad_args, + install: false) diff --git a/subprojects/gst-plugins-bad/tests/examples/qsv/qsvenc-dynamic-reconfigure.c b/subprojects/gst-plugins-bad/tests/examples/qsv/qsvenc-dynamic-reconfigure.c new file mode 100644 index 0000000000..ecb1ee1dc3 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qsv/qsvenc-dynamic-reconfigure.c @@ -0,0 +1,569 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "key-handler.h" + +typedef enum +{ + RC_MODE_CBR, + RC_MODE_VBR, + RC_MODE_AVBR, + RC_MODE_CQP, +} RcMode; + +typedef enum +{ + CODEC_AVC, + CODEC_HEVC, + CODEC_VP9 +} Codec; + +static GMainLoop *loop = NULL; +static gint width = 640; +static gint height = 480; +static guint bitrate = 1000; +static guint max_bitrate = 2000; +static guint avbr_accuracy = 0; +static guint convergence = 0; +static RcMode rc_mode = RC_MODE_CBR; +static Codec codec = CODEC_AVC; +static guint qp_i = 24; +static guint qp_p = 24; +static guint qp_b = 24; +static guint max_qp = 51; + +G_LOCK_DEFINE_STATIC (input_lock); + +typedef struct +{ + GstElement *pipeline; + GstElement *capsfilter; + GstElement *encoder; + gulong probe_id; + + gint prev_width; + gint prev_height; +} TestCallbackData; + + +static void +print_keyboard_help (void) +{ + /* *INDENT-OFF* */ + static struct + { + const gchar *key_desc; + const gchar *key_help; + } key_controls[] = { + { + "q", "Quit"}, { + "right arrow", "Increase Width"}, { + "left arrow", "Decrease Width"}, { + "up arrow", "Increase Height"}, { + "down arrow", "Decrease Height"}, { + ">", "Increase bitrate by 100 kbps"}, { + "<", "Decrease bitrate by 100 kbps"}, { + "]", "Increase max-bitrate by 100 kbps"}, { + "[", "Decrease max-bitrate by 100 kbps"}, { + "A", "Increase AVBR accuracy by 10 percent"}, { + "a", "Decrease AVBR accuracy by 10 percent"}, { + "C", "Increase AVBR convergence by 100 frame"}, { + "c", "Decrease AVBR convergence by 100 frame"}, { + "I", "Increase QP-I"}, { + "i", "Decrease QP-I"}, { + "P", "Increase QP-P"}, { + "p", "Decrease QP-P"}, { + "B", "Increase QP-B"}, { + "b", "Decrease QP-B"}, { + "k", "show keyboard shortcuts"} + }; + /* *INDENT-ON* */ + + guint i, chars_to_pad, desc_len, max_desc_len = 0; + + gst_print ("\n\n%s\n\n", "Keyboard controls:"); + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + desc_len = g_utf8_strlen (key_controls[i].key_desc, -1); + max_desc_len = MAX (max_desc_len, desc_len); + } + ++max_desc_len; + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1); + gst_print ("\t%s", key_controls[i].key_desc); + gst_print ("%-*s: ", chars_to_pad, ""); + gst_print ("%s\n", key_controls[i].key_help); + } + gst_print ("\n"); +} + +static void +keyboard_cb (gchar input, gboolean is_ascii, gpointer user_data) +{ + TestCallbackData *data = (TestCallbackData *) user_data; + + G_LOCK (input_lock); + + if (!is_ascii) { + switch (input) { + case KB_ARROW_UP: + height += 2; + gst_println ("Increase height to %d", height); + break; + case KB_ARROW_DOWN: + height -= 2; + height = MAX (height, 16); + gst_println ("Decrease height to %d", height); + break; + case KB_ARROW_LEFT: + width -= 2; + width = MAX (width, 16); + gst_println ("Decrease width to %d", width); + break; + case KB_ARROW_RIGHT: + height += 2; + gst_println ("Increase height to %d", height); + break; + default: + break; + } + } else { + switch (input) { + case 'k': + case 'K': + print_keyboard_help (); + break; + case 'q': + case 'Q': + gst_element_send_event (data->pipeline, gst_event_new_eos ()); + g_main_loop_quit (loop); + break; + case '>': + if (rc_mode != RC_MODE_CQP) { + bitrate += 100; + bitrate = MIN (bitrate, 0xffff); + + if (rc_mode == RC_MODE_VBR) + bitrate = MIN (bitrate, max_bitrate); + gst_println ("Increase bitrate to %d", bitrate); + g_object_set (data->encoder, "bitrate", bitrate, NULL); + } + break; + case '<': + if (rc_mode != RC_MODE_CQP) { + bitrate -= 100; + bitrate = MAX (bitrate, 100); + + if (rc_mode == RC_MODE_VBR) + bitrate = MIN (bitrate, max_bitrate); + gst_println ("Decrease bitrate to %d", bitrate); + g_object_set (data->encoder, "bitrate", bitrate, NULL); + } + break; + case ']': + if (rc_mode == RC_MODE_VBR) { + max_bitrate += 100; + max_bitrate = MIN (max_bitrate, 0xffff); + max_bitrate = MAX (max_bitrate, bitrate); + gst_println ("Increase max-bitrate to %d", max_bitrate); + g_object_set (data->encoder, "max-bitrate", max_bitrate, NULL); + } + break; + case '[': + if (rc_mode == RC_MODE_VBR) { + max_bitrate -= 100; + max_bitrate = MAX (max_bitrate, 100); + max_bitrate = MAX (max_bitrate, bitrate); + gst_println ("Decrease max-bitrate to %d", max_bitrate); + g_object_set (data->encoder, "max-bitrate", max_bitrate, NULL); + } + break; + case 'A': + if (rc_mode == RC_MODE_AVBR && avbr_accuracy <= 900) { + avbr_accuracy += 100; + gst_println ("Increase AVBR accuracy to %d", avbr_accuracy); + g_object_set (data->encoder, "avbr-accuracy", avbr_accuracy, NULL); + } + break; + case 'a': + if (rc_mode == RC_MODE_AVBR && avbr_accuracy >= 100) { + avbr_accuracy -= 100; + gst_println ("Decrease AVBR accuracy to %d", avbr_accuracy); + g_object_set (data->encoder, "avbr-accuracy", avbr_accuracy, NULL); + } + break; + case 'C': + if (rc_mode == RC_MODE_AVBR && convergence < G_MAXINT16) { + gst_println ("Increase AVBR Convergence to %d", convergence++); + g_object_set (data->encoder, "avbr-convergence", convergence, NULL); + } + break; + case 'c': + if (rc_mode == RC_MODE_AVBR && convergence > 0) { + gst_println ("Decrease AVBR Convergence to %d", convergence++); + g_object_set (data->encoder, "avbr-convergence", convergence, NULL); + } + break; + case 'I': + if (rc_mode == RC_MODE_CQP && qp_i < max_qp) { + gst_println ("Increase QP-I to %d", ++qp_i); + g_object_set (data->encoder, "qpi", qp_i, NULL); + } + break; + case 'i': + if (rc_mode == RC_MODE_CQP && qp_i > 0) { + gst_println ("Decrease QP-I to %d", --qp_i); + g_object_set (data->encoder, "qpi", qp_i, NULL); + } + break; + case 'P': + if (rc_mode == RC_MODE_CQP && qp_p < max_qp) { + gst_println ("Increase QP-P to %d", ++qp_p); + g_object_set (data->encoder, "qpp", qp_p, NULL); + } + break; + case 'p': + if (rc_mode == RC_MODE_CQP && qp_p > 0) { + gst_println ("Decrease QP-P to %d", --qp_p); + g_object_set (data->encoder, "qpp", qp_p, NULL); + } + break; + case 'B': + if (rc_mode == RC_MODE_CQP && qp_b < max_qp && codec != CODEC_VP9) { + gst_println ("Increase QP-B to %d", ++qp_b); + g_object_set (data->encoder, "qpb", qp_b, NULL); + } + break; + case 'b': + if (rc_mode == RC_MODE_CQP && qp_b > 0 && codec != CODEC_VP9) { + gst_println ("Decrease QP-B to %d", --qp_b); + g_object_set (data->encoder, "qpb", qp_b, NULL); + } + break; + default: + break; + } + } + + G_UNLOCK (input_lock); +} + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + gst_printerrln ("ERROR %s", err->message); + if (dbg != NULL) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +check_qsvencoder_available (const gchar * encoder_name) +{ + gboolean ret = TRUE; + GstElement *elem; + + elem = gst_element_factory_make (encoder_name, NULL); + if (!elem) { + gst_printerrln ("%s is not available", encoder_name); + return FALSE; + } + + if (gst_element_set_state (elem, + GST_STATE_PAUSED) != GST_STATE_CHANGE_SUCCESS) { + gst_printerrln ("cannot open device"); + ret = FALSE; + } + + gst_element_set_state (elem, GST_STATE_NULL); + gst_object_unref (elem); + + return ret; +} + +static GstPadProbeReturn +resolution_change_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + TestCallbackData *data = (TestCallbackData *) user_data; + + G_LOCK (input_lock); + + if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info))) { + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info); + GstPad *peer = gst_pad_get_peer (pad); + GstFlowReturn flow_ret = GST_FLOW_OK; + + ret = GST_PAD_PROBE_HANDLED; + + if (peer) { + flow_ret = gst_pad_chain (peer, buffer); + + if (flow_ret != GST_FLOW_OK) { + gst_pad_remove_probe (pad, data->probe_id); + data->probe_id = 0; + } else { + if (data->prev_width != width || data->prev_height != height) { + GstCaps *caps = NULL; + gint next_width, next_height; + + next_width = width; + next_height = height; + + g_object_get (data->capsfilter, "caps", &caps, NULL); + caps = gst_caps_make_writable (caps); + gst_caps_set_simple (caps, + "width", G_TYPE_INT, next_width, "height", G_TYPE_INT, + next_height, NULL); + g_object_set (data->capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + data->prev_width = next_width; + data->prev_height = next_height; + } + } + } + } + + G_UNLOCK (input_lock); + + return ret; +} + +gint +main (gint argc, gchar ** argv) +{ + GstElement *pipeline; + GstElement *src, *capsfilter, *enc, *enc_queue, *dec, *parser, *queue, *sink; + GstStateChangeReturn sret; + GError *error = NULL; + GOptionContext *option_ctx; + GstCaps *caps; + TestCallbackData data = { 0, }; + GstPad *pad; + gchar *encoder_name = NULL; + gchar *rate_control = NULL; + gint bframes = 0; + /* *INDENT-OFF* */ + GOptionEntry options[] = { + {"encoder", 0, 0, G_OPTION_ARG_STRING, &encoder_name, + "QSV video encoder element to test, default: qsvh264enc"}, + {"rate-control", 0, 0, G_OPTION_ARG_STRING, &rate_control, + "Rate control method to test, default: cbr"}, + {"b-frames", 0, 0, G_OPTION_ARG_INT, &bframes, + "Number of B frames between I and P frames, default: 0"}, + {NULL} + }; + /* *INDENT-ON* */ + +#define MAKE_ELEMENT_AND_ADD(elem, name) G_STMT_START { \ + GstElement *_elem = gst_element_factory_make (name, NULL); \ + if (!_elem) { \ + gst_printerrln ("%s is not available", name); \ + exit (1); \ + } \ + gst_println ("Adding element %s", name); \ + elem = _elem; \ + gst_bin_add (GST_BIN (pipeline), elem); \ +} G_STMT_END + + option_ctx = + g_option_context_new ("QSV video encoder dynamic reconfigure example"); + g_option_context_add_main_entries (option_ctx, options, NULL); + g_option_context_set_help_enabled (option_ctx, TRUE); + if (!g_option_context_parse (option_ctx, &argc, &argv, &error)) { + gst_printerrln ("option parsing failed: %s\n", error->message); + g_clear_error (&error); + exit (1); + } + + g_option_context_free (option_ctx); + gst_init (NULL, NULL); + + if (!encoder_name) + encoder_name = g_strdup ("qsvh264enc"); + if (!rate_control) + rate_control = g_strdup ("cbr"); + + if (g_strcmp0 (encoder_name, "qsvh264enc") == 0) { + codec = CODEC_AVC; + } else if (g_strcmp0 (encoder_name, "qsvh265enc") == 0) { + codec = CODEC_HEVC; + } else if (g_strcmp0 (encoder_name, "qsvvp9enc") == 0) { + codec = CODEC_VP9; + max_qp = 255; + qp_i = 128; + qp_p = 128; + } else { + gst_printerrln ("Unexpected encoder %s", encoder_name); + exit (1); + } + + if (g_strcmp0 (rate_control, "cbr") == 0) { + rc_mode = RC_MODE_CBR; + } else if (g_strcmp0 (rate_control, "vbr") == 0) { + rc_mode = RC_MODE_VBR; + } else if (g_strcmp0 (rate_control, "avbr") == 0 && codec == CODEC_AVC) { + rc_mode = RC_MODE_AVBR; + } else if (g_strcmp0 (rate_control, "cqp") == 0) { + rc_mode = RC_MODE_CQP; + } else { + gst_printerrln ("Unexpected rate-control method %s for encoder %s", + rate_control, encoder_name); + exit (1); + } + + if (!check_qsvencoder_available (encoder_name)) { + gst_printerrln ("Cannot load %s plugin", encoder_name); + exit (1); + } + + /* prepare the pipeline */ + loop = g_main_loop_new (NULL, FALSE); + + pipeline = gst_pipeline_new (NULL); + + MAKE_ELEMENT_AND_ADD (src, "videotestsrc"); + g_object_set (src, "pattern", 1, NULL); + + MAKE_ELEMENT_AND_ADD (capsfilter, "capsfilter"); + MAKE_ELEMENT_AND_ADD (enc, encoder_name); + + g_object_set (enc, "bitrate", bitrate, "max-bitrate", max_bitrate, + "qpi", qp_i, "qpp", qp_p, "gop-size", 30, NULL); + if (codec != CODEC_VP9) + g_object_set (enc, "qpb", qp_b, NULL); + + gst_util_set_object_arg (G_OBJECT (enc), "rate-control", rate_control); + + MAKE_ELEMENT_AND_ADD (enc_queue, "queue"); + if (g_strrstr (encoder_name, "h265")) { + if (bframes > 0) + g_object_set (enc, "b-frames", bframes, NULL); + if (rc_mode == RC_MODE_CBR || rc_mode == RC_MODE_VBR) { + /* Disable HRD conformance for dynamic bitrate update */ + g_object_set (enc, "disable-hrd-conformance", TRUE, NULL); + } + + MAKE_ELEMENT_AND_ADD (parser, "h265parse"); +#ifdef G_OS_WIN32 + MAKE_ELEMENT_AND_ADD (dec, "d3d11h265dec"); +#else + MAKE_ELEMENT_AND_ADD (dec, "vah265dec"); +#endif + } else if (g_strrstr (encoder_name, "vp9")) { + MAKE_ELEMENT_AND_ADD (parser, "vp9parse"); +#ifdef G_OS_WIN32 + MAKE_ELEMENT_AND_ADD (dec, "d3d11vp9dec"); +#else + MAKE_ELEMENT_AND_ADD (dec, "vavp9dec"); +#endif + } else { + if (bframes > 0) + g_object_set (enc, "b-frames", bframes, NULL); + + if (rc_mode == RC_MODE_CBR || rc_mode == RC_MODE_VBR) { + /* Disable HRD conformance for dynamic bitrate update */ + g_object_set (enc, "disable-hrd-conformance", TRUE, NULL); + } + + MAKE_ELEMENT_AND_ADD (parser, "h264parse"); +#ifdef G_OS_WIN32 + MAKE_ELEMENT_AND_ADD (dec, "d3d11h264dec"); +#else + MAKE_ELEMENT_AND_ADD (dec, "vah264dec"); +#endif + } + MAKE_ELEMENT_AND_ADD (queue, "queue"); + +#ifdef G_OS_WIN32 + MAKE_ELEMENT_AND_ADD (sink, "d3d11videosink"); +#else + MAKE_ELEMENT_AND_ADD (sink, "glimagesink"); +#endif + + if (!gst_element_link_many (src, capsfilter, enc, enc_queue, + parser, dec, queue, sink, NULL)) { + gst_printerrln ("Failed to link element"); + exit (1); + } + + caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, + width, "height", G_TYPE_INT, height, NULL); + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + data.pipeline = pipeline; + data.capsfilter = capsfilter; + data.encoder = enc; + + pad = gst_element_get_static_pad (capsfilter, "src"); + data.probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) resolution_change_probe, &data, NULL); + gst_object_unref (pad); + data.prev_width = width; + data.prev_height = height; + + gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, &data); + + /* run the pipeline */ + sret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (sret == GST_STATE_CHANGE_FAILURE) { + gst_printerrln ("Pipeline doesn't want to playing\n"); + } else { + set_key_handler ((KeyInputCallback) keyboard_cb, &data); + g_main_loop_run (loop); + unset_key_handler (); + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline)); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + g_free (encoder_name); + g_free (rate_control); + + return 0; +}