From 886cfecd362c7ada595d127c9c049689e3f93fe4 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Sun, 27 Mar 2022 23:27:54 +0900 Subject: [PATCH] qsvencoder: Add support for dynamic bitrate update ... and add more encoding options. QSV API supports dynamic bitrate change without IDR insertion. That's more efficient way of runtime encoding option update than starting from new sequence with IDR per bitrate option change. Part-of: --- .../gst-plugins-bad/sys/qsv/gstqsvencoder.cpp | 111 ++-- .../gst-plugins-bad/sys/qsv/gstqsvencoder.h | 4 +- .../gst-plugins-bad/sys/qsv/gstqsvh264enc.cpp | 198 ++++--- .../gst-plugins-bad/sys/qsv/gstqsvh265enc.cpp | 350 +++++++++++-- .../gst-plugins-bad/sys/qsv/gstqsvvp9enc.cpp | 166 ++++-- .../gst-plugins-bad/tests/examples/meson.build | 1 + .../tests/examples/qsv/key-handler.c | 268 ++++++++++ .../tests/examples/qsv/key-handler.h | 40 ++ .../gst-plugins-bad/tests/examples/qsv/meson.build | 10 + .../examples/qsv/qsvenc-dynamic-reconfigure.c | 569 +++++++++++++++++++++ 10 files changed, 1535 insertions(+), 182 deletions(-) create mode 100644 subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.c create mode 100644 subprojects/gst-plugins-bad/tests/examples/qsv/key-handler.h create mode 100644 subprojects/gst-plugins-bad/tests/examples/qsv/meson.build create mode 100644 subprojects/gst-plugins-bad/tests/examples/qsv/qsvenc-dynamic-reconfigure.c diff --git a/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp b/subprojects/gst-plugins-bad/sys/qsv/gstqsvencoder.cpp index 9f1bc53..b31d3cb 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); + 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); - if (!rst) - return GST_FLOW_NOT_NEGOTIATED; + 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 2f301ba..079db64 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 bd2843f..1a6b3af 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); @@ -528,6 +536,19 @@ gst_qsv_h264_enc_check_update_enum (GstQsvH264Enc * self, mfxU16 * old_val, } 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); - - return GST_QSV_ENCODER_RECONFIGURE_BITRATE; + 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; + + 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 98987e8..7324952 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); @@ -341,16 +454,69 @@ gst_qsv_h265_enc_check_update_enum (GstQsvH265Enc * self, mfxU16 * old_val, } 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) { 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); - - return GST_QSV_ENCODER_RECONFIGURE_BITRATE; + 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; + + 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 0c893f0..173f486 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; - - 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); - } + 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); + } - 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 7a2057e..e9deaec 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 0000000..b83bb82 --- /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 0000000..0effdb1 --- /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 0000000..5203e62 --- /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 0000000..ecb1ee1 --- /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; +} -- 2.7.4