summaryrefslogtreecommitdiff
path: root/gst/isomp4/qtdemux.c
diff options
context:
space:
mode:
authorPhilippe Normand <philn@igalia.com>2015-07-10 09:44:15 +0200
committerTim-Philipp Müller <tim@centricular.com>2016-04-02 18:01:10 +0100
commitfd7964e7462544a0c120ebf1d4c8e4b0174a1518 (patch)
treec73ac4bcfdb9e81256f823485a9856622bc09da2 /gst/isomp4/qtdemux.c
parent4b7e377d2563697a4b13d401112195d1fa4cb451 (diff)
qtdemux: PIFF box detection and parsing support
The PIFF data is stored in a custom UUID box which is parsed and the crypto_info of the element is updated accordingly. This allows downstream decryptors to process and decrypt the protected content. https://bugzilla.gnome.org/show_bug.cgi?id=753614
Diffstat (limited to 'gst/isomp4/qtdemux.c')
-rw-r--r--gst/isomp4/qtdemux.c191
1 files changed, 191 insertions, 0 deletions
diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c
index d3653fa22..b568c5022 100644
--- a/gst/isomp4/qtdemux.c
+++ b/gst/isomp4/qtdemux.c
@@ -530,8 +530,13 @@ 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 gchar *qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes);
+
+static GstStructure *qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
+ QtDemuxStream * stream, guint sample_index);
static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
const gchar * id);
+static void qtdemux_gst_structure_free (GstStructure * gststructure);
static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
@@ -2448,6 +2453,174 @@ qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist,
}
static void
+qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
+ guint offset)
+{
+ GstByteReader br;
+ guint8 version;
+ guint32 flags = 0;
+ guint i;
+ guint8 iv_size = 8;
+ QtDemuxStream *stream;
+ GstStructure *structure;
+ QtDemuxCencSampleSetInfo *ss_info = NULL;
+ const gchar *system_id;
+ gboolean uses_sub_sample_encryption = FALSE;
+
+ if (!qtdemux->streams)
+ return;
+
+ stream = qtdemux->streams[0];
+
+ structure = gst_caps_get_structure (stream->caps, 0);
+ if (!gst_structure_has_name (structure, "application/x-cenc")) {
+ GST_WARNING_OBJECT (qtdemux,
+ "Attempting PIFF box parsing on an unencrypted stream.");
+ return;
+ }
+
+ gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD,
+ G_TYPE_STRING, &system_id, NULL);
+ gst_qtdemux_append_protection_system_id (qtdemux, system_id);
+
+ stream->protected = TRUE;
+ stream->protection_scheme_type = FOURCC_cenc;
+
+ if (!stream->protection_scheme_info)
+ stream->protection_scheme_info = g_new0 (QtDemuxCencSampleSetInfo, 1);
+
+ ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+
+ if (ss_info->default_properties)
+ gst_structure_free (ss_info->default_properties);
+
+ ss_info->default_properties =
+ gst_structure_new ("application/x-cenc",
+ "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL);
+
+ 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 = NULL;
+ }
+
+ /* skip UUID */
+ gst_byte_reader_init (&br, buffer + offset + 16, length - offset - 16);
+
+ if (!gst_byte_reader_get_uint8 (&br, &version)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting box's version field");
+ return;
+ }
+
+ if (!gst_byte_reader_get_uint24_be (&br, &flags)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting box's flags field");
+ return;
+ }
+
+ if ((flags & 0x000001)) {
+ guint32 algorithm_id = 0;
+ const guint8 *kid;
+ GstBuffer *kid_buf;
+ gboolean is_encrypted = TRUE;
+
+ if (!gst_byte_reader_get_uint24_le (&br, &algorithm_id)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
+ return;
+ }
+
+ algorithm_id >>= 8;
+ if (algorithm_id == 0) {
+ is_encrypted = FALSE;
+ } else if (algorithm_id == 1) {
+ /* FIXME: maybe store this in properties? */
+ GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
+ } else if (algorithm_id == 2) {
+ /* FIXME: maybe store this in properties? */
+ GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
+ }
+
+ if (!gst_byte_reader_get_uint8 (&br, &iv_size))
+ return;
+
+ if (!gst_byte_reader_get_data (&br, 16, &kid))
+ return;
+
+ kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
+ gst_buffer_fill (kid_buf, 0, kid, 16);
+ if (ss_info->default_properties)
+ gst_structure_free (ss_info->default_properties);
+ ss_info->default_properties =
+ gst_structure_new ("application/x-cenc",
+ "iv_size", G_TYPE_UINT, iv_size,
+ "encrypted", G_TYPE_BOOLEAN, is_encrypted,
+ "kid", GST_TYPE_BUFFER, kid_buf, NULL);
+ GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
+ "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
+ gst_buffer_unref (kid_buf);
+ } else if ((flags & 0x000002)) {
+ uses_sub_sample_encryption = TRUE;
+ }
+
+ if (!gst_byte_reader_get_uint32_be (&br, &qtdemux->cenc_aux_sample_count)) {
+ GST_ERROR_OBJECT (qtdemux, "Error getting box's sample count field");
+ return;
+ }
+
+ ss_info->crypto_info =
+ g_ptr_array_new_full (qtdemux->cenc_aux_sample_count,
+ (GDestroyNotify) qtdemux_gst_structure_free);
+
+ for (i = 0; i < qtdemux->cenc_aux_sample_count; ++i) {
+ GstStructure *properties;
+ guint8 *data;
+ 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;
+ }
+
+ if (!gst_byte_reader_dup_data (&br, iv_size, &data)) {
+ GST_ERROR_OBJECT (qtdemux, "IV data not present for sample %u", i);
+ gst_structure_free (properties);
+ return;
+ }
+ buf = gst_buffer_new_wrapped (data, iv_size);
+ gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL);
+ gst_buffer_unref (buf);
+
+ if (uses_sub_sample_encryption) {
+ guint16 n_subsamples;
+
+ if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples)
+ || n_subsamples == 0) {
+ GST_ERROR_OBJECT (qtdemux,
+ "failed to get subsample count for sample %u", i);
+ gst_structure_free (properties);
+ return;
+ }
+ 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;
+ }
+ buf = gst_buffer_new_wrapped (data, n_subsamples * 6);
+ gst_structure_set (properties,
+ "subsample_count", G_TYPE_UINT, n_subsamples,
+ "subsamples", GST_TYPE_BUFFER, buf, NULL);
+ gst_buffer_unref (buf);
+ } else {
+ gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL);
+ }
+
+ g_ptr_array_add (ss_info->crypto_info, properties);
+ }
+}
+
+static void
qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
{
static const guint8 xmp_uuid[] = { 0xBE, 0x7A, 0xCF, 0xCB,
@@ -2459,6 +2632,12 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
0xd0, 0x8a, 0x4f, 0x18, 0x10, 0xf3, 0x4a, 0x82,
0xb6, 0xc8, 0x32, 0xd8, 0xab, 0xa1, 0x83, 0xd3
};
+
+ static const guint8 piff_sample_encryption_uuid[] = {
+ 0xa2, 0x39, 0x4f, 0x52, 0x5a, 0x9b, 0x4f, 0x14,
+ 0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4
+ };
+
guint offset;
/* counts as header data */
@@ -2497,6 +2676,8 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT,
(_("Cannot play stream because it is encrypted with PlayReady DRM.")),
(NULL));
+ } else if (memcmp (buffer + offset, piff_sample_encryption_uuid, 16) == 0) {
+ qtdemux_parse_piff (qtdemux, buffer, length, offset);
} else {
GST_DEBUG_OBJECT (qtdemux, "Ignoring unknown uuid: %08x-%08x-%08x-%08x",
GST_READ_UINT32_LE (buffer + offset),
@@ -3390,6 +3571,7 @@ 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;
+ GNode *uuid_node;
GstByteReader mfhd_data, trun_data, tfhd_data, tfdt_data;
GNode *saiz_node, *saio_node, *pssh_node;
GstByteReader saiz_data, saio_data;
@@ -3538,6 +3720,15 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
trun_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun,
&trun_data);
}
+
+ uuid_node = qtdemux_tree_get_child_by_type (traf_node, FOURCC_uuid);
+ if (uuid_node) {
+ guint8 *uuid_buffer = (guint8 *) uuid_node->data;
+ guint32 box_length = QT_UINT32 (uuid_buffer);
+
+ qtdemux_parse_uuid (qtdemux, uuid_buffer, box_length);
+ }
+
/* if no new base_offset provided for next traf,
* base is end of current traf */
base_offset = running_offset;