diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index d3653fa225..b568c50225 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) @@ -2447,6 +2452,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) { @@ -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;