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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2039>
This commit is contained in:
Seungha Yang 2022-03-27 23:27:54 +09:00 committed by GStreamer Marge Bot
parent a8d7b10cc4
commit 886cfecd36
10 changed files with 1534 additions and 181 deletions

View file

@ -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 (&param, 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,
&param.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;
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -10,6 +10,7 @@ subdir('msdk')
subdir('mxf')
subdir('nvcodec')
subdir('opencv', if_found: opencv_dep)
subdir('qsv')
subdir('uvch264')
subdir('va')
subdir('waylandsink')

View file

@ -0,0 +1,268 @@
/* GStreamer
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
*
* 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 <windows.h>
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 <termios.h>
#include <unistd.h>
#include <stdio.h>
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

View file

@ -0,0 +1,40 @@
/* GStreamer
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
*
* 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 <gst/gst.h>
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

View file

@ -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)

View file

@ -0,0 +1,569 @@
/* GStreamer
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
*
* 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 <config.h>
#endif
#include <gst/gst.h>
#include <gst/video/video.h>
#include <stdlib.h>
#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;
}