mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-30 11:08:34 +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 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;
|
||||||
|
|
Loading…
Reference in a new issue