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
This commit is contained in:
Philippe Normand 2015-07-10 09:44:15 +02:00 committed by Tim-Philipp Müller
parent 4b7e377d25
commit fd7964e746

View file

@ -530,8 +530,13 @@ 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 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, static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
const gchar * id); const gchar * id);
static void qtdemux_gst_structure_free (GstStructure * gststructure);
static void static void
gst_qtdemux_class_init (GstQTDemuxClass * klass) 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 static void
qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length) 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, 0xd0, 0x8a, 0x4f, 0x18, 0x10, 0xf3, 0x4a, 0x82,
0xb6, 0xc8, 0x32, 0xd8, 0xab, 0xa1, 0x83, 0xd3 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; guint offset;
/* counts as header data */ /* counts as header data */
@ -2497,6 +2676,8 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT,
(_("Cannot play stream because it is encrypted with PlayReady DRM.")), (_("Cannot play stream because it is encrypted with PlayReady DRM.")),
(NULL)); (NULL));
} else if (memcmp (buffer + offset, piff_sample_encryption_uuid, 16) == 0) {
qtdemux_parse_piff (qtdemux, buffer, length, offset);
} else { } else {
GST_DEBUG_OBJECT (qtdemux, "Ignoring unknown uuid: %08x-%08x-%08x-%08x", GST_DEBUG_OBJECT (qtdemux, "Ignoring unknown uuid: %08x-%08x-%08x-%08x",
GST_READ_UINT32_LE (buffer + offset), 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) 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;
GNode *uuid_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; GNode *saiz_node, *saio_node, *pssh_node;
GstByteReader saiz_data, saio_data; 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_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun,
&trun_data); &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, /* if no new base_offset provided for next traf,
* base is end of current traf */ * base is end of current traf */
base_offset = running_offset; base_offset = running_offset;