qtdemux: add support for ISOBMFF Common Encryption

This commit adds support for ISOBMFF Common Encryption (cenc), as
defined in ISO/IEC 23001-7. It uses a GstProtection event to
pass the contents of PSSH boxes to downstream decryptor elements
and attached GstProtectionMeta to each sample.

https://bugzilla.gnome.org/show_bug.cgi?id=705991
This commit is contained in:
Alex Ashley 2015-07-29 14:14:50 +01:00 committed by Tim-Philipp Müller
parent 9c5c16eb57
commit 7d7e54ce68
4 changed files with 695 additions and 5 deletions

View file

@ -235,6 +235,8 @@ G_BEGIN_DECLS
#define FOURCC_apcs GST_MAKE_FOURCC('a','p','c','s') #define FOURCC_apcs GST_MAKE_FOURCC('a','p','c','s')
#define FOURCC_m1v GST_MAKE_FOURCC('m','1','v',' ') #define FOURCC_m1v GST_MAKE_FOURCC('m','1','v',' ')
#define FOURCC_vivo GST_MAKE_FOURCC('v','i','v','o') #define FOURCC_vivo GST_MAKE_FOURCC('v','i','v','o')
#define FOURCC_saiz GST_MAKE_FOURCC('s','a','i','z')
#define FOURCC_saio GST_MAKE_FOURCC('s','a','i','o')
#define FOURCC_3gg6 GST_MAKE_FOURCC('3','g','g','6') #define FOURCC_3gg6 GST_MAKE_FOURCC('3','g','g','6')
#define FOURCC_3gg7 GST_MAKE_FOURCC('3','g','g','7') #define FOURCC_3gg7 GST_MAKE_FOURCC('3','g','g','7')
@ -329,6 +331,21 @@ G_BEGIN_DECLS
#define FOURCC_svmi GST_MAKE_FOURCC('s','v','m','i') #define FOURCC_svmi GST_MAKE_FOURCC('s','v','m','i')
#define FOURCC_scdi GST_MAKE_FOURCC('s','c','d','i') #define FOURCC_scdi GST_MAKE_FOURCC('s','c','d','i')
/* Protected streams */
#define FOURCC_encv GST_MAKE_FOURCC('e','n','c','v')
#define FOURCC_enca GST_MAKE_FOURCC('e','n','c','a')
#define FOURCC_enct GST_MAKE_FOURCC('e','n','c','t')
#define FOURCC_encs GST_MAKE_FOURCC('e','n','c','s')
#define FOURCC_sinf GST_MAKE_FOURCC('s','i','n','f')
#define FOURCC_frma GST_MAKE_FOURCC('f','r','m','a')
#define FOURCC_schm GST_MAKE_FOURCC('s','c','h','m')
#define FOURCC_schi GST_MAKE_FOURCC('s','c','h','i')
/* Common Encryption */
#define FOURCC_pssh GST_MAKE_FOURCC('p','s','s','h')
#define FOURCC_tenc GST_MAKE_FOURCC('t','e','n','c')
#define FOURCC_cenc GST_MAKE_FOURCC('c','e','n','c')
G_END_DECLS G_END_DECLS
#endif /* __FOURCC_H__ */ #endif /* __FOURCC_H__ */

View file

