qtdemux: Add support for cenc sample grouping

Co-authored-by: Xabier Rodriguez Calvar <calvaris@igalia.com>
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3551>
This commit is contained in:
Sebastian Szczepaniak 2022-12-09 11:16:21 +01:00 committed by GStreamer Marge Bot
parent 0d254f59b8
commit 277a9f0cef
3 changed files with 343 additions and 3 deletions

View file

@ -425,6 +425,9 @@ G_BEGIN_DECLS
#define FOURCC_tenc GST_MAKE_FOURCC('t','e','n','c')
#define FOURCC_cenc GST_MAKE_FOURCC('c','e','n','c')
#define FOURCC_cbcs GST_MAKE_FOURCC('c','b','c','s')
#define FOURCC_sbgp GST_MAKE_FOURCC('s','b','g','p')
#define FOURCC_sgpd GST_MAKE_FOURCC('s','g','p','d')
#define FOURCC_seig GST_MAKE_FOURCC('s','e','i','g')
/* Audible AAX encrypted audio */
#define FOURCC_aavd GST_MAKE_FOURCC('a','a','v','d')

View file

@ -233,6 +233,11 @@ struct _QtDemuxCencSampleSetInfo
{
GstStructure *default_properties;
/* sample groups */
GPtrArray *track_group_properties;
GPtrArray *fragment_group_properties;
GPtrArray *sample_to_group_map;
/* @crypto_info holds one GstStructure per sample */
GPtrArray *crypto_info;
};
@ -2576,6 +2581,12 @@ gst_qtdemux_stream_clear (QtDemuxStream * stream)
gst_structure_free (info->default_properties);
if (info->crypto_info)
g_ptr_array_free (info->crypto_info, TRUE);
if (info->fragment_group_properties)
g_ptr_array_free (info->fragment_group_properties, TRUE);
if (info->track_group_properties)
g_ptr_array_free (info->track_group_properties, TRUE);
if (info->sample_to_group_map)
g_ptr_array_free (info->sample_to_group_map, FALSE);
}
if (stream->protection_scheme_type == FOURCC_aavd) {
QtDemuxAavdEncryptionInfo *info =
@ -3783,6 +3794,7 @@ qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
QtDemuxStream * stream, guint sample_index)
{
QtDemuxCencSampleSetInfo *info = NULL;
GstStructure *properties = NULL;
g_return_val_if_fail (stream != NULL, NULL);
g_return_val_if_fail (stream->protected, NULL);
@ -3790,9 +3802,264 @@ qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
/* Currently, cenc properties for groups of samples are not supported, so
* simply return a copy of the default sample properties */
return gst_structure_copy (info->default_properties);
/* First check if the sample is associated with the 'seig' sample group. */
if (info->sample_to_group_map
&& sample_index < info->sample_to_group_map->len)
properties = g_ptr_array_index (info->sample_to_group_map, sample_index);
/* If not, use the default properties for this sample. */
if (!properties)
properties = info->default_properties;
return gst_structure_copy (properties);
}
static gboolean
qtdemux_parse_sbgp (GstQTDemux * qtdemux, QtDemuxStream * stream,
GstByteReader * br, guint32 group, GPtrArray ** sample_to_group_array,
GstStructure * default_properties, GPtrArray * tack_properties_array,
GPtrArray * group_properties_array)
{
guint32 flags = 0;
guint8 version = 0;
guint32 count = 0;
const guint8 *grouping_type_data = NULL;
guint32 grouping_type = 0;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
g_return_val_if_fail (br != NULL, FALSE);
g_return_val_if_fail (*sample_to_group_array == NULL, FALSE);
g_return_val_if_fail (group_properties_array != NULL, FALSE);
if (!gst_byte_reader_get_uint32_be (br, &flags))
return FALSE;
if (!gst_byte_reader_get_data (br, 4, &grouping_type_data))
return FALSE;
grouping_type = QT_FOURCC (grouping_type_data);
if (grouping_type != group) {
/* There may be other groups, so just log this... */
GST_DEBUG_OBJECT (qtdemux, "Unsupported grouping type: '%"
GST_FOURCC_FORMAT "'", GST_FOURCC_ARGS (grouping_type));
return FALSE;
}
version = (flags >> 24);
if (version > 0) {
GST_WARNING_OBJECT (qtdemux, "Unsupported 'sbgp' box version: %hhu",
version);
return FALSE;
}
if (!gst_byte_reader_get_uint32_be (br, &count))
return FALSE;
GST_LOG_OBJECT (qtdemux, "flags: %08x, type: '%" GST_FOURCC_FORMAT
"', count: %u", flags, GST_FOURCC_ARGS (grouping_type), count);
if (count > 0)
*sample_to_group_array = g_ptr_array_sized_new (count);
while (count--) {
guint32 samples;
guint32 index;
GstStructure *properties = NULL;
if (!gst_byte_reader_get_uint32_be (br, &samples))
goto error;
if (!gst_byte_reader_get_uint32_be (br, &index))
goto error;
if (index > 0x10000) {
/* Index is referring the current fragment. */
index -= 0x10001;
if (index < group_properties_array->len)
properties = g_ptr_array_index (group_properties_array, index);
else
GST_ERROR_OBJECT (qtdemux, "invalid group index %u", index);
} else if (index > 0) {
/* Index is referring to the whole track. */
index--;
if (index < tack_properties_array->len)
properties = g_ptr_array_index (tack_properties_array, index);
else
GST_ERROR_OBJECT (qtdemux, "invalid group index %u", index);
} else {
/* If zero, then this range of samples does not belong to this group,
perhaps to another one or to none at all. */
}
GST_DEBUG_OBJECT (qtdemux, "assigning group '%" GST_FOURCC_FORMAT
"' index %i for the next %i samples: %" GST_PTR_FORMAT,
GST_FOURCC_ARGS (grouping_type), index, samples, properties);
while (samples--)
g_ptr_array_add (*sample_to_group_array, properties);
}
return TRUE;
error:
g_ptr_array_free (*sample_to_group_array, TRUE);
*sample_to_group_array = NULL;
return FALSE;
}
static gboolean
qtdemux_parse_sgpd (GstQTDemux * qtdemux, QtDemuxStream * stream,
GstByteReader * br, guint32 group, GPtrArray ** properties)
{
guint32 flags = 0;
guint8 version = 0;
guint32 default_length = 0;
guint32 count = 0;
const guint8 *grouping_type_data = NULL;
guint32 grouping_type = 0;
const guint32 min_entry_size = 20;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
g_return_val_if_fail (br != NULL, FALSE);
g_return_val_if_fail (*properties == NULL, FALSE);
if (!gst_byte_reader_get_uint32_be (br, &flags))
return FALSE;
if (!gst_byte_reader_get_data (br, 4, &grouping_type_data))
return FALSE;
grouping_type = QT_FOURCC (grouping_type_data);
if (grouping_type != group) {
GST_WARNING_OBJECT (qtdemux, "Unhandled grouping type: '%"
GST_FOURCC_FORMAT "'", GST_FOURCC_ARGS (grouping_type));
return FALSE;
}
version = (flags >> 24);
if (version == 1) {
if (!gst_byte_reader_get_uint32_be (br, &default_length))
return FALSE;
} else if (version > 1) {
GST_WARNING_OBJECT (qtdemux, "Unsupported 'sgpd' box version: %hhu",
version);
return FALSE;
}
if (!gst_byte_reader_get_uint32_be (br, &count))
return FALSE;
GST_LOG_OBJECT (qtdemux, "flags: %08x, type: '%" GST_FOURCC_FORMAT
"', count: %u", flags, GST_FOURCC_ARGS (grouping_type), count);
if (count)
*properties = g_ptr_array_sized_new (count);
for (guint32 index = 0; index < count; index++) {
GstStructure *props = NULL;
guint32 length = default_length;
const guint8 *entry_data = NULL;
guint8 is_encrypted = 0;
guint8 iv_size = 0;
guint8 constant_iv_size = 0;
const guint8 *kid = NULL;
guint8 crypt_byte_block = 0;
guint8 skip_byte_block = 0;
const guint8 *constant_iv = NULL;
GstBuffer *kid_buf;
if (version == 1 && length == 0) {
if (!gst_byte_reader_get_uint32_be (br, &length))
goto error;
}
if (G_UNLIKELY (length < min_entry_size)) {
GST_ERROR_OBJECT (qtdemux, "Invalid entry size: %u", length);
goto error;
}
if (!gst_byte_reader_get_data (br, length, &entry_data))
goto error;
/* Follows tenc format... */
is_encrypted = QT_UINT8 (entry_data + 2);
iv_size = QT_UINT8 (entry_data + 3);
kid = (entry_data + 4);
if (stream->protection_scheme_type == FOURCC_cbcs) {
guint8 possible_pattern_info;
if (iv_size == 0) {
if (G_UNLIKELY (length < min_entry_size + 1)) {
GST_ERROR_OBJECT (qtdemux, "Invalid entry size: %u", length);
goto error;
}
constant_iv_size = QT_UINT8 (entry_data + 20);
if (G_UNLIKELY (constant_iv_size != 8 && constant_iv_size != 16)) {
GST_ERROR_OBJECT (qtdemux,
"constant IV size should be 8 or 16, not %hhu", constant_iv_size);
goto error;
}
if (G_UNLIKELY (length < min_entry_size + 1 + constant_iv_size)) {
GST_ERROR_OBJECT (qtdemux, "Invalid entry size: %u", length);
goto error;
}
constant_iv = (entry_data + 21);
}
possible_pattern_info = QT_UINT8 (entry_data + 1);
crypt_byte_block = (possible_pattern_info >> 4) & 0x0f;
skip_byte_block = possible_pattern_info & 0x0f;
}
kid_buf = _gst_buffer_new_wrapped ((guint8 *) kid, 16, NULL);
props = gst_structure_new ("application/x-cenc",
"iv_size", G_TYPE_UINT, iv_size,
"encrypted", G_TYPE_BOOLEAN, is_encrypted == 1,
"kid", GST_TYPE_BUFFER, kid_buf, NULL);
gst_buffer_unref (kid_buf);
if (stream->protection_scheme_type == FOURCC_cbcs) {
if (crypt_byte_block != 0 || skip_byte_block != 0) {
gst_structure_set (props,
"crypt_byte_block", G_TYPE_UINT, crypt_byte_block,
"skip_byte_block", G_TYPE_UINT, skip_byte_block, NULL);
}
if (constant_iv != NULL) {
GstBuffer *constant_iv_buf = _gst_buffer_new_wrapped (
(guint8 *) constant_iv, constant_iv_size, NULL);
gst_structure_set (props,
"constant_iv_size", G_TYPE_UINT, constant_iv_size,
"iv", GST_TYPE_BUFFER, constant_iv_buf, NULL);
gst_buffer_unref (constant_iv_buf);
}
gst_structure_set (props, "cipher-mode", G_TYPE_STRING, "cbcs", NULL);
} else {
gst_structure_set (props, "cipher-mode", G_TYPE_STRING, "cenc", NULL);
}
GST_INFO_OBJECT (qtdemux, "properties for group '%"
GST_FOURCC_FORMAT "' at index %u: %" GST_PTR_FORMAT,
GST_FOURCC_ARGS (grouping_type), index, props);
g_ptr_array_add (*properties, props);
}
return TRUE;
error:
g_ptr_array_free (*properties, TRUE);
*properties = NULL;
return FALSE;
}
/* Parses the sizes of sample auxiliary information contained within a stream,
@ -4160,6 +4427,51 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
&ds_size, &ds_flags, &base_offset))
goto missing_tfhd;
/* Sample grouping support */
if (stream->protected && (stream->protection_scheme_type == FOURCC_cenc
|| stream->protection_scheme_type == FOURCC_cbcs)) {
QtDemuxCencSampleSetInfo *info = stream->protection_scheme_info;
GNode *sbgp_node, *sgpd_node;
GstByteReader sgpd_data, sbgp_data;
if (info->fragment_group_properties) {
g_ptr_array_free (info->fragment_group_properties, TRUE);
info->fragment_group_properties = NULL;
}
if (info->sample_to_group_map) {
g_ptr_array_free (info->sample_to_group_map, FALSE);
info->sample_to_group_map = NULL;
}
/* Check if sample grouping information is present for this segment. */
/* However look only for 'seig' (CENC encryption) grouping type... */
sgpd_node = qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sgpd,
&sgpd_data);
while (sgpd_node) {
if (qtdemux_parse_sgpd (qtdemux, stream, &sgpd_data, FOURCC_seig,
&info->fragment_group_properties)) {
/* CENC encryption grouping found, don't look further. */
break;
}
sgpd_node = qtdemux_tree_get_sibling_by_type_full (sgpd_node,
FOURCC_sgpd, &sgpd_data);
}
sbgp_node = qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sbgp,
&sbgp_data);
while (sbgp_node) {
if (qtdemux_parse_sbgp (qtdemux, stream, &sbgp_data, FOURCC_seig,
&info->sample_to_group_map, info->default_properties,
info->track_group_properties,
info->fragment_group_properties)) {
break;
}
sbgp_node = qtdemux_tree_get_sibling_by_type_full (sbgp_node,
FOURCC_sgpd, &sbgp_data);
}
}
/* The following code assumes at most a single set of sample auxiliary
* data in the fragment (consisting of a saiz box and a corresponding saio
* box); in theory, however, there could be multiple sets of sample
@ -13416,7 +13728,30 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
stsd_entry_data += len;
remaining_stsd_len -= len;
}
/* Sample grouping support */
if (stream->protected && (stream->protection_scheme_type == FOURCC_cenc
|| stream->protection_scheme_type == FOURCC_cbcs)) {
QtDemuxCencSampleSetInfo *info = stream->protection_scheme_info;
GNode *sgpd_node;
GstByteReader sgpd_data;
if (info->track_group_properties) {
g_ptr_array_free (info->fragment_group_properties, TRUE);
info->fragment_group_properties = NULL;
}
sgpd_node = qtdemux_tree_get_child_by_type_full (stbl, FOURCC_sgpd,
&sgpd_data);
while (sgpd_node) {
if (qtdemux_parse_sgpd (qtdemux, stream, &sgpd_data, FOURCC_seig,
&info->track_group_properties)) {
break;
}
sgpd_node = qtdemux_tree_get_sibling_by_type_full (sgpd_node,
FOURCC_sgpd, &sgpd_data);
}
}
/* collect sample information */

View file

@ -231,6 +231,8 @@ static const QtNodeType qt_node_types[] = {
{FOURCC_schi, "scheme information", QT_FLAG_CONTAINER},
{FOURCC_pssh, "protection system specific header", 0},
{FOURCC_tenc, "track encryption", 0},
{FOURCC_sgpd, "sample group description", 0},
{FOURCC_sbgp, "sample to group", 0},
{FOURCC_stpp, "XML subtitle sample entry", 0},
{FOURCC_wvtt, "WebVTT subtitle sample entry", 0},
{FOURCC_clcp, "Closed Caption", 0},