summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Ashley <bugzilla@ashley-family.net>2015-07-29 14:14:50 +0100
committerTim-Philipp Müller <tim@centricular.com>2015-08-10 12:32:17 +0100
commit7d7e54ce6863ff53e188d0276d2651b65082ffdb (patch)
tree4f9b3c9cd6974758a4b682d59bf7272943076b3c
parent9c5c16eb57c1b12d10cb2bca7c4abd59e04583eb (diff)
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
-rw-r--r--gst/isomp4/fourcc.h17
-rw-r--r--gst/isomp4/qtdemux.c667
-rw-r--r--gst/isomp4/qtdemux.h4
-rw-r--r--gst/isomp4/qtdemux_types.c12
4 files changed, 695 insertions, 5 deletions
diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h
index 9e14a78f2..4bff1e0d5 100644
--- a/gst/isomp4/fourcc.h
+++ b/gst/isomp4/fourcc.h
@@ -235,6 +235,8 @@ G_BEGIN_DECLS
#define FOURCC_apcs GST_MAKE_FOURCC('a','p','c','s')
#define FOURCC_m1v GST_MAKE_FOURCC('m','1','v',' ')
#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_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_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
#endif /* __FOURCC_H__ */
diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c
index 46fbf2b0c..4a3cc7ce4 100644
--- a/gst/isomp4/qtdemux.c
+++ b/gst/isomp4/qtdemux.c
@@ -8,6 +8,7 @@
* Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com>
* Copyright (C) <2013> Intel Corporation
* Copyright (C) <2014> Centricular Ltd
+ * Copyright (C) <2015> YouView TV Ltd.
*
* This library is free software; you can redistribute it and/or
* 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 _QtDemuxSample QtDemuxSample;
+typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo;
+
/*struct _QtNode
{
guint32 type;
@@ -391,6 +394,23 @@ struct _QtDemuxStream
/* stereoscopic video streams */
GstVideoMultiviewMode multiview_mode;
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
@@ -507,6 +527,9 @@ static void qtdemux_do_allocation (GstQTDemux * qtdemux,
static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux);
static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration);
+static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
+ const gchar * id);
+
static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
{
@@ -579,6 +602,8 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
qtdemux->upstream_format_is_time = FALSE;
qtdemux->have_group_id = FALSE;
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);
qtdemux->flowcombiner = gst_flow_combiner_new ();
@@ -595,6 +620,9 @@ gst_qtdemux_dispose (GObject * object)
qtdemux->adapter = NULL;
}
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);
}
@@ -1755,6 +1783,11 @@ _create_stream (void)
stream->new_stream = TRUE;
stream->multiview_mode = GST_VIDEO_MULTIVIEW_MODE_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;
}
@@ -1893,6 +1926,14 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
qtdemux->chapters_track_id = 0;
qtdemux->have_group_id = FALSE;
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;
gst_adapter_clear (qtdemux->adapter);
@@ -2095,6 +2136,21 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
gst_event_unref (event);
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:
break;
}
@@ -2213,7 +2269,24 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
stream->redirect_uri = NULL;
stream->sent_eos = 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_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
qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
guint64 moof_offset, QtDemuxStream * stream)
{
GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node, *mfhd_node;
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;
gint64 base_offset, running_offset;
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,
&ds_size, &ds_flags, &base_offset))
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 =
qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfdt,
&tfdt_data);
@@ -3048,6 +3464,15 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
/* iterate all siblings */
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);
return TRUE;
@@ -4453,6 +4878,27 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
GST_TIME_ARGS (pts), GST_TIME_ARGS (duration),
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);
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);
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:
if (!strcmp (type->name, "unknown"))
GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4);
@@ -6266,6 +6722,46 @@ qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
}
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
gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
{
if (stream->subtype == FOURCC_vide) {
@@ -6384,6 +6880,14 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
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);
if (stream->new_stream) {
gchar *stream_id;
@@ -6486,6 +6990,13 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
list = NULL;
/* global tags go on each pad anyway */
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:
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.
* With each track we associate a new QtDemuxStream that contains all the info
* about the trak.
@@ -8027,10 +8640,16 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
GST_FOURCC_ARGS (stream->fourcc));
GST_LOG_OBJECT (qtdemux, "stsd type len: %d", len);
- if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi) ||
- ((fourcc & 0x00FFFFFF) == GST_MAKE_FOURCC ('e', 'n', 'c', 0)))
+ if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi))
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) {
guint32 w = 0, h = 0;
gboolean gray;
@@ -8202,7 +8821,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
esds = NULL;
pasp = NULL;
/* pick 'the' stsd child */
- mp4v = qtdemux_tree_get_child_by_type (stsd, fourcc);
+ if (!stream->protected)
+ mp4v = qtdemux_tree_get_child_by_type (stsd, fourcc);
+ else
+ mp4v = qtdemux_tree_get_child_by_type (stsd, FOURCC_encv);
+
if (mp4v) {
esds = qtdemux_tree_get_child_by_type (mp4v, FOURCC_esds);
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);
}
- mp4a = qtdemux_tree_get_child_by_type (stsd, FOURCC_mp4a);
+ 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);
+
wave = NULL;
esds = NULL;
if (mp4a) {
@@ -10908,6 +11535,7 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
GNode *udta;
GNode *mvex;
GstClockTime duration;
+ GNode *pssh;
guint64 creation_time;
GstDateTime *datetime = NULL;
gint version;
@@ -11034,6 +11662,14 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
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);
return TRUE;
@@ -12075,3 +12711,24 @@ qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
}
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));
+}
diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h
index b232d4939..8f0553bc8 100644
--- a/gst/isomp4/qtdemux.h
+++ b/gst/isomp4/qtdemux.h
@@ -145,6 +145,10 @@ struct _GstQTDemux {
guint64 fragment_start_offset;
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 {
diff --git a/gst/isomp4/qtdemux_types.c b/gst/isomp4/qtdemux_types.c
index ecd02f265..8e8189dea 100644
--- a/gst/isomp4/qtdemux_types.c
+++ b/gst/isomp4/qtdemux_types.c
@@ -189,6 +189,18 @@ static const QtNodeType qt_node_types[] = {
qtdemux_dump_svmi},
{FOURCC_scdi, "Stereoscopic Camera and Display Information", 0,
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,},
};