@ -8,6 +8,7 @@
* Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com> * Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com>
* Copyright (C) <2013> Intel Corporation * Copyright (C) <2013> Intel Corporation
* Copyright (C) <2014> Centricular Ltd * Copyright (C) <2014> Centricular Ltd
* Copyright (C) <2015> YouView TV Ltd.
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
@ -100,6 +101,8 @@ GST_DEBUG_CATEGORY (qtdemux_debug);
typedef struct _QtDemuxSegment QtDemuxSegment; typedef struct _QtDemuxSegment QtDemuxSegment;
typedef struct _QtDemuxSample QtDemuxSample; typedef struct _QtDemuxSample QtDemuxSample;
typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo;
/*struct _QtNode /*struct _QtNode
{ {
guint32 type; guint32 type;
@ -391,6 +394,23 @@ struct _QtDemuxStream
/* stereoscopic video streams */ /* stereoscopic video streams */
GstVideoMultiviewMode multiview_mode; GstVideoMultiviewMode multiview_mode;
GstVideoMultiviewFlags multiview_flags; GstVideoMultiviewFlags multiview_flags;
/* protected streams */
gboolean protected;
guint32 protection_scheme_type;
guint32 protection_scheme_version;
gpointer protection_scheme_info; /* specific to the protection scheme */
GQueue protection_scheme_event_queue;
};
/* Contains properties and cryptographic info for a set of samples from a
* track protected using Common Encryption (cenc) */
struct _QtDemuxCencSampleSetInfo
{
GstStructure *default_properties;
/* @crypto_info holds one GstStructure per sample */
GPtrArray *crypto_info;
}; };
enum QtDemuxState enum QtDemuxState
@ -507,6 +527,9 @@ static void qtdemux_do_allocation (GstQTDemux * qtdemux,
static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux); static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux);
static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration); static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration);
static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
const gchar * id);
static void static void
gst_qtdemux_class_init (GstQTDemuxClass * klass) gst_qtdemux_class_init (GstQTDemuxClass * klass)
{ {
@ -579,6 +602,8 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
qtdemux->upstream_format_is_time = FALSE; qtdemux->upstream_format_is_time = FALSE;
qtdemux->have_group_id = FALSE; qtdemux->have_group_id = FALSE;
qtdemux->group_id = G_MAXUINT; qtdemux->group_id = G_MAXUINT;
qtdemux->protection_system_ids = NULL;
g_queue_init (&qtdemux->protection_event_queue);
gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
qtdemux->flowcombiner = gst_flow_combiner_new (); qtdemux->flowcombiner = gst_flow_combiner_new ();
@ -595,6 +620,9 @@ gst_qtdemux_dispose (GObject * object)
qtdemux->adapter = NULL; qtdemux->adapter = NULL;
} }
gst_flow_combiner_free (qtdemux->flowcombiner); gst_flow_combiner_free (qtdemux->flowcombiner);
g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
NULL);
g_queue_clear (&qtdemux->protection_event_queue);
G_OBJECT_CLASS (parent_class)->dispose (object); G_OBJECT_CLASS (parent_class)->dispose (object);
} }
@ -1755,6 +1783,11 @@ _create_stream (void)
stream->new_stream = TRUE; stream->new_stream = TRUE;
stream->multiview_mode = GST_VIDEO_MULTIVIEW_MODE_NONE; stream->multiview_mode = GST_VIDEO_MULTIVIEW_MODE_NONE;
stream->multiview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE; stream->multiview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
stream->protected = FALSE;
stream->protection_scheme_type = 0;
stream->protection_scheme_version = 0;
stream->protection_scheme_info = NULL;
g_queue_init (&stream->protection_scheme_event_queue);
return stream; return stream;
} }
@ -1893,6 +1926,14 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
qtdemux->chapters_track_id = 0; qtdemux->chapters_track_id = 0;
qtdemux->have_group_id = FALSE; qtdemux->have_group_id = FALSE;
qtdemux->group_id = G_MAXUINT; qtdemux->group_id = G_MAXUINT;
if (qtdemux->protection_system_ids) {
g_ptr_array_free (qtdemux->protection_system_ids, TRUE);
qtdemux->protection_system_ids = NULL;
}
g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
NULL);
g_queue_clear (&qtdemux->protection_event_queue);
} }
qtdemux->offset = 0; qtdemux->offset = 0;
gst_adapter_clear (qtdemux->adapter); gst_adapter_clear (qtdemux->adapter);
@ -2095,6 +2136,21 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
gst_event_unref (event); gst_event_unref (event);
goto drop; goto drop;
} }
case GST_EVENT_PROTECTION:
{
const gchar *system_id = NULL;
gst_event_parse_protection (event, &system_id, NULL, NULL);
GST_DEBUG_OBJECT (demux, "Received protection event for system ID %s",
system_id);
gst_qtdemux_append_protection_system_id (demux, system_id);
/* save the event for later, for source pads that have not been created */
g_queue_push_tail (&demux->protection_event_queue, gst_event_ref (event));
/* send it to all pads that already exist */
gst_qtdemux_push_event (demux, event);
res = TRUE;
goto drop;
}
default: default:
break; break;
} }
@ -2213,7 +2269,24 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
stream->redirect_uri = NULL; stream->redirect_uri = NULL;
stream->sent_eos = FALSE; stream->sent_eos = FALSE;
stream->sparse = FALSE; stream->sparse = FALSE;
stream->protected = FALSE;
if (stream->protection_scheme_info) {
if (stream->protection_scheme_type == FOURCC_cenc) {
QtDemuxCencSampleSetInfo *info =
(QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
if (info->default_properties)
gst_structure_free (info->default_properties);
if (info->crypto_info)
g_ptr_array_free (info->crypto_info, TRUE);
}
g_free (stream->protection_scheme_info);
stream->protection_scheme_info = NULL;
}
stream->protection_scheme_type = 0;
stream->protection_scheme_version = 0;
g_queue_foreach (&stream->protection_scheme_event_queue,
(GFunc) gst_event_unref, NULL);
g_queue_clear (&stream->protection_scheme_event_queue);
gst_qtdemux_stream_flush_segments_data (qtdemux, stream); gst_qtdemux_stream_flush_segments_data (qtdemux, stream);
gst_qtdemux_stream_flush_samples_data (qtdemux, stream); gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
} }
@ -2953,12 +3026,300 @@ failed:
} }
} }
/* Returns a pointer to a GstStructure containing the properties of
* the stream sample identified by @sample_index. The caller must unref
* the returned object after use. Returns NULL if unsuccessful. */
static GstStructure *
qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
QtDemuxStream * stream, guint sample_index)
{
QtDemuxCencSampleSetInfo *info = NULL;
g_return_val_if_fail (stream != NULL, NULL);
g_return_val_if_fail (stream->protected, NULL);
g_return_val_if_fail (stream->protection_scheme_info != NULL, NULL);
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);
}
/* Parses the sizes of sample auxiliary information contained within a stream,
* as given in a saiz box. Returns array of sample_count guint8 size values,
* or NULL on failure */
static guint8 *
qtdemux_parse_saiz (GstQTDemux * qtdemux, QtDemuxStream * stream,
GstByteReader * br, guint32 * sample_count)
{
guint32 flags = 0;
guint8 *info_sizes;
guint8 default_info_size;
g_return_val_if_fail (qtdemux != NULL, NULL);
g_return_val_if_fail (stream != NULL, NULL);
g_return_val_if_fail (br != NULL, NULL);
g_return_val_if_fail (sample_count != NULL, NULL);
if (!gst_byte_reader_get_uint32_be (br, &flags))
return NULL;
if (flags & 0x1) {
/* aux_info_type and aux_info_type_parameter are ignored */
if (!gst_byte_reader_skip (br, 8))
return NULL;
}
if (!gst_byte_reader_get_uint8 (br, &default_info_size))
return NULL;
GST_DEBUG_OBJECT (qtdemux, "default_info_size: %u", default_info_size);
if (!gst_byte_reader_get_uint32_be (br, sample_count))
return NULL;
GST_DEBUG_OBJECT (qtdemux, "sample_count: %u", *sample_count);
if (default_info_size == 0) {
if (!gst_byte_reader_dup_data (br, *sample_count, &info_sizes)) {
return NULL;
}
} else {
info_sizes = g_new (guint8, *sample_count);
memset (info_sizes, default_info_size, *sample_count);
}
return info_sizes;
}
/* Parses the offset of sample auxiliary information contained within a stream,
* as given in a saio box. Returns TRUE if successful; FALSE otherwise. */
static gboolean
qtdemux_parse_saio (GstQTDemux * qtdemux, QtDemuxStream * stream,
GstByteReader * br, guint32 * info_type, guint32 * info_type_parameter,
guint64 * offset)
{
guint8 version = 0;
guint32 flags = 0;
guint32 aux_info_type = 0;
guint32 aux_info_type_parameter = 0;
guint32 entry_count;
guint32 off_32;
guint64 off_64;
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 (offset != NULL, FALSE);
if (!gst_byte_reader_get_uint8 (br, &version))
return FALSE;
if (!gst_byte_reader_get_uint24_be (br, &flags))
return FALSE;
if (flags & 0x1) {
if (!gst_byte_reader_get_uint32_be (br, &aux_info_type))
return FALSE;
if (!gst_byte_reader_get_uint32_be (br, &aux_info_type_parameter))
return FALSE;
} else if (stream->protected) {
aux_info_type = stream->protection_scheme_type;
} else {
aux_info_type = stream->fourcc;
}
if (info_type)
*info_type = aux_info_type;
if (info_type_parameter)
*info_type_parameter = aux_info_type_parameter;
GST_DEBUG_OBJECT (qtdemux, "aux_info_type: '%" GST_FOURCC_FORMAT "', "
"aux_info_type_parameter: %#06x",
GST_FOURCC_ARGS (aux_info_type), aux_info_type_parameter);
if (!gst_byte_reader_get_uint32_be (br, &entry_count))
return FALSE;
if (entry_count != 1) {
GST_ERROR_OBJECT (qtdemux, "multiple offsets are not supported");
return FALSE;
}
if (version == 0) {
if (!gst_byte_reader_get_uint32_be (br, &off_32))
return FALSE;
*offset = (guint64) off_32;
} else {
if (!gst_byte_reader_get_uint64_be (br, &off_64))
return FALSE;
*offset = off_64;
}
GST_DEBUG_OBJECT (qtdemux, "offset: %" G_GUINT64_FORMAT, *offset);
return TRUE;
}
static void
qtdemux_gst_structure_free (GstStructure * gststructure)
{
if (gststructure) {
gst_structure_free (gststructure);
}
}
/* Parses auxiliary information relating to samples protected using Common
* Encryption (cenc); the format of this information is defined in
* ISO/IEC 23001-7. Returns TRUE if successful; FALSE otherwise. */
static gboolean
qtdemux_parse_cenc_aux_info (GstQTDemux * qtdemux, QtDemuxStream * stream,
GstByteReader * br, guint8 * info_sizes, guint32 sample_count)
{
QtDemuxCencSampleSetInfo *ss_info = NULL;
guint8 size;
gint i;
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 (stream->protected, FALSE);
g_return_val_if_fail (stream->protection_scheme_info != NULL, FALSE);
ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
if (ss_info->crypto_info) {
GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info");
g_ptr_array_free (ss_info->crypto_info, TRUE);
}
ss_info->crypto_info =
g_ptr_array_new_full (sample_count,
(GDestroyNotify) qtdemux_gst_structure_free);
for (i = 0; i < sample_count; ++i) {
GstStructure *properties;
guint16 n_subsamples;
guint8 *data;
guint iv_size;
GstBuffer *buf;
properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i);
if (properties == NULL) {
GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i);
return FALSE;
}
if (!gst_structure_get_uint (properties, "iv_size", &iv_size)) {
GST_ERROR_OBJECT (qtdemux, "failed to get iv_size for sample %u", i);
gst_structure_free (properties);
return FALSE;
}
if (!gst_byte_reader_dup_data (br, iv_size, &data)) {
GST_ERROR_OBJECT (qtdemux, "failed to get IV for sample %u", i);
gst_structure_free (properties);
return FALSE;
}
buf = gst_buffer_new_wrapped (data, iv_size);
gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL);
size = info_sizes[i];
if (size > iv_size) {
if (!gst_byte_reader_get_uint16_be (br, &n_subsamples)
|| !(n_subsamples > 0)) {
gst_structure_free (properties);
GST_ERROR_OBJECT (qtdemux,
"failed to get subsample count for sample %u", i);
return FALSE;
}
GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples);
if (!gst_byte_reader_dup_data (br, n_subsamples * 6, &data)) {
GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u",
i);
gst_structure_free (properties);
return FALSE;
}
buf = gst_buffer_new_wrapped (data, n_subsamples * 6);
if (!buf) {
gst_structure_free (properties);
return FALSE;
}
gst_structure_set (properties,
"subsample_count", G_TYPE_UINT, n_subsamples,
"subsamples", GST_TYPE_BUFFER, buf, NULL);
} else {
gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL);
}
g_ptr_array_add (ss_info->crypto_info, properties);
}
return TRUE;
}
/* Converts a UUID in raw byte form to a string representation, as defined in
* RFC 4122. The caller takes ownership of the returned string and is
* responsible for freeing it after use. */
static gchar *
qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes)
{
const guint8 *uuid = (const guint8 *) uuid_bytes;
return g_strdup_printf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-"
"%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15]);
}
/* Parses a Protection System Specific Header box (pssh), as defined in the
* Common Encryption (cenc) standard (ISO/IEC 23001-7), which contains
* information needed by a specific content protection system in order to
* decrypt cenc-protected tracks. Returns TRUE if successful; FALSE
* otherwise. */
static gboolean
qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node)
{
gchar *sysid_string;
guint32 pssh_size = QT_UINT32 (node->data);
GstBuffer *pssh = NULL;
GstEvent *event = NULL;
guint32 parent_box_type;
gint i;
if (G_UNLIKELY (pssh_size < 32U)) {
GST_ERROR_OBJECT (qtdemux, "invalid box size");
return FALSE;
}
sysid_string =
qtdemux_uuid_bytes_to_string ((const guint8 *) node->data + 12);
gst_qtdemux_append_protection_system_id (qtdemux, sysid_string);
pssh = gst_buffer_new_wrapped (g_memdup (node->data, pssh_size), pssh_size);
GST_LOG_OBJECT (qtdemux, "cenc pssh size: %" G_GSIZE_FORMAT,
gst_buffer_get_size (pssh));
parent_box_type = QT_FOURCC ((const guint8 *) node->parent->data + 4);
/* Push an event containing the pssh box onto the queues of all streams. */
event = gst_event_new_protection (sysid_string, pssh,
(parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof");
for (i = 0; i < qtdemux->n_streams; ++i) {
g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue,
gst_event_ref (event));
}
g_free (sysid_string);
gst_event_unref (event);
gst_buffer_unref (pssh);
return TRUE;
}
static gboolean static gboolean
qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
guint64 moof_offset, QtDemuxStream * stream) guint64 moof_offset, QtDemuxStream * stream)
{ {
GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node, *mfhd_node; GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node, *mfhd_node;
GstByteReader mfhd_data, trun_data, tfhd_data, tfdt_data; GstByteReader mfhd_data, trun_data, tfhd_data, tfdt_data;
GNode *saiz_node, *saio_node, *pssh_node;
GstByteReader saiz_data, saio_data;
guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; guint32 ds_size = 0, ds_duration = 0, ds_flags = 0;
gint64 base_offset, running_offset; gint64 base_offset, running_offset;
guint32 frag_num; guint32 frag_num;
@ -2993,6 +3354,61 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &stream, &ds_duration, if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &stream, &ds_duration,
&ds_size, &ds_flags, &base_offset)) &ds_size, &ds_flags, &base_offset))
goto missing_tfhd; goto missing_tfhd;
/* 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
* auxiliary data in a fragment. */
saiz_node =
qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saiz,
&saiz_data);
if (saiz_node) {
guint8 *info_sizes;
guint32 sample_count;
guint32 info_type = 0;
guint64 offset = 0;
guint32 info_type_parameter = 0;
info_sizes = qtdemux_parse_saiz (qtdemux, stream, &saiz_data,
&sample_count);
if (G_UNLIKELY (info_sizes == NULL)) {
GST_ERROR_OBJECT (qtdemux, "failed to parse saiz box");
goto fail;
}
saio_node =
qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saio,
&saio_data);
if (!saio_node) {
GST_ERROR_OBJECT (qtdemux, "saiz box without a corresponding saio box");
goto fail;
}
if (G_UNLIKELY (!qtdemux_parse_saio (qtdemux, stream, &saio_data,
&info_type, &info_type_parameter, &offset))) {
GST_ERROR_OBJECT (qtdemux, "failed to parse saio box");
g_free (info_sizes);
goto fail;
}
offset += (base_offset > 0) ? (guint64) base_offset : 0;
if (info_type == FOURCC_cenc && info_type_parameter == 0U) {
GstByteReader br;
if (offset > length) {
GST_ERROR_OBJECT (qtdemux, "cenc auxiliary info outside moof "
"boxes is not supported");
g_free (info_sizes);
goto fail;
}
gst_byte_reader_init (&br, buffer + offset, length - offset);
if (!qtdemux_parse_cenc_aux_info (qtdemux, stream, &br,
info_sizes, sample_count)) {
GST_ERROR_OBJECT (qtdemux, "failed to parse cenc auxiliary info");
goto fail;
}
}
g_free (info_sizes);
}
tfdt_node = tfdt_node =
qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfdt, qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfdt,
&tfdt_data); &tfdt_data);
@ -3048,6 +3464,15 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
/* iterate all siblings */ /* iterate all siblings */
traf_node = qtdemux_tree_get_sibling_by_type (traf_node, FOURCC_traf); traf_node = qtdemux_tree_get_sibling_by_type (traf_node, FOURCC_traf);
} }
/* parse any protection system info */
pssh_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_pssh);
while (pssh_node) {
GST_LOG_OBJECT (qtdemux, "Parsing pssh box.");
qtdemux_parse_pssh (qtdemux, pssh_node);
pssh_node = qtdemux_tree_get_sibling_by_type (pssh_node, FOURCC_pssh);
}
g_node_destroy (moof_node); g_node_destroy (moof_node);
return TRUE; return TRUE;
@ -4453,6 +4878,27 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
GST_TIME_ARGS (pts), GST_TIME_ARGS (duration), GST_TIME_ARGS (pts), GST_TIME_ARGS (duration),
GST_PAD_NAME (stream->pad)); GST_PAD_NAME (stream->pad));
if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
GstStructure *crypto_info;
QtDemuxCencSampleSetInfo *info =
(QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
guint index;
GstEvent *event;
while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
gst_pad_push_event (stream->pad, event);
}
index = stream->sample_index - (stream->n_samples - info->crypto_info->len);
if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
crypto_info =
g_steal_pointer (&g_ptr_array_index (info->crypto_info, index));
GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u]", index);
if (!gst_buffer_add_protection_meta (buf, crypto_info))
GST_ERROR_OBJECT (qtdemux, "failed to attach cenc metadata to buffer");
}
}
ret = gst_pad_push (stream->pad, buf); ret = gst_pad_push (stream->pad, buf);
if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) { if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
@ -6119,6 +6565,16 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
qtdemux_parse_uuid (qtdemux, buffer, end - buffer); qtdemux_parse_uuid (qtdemux, buffer, end - buffer);
break; break;
} }
case FOURCC_encv:
{
qtdemux_parse_container (qtdemux, node, buffer + 86, end);
break;
}
case FOURCC_enca:
{
qtdemux_parse_container (qtdemux, node, buffer + 36, end);
break;
}
default: default:
if (!strcmp (type->name, "unknown")) if (!strcmp (type->name, "unknown"))
GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4); GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4);
@ -6265,6 +6721,46 @@ qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
#endif #endif
} }
static gboolean
gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
QtDemuxStream * stream)
{
GstStructure *s;
const gchar *selected_system;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
g_return_val_if_fail (gst_caps_get_size (stream->caps) == 1, FALSE);
if (stream->protection_scheme_type != FOURCC_cenc) {
GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme");
return FALSE;
}
if (qtdemux->protection_system_ids == NULL) {
GST_ERROR_OBJECT (qtdemux, "stream is protected using cenc, but no "
"cenc protection system information has been found");
return FALSE;
}
g_ptr_array_add (qtdemux->protection_system_ids, NULL);
selected_system = gst_protection_select_system ((const gchar **)
qtdemux->protection_system_ids->pdata);
g_ptr_array_remove_index (qtdemux->protection_system_ids,
qtdemux->protection_system_ids->len - 1);
if (!selected_system) {
GST_ERROR_OBJECT (qtdemux, "stream is protected, but no "
"suitable decryptor element has been found");
return FALSE;
}
s = gst_caps_get_structure (stream->caps, 0);
gst_structure_set (s,
"original-media-type", G_TYPE_STRING, gst_structure_get_name (s),
GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, G_TYPE_STRING, selected_system,
NULL);
gst_structure_set_name (s, "application/x-cenc");
return TRUE;
}
static gboolean static gboolean
gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
{ {
@ -6384,6 +6880,14 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
gst_pad_use_fixed_caps (stream->pad); gst_pad_use_fixed_caps (stream->pad);
if (stream->protected) {
if (!gst_qtdemux_configure_protected_caps (qtdemux, stream)) {
GST_ERROR_OBJECT (qtdemux,
"Failed to configure protected stream caps.");
return FALSE;
}
}
GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, stream->caps); GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, stream->caps);
if (stream->new_stream) { if (stream->new_stream) {
gchar *stream_id; gchar *stream_id;
@ -6486,6 +6990,13 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
list = NULL; list = NULL;
/* global tags go on each pad anyway */ /* global tags go on each pad anyway */
stream->send_global_tags = TRUE; stream->send_global_tags = TRUE;
/* send upstream GST_EVENT_PROTECTION events that were received before
this source pad was created */
for (int i = 0; i < qtdemux->protection_event_queue.length; ++i) {
GstEvent *event =
(GstEvent *) g_queue_peek_nth (&qtdemux->protection_event_queue, i);
gst_pad_push_event (stream->pad, gst_event_ref (event));
}
} }
done: done:
if (list) if (list)
@ -7770,6 +8281,108 @@ qtdemux_inspect_transformation_matrix (GstQTDemux * qtdemux,
} }
} }
/* Parses the boxes defined in ISO/IEC 14496-12 that enable support for
* protected streams (sinf, frma, schm and schi); if the protection scheme is
* Common Encryption (cenc), the function will also parse the tenc box (defined
* in ISO/IEC 23001-7). @container points to the node that contains these boxes
* (typically an enc[v|a|t|s] sample entry); the function will set
* @original_fmt to the fourcc of the original unencrypted stream format.
* Returns TRUE if successful; FALSE otherwise. */
static gboolean
qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
QtDemuxStream * stream, GNode * container, guint32 * original_fmt)
{
GNode *sinf;
GNode *frma;
GNode *schm;
GNode *schi;
g_return_val_if_fail (qtdemux != NULL, FALSE);
g_return_val_if_fail (stream != NULL, FALSE);
g_return_val_if_fail (container != NULL, FALSE);
g_return_val_if_fail (original_fmt != NULL, FALSE);
sinf = qtdemux_tree_get_child_by_type (container, FOURCC_sinf);
if (G_UNLIKELY (!sinf)) {
if (stream->protection_scheme_type == FOURCC_cenc) {
GST_ERROR_OBJECT (qtdemux, "sinf box does not contain schi box, which is "
"mandatory for Common Encryption");
return FALSE;
}
return TRUE;
}
frma = qtdemux_tree_get_child_by_type (sinf, FOURCC_frma);
if (G_UNLIKELY (!frma)) {
GST_ERROR_OBJECT (qtdemux, "sinf box does not contain mandatory frma box");
return FALSE;
}
*original_fmt = QT_FOURCC ((const guint8 *) frma->data + 8);
GST_DEBUG_OBJECT (qtdemux, "original stream format: '%" GST_FOURCC_FORMAT "'",
GST_FOURCC_ARGS (*original_fmt));
schm = qtdemux_tree_get_child_by_type (sinf, FOURCC_schm);
if (!schm) {
GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schm box");
return FALSE;
}
stream->protection_scheme_type = QT_FOURCC ((const guint8 *) schm->data + 12);
stream->protection_scheme_version =
QT_UINT32 ((const guint8 *) schm->data + 16);
GST_DEBUG_OBJECT (qtdemux,
"protection_scheme_type: %" GST_FOURCC_FORMAT ", "
"protection_scheme_version: %#010x",
GST_FOURCC_ARGS (stream->protection_scheme_type),
stream->protection_scheme_version);
schi = qtdemux_tree_get_child_by_type (sinf, FOURCC_schi);
if (!schi) {
GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box");
return FALSE;
}
if (stream->protection_scheme_type == FOURCC_cenc) {
QtDemuxCencSampleSetInfo *info;
GNode *tenc;
const guint8 *tenc_data;
guint32 isEncrypted;
guint8 iv_size;
const guint8 *default_kid;
GstBuffer *kid_buf;
if (G_UNLIKELY (!stream->protection_scheme_info))
stream->protection_scheme_info =
g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_tenc);
if (!tenc) {
GST_ERROR_OBJECT (qtdemux, "schi box does not contain tenc box, "
"which is mandatory for Common Encryption");
return FALSE;
}
tenc_data = (const guint8 *) tenc->data + 12;
isEncrypted = QT_UINT24 (tenc_data);
iv_size = QT_UINT8 (tenc_data + 3);
default_kid = (tenc_data + 4);
kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
gst_buffer_fill (kid_buf, 0, default_kid, 16);
if (info->default_properties)
gst_structure_free (info->default_properties);
info->default_properties =
gst_structure_new ("application/x-cenc",
"iv_size", G_TYPE_UINT, iv_size,
"encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1),
"kid", GST_TYPE_BUFFER, kid_buf, NULL);
GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
"is_encrypted=%u, iv_size=%u", isEncrypted, iv_size);
gst_buffer_unref (kid_buf);
}
return TRUE;
}
/* parse the traks. /* parse the traks.
* With each track we associate a new QtDemuxStream that contains all the info * With each track we associate a new QtDemuxStream that contains all the info
* about the trak. * about the trak.
@ -8027,10 +8640,16 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_FOURCC_ARGS (stream->fourcc)); GST_FOURCC_ARGS (stream->fourcc));
GST_LOG_OBJECT (qtdemux, "stsd type len: %d", len); GST_LOG_OBJECT (qtdemux, "stsd type len: %d", len);
if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi) || if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi))
((fourcc & 0x00FFFFFF) == GST_MAKE_FOURCC ('e', 'n', 'c', 0)))
goto error_encrypted; goto error_encrypted;
if (fourcc == FOURCC_encv || fourcc == FOURCC_enca) {
GNode *enc = qtdemux_tree_get_child_by_type (stsd, fourcc);
stream->protected = TRUE;
if (!qtdemux_parse_protection_scheme_info (qtdemux, stream, enc, &fourcc))
GST_ERROR_OBJECT (qtdemux, "Failed to parse protection scheme info");
}
if (stream->subtype == FOURCC_vide) { if (stream->subtype == FOURCC_vide) {
guint32 w = 0, h = 0; guint32 w = 0, h = 0;
gboolean gray; gboolean gray;
@ -8202,7 +8821,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
esds = NULL; esds = NULL;
pasp = NULL; pasp = NULL;
/* pick 'the' stsd child */ /* pick 'the' stsd child */
if (!stream->protected)
mp4v = qtdemux_tree_get_child_by_type (stsd, fourcc); mp4v = qtdemux_tree_get_child_by_type (stsd, fourcc);
else
mp4v = qtdemux_tree_get_child_by_type (stsd, FOURCC_encv);
if (mp4v) { if (mp4v) {
esds = qtdemux_tree_get_child_by_type (mp4v, FOURCC_esds); esds = qtdemux_tree_get_child_by_type (mp4v, FOURCC_esds);
pasp = qtdemux_tree_get_child_by_type (mp4v, FOURCC_pasp); pasp = qtdemux_tree_get_child_by_type (mp4v, FOURCC_pasp);
@ -9095,7 +9718,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_TAG_BITRATE, bitrate, NULL); GST_TAG_BITRATE, bitrate, NULL);
} }
if (stream->protected && fourcc == FOURCC_mp4a)
mp4a = qtdemux_tree_get_child_by_type (stsd, FOURCC_enca);
else
mp4a = qtdemux_tree_get_child_by_type (stsd, FOURCC_mp4a); mp4a = qtdemux_tree_get_child_by_type (stsd, FOURCC_mp4a);
wave = NULL; wave = NULL;
esds = NULL; esds = NULL;
if (mp4a) { if (mp4a) {
@ -10908,6 +11535,7 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
GNode *udta; GNode *udta;
GNode *mvex; GNode *mvex;
GstClockTime duration; GstClockTime duration;
GNode *pssh;
guint64 creation_time; guint64 creation_time;
GstDateTime *datetime = NULL; GstDateTime *datetime = NULL;
gint version; gint version;
@ -11034,6 +11662,14 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
GST_LOG_OBJECT (qtdemux, "No meta node found."); GST_LOG_OBJECT (qtdemux, "No meta node found.");
} }
/* parse any protection system info */
pssh = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_pssh);
while (pssh) {
GST_LOG_OBJECT (qtdemux, "Parsing pssh box.");
qtdemux_parse_pssh (qtdemux, pssh);
pssh = qtdemux_tree_get_sibling_by_type (pssh, FOURCC_pssh);
}
qtdemux->tag_list = qtdemux_add_container_format (qtdemux, qtdemux->tag_list); qtdemux->tag_list = qtdemux_add_container_format (qtdemux, qtdemux->tag_list);
return TRUE; return TRUE;
@ -12075,3 +12711,24 @@ qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
} }
return caps; return caps;
} }
static void
gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
const gchar * system_id)
{
gint i;
if (!qtdemux->protection_system_ids)
qtdemux->protection_system_ids =
g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
/* Check whether we already have an entry for this system ID. */
for (i = 0; i < qtdemux->protection_system_ids->len; ++i) {
const gchar *id = g_ptr_array_index (qtdemux->protection_system_ids, i);
if (g_ascii_strcasecmp (system_id, id) == 0) {
return;
}
}
GST_DEBUG_OBJECT (qtdemux, "Adding cenc protection system ID %s", system_id);
g_ptr_array_add (qtdemux->protection_system_ids, g_ascii_strdown (system_id,
-1));
}

