mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-18 05:16:05 +00:00
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:
parent
4b7e377d25
commit
fd7964e746
1 changed files with 191 additions and 0 deletions
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue