vah264enc: Added option to insert CEA-708 closed captions

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2910>
This commit is contained in:
Eric Knapp 2022-10-27 10:03:34 -04:00 committed by GStreamer Marge Bot
parent be1771126d
commit 87e9952b8f

View file

@ -102,6 +102,7 @@ enum
PROP_RATE_CONTROL, PROP_RATE_CONTROL,
PROP_CPB_SIZE, PROP_CPB_SIZE,
PROP_AUD, PROP_AUD,
PROP_CC,
N_PROPERTIES N_PROPERTIES
}; };
@ -159,6 +160,7 @@ struct _GstVaH264Enc
gboolean use_dct8x8; gboolean use_dct8x8;
gboolean use_trellis; gboolean use_trellis;
gboolean aud; gboolean aud;
gboolean cc;
guint32 mbbrc; guint32 mbbrc;
guint32 num_slices; guint32 num_slices;
guint32 cpb_size; guint32 cpb_size;
@ -177,6 +179,7 @@ struct _GstVaH264Enc
gboolean use_dct8x8; gboolean use_dct8x8;
gboolean use_trellis; gboolean use_trellis;
gboolean aud; gboolean aud;
gboolean cc;
guint32 num_slices; guint32 num_slices;
guint32 packed_headers; guint32 packed_headers;
@ -1468,6 +1471,7 @@ gst_va_h264_enc_reset_state (GstVaBaseEnc * base)
self->use_dct8x8 = self->prop.use_dct8x8; self->use_dct8x8 = self->prop.use_dct8x8;
self->use_trellis = self->prop.use_trellis; self->use_trellis = self->prop.use_trellis;
self->aud = self->prop.aud; self->aud = self->prop.aud;
self->cc = self->prop.cc;
self->num_slices = self->prop.num_slices; self->num_slices = self->prop.num_slices;
self->gop.idr_period = self->prop.key_int_max; self->gop.idr_period = self->prop.key_int_max;
@ -1578,6 +1582,9 @@ gst_va_h264_enc_reconfig (GstVaBaseEnc * base)
self->aud = self->aud && self->packed_headers & VA_ENC_PACKED_HEADER_RAW_DATA; self->aud = self->aud && self->packed_headers & VA_ENC_PACKED_HEADER_RAW_DATA;
update_property_bool (base, &self->prop.aud, self->aud, PROP_AUD); update_property_bool (base, &self->prop.aud, self->aud, PROP_AUD);
self->cc = self->cc && self->packed_headers & VA_ENC_PACKED_HEADER_RAW_DATA;
update_property_bool (base, &self->prop.cc, self->cc, PROP_CC);
max_ref_frames = self->gop.num_ref_frames + 3 /* scratch frames */ ; max_ref_frames = self->gop.num_ref_frames + 3 /* scratch frames */ ;
if (!gst_va_encoder_open (base->encoder, base->profile, if (!gst_va_encoder_open (base->encoder, base->profile,
GST_VIDEO_INFO_FORMAT (&base->input_state->info), base->rt_format, GST_VIDEO_INFO_FORMAT (&base->input_state->info), base->rt_format,
@ -2692,6 +2699,124 @@ _add_aud (GstVaH264Enc * self, GstVaH264EncFrame * frame)
return TRUE; return TRUE;
} }
static void
_create_sei_cc_message (GstVideoCaptionMeta * cc_meta,
GstH264SEIMessage * sei_msg)
{
guint8 *data;
GstH264RegisteredUserData *user_data;
sei_msg->payloadType = GST_H264_SEI_REGISTERED_USER_DATA;
user_data = &sei_msg->payload.registered_user_data;
user_data->country_code = 181;
user_data->size = 10 + cc_meta->size;
data = g_malloc (user_data->size);
/* 16-bits itu_t_t35_provider_code */
data[0] = 0;
data[1] = 49;
/* 32-bits ATSC_user_identifier */
data[2] = 'G';
data[3] = 'A';
data[4] = '9';
data[5] = '4';
/* 8-bits ATSC1_data_user_data_type_code */
data[6] = 3;
/* 8-bits:
* 1 bit process_em_data_flag (0)
* 1 bit process_cc_data_flag (1)
* 1 bit additional_data_flag (0)
* 5-bits cc_count
*/
data[7] = ((cc_meta->size / 3) & 0x1f) | 0x40;
/* 8 bits em_data, unused */
data[8] = 255;
memcpy (data + 9, cc_meta->data, cc_meta->size);
/* 8 marker bits */
data[user_data->size - 1] = 255;
user_data->data = data;
}
static gboolean
_create_sei_cc_data (GPtrArray * cc_list, guint8 * sei_data, guint * data_size)
{
GArray *msg_list = NULL;
GstH264BitWriterResult ret;
gint i;
msg_list = g_array_new (TRUE, TRUE, sizeof (GstH264SEIMessage));
g_array_set_clear_func (msg_list, (GDestroyNotify) gst_h264_sei_clear);
g_array_set_size (msg_list, cc_list->len);
for (i = 0; i < cc_list->len; i++) {
GstH264SEIMessage *msg = &g_array_index (msg_list, GstH264SEIMessage, i);
_create_sei_cc_message (g_ptr_array_index (cc_list, i), msg);
}
ret = gst_h264_bit_writer_sei (msg_list, TRUE, sei_data, data_size);
g_array_unref (msg_list);
return (ret == GST_H264_BIT_WRITER_OK);
}
static void
_add_sei_cc (GstVaH264Enc * self, GstVideoCodecFrame * gst_frame)
{
GstVaBaseEnc *base = GST_VA_BASE_ENC (self);
GstVaH264EncFrame *frame;
GPtrArray *cc_list = NULL;
GstVideoCaptionMeta *cc_meta;
gpointer iter = NULL;
guint8 *packed_sei = NULL;
guint sei_size = 0;
frame = _enc_frame (gst_frame);
/* SEI header size */
sei_size = 6;
while ((cc_meta = (GstVideoCaptionMeta *)
gst_buffer_iterate_meta_filtered (gst_frame->input_buffer, &iter,
GST_VIDEO_CAPTION_META_API_TYPE))) {
if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA708_RAW)
continue;
if (!cc_list)
cc_list = g_ptr_array_new ();
g_ptr_array_add (cc_list, cc_meta);
/* Add enough SEI message size for bitwriter. */
sei_size += cc_meta->size + 50;
}
if (!cc_list)
goto out;
packed_sei = g_malloc0 (sei_size);
if (!_create_sei_cc_data (cc_list, packed_sei, &sei_size)) {
GST_WARNING_OBJECT (self, "Failed to write the SEI CC data");
goto out;
}
if (!gst_va_encoder_add_packed_header (base->encoder, frame->picture,
VAEncPackedHeaderRawData, packed_sei, sei_size * 8, FALSE)) {
GST_WARNING_OBJECT (self, "Failed to add SEI CC data");
goto out;
}
out:
g_clear_pointer (&cc_list, g_ptr_array_unref);
if (packed_sei)
g_free (packed_sei);
}
static gboolean static gboolean
_encode_one_frame (GstVaH264Enc * self, GstVideoCodecFrame * gst_frame) _encode_one_frame (GstVaH264Enc * self, GstVideoCodecFrame * gst_frame)
{ {
@ -2808,6 +2933,11 @@ _encode_one_frame (GstVaH264Enc * self, GstVideoCodecFrame * gst_frame)
&& !_add_picture_header (self, frame, &pps)) && !_add_picture_header (self, frame, &pps))
return FALSE; return FALSE;
if (self->cc) {
/* CC errors are not fatal */
_add_sei_cc (self, gst_frame);
}
slice_of_mbs = self->mb_width * self->mb_height / self->num_slices; slice_of_mbs = self->mb_width * self->mb_height / self->num_slices;
slice_mod_mbs = self->mb_width * self->mb_height % self->num_slices; slice_mod_mbs = self->mb_width * self->mb_height % self->num_slices;
slice_start_mb = 0; slice_start_mb = 0;
@ -3044,6 +3174,7 @@ gst_va_h264_enc_init (GTypeInstance * instance, gpointer g_class)
self->prop.use_cabac = TRUE; self->prop.use_cabac = TRUE;
self->prop.use_trellis = FALSE; self->prop.use_trellis = FALSE;
self->prop.aud = FALSE; self->prop.aud = FALSE;
self->prop.cc = TRUE;
self->prop.mbbrc = 0; self->prop.mbbrc = 0;
self->prop.bitrate = 0; self->prop.bitrate = 0;
self->prop.target_percentage = 66; self->prop.target_percentage = 66;
@ -3118,6 +3249,9 @@ gst_va_h264_enc_set_property (GObject * object, guint prop_id,
case PROP_AUD: case PROP_AUD:
self->prop.aud = g_value_get_boolean (value); self->prop.aud = g_value_get_boolean (value);
break; break;
case PROP_CC:
self->prop.cc = g_value_get_boolean (value);
break;
case PROP_MBBRC:{ case PROP_MBBRC:{
/* Macroblock-level rate control. /* Macroblock-level rate control.
* 0: use default, * 0: use default,
@ -3213,6 +3347,9 @@ gst_va_h264_enc_get_property (GObject * object, guint prop_id,
case PROP_AUD: case PROP_AUD:
g_value_set_boolean (value, self->prop.aud); g_value_set_boolean (value, self->prop.aud);
break; break;
case PROP_CC:
g_value_set_boolean (value, self->prop.cc);
break;
case PROP_MBBRC: case PROP_MBBRC:
g_value_set_enum (value, self->prop.mbbrc); g_value_set_enum (value, self->prop.mbbrc);
break; break;
@ -3485,6 +3622,17 @@ gst_va_h264_enc_class_init (gpointer g_klass, gpointer class_data)
"Insert AU (Access Unit) delimeter for each frame", FALSE, "Insert AU (Access Unit) delimeter for each frame", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:cc-insert:
*
* Closed Caption Insert mode.
* Only CEA-708 RAW format is supported for now.
*/
properties[PROP_CC] = g_param_spec_boolean ("cc-insert",
"Insert Closed Captions",
"Insert CEA-708 Closed Captions",
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/** /**
* GstVaH264Enc:mbbrc: * GstVaH264Enc:mbbrc:
* *