View file

@ -145,6 +145,10 @@ struct _GstQTDemux {
guint64 fragment_start_offset; guint64 fragment_start_offset;
gint64 chapters_track_id; gint64 chapters_track_id;
/* protection support */
GPtrArray *protection_system_ids; /* Holds identifiers of all content protection systems for all tracks */
GQueue protection_event_queue; /* holds copy of upstream protection events */
}; };
struct _GstQTDemuxClass { struct _GstQTDemuxClass {

View file

@ -189,6 +189,18 @@ static const QtNodeType qt_node_types[] = {
qtdemux_dump_svmi}, qtdemux_dump_svmi},
{FOURCC_scdi, "Stereoscopic Camera and Display Information", 0, {FOURCC_scdi, "Stereoscopic Camera and Display Information", 0,
qtdemux_dump_unknown}, qtdemux_dump_unknown},
{FOURCC_saiz, "sample auxiliary information sizes", 0},
{FOURCC_saio, "sample auxiliary information offsets", 0},
{FOURCC_encv, "encrypted visual sample entry", 0},
{FOURCC_enca, "encrypted audio sample entry", 0},
{FOURCC_enct, "encrypted text sample entry", 0},
{FOURCC_encs, "encrypted system sample entry", 0},
{FOURCC_sinf, "protection scheme information", QT_FLAG_CONTAINER},
{FOURCC_frma, "original format", 0},
{FOURCC_schm, "scheme type", 0},
{FOURCC_schi, "scheme information", QT_FLAG_CONTAINER},
{FOURCC_pssh, "protection system specific header", 0},
{FOURCC_tenc, "track encryption", 0},
{0, "unknown", 0,}, {0, "unknown", 0,},
}; };