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 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;