diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh264enc.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh264enc.cpp index 9adbcc7336..817c0db87e 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh264enc.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh264enc.cpp @@ -193,6 +193,10 @@ typedef struct _GstMFH264Enc { GstMFVideoEncoder parent; + GMutex prop_lock; + + gboolean prop_updated; + /* properties */ guint bitrate; @@ -237,6 +241,7 @@ static gboolean gst_mf_h264_enc_set_option (GstMFVideoEncoder * mfenc, GstVideoCodecState * state, IMFMediaType * output_type); static gboolean gst_mf_h264_enc_set_src_caps (GstMFVideoEncoder * mfenc, GstVideoCodecState * state, IMFMediaType * output_type); +static gboolean gst_mf_h264_enc_check_reconfigure (GstMFVideoEncoder * encoder); static void gst_mf_h264_enc_class_init (GstMFH264EncClass * klass, gpointer data) @@ -518,6 +523,8 @@ gst_mf_h264_enc_class_init (GstMFH264EncClass * klass, gpointer data) mfenc_class->set_option = GST_DEBUG_FUNCPTR (gst_mf_h264_enc_set_option); mfenc_class->set_src_caps = GST_DEBUG_FUNCPTR (gst_mf_h264_enc_set_src_caps); + mfenc_class->check_reconfigure = + GST_DEBUG_FUNCPTR (gst_mf_h264_enc_check_reconfigure); mfenc_class->codec_id = MFVideoFormat_H264; mfenc_class->enum_flags = cdata->enum_flags; @@ -533,6 +540,8 @@ gst_mf_h264_enc_class_init (GstMFH264EncClass * klass, gpointer data) static void gst_mf_h264_enc_init (GstMFH264Enc * self) { + g_mutex_init (&self->prop_lock); + self->bitrate = DEFAULT_BITRATE; self->rc_mode = DEFAULT_RC_MODE; self->quality = DEFAULT_QUALITY_LEVEL; @@ -562,6 +571,7 @@ gst_mf_h264_enc_finalize (GObject * object) GstMFH264Enc *self = (GstMFH264Enc *) (object); g_free (self->profile_str); + g_mutex_clear (&self->prop_lock); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -652,83 +662,133 @@ gst_mf_h264_enc_get_property (GObject * object, guint prop_id, } } +static void +update_boolean (GstMFH264Enc * self, gboolean * old_val, const GValue * new_val) +{ + gboolean val = g_value_get_boolean (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_int (GstMFH264Enc * self, gint * old_val, const GValue * new_val) +{ + gint val = g_value_get_int (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_uint (GstMFH264Enc * self, guint * old_val, const GValue * new_val) +{ + guint val = g_value_get_uint (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_enum (GstMFH264Enc * self, guint * old_val, const GValue * new_val) +{ + gint val = g_value_get_enum (new_val); + + if (*old_val == (guint) val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + static void gst_mf_h264_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMFH264Enc *self = (GstMFH264Enc *) (object); + g_mutex_lock (&self->prop_lock); switch (prop_id) { case PROP_BITRATE: - self->bitrate = g_value_get_uint (value); + update_uint (self, &self->bitrate, value); break; case PROP_RC_MODE: - self->rc_mode = g_value_get_enum (value); + update_enum (self, &self->rc_mode, value); break; case PROP_QUALITY: - self->quality = g_value_get_uint (value); + update_uint (self, &self->quality, value); break; case PROP_ADAPTIVE_MODE: - self->adaptive_mode = g_value_get_enum (value); + update_enum (self, &self->adaptive_mode, value); break; case PROP_BUFFER_SIZE: - self->buffer_size = g_value_get_uint (value); + update_uint (self, &self->buffer_size, value); break; case PROP_MAX_BITRATE: - self->max_bitrate = g_value_get_uint (value); + update_uint (self, &self->max_bitrate, value); break; case PROP_QUALITY_VS_SPEED: - self->quality_vs_speed = g_value_get_uint (value); + update_uint (self, &self->quality_vs_speed, value); break; case PROP_CABAC: - self->cabac = g_value_get_boolean (value); + update_boolean (self, &self->cabac, value); break; case PROP_SPS_ID: - self->sps_id = g_value_get_uint (value); + update_uint (self, &self->sps_id, value); break; case PROP_PPS_ID: - self->pps_id = g_value_get_uint (value); + update_uint (self, &self->pps_id, value); break; case PROP_BFRAMES: - self->bframes = g_value_get_uint (value); + update_uint (self, &self->bframes, value); break; case PROP_GOP_SIZE: - self->gop_size = g_value_get_int (value); + update_int (self, &self->gop_size, value); break; case PROP_THREADS: - self->threads = g_value_get_uint (value); + update_uint (self, &self->threads, value); break; case PROP_CONTENT_TYPE: - self->content_type = g_value_get_enum (value); + update_enum (self, &self->content_type, value); break; case PROP_QP: - self->qp = g_value_get_uint (value); + update_uint (self, &self->qp, value); break; case PROP_LOW_LATENCY: - self->low_latency = g_value_get_boolean (value); + update_boolean (self, &self->low_latency, value); break; case PROP_MIN_QP: - self->min_qp = g_value_get_uint (value); + update_uint (self, &self->min_qp, value); break; case PROP_MAX_QP: - self->max_qp = g_value_get_uint (value); + update_uint (self, &self->max_qp, value); break; case PROP_QP_I: - self->qp_i = g_value_get_uint (value); + update_uint (self, &self->qp_i, value); break; case PROP_QP_P: - self->qp_p = g_value_get_uint (value); + update_uint (self, &self->qp_p, value); break; case PROP_QP_B: - self->qp_b = g_value_get_uint (value); + update_uint (self, &self->qp_b, value); break; case PROP_REF: - self->max_num_ref = g_value_get_uint (value); + update_uint (self, &self->max_num_ref, value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + g_mutex_unlock (&self->prop_lock); } static guint @@ -860,10 +920,14 @@ gst_mf_h264_enc_set_option (GstMFVideoEncoder * mfenc, return FALSE; } + g_mutex_lock (&self->prop_lock); hr = output_type->SetUINT32 (MF_MT_AVG_BITRATE, MIN (self->bitrate * 1024, G_MAXUINT - 1)); - if (!gst_mf_result (hr)) + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to set bitrate"); + g_mutex_unlock (&self->prop_lock); return FALSE; + } if (device_caps->rc_mode) { guint rc_mode; @@ -1018,6 +1082,9 @@ gst_mf_h264_enc_set_option (GstMFVideoEncoder * mfenc, WARNING_HR (hr, CODECAPI_AVEncVideoMaxNumRefFrame); } + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + return TRUE; } @@ -1057,6 +1124,20 @@ gst_mf_h264_enc_set_src_caps (GstMFVideoEncoder * mfenc, return TRUE; } +static gboolean +gst_mf_h264_enc_check_reconfigure (GstMFVideoEncoder * encoder) +{ + GstMFH264Enc *self = (GstMFH264Enc *) encoder; + gboolean ret; + + g_mutex_lock (&self->prop_lock); + ret = self->prop_updated; + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + + return ret; +} + void gst_mf_h264_enc_plugin_init (GstPlugin * plugin, guint rank, GList * d3d11_device) diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh265enc.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh265enc.cpp index 6a42dedafc..9e75ff165e 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh265enc.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfh265enc.cpp @@ -144,6 +144,10 @@ typedef struct _GstMFH265Enc { GstMFVideoEncoder parent; + GMutex prop_lock; + + gboolean prop_updated; + /* properties */ guint bitrate; @@ -153,7 +157,7 @@ typedef struct _GstMFH265Enc guint max_bitrate; guint quality_vs_speed; guint bframes; - guint gop_size; + gint gop_size; guint threads; guint content_type; guint qp; @@ -173,6 +177,7 @@ typedef struct _GstMFH265EncClass static GstElementClass *parent_class = nullptr; +static void gst_mf_h265_enc_finalize (GObject * object); static void gst_mf_h265_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_mf_h265_enc_set_property (GObject * object, guint prop_id, @@ -181,6 +186,7 @@ static gboolean gst_mf_h265_enc_set_option (GstMFVideoEncoder * encoder, GstVideoCodecState * state, IMFMediaType * output_type); static gboolean gst_mf_h265_enc_set_src_caps (GstMFVideoEncoder * encoder, GstVideoCodecState * state, IMFMediaType * output_type); +static gboolean gst_mf_h265_enc_check_reconfigure (GstMFVideoEncoder * encoder); static void gst_mf_h265_enc_class_init (GstMFH265EncClass * klass, gpointer data) @@ -195,6 +201,7 @@ gst_mf_h265_enc_class_init (GstMFH265EncClass * klass, gpointer data) parent_class = (GstElementClass *) g_type_class_peek_parent (klass); + gobject_class->finalize = gst_mf_h265_enc_finalize; gobject_class->get_property = gst_mf_h265_enc_get_property; gobject_class->set_property = gst_mf_h265_enc_set_property; @@ -407,6 +414,8 @@ gst_mf_h265_enc_class_init (GstMFH265EncClass * klass, gpointer data) encoder_class->set_option = GST_DEBUG_FUNCPTR (gst_mf_h265_enc_set_option); encoder_class->set_src_caps = GST_DEBUG_FUNCPTR (gst_mf_h265_enc_set_src_caps); + encoder_class->check_reconfigure = + GST_DEBUG_FUNCPTR (gst_mf_h265_enc_check_reconfigure); encoder_class->codec_id = MFVideoFormat_HEVC; encoder_class->enum_flags = cdata->enum_flags; @@ -422,6 +431,8 @@ gst_mf_h265_enc_class_init (GstMFH265EncClass * klass, gpointer data) static void gst_mf_h265_enc_init (GstMFH265Enc * self) { + g_mutex_init (&self->prop_lock); + self->bitrate = DEFAULT_BITRATE; self->rc_mode = DEFAULT_RC_MODE; self->max_bitrate = DEFAULT_MAX_BITRATE; @@ -440,6 +451,16 @@ gst_mf_h265_enc_init (GstMFH265Enc * self) self->max_num_ref = DEFAULT_REF; } +static void +gst_mf_h265_enc_finalize (GObject * object) +{ + GstMFH265Enc *self = (GstMFH265Enc *) (object); + + g_mutex_clear (&self->prop_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + static void gst_mf_h265_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) @@ -511,68 +532,118 @@ gst_mf_h265_enc_get_property (GObject * object, guint prop_id, } } +static void +update_boolean (GstMFH265Enc * self, gboolean * old_val, const GValue * new_val) +{ + gboolean val = g_value_get_boolean (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_int (GstMFH265Enc * self, gint * old_val, const GValue * new_val) +{ + gint val = g_value_get_int (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_uint (GstMFH265Enc * self, guint * old_val, const GValue * new_val) +{ + guint val = g_value_get_uint (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_enum (GstMFH265Enc * self, guint * old_val, const GValue * new_val) +{ + gint val = g_value_get_enum (new_val); + + if (*old_val == (guint) val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + static void gst_mf_h265_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMFH265Enc *self = (GstMFH265Enc *) (object); + g_mutex_lock (&self->prop_lock); switch (prop_id) { case PROP_BITRATE: - self->bitrate = g_value_get_uint (value); + update_uint (self, &self->bitrate, value); break; case PROP_RC_MODE: - self->rc_mode = g_value_get_enum (value); + update_enum (self, &self->rc_mode, value); break; case PROP_BUFFER_SIZE: - self->buffer_size = g_value_get_uint (value); + update_uint (self, &self->buffer_size, value); break; case PROP_MAX_BITRATE: - self->max_bitrate = g_value_get_uint (value); + update_uint (self, &self->max_bitrate, value); break; case PROP_QUALITY_VS_SPEED: - self->quality_vs_speed = g_value_get_uint (value); + update_uint (self, &self->quality_vs_speed, value); break; case PROP_BFRAMES: - self->bframes = g_value_get_uint (value); + update_uint (self, &self->bframes, value); break; case PROP_GOP_SIZE: - self->gop_size = g_value_get_int (value); + update_int (self, &self->gop_size, value); break; case PROP_THREADS: - self->threads = g_value_get_uint (value); + update_uint (self, &self->threads, value); break; case PROP_CONTENT_TYPE: - self->content_type = g_value_get_enum (value); + update_enum (self, &self->content_type, value); break; case PROP_QP: - self->qp = g_value_get_uint (value); + update_uint (self, &self->qp, value); break; case PROP_LOW_LATENCY: - self->low_latency = g_value_get_boolean (value); + update_boolean (self, &self->low_latency, value); break; case PROP_MIN_QP: - self->min_qp = g_value_get_uint (value); + update_uint (self, &self->min_qp, value); break; case PROP_MAX_QP: - self->max_qp = g_value_get_uint (value); + update_uint (self, &self->max_qp, value); break; case PROP_QP_I: - self->qp_i = g_value_get_uint (value); + update_uint (self, &self->qp_i, value); break; case PROP_QP_P: - self->qp_p = g_value_get_uint (value); + update_uint (self, &self->qp_p, value); break; case PROP_QP_B: - self->qp_b = g_value_get_uint (value); + update_uint (self, &self->qp_b, value); break; case PROP_REF: - self->max_num_ref = g_value_get_uint (value); + update_uint (self, &self->max_num_ref, value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + g_mutex_unlock (&self->prop_lock); } static guint @@ -634,10 +705,14 @@ gst_mf_h265_enc_set_option (GstMFVideoEncoder * encoder, if (!gst_mf_result (hr)) return FALSE; + g_mutex_lock (&self->prop_lock); hr = output_type->SetUINT32 (MF_MT_AVG_BITRATE, MIN (self->bitrate * 1024, G_MAXUINT - 1)); - if (!gst_mf_result (hr)) + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to set bitrate"); + g_mutex_unlock (&self->prop_lock); return FALSE; + } if (device_caps->rc_mode) { guint rc_mode; @@ -758,6 +833,9 @@ gst_mf_h265_enc_set_option (GstMFVideoEncoder * encoder, WARNING_HR (hr, CODECAPI_AVEncVideoMaxNumRefFrame); } + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + return TRUE; } @@ -803,6 +881,20 @@ gst_mf_h265_enc_set_src_caps (GstMFVideoEncoder * encoder, return TRUE; } +static gboolean +gst_mf_h265_enc_check_reconfigure (GstMFVideoEncoder * encoder) +{ + GstMFH265Enc *self = (GstMFH265Enc *) encoder; + gboolean ret; + + g_mutex_lock (&self->prop_lock); + ret = self->prop_updated; + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + + return ret; +} + void gst_mf_h265_enc_plugin_init (GstPlugin * plugin, guint rank, GList * d3d11_device) diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.cpp index e318359534..eed4394ac5 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.cpp @@ -280,12 +280,11 @@ gst_mf_video_encoder_start (GstVideoEncoder * enc) } static gboolean -gst_mf_video_encoder_set_format (GstVideoEncoder * enc, - GstVideoCodecState * state) +gst_mf_video_encoder_init_mft (GstMFVideoEncoder * self) { - GstMFVideoEncoder *self = GST_MF_VIDEO_ENCODER (enc); - GstMFVideoEncoderClass *klass = GST_MF_VIDEO_ENCODER_GET_CLASS (enc); - GstVideoInfo *info = &state->info; + GstMFVideoEncoderClass *klass = GST_MF_VIDEO_ENCODER_GET_CLASS (self); + GstVideoInfo *info = &self->input_state->info; + GstCaps *caps = self->input_state->caps; ComPtr < IMFMediaType > in_type; ComPtr < IMFMediaType > out_type; GList *input_types = nullptr; @@ -295,16 +294,12 @@ gst_mf_video_encoder_set_format (GstVideoEncoder * enc, GST_DEBUG_OBJECT (self, "Set format"); - gst_mf_video_encoder_finish (enc); + gst_mf_video_encoder_finish (GST_VIDEO_ENCODER (self)); self->mf_pts_offset = 0; self->has_reorder_frame = FALSE; self->last_ret = GST_FLOW_OK; - if (self->input_state) - gst_video_codec_state_unref (self->input_state); - self->input_state = gst_video_codec_state_ref (state); - if (!gst_mf_transform_open (self->transform)) { GST_ERROR_OBJECT (self, "Failed to open MFT"); return FALSE; @@ -408,8 +403,7 @@ gst_mf_video_encoder_set_format (GstVideoEncoder * enc, if (!in_type) { GST_ERROR_OBJECT (self, - "Couldn't convert input caps %" GST_PTR_FORMAT " to media type", - state->caps); + "Couldn't convert input caps %" GST_PTR_FORMAT " to media type", caps); return FALSE; } @@ -467,78 +461,90 @@ gst_mf_video_encoder_set_format (GstVideoEncoder * enc, } /* Check whether upstream is d3d11 element */ - if (state->caps) { - GstCapsFeatures *features; - ComPtr < IMFVideoSampleAllocatorEx > allocator; + GstCapsFeatures *features; + ComPtr < IMFVideoSampleAllocatorEx > allocator; - features = gst_caps_get_features (state->caps, 0); + features = gst_caps_get_features (caps, 0); - if (features && - gst_caps_features_contains (features, - GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { - GST_DEBUG_OBJECT (self, "found D3D11 memory feature"); + if (features && + gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { + GST_DEBUG_OBJECT (self, "found D3D11 memory feature"); - hr = GstMFCreateVideoSampleAllocatorEx (IID_PPV_ARGS (&allocator)); + hr = GstMFCreateVideoSampleAllocatorEx (IID_PPV_ARGS (&allocator)); + if (!gst_mf_result (hr)) + GST_WARNING_OBJECT (self, + "IMFVideoSampleAllocatorEx interface is unavailable"); + } + + if (allocator) { + do { + ComPtr < IMFAttributes > attr; + + hr = MFCreateAttributes (&attr, 4); if (!gst_mf_result (hr)) - GST_WARNING_OBJECT (self, - "IMFVideoSampleAllocatorEx interface is unavailable"); - } + break; - if (allocator) { - do { - ComPtr < IMFAttributes > attr; + /* Only one buffer per sample + * (multiple sample is usually for multi-view things) */ + hr = attr->SetUINT32 (GST_GUID_MF_SA_BUFFERS_PER_SAMPLE, 1); + if (!gst_mf_result (hr)) + break; - hr = MFCreateAttributes (&attr, 4); - if (!gst_mf_result (hr)) - break; + hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_USAGE, D3D11_USAGE_DEFAULT); + if (!gst_mf_result (hr)) + break; - /* Only one buffer per sample - * (multiple sample is usually for multi-view things) */ - hr = attr->SetUINT32 (GST_GUID_MF_SA_BUFFERS_PER_SAMPLE, 1); - if (!gst_mf_result (hr)) - break; + /* TODO: Check if we need to use keyed-mutex */ + hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_SHARED_WITHOUT_MUTEX, TRUE); + if (!gst_mf_result (hr)) + break; - hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_USAGE, D3D11_USAGE_DEFAULT); - if (!gst_mf_result (hr)) - break; + hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_BINDFLAGS, + D3D11_BIND_VIDEO_ENCODER); + if (!gst_mf_result (hr)) + break; - /* TODO: Check if we need to use keyed-mutex */ - hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_SHARED_WITHOUT_MUTEX, TRUE); - if (!gst_mf_result (hr)) - break; + hr = allocator->SetDirectXManager (self->device_manager); + if (!gst_mf_result (hr)) + break; - hr = attr->SetUINT32 (GST_GUID_MF_SA_D3D11_BINDFLAGS, - D3D11_BIND_VIDEO_ENCODER); - if (!gst_mf_result (hr)) - break; + hr = allocator->InitializeSampleAllocatorEx ( + /* min samples, since we are running on async mode, + * at least 2 samples would be required */ + 2, + /* max samples, why 16 + 2? it's just magic number + * (H264 max dpb size 16 + our min sample size 2) */ + 16 + 2, attr.Get (), in_type.Get () + ); - hr = allocator->SetDirectXManager (self->device_manager); - if (!gst_mf_result (hr)) - break; + if (!gst_mf_result (hr)) + break; - hr = allocator->InitializeSampleAllocatorEx ( - /* min samples, since we are running on async mode, - * at least 2 samples would be required */ - 2, - /* max samples, why 16 + 2? it's just magic number - * (H264 max dpb size 16 + our min sample size 2) */ - 16 + 2, attr.Get (), in_type.Get () - ); + GST_DEBUG_OBJECT (self, "IMFVideoSampleAllocatorEx is initialized"); - if (!gst_mf_result (hr)) - break; - - GST_DEBUG_OBJECT (self, "IMFVideoSampleAllocatorEx is initialized"); - - self->mf_allocator = allocator.Detach (); - } while (0); - } + self->mf_allocator = allocator.Detach (); + } while (0); } #endif return TRUE; } +static gboolean +gst_mf_video_encoder_set_format (GstVideoEncoder * enc, + GstVideoCodecState * state) +{ + GstMFVideoEncoder *self = GST_MF_VIDEO_ENCODER (enc); + GST_DEBUG_OBJECT (self, "Set format"); + + if (self->input_state) + gst_video_codec_state_unref (self->input_state); + self->input_state = gst_video_codec_state_ref (state); + + return gst_mf_video_encoder_init_mft (self); +} + static void gst_mf_video_buffer_free (GstVideoFrame * frame) { @@ -1201,12 +1207,19 @@ gst_mf_video_encoder_handle_frame (GstVideoEncoder * enc, GstMFVideoEncoder *self = GST_MF_VIDEO_ENCODER (enc); GstFlowReturn ret = GST_FLOW_OK; ComPtr < IMFSample > sample; + GstMFVideoEncoderClass *klass = GST_MF_VIDEO_ENCODER_GET_CLASS (self); if (self->last_ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (self, "Last return was %s", gst_flow_get_name (ret)); ret = self->last_ret; goto done; } + + if (klass->check_reconfigure (self) && !gst_mf_video_encoder_init_mft (self)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (nullptr), + ("Failed to reconfigure encoder")); + return GST_FLOW_ERROR; + } #if GST_MF_HAVE_D3D11 if (self->mf_allocator && !gst_mf_video_encoder_create_input_sample_d3d11 (self, frame, &sample)) { @@ -1737,6 +1750,7 @@ gst_mf_video_encoder_enum_internal (GstMFTransform * transform, GUID & subtype, CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncCommonQuality, quality); CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncAdaptiveMode, adaptive_mode); CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncCommonBufferSize, buffer_size); + CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncCommonMeanBitRate, mean_bitrate); CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncCommonMaxBitRate, max_bitrate); CHECK_DEVICE_CAPS (codec_api, CODECAPI_AVEncCommonQualityVsSpeed, quality_vs_speed); diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.h b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.h index e59c40914f..aa96b756c3 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.h +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideoencoder.h @@ -52,6 +52,7 @@ struct _GstMFVideoEncoderDeviceCaps gboolean adaptive_mode; /* AVEncAdaptiveMode */ gboolean buffer_size; /* AVEncCommonBufferSize */ + gboolean mean_bitrate; /* AVEncCommonMeanBitRate */ gboolean max_bitrate; /* AVEncCommonMaxBitRate */ gboolean quality_vs_speed; /* AVEncCommonQualityVsSpeed */ gboolean cabac; /* AVEncH264CABACEnable */ @@ -133,6 +134,8 @@ struct _GstMFVideoEncoderClass gboolean (*set_src_caps) (GstMFVideoEncoder * encoder, GstVideoCodecState * state, IMFMediaType * output_type); + + gboolean (*check_reconfigure) (GstMFVideoEncoder * encoder); }; GType gst_mf_video_encoder_get_type (void); diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvp9enc.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvp9enc.cpp index 5f2c7dbfe9..40af8dad78 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvp9enc.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvp9enc.cpp @@ -124,6 +124,10 @@ typedef struct _GstMFVP9Enc { GstMFVideoEncoder parent; + GMutex prop_lock; + + gboolean prop_updated; + /* properties */ guint bitrate; @@ -131,7 +135,7 @@ typedef struct _GstMFVP9Enc guint rc_mode; guint max_bitrate; guint quality_vs_speed; - guint gop_size; + gint gop_size; guint threads; guint content_type; gboolean low_latency; @@ -144,6 +148,7 @@ typedef struct _GstMFVP9EncClass static GstElementClass *parent_class = nullptr; +static void gst_mf_vp9_enc_finalize (GObject * object); static void gst_mf_vp9_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_mf_vp9_enc_set_property (GObject * object, guint prop_id, @@ -152,6 +157,7 @@ static gboolean gst_mf_vp9_enc_set_option (GstMFVideoEncoder * encoder, GstVideoCodecState * state, IMFMediaType * output_type); static gboolean gst_mf_vp9_enc_set_src_caps (GstMFVideoEncoder * encoder, GstVideoCodecState * state, IMFMediaType * output_type); +static gboolean gst_mf_vp9_enc_check_reconfigure (GstMFVideoEncoder * encoder); static void gst_mf_vp9_enc_class_init (GstMFVP9EncClass * klass, gpointer data) @@ -166,6 +172,7 @@ gst_mf_vp9_enc_class_init (GstMFVP9EncClass * klass, gpointer data) parent_class = (GstElementClass *) g_type_class_peek_parent (klass); + gobject_class->finalize = gst_mf_vp9_enc_finalize; gobject_class->get_property = gst_mf_vp9_enc_get_property; gobject_class->set_property = gst_mf_vp9_enc_set_property; @@ -303,6 +310,8 @@ gst_mf_vp9_enc_class_init (GstMFVP9EncClass * klass, gpointer data) encoder_class->set_option = GST_DEBUG_FUNCPTR (gst_mf_vp9_enc_set_option); encoder_class->set_src_caps = GST_DEBUG_FUNCPTR (gst_mf_vp9_enc_set_src_caps); + encoder_class->check_reconfigure = + GST_DEBUG_FUNCPTR (gst_mf_vp9_enc_check_reconfigure); encoder_class->codec_id = MFVideoFormat_VP90; encoder_class->enum_flags = cdata->enum_flags; @@ -318,6 +327,8 @@ gst_mf_vp9_enc_class_init (GstMFVP9EncClass * klass, gpointer data) static void gst_mf_vp9_enc_init (GstMFVP9Enc * self) { + g_mutex_init (&self->prop_lock); + self->bitrate = DEFAULT_BITRATE; self->rc_mode = DEFAULT_RC_MODE; self->max_bitrate = DEFAULT_MAX_BITRATE; @@ -328,6 +339,16 @@ gst_mf_vp9_enc_init (GstMFVP9Enc * self) self->low_latency = DEFAULT_LOW_LATENCY; } +static void +gst_mf_vp9_enc_finalize (GObject * object) +{ + GstMFVP9Enc *self = (GstMFVP9Enc *) (object); + + g_mutex_clear (&self->prop_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + static void gst_mf_vp9_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) @@ -372,41 +393,91 @@ gst_mf_vp9_enc_get_property (GObject * object, guint prop_id, } } +static void +update_boolean (GstMFVP9Enc * self, gboolean * old_val, const GValue * new_val) +{ + gboolean val = g_value_get_boolean (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_int (GstMFVP9Enc * self, gint * old_val, const GValue * new_val) +{ + gint val = g_value_get_int (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_uint (GstMFVP9Enc * self, guint * old_val, const GValue * new_val) +{ + guint val = g_value_get_uint (new_val); + + if (*old_val == val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + +static void +update_enum (GstMFVP9Enc * self, guint * old_val, const GValue * new_val) +{ + gint val = g_value_get_enum (new_val); + + if (*old_val == (guint) val) + return; + + *old_val = val; + self->prop_updated = TRUE; +} + static void gst_mf_vp9_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMFVP9Enc *self = (GstMFVP9Enc *) (object); + g_mutex_lock (&self->prop_lock); switch (prop_id) { case PROP_BITRATE: - self->bitrate = g_value_get_uint (value); + update_uint (self, &self->bitrate, value); break; case PROP_RC_MODE: - self->rc_mode = g_value_get_enum (value); + update_enum (self, &self->rc_mode, value); break; case PROP_MAX_BITRATE: - self->max_bitrate = g_value_get_uint (value); + update_uint (self, &self->max_bitrate, value); break; case PROP_QUALITY_VS_SPEED: - self->quality_vs_speed = g_value_get_uint (value); + update_uint (self, &self->quality_vs_speed, value); break; case PROP_GOP_SIZE: - self->gop_size = g_value_get_int (value); + update_int (self, &self->gop_size, value); break; case PROP_THREADS: - self->threads = g_value_get_uint (value); + update_uint (self, &self->threads, value); break; case PROP_CONTENT_TYPE: - self->content_type = g_value_get_enum (value); + update_enum (self, &self->content_type, value); break; case PROP_LOW_LATENCY: - self->low_latency = g_value_get_boolean (value); + update_boolean (self, &self->low_latency, value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + g_mutex_unlock (&self->prop_lock); } static guint @@ -459,10 +530,14 @@ gst_mf_vp9_enc_set_option (GstMFVideoEncoder * encoder, if (!gst_mf_result (hr)) return FALSE; + g_mutex_lock (&self->prop_lock); hr = output_type->SetUINT32 (MF_MT_AVG_BITRATE, MIN (self->bitrate * 1024, G_MAXUINT - 1)); - if (!gst_mf_result (hr)) + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Failed to set bitrate"); + g_mutex_unlock (&self->prop_lock); return FALSE; + } if (device_caps->rc_mode) { guint rc_mode; @@ -532,6 +607,9 @@ gst_mf_vp9_enc_set_option (GstMFVideoEncoder * encoder, WARNING_HR (hr, CODECAPI_AVLowLatencyMode); } + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + return TRUE; } @@ -567,6 +645,20 @@ gst_mf_vp9_enc_set_src_caps (GstMFVideoEncoder * encoder, return TRUE; } +static gboolean +gst_mf_vp9_enc_check_reconfigure (GstMFVideoEncoder * encoder) +{ + GstMFVP9Enc *self = (GstMFVP9Enc *) encoder; + gboolean ret; + + g_mutex_lock (&self->prop_lock); + ret = self->prop_updated; + self->prop_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + + return ret; +} + void gst_mf_vp9_enc_plugin_init (GstPlugin * plugin, guint rank, GList * d3d11_device) diff --git a/subprojects/gst-plugins-bad/tests/examples/mediafoundation/meson.build b/subprojects/gst-plugins-bad/tests/examples/mediafoundation/meson.build new file mode 100644 index 0000000000..2584728688 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/mediafoundation/meson.build @@ -0,0 +1,10 @@ +if host_system != 'windows' + subdir_done() +endif + +executable('mfvideoenc-dynamic-reconfigure', + ['mfvideoenc-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/mediafoundation/mfvideoenc-dynamic-reconfigure.c b/subprojects/gst-plugins-bad/tests/examples/mediafoundation/mfvideoenc-dynamic-reconfigure.c new file mode 100644 index 0000000000..8d612c52ac --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/mediafoundation/mfvideoenc-dynamic-reconfigure.c @@ -0,0 +1,399 @@ +/* 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" + +static GMainLoop *loop = NULL; +static gint width = 640; +static gint height = 480; +static guint bitrate = 1000; + +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 kbit/sec"}, { + "<", "Decrease bitrate by 100 kbit/sec"}, { + "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_print ("Increase height to %d\n", height); + break; + case KB_ARROW_DOWN: + height -= 2; + height = MAX (height, 16); + gst_print ("Decrease height to %d\n", height); + break; + case KB_ARROW_LEFT: + width -= 2; + width = MAX (width, 16); + gst_print ("Decrease width to %d\n", width); + break; + case KB_ARROW_RIGHT: + width += 2; + gst_print ("Increase width to %d\n", width); + 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 '>': + bitrate += 100; + bitrate = MIN (bitrate, 2048000); + gst_print ("Increase bitrate to %d\n", bitrate); + g_object_set (data->encoder, "bitrate", bitrate, NULL); + break; + case '<': + bitrate -= 100; + bitrate = MAX (bitrate, 100); + gst_print ("Decrease bitrate to %d\n", bitrate); + g_object_set (data->encoder, "bitrate", bitrate, NULL); + break; + default: + break; + } + } + + G_UNLOCK (input_lock); +} + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + TestCallbackData *data = (TestCallbackData *) user_data; + GstElement *pipeline = data->pipeline; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STATE_CHANGED: + if (GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (pipeline)) { + gchar *state_transition_name; + GstState old, new, pending; + + gst_message_parse_state_changed (msg, &old, &new, &pending); + + state_transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (old), gst_element_state_get_name (new)); + + /* dump graph for (some) pipeline state changes */ + { + gchar *dump_name = g_strconcat ("mfvideoenc.", state_transition_name, + NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, dump_name); + g_free (dump_name); + } + g_free (state_transition_name); + } + break; + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *dbg; + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "mfvideoenc.error"); + + gst_message_parse_error (msg, &err, &dbg); + gst_printerr ("ERROR %s \n", err->message); + if (dbg != NULL) + gst_printerr ("ERROR debug information: %s\n", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +check_mfvideoenc_available (const gchar * encoder_name) +{ + gboolean ret = TRUE; + GstElement *elem; + + elem = gst_element_factory_make (encoder_name, NULL); + if (!elem) { + GST_WARNING ("%s is not available", encoder_name); + return FALSE; + } + + /* GST_STATE_READY is meaning that driver could be loaded */ + if (gst_element_set_state (elem, + GST_STATE_PAUSED) != GST_STATE_CHANGE_SUCCESS) { + GST_WARNING ("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; + /* *INDENT-OFF* */ + GOptionEntry options[] = { + {"encoder", 0, 0, G_OPTION_ARG_STRING, &encoder_name, + "MediaFoundation encoder element to test, default: mfh264enc"}, + {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 + ("MediaFoundation 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 ("mfh264enc"); + + if (!check_mfvideoenc_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 (G_OBJECT (enc), "bitrate", bitrate, "gop-size", 30, + "rc-mode", 0, NULL); + + MAKE_ELEMENT_AND_ADD (enc_queue, "queue"); + if (g_strrstr (encoder_name, "h265")) { + MAKE_ELEMENT_AND_ADD (parser, "h265parse"); + MAKE_ELEMENT_AND_ADD (dec, "d3d11h265dec"); + } else if (g_strrstr (encoder_name, "vp9")) { + MAKE_ELEMENT_AND_ADD (parser, "vp9parse"); + MAKE_ELEMENT_AND_ADD (dec, "d3d11vp9dec"); + } else { + MAKE_ELEMENT_AND_ADD (parser, "h264parse"); + MAKE_ELEMENT_AND_ADD (dec, "d3d11h264dec"); + } + MAKE_ELEMENT_AND_ADD (queue, "queue"); + MAKE_ELEMENT_AND_ADD (sink, "d3d11videosink"); + + 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); + + return 0; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/meson.build b/subprojects/gst-plugins-bad/tests/examples/meson.build index e9deaec983..0f3695f198 100644 --- a/subprojects/gst-plugins-bad/tests/examples/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/meson.build @@ -5,6 +5,7 @@ subdir('codecparsers') subdir('d3d11') subdir('directfb') subdir('ipcpipeline') +subdir('mediafoundation') subdir('mpegts') subdir('msdk') subdir('mxf')