| From e09ab95ad39264783bf0e57dfb89165ad9b83802 Mon Sep 17 00:00:00 2001 |
| From: Xabier Rodriguez Calvar <calvaris@igalia.com> |
| Date: Wed, 21 Jun 2017 17:59:21 +0200 |
| Subject: [PATCH] qtdemux: add context for a preferred protection |
| |
| qtdemux selected the first system corresponding to a working GStreamer |
| decryptor. With this change, before selecting that decryptor, qtdemux |
| will check if it has context (a preferred decryptor id) and if not, it |
| will request it. |
| |
| The request includes track-id, available key system ids for the |
| available decryptors and even the events so that the init data is |
| accessible. |
| |
| [eocanha@igalia.com: select the preferred protection system even if not available] |
| |
| Test "4. ClearKeyVideo" in YouTube leanback EME conformance tests 2016 for |
| H.264[1] uses a media file[2] with cenc encryption which embeds 'pssh' boxes |
| with the init data for the Playready and Widevine encryption systems, but not |
| for the ClearKey encryption system (as defined by the EMEv0.1b spec[3] and with |
| the encryption system id defined in [4]). |
| |
| Instead, the ClearKey encryption system is manually selected by the web page |
| code (even if not originally detected by qtdemux) and the proper decryption key |
| is dispatched to the decryptor, which can then decrypt the video successfully. |
| |
| [1] http://yt-dash-mse-test.commondatastorage.googleapis.com/unit-tests/2016.html?test_type=encryptedmedia-test&webm=false |
| [2] http://yt-dash-mse-test.commondatastorage.googleapis.com/unit-tests/media/car_cenc-20120827-86.mp4 |
| [3] https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#simple-decryption-clear-key |
| [4] https://www.w3.org/Bugs/Public/show_bug.cgi?id=24027#c2 |
| |
| https://bugzilla.gnome.org/show_bug.cgi?id=770107 |
| --- |
| gst/isomp4/qtdemux.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++-- |
| gst/isomp4/qtdemux.h | 1 + |
| 2 files changed, 195 insertions(+), 6 deletions(-) |
| |
| diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c |
| index f0f8320e2..f710d2685 100644 |
| --- a/gst/isomp4/qtdemux.c |
| +++ b/gst/isomp4/qtdemux.c |
| @@ -513,6 +513,8 @@ static GstIndex *gst_qtdemux_get_index (GstElement * element); |
| #endif |
| static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element, |
| GstStateChange transition); |
| +static void gst_qtdemux_set_context (GstElement * element, |
| + GstContext * context); |
| static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent); |
| static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| @@ -602,6 +604,7 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass) |
| gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index); |
| gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index); |
| #endif |
| + gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_qtdemux_set_context); |
| |
| gst_tag_register_musicbrainz_tags (); |
| |
| @@ -660,6 +663,7 @@ gst_qtdemux_init (GstQTDemux * qtdemux) |
| qtdemux->cenc_aux_info_sizes = NULL; |
| qtdemux->cenc_aux_sample_count = 0; |
| qtdemux->protection_system_ids = NULL; |
| + qtdemux->preferred_protection_system_id = NULL; |
| g_queue_init (&qtdemux->protection_event_queue); |
| gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); |
| qtdemux->tag_list = gst_tag_list_new_empty (); |
| @@ -2114,6 +2118,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) |
| g_ptr_array_free (qtdemux->protection_system_ids, TRUE); |
| qtdemux->protection_system_ids = NULL; |
| } |
| + if (qtdemux->preferred_protection_system_id) { |
| + g_free (qtdemux->preferred_protection_system_id); |
| + qtdemux->preferred_protection_system_id = NULL; |
| + } |
| } else if (qtdemux->mss_mode) { |
| gst_flow_combiner_reset (qtdemux->flowcombiner); |
| for (n = 0; n < qtdemux->n_streams; n++) |
| @@ -2600,6 +2608,28 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition) |
| } |
| |
| static void |
| +gst_qtdemux_set_context (GstElement * element, GstContext * context) |
| +{ |
| + GstQTDemux *qtdemux = GST_QTDEMUX (element); |
| + |
| + g_return_if_fail (GST_IS_CONTEXT (context)); |
| + |
| + if (gst_context_has_context_type (context, |
| + "drm-preferred-decryption-system-id")) { |
| + const GstStructure *s; |
| + |
| + s = gst_context_get_structure (context); |
| + g_free (qtdemux->preferred_protection_system_id); |
| + qtdemux->preferred_protection_system_id = |
| + g_strdup (gst_structure_get_string (s, "decryption-system-id")); |
| + GST_DEBUG_OBJECT (element, "set preferred decryption system to %s", |
| + qtdemux->preferred_protection_system_id); |
| + } |
| + |
| + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); |
| +} |
| + |
| +static void |
| qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) |
| { |
| /* counts as header data */ |
| @@ -3829,6 +3859,8 @@ qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node) |
| event = gst_event_new_protection (sysid_string, pssh, |
| (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof"); |
| for (i = 0; i < qtdemux->n_streams; ++i) { |
| + GST_TRACE_OBJECT (qtdemux, |
| + "adding protection event for stream %d and system %s", i, sysid_string); |
| g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue, |
| gst_event_ref (event)); |
| } |
| @@ -5538,6 +5570,8 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, |
| GstEvent *event; |
| |
| while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) { |
| + GST_TRACE_OBJECT (stream->pad, "pushing protection event: %" |
| + GST_PTR_FORMAT, event); |
| gst_pad_push_event (stream->pad, event); |
| } |
| |
| @@ -7696,11 +7730,141 @@ qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream) |
| } |
| |
| static gboolean |
| +pad_query (const GValue * item, GValue * value, gpointer user_data) |
| +{ |
| + GstPad *pad = g_value_get_object (item); |
| + GstQuery *query = user_data; |
| + gboolean res; |
| + |
| + res = gst_pad_peer_query (pad, query); |
| + |
| + if (res) { |
| + g_value_set_boolean (value, TRUE); |
| + return FALSE; |
| + } |
| + |
| + GST_INFO_OBJECT (pad, "pad peer query failed"); |
| + return TRUE; |
| +} |
| + |
| +static gboolean |
| +gst_qtdemux_run_query (GstElement * element, GstQuery * query, |
| + GstPadDirection direction) |
| +{ |
| + GstIterator *it; |
| + GstIteratorFoldFunction func = pad_query; |
| + GValue res = { 0, }; |
| + |
| + g_value_init (&res, G_TYPE_BOOLEAN); |
| + g_value_set_boolean (&res, FALSE); |
| + |
| + /* Ask neighbor */ |
| + if (direction == GST_PAD_SRC) |
| + it = gst_element_iterate_src_pads (element); |
| + else |
| + it = gst_element_iterate_sink_pads (element); |
| + |
| + while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC) |
| + gst_iterator_resync (it); |
| + |
| + gst_iterator_free (it); |
| + |
| + return g_value_get_boolean (&res); |
| +} |
| + |
| +static void |
| +gst_qtdemux_request_protection_context (GstQTDemux * qtdemux, |
| + QtDemuxStream * stream) |
| +{ |
| + GstQuery *query; |
| + GstContext *ctxt; |
| + GstElement *element = GST_ELEMENT (qtdemux); |
| + GstStructure *st; |
| + gchar **filtered_sys_ids; |
| + GValue event_list = G_VALUE_INIT; |
| + GList *walk; |
| + |
| + /* 1. Check if we already have the context. */ |
| + if (qtdemux->preferred_protection_system_id != NULL) { |
| + GST_LOG_OBJECT (element, |
| + "already have the protection context, no need to request it again"); |
| + return; |
| + } |
| + |
| + g_ptr_array_add (qtdemux->protection_system_ids, NULL); |
| + filtered_sys_ids = gst_protection_filter_systems_by_available_decryptors ( |
| + (const gchar **) qtdemux->protection_system_ids->pdata); |
| + g_ptr_array_remove_index (qtdemux->protection_system_ids, |
| + qtdemux->protection_system_ids->len - 1); |
| + GST_TRACE_OBJECT (qtdemux, "detected %u protection systems, we have " |
| + "decryptors for %u of them, running context request", |
| + qtdemux->protection_system_ids->len, g_strv_length (filtered_sys_ids)); |
| + |
| + if (stream->protection_scheme_event_queue.length) { |
| + GST_TRACE_OBJECT (qtdemux, "using stream event queue, length %u", |
| + stream->protection_scheme_event_queue.length); |
| + walk = stream->protection_scheme_event_queue.tail; |
| + } else { |
| + GST_TRACE_OBJECT (qtdemux, "using demuxer event queue, length %u", |
| + qtdemux->protection_event_queue.length); |
| + walk = qtdemux->protection_event_queue.tail; |
| + } |
| + |
| + g_value_init (&event_list, GST_TYPE_LIST); |
| + for (; walk; walk = g_list_previous (walk)) { |
| + GValue *event_value = g_new0 (GValue, 1); |
| + g_value_init (event_value, GST_TYPE_EVENT); |
| + g_value_set_boxed (event_value, walk->data); |
| + gst_value_list_append_and_take_value (&event_list, event_value); |
| + } |
| + |
| + /* 2a) Query downstream with GST_QUERY_CONTEXT for the context and |
| + * check if downstream already has a context of the specific type |
| + * 2b) Query upstream as above. |
| + */ |
| + query = gst_query_new_context ("drm-preferred-decryption-system-id"); |
| + st = gst_query_writable_structure (query); |
| + gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id, |
| + "stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids, NULL); |
| + gst_structure_set_value (st, "stream-encryption-events", &event_list); |
| + if (gst_qtdemux_run_query (element, query, GST_PAD_SRC)) { |
| + gst_query_parse_context (query, &ctxt); |
| + GST_INFO_OBJECT (element, "found context (%p) in downstream query", ctxt); |
| + gst_element_set_context (element, ctxt); |
| + } else if (gst_qtdemux_run_query (element, query, GST_PAD_SINK)) { |
| + gst_query_parse_context (query, &ctxt); |
| + GST_INFO_OBJECT (element, "found context (%p) in upstream query", ctxt); |
| + gst_element_set_context (element, ctxt); |
| + } else { |
| + /* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with |
| + * the required context type and afterwards check if a |
| + * usable context was set now as in 1). The message could |
| + * be handled by the parent bins of the element and the |
| + * application. |
| + */ |
| + GstMessage *msg; |
| + |
| + GST_INFO_OBJECT (element, "posting need context message"); |
| + msg = gst_message_new_need_context (GST_OBJECT_CAST (element), |
| + "drm-preferred-decryption-system-id"); |
| + st = (GstStructure *) gst_message_get_structure (msg); |
| + gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id, |
| + "stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids, NULL); |
| + gst_structure_set_value (st, "stream-encryption-events", &event_list); |
| + gst_element_post_message (element, msg); |
| + } |
| + |
| + g_strfreev (filtered_sys_ids); |
| + g_value_unset (&event_list); |
| + gst_query_unref (query); |
| +} |
| + |
| +static gboolean |
| gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux, |
| QtDemuxStream * stream) |
| { |
| GstStructure *s; |
| - const gchar *selected_system; |
| + const gchar *selected_system = NULL; |
| |
| g_return_val_if_fail (qtdemux != NULL, FALSE); |
| g_return_val_if_fail (stream != NULL, FALSE); |
| @@ -7716,17 +7880,41 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux, |
| "cenc protection system information has been found"); |
| return FALSE; |
| } |
| - g_ptr_array_add (qtdemux->protection_system_ids, NULL); |
| - selected_system = gst_protection_select_system ((const gchar **) |
| - qtdemux->protection_system_ids->pdata); |
| - g_ptr_array_remove_index (qtdemux->protection_system_ids, |
| - qtdemux->protection_system_ids->len - 1); |
| + |
| + gst_qtdemux_request_protection_context (qtdemux, stream); |
| + if (qtdemux->preferred_protection_system_id != NULL) { |
| + const gchar *preferred_system_array[] = |
| + { qtdemux->preferred_protection_system_id, NULL }; |
| + |
| + selected_system = gst_protection_select_system (preferred_system_array); |
| + |
| + if (selected_system) { |
| + GST_TRACE_OBJECT (qtdemux, "selected preferred system %s", |
| + qtdemux->preferred_protection_system_id); |
| + } else { |
| + GST_WARNING_OBJECT (qtdemux, "could not select preferred system %s " |
| + "because there is no available decryptor", |
| + qtdemux->preferred_protection_system_id); |
| + } |
| + } |
| + |
| + if (!selected_system) { |
| + g_ptr_array_add (qtdemux->protection_system_ids, NULL); |
| + selected_system = gst_protection_select_system ((const gchar **) |
| + qtdemux->protection_system_ids->pdata); |
| + g_ptr_array_remove_index (qtdemux->protection_system_ids, |
| + qtdemux->protection_system_ids->len - 1); |
| + } |
| + |
| if (!selected_system) { |
| GST_ERROR_OBJECT (qtdemux, "stream is protected, but no " |
| "suitable decryptor element has been found"); |
| return FALSE; |
| } |
| |
| + GST_DEBUG_OBJECT (qtdemux, "selected protection system is %s", |
| + selected_system); |
| + |
| s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0); |
| if (!gst_structure_has_name (s, "application/x-cenc")) { |
| gst_structure_set (s, |
| diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h |
| index ebd725871..b3d64a4e8 100644 |
| --- a/gst/isomp4/qtdemux.h |
| +++ b/gst/isomp4/qtdemux.h |
| @@ -154,6 +154,7 @@ struct _GstQTDemux { |
| guint64 cenc_aux_info_offset; |
| guint8 *cenc_aux_info_sizes; |
| guint32 cenc_aux_sample_count; |
| + gchar *preferred_protection_system_id; |
| |
| |
| /* |
| -- |
| 2.11.0 |
| |