[EME][GStreamer] Fixed decryptor selection
authorcalvaris@igalia.com <calvaris@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2017 16:54:48 +0000 (16:54 +0000)
committercalvaris@igalia.com <calvaris@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2017 16:54:48 +0000 (16:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167588

Reviewed by Žan Doberšek.

Source/WebCore:

GStreamer selected a decryptor automatically but we need to be
able to tell it which decryptor we want because one file can be
encrypted with more than one system and the one we want to use can
depend on what the user tells us.

Now when the demuxer is about to select the demuxer, it runs a
GstContext query with the events, which are forwarded through the
EME api to the application, which will answer the the
generateKeyRequest and then we can instruct the demuxer to select
the one we tell it, not the one that is selected automatically. If
the demuxer has already a preferred decryptor, the codepath will
be similar to the older one.

Something that is also fixed is the report of the keyneeded event,
which will contain all pssh boxes as requested by the spec.

* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::handleMessage): Intercept
the event and handle it.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp:
(WebCore::MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase):
Initialize the CDM session.
(WebCore::MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase):
Unlock the protection condition just in case and set CDM to null.
(WebCore::extractEventsAndSystemsFromMessage): Access the events
and accepted key systems coming at the message from the demuxer.
(WebCore::MediaPlayerPrivateGStreamerBase::handleSyncMessage):
Handle the protection event not as an element event but as a
context request. It also concatenates all the initdatas in case
there is more than one. The event is sent in the main thread and
the current one blocks until a timeout is hit or the
generateKeyRequest is called.
(WebCore::MediaPlayerPrivateGStreamerBase::handleProtectionEvent):
Check if the event was already handled and if not, run the need
key event.
(WebCore::MediaPlayerPrivateGStreamerBase::receivedGenerateKeyRequest):
Notify the that the query arrived so that we can unlock the
demuxer thread.
(WebCore::keySystemIdToUuid): Translates ids to uuids, meaning
the ones coming from JS to the ones coming from the media engine.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h:
Attributes and methods.
* platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp:
(webkit_media_clear_key_decrypt_class_init):
(webKitMediaClearKeyDecryptorRequestDecryptionKey): Deleted.
* platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h:
Added the ui and uuid.
* platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
(webkitMediaCommonEncryptionDecryptSinkEventHandler): Do not rely
on the subclass, just forward the event to the pipeline inside a message.
* platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h:
Remove unused function signature.
* platform/graphics/gstreamer/mse/AppendPipeline.cpp:
(WebCore::appendPipelineNeedContextMessageCallback): Renamed from
appendPipelineElementMessageCallback.
(WebCore::AppendPipeline::AppendPipeline): Connect to the
need-context message instead of the element one.
(WebCore::AppendPipeline::dispatchPendingDecryptionKey):
Dispatches the drm-cypher message if there is one pending.
(WebCore::AppendPipeline::dispatchDecryptionKey): Dispatches the
drm-cypher.
(WebCore::AppendPipeline::handleNeedContextSyncMessage): Handles
the need-context event instead of the element one.
(WebCore::AppendPipeline::connectDemuxerSrcPadToAppsinkFromAnyThread):
Dispatches the key to the pipeline if there's any pending.
(WebCore::appendPipelineElementMessageCallback): Deleted.
(WebCore::AppendPipeline::handleElementMessage): Deleted.
* platform/graphics/gstreamer/mse/AppendPipeline.h: New methods
and attributes.
* platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
(WebCore::MediaPlayerPrivateGStreamerMSE::dispatchDecryptionKey):
Use the dispatchDecryptionKey method of the AppendPipeline.

Tools:

This patches are pending at https://bugzilla.gnome.org/show_bug.cgi?id=770107

* gtk/jhbuild.modules: Added patches to gstreamer and
gst-plugins-good packages.
* gtk/patches/gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch: Added.
* gtk/patches/gstreamer-0001-protection-added-function-to-filter-system-ids.patch: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@211562 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h
Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h
Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h
Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp
Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.h
Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp
Tools/ChangeLog
Tools/gtk/jhbuild.modules
Tools/gtk/patches/gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch [new file with mode: 0644]
Tools/gtk/patches/gstreamer-0001-protection-added-function-to-filter-system-ids.patch [new file with mode: 0644]

index 4aacadd..28d9794 100644 (file)
@@ -1,3 +1,83 @@
+2017-02-02  Xabier Rodriguez Calvar  <calvaris@igalia.com>
+
+        [EME][GStreamer] Fixed decryptor selection
+        https://bugs.webkit.org/show_bug.cgi?id=167588
+
+        Reviewed by Žan Doberšek.
+
+        GStreamer selected a decryptor automatically but we need to be
+        able to tell it which decryptor we want because one file can be
+        encrypted with more than one system and the one we want to use can
+        depend on what the user tells us.
+
+        Now when the demuxer is about to select the demuxer, it runs a
+        GstContext query with the events, which are forwarded through the
+        EME api to the application, which will answer the the
+        generateKeyRequest and then we can instruct the demuxer to select
+        the one we tell it, not the one that is selected automatically. If
+        the demuxer has already a preferred decryptor, the codepath will
+        be similar to the older one.
+
+        Something that is also fixed is the report of the keyneeded event,
+        which will contain all pssh boxes as requested by the spec.
+
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
+        (WebCore::MediaPlayerPrivateGStreamer::handleMessage): Intercept
+        the event and handle it.
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.cpp:
+        (WebCore::MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase):
+        Initialize the CDM session.
+        (WebCore::MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase):
+        Unlock the protection condition just in case and set CDM to null.
+        (WebCore::extractEventsAndSystemsFromMessage): Access the events
+        and accepted key systems coming at the message from the demuxer.
+        (WebCore::MediaPlayerPrivateGStreamerBase::handleSyncMessage):
+        Handle the protection event not as an element event but as a
+        context request. It also concatenates all the initdatas in case
+        there is more than one. The event is sent in the main thread and
+        the current one blocks until a timeout is hit or the
+        generateKeyRequest is called.
+        (WebCore::MediaPlayerPrivateGStreamerBase::handleProtectionEvent):
+        Check if the event was already handled and if not, run the need
+        key event.
+        (WebCore::MediaPlayerPrivateGStreamerBase::receivedGenerateKeyRequest):
+        Notify the that the query arrived so that we can unlock the
+        demuxer thread.
+        (WebCore::keySystemIdToUuid): Translates ids to uuids, meaning
+        the ones coming from JS to the ones coming from the media engine.
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamerBase.h:
+        Attributes and methods.
+        * platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp:
+        (webkit_media_clear_key_decrypt_class_init):
+        (webKitMediaClearKeyDecryptorRequestDecryptionKey): Deleted.
+        * platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h:
+        Added the ui and uuid.
+        * platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
+        (webkitMediaCommonEncryptionDecryptSinkEventHandler): Do not rely
+        on the subclass, just forward the event to the pipeline inside a message.
+        * platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h:
+        Remove unused function signature.
+        * platform/graphics/gstreamer/mse/AppendPipeline.cpp:
+        (WebCore::appendPipelineNeedContextMessageCallback): Renamed from
+        appendPipelineElementMessageCallback.
+        (WebCore::AppendPipeline::AppendPipeline): Connect to the
+        need-context message instead of the element one.
+        (WebCore::AppendPipeline::dispatchPendingDecryptionKey):
+        Dispatches the drm-cypher message if there is one pending.
+        (WebCore::AppendPipeline::dispatchDecryptionKey): Dispatches the
+        drm-cypher.
+        (WebCore::AppendPipeline::handleNeedContextSyncMessage): Handles
+        the need-context event instead of the element one.
+        (WebCore::AppendPipeline::connectDemuxerSrcPadToAppsinkFromAnyThread):
+        Dispatches the key to the pipeline if there's any pending.
+        (WebCore::appendPipelineElementMessageCallback): Deleted.
+        (WebCore::AppendPipeline::handleElementMessage): Deleted.
+        * platform/graphics/gstreamer/mse/AppendPipeline.h: New methods
+        and attributes.
+        * platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp:
+        (WebCore::MediaPlayerPrivateGStreamerMSE::dispatchDecryptionKey):
+        Use the dispatchDecryptionKey method of the AppendPipeline.
+
 2017-02-02  Joseph Pecoraro  <pecoraro@apple.com>
 
         Removed unused EventHandler members
index f03f24f..6502cfe 100644 (file)
@@ -1048,6 +1048,14 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
                 m_player->client().requestInstallMissingPlugins(String::fromUTF8(detail.get()), String::fromUTF8(description.get()), *m_missingPluginsCallback);
             }
         }
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+        else if (gst_structure_has_name(structure, "drm-key-needed")) {
+            GST_DEBUG("drm-key-needed message from %s", GST_MESSAGE_SRC_NAME(message));
+            GRefPtr<GstEvent> event;
+            gst_structure_get(structure, "event", GST_TYPE_EVENT, &event.outPtr(), nullptr);
+            handleProtectionEvent(event.get());
+        }
+#endif
 #if ENABLE(VIDEO_TRACK) && USE(GSTREAMER_MPEGTS)
         else {
             GstMpegtsSection* section = gst_message_parse_mpegts_section(message);
index e16f583..ef098a3 100644 (file)
@@ -38,6 +38,8 @@
 #include "VideoSinkGStreamer.h"
 #include "WebKitWebSourceGStreamer.h"
 #include <wtf/glib/GMutexLocker.h>
+#include <wtf/glib/GUniquePtr.h>
+#include <wtf/text/AtomicString.h>
 #include <wtf/text/CString.h>
 #include <wtf/MathExtras.h>
 
@@ -91,6 +93,7 @@
 #endif
 
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
+#include "SharedBuffer.h"
 #include "WebKitClearKeyDecryptorGStreamer.h"
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
 #include "UUID.h"
@@ -107,6 +110,10 @@ using namespace std;
 
 namespace WebCore {
 
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+static AtomicString keySystemIdToUuid(const AtomicString&);
+#endif
+
 void registerWebKitGStreamerElements()
 {
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
@@ -201,6 +208,9 @@ MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase(MediaPlayer* pl
     , m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamerBase::repaint)
 #endif
     , m_usingFallbackVideoSink(false)
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    , m_cdmSession(0)
+#endif
 {
     g_mutex_init(&m_sampleMutex);
 #if USE(COORDINATED_GRAPHICS_THREADED)
@@ -210,6 +220,9 @@ MediaPlayerPrivateGStreamerBase::MediaPlayerPrivateGStreamerBase(MediaPlayer* pl
 
 MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase()
 {
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    m_protectionCondition.notifyOne();
+#endif
     m_notifier.cancelPendingNotifications();
 
     if (m_videoSink) {
@@ -233,6 +246,10 @@ MediaPlayerPrivateGStreamerBase::~MediaPlayerPrivateGStreamerBase()
     if (client())
         client()->platformLayerWillBeDestroyed();
 #endif
+
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    m_cdmSession = nullptr;
+#endif
 }
 
 void MediaPlayerPrivateGStreamerBase::setPipeline(GstElement* pipeline)
@@ -240,31 +257,66 @@ void MediaPlayerPrivateGStreamerBase::setPipeline(GstElement* pipeline)
     m_pipeline = pipeline;
 }
 
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+static std::pair<Vector<GRefPtr<GstEvent>>, Vector<String>> extractEventsAndSystemsFromMessage(GstMessage* message)
+{
+    const GstStructure* structure = gst_message_get_structure(message);
+
+    const GValue* streamEncryptionAllowedSystemsValue = gst_structure_get_value(structure, "stream-encryption-systems");
+    ASSERT(streamEncryptionAllowedSystemsValue && G_VALUE_HOLDS(streamEncryptionAllowedSystemsValue, G_TYPE_STRV));
+    const char** streamEncryptionAllowedSystems = reinterpret_cast<const char**>(g_value_get_boxed(streamEncryptionAllowedSystemsValue));
+    ASSERT(streamEncryptionAllowedSystems);
+    Vector<String> streamEncryptionAllowedSystemsVector;
+    unsigned i;
+    for (i = 0; !streamEncryptionAllowedSystems[i]; ++i)
+        streamEncryptionAllowedSystemsVector.append(streamEncryptionAllowedSystems[i]);
+
+    const GValue* streamEncryptionEventsList = gst_structure_get_value(structure, "stream-encryption-events");
+    ASSERT(streamEncryptionEventsList && GST_VALUE_HOLDS_LIST(streamEncryptionEventsList));
+    unsigned streamEncryptionEventsListSize = gst_value_list_get_size(streamEncryptionEventsList);
+    Vector<GRefPtr<GstEvent>> streamEncryptionEventsVector;
+    for (i = 0; i < streamEncryptionEventsListSize; ++i)
+        streamEncryptionEventsVector.append(GRefPtr<GstEvent>(static_cast<GstEvent*>(g_value_get_boxed(gst_value_list_get_value(streamEncryptionEventsList, i)))));
+
+    return std::make_pair(streamEncryptionEventsVector, streamEncryptionAllowedSystemsVector);
+}
+#endif
+
 bool MediaPlayerPrivateGStreamerBase::handleSyncMessage(GstMessage* message)
 {
     UNUSED_PARAM(message);
-#if USE(GSTREAMER_GL)
     if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_NEED_CONTEXT)
         return false;
 
     const gchar* contextType;
     gst_message_parse_context_type(message, &contextType);
 
+#if USE(GSTREAMER_GL)
     GRefPtr<GstContext> elementContext = adoptGRef(requestGLContext(contextType, this));
-    if (!elementContext)
-        return false;
-
-    gst_element_set_context(GST_ELEMENT(message->src), elementContext.get());
-    return true;
-#else
-    UNUSED_PARAM(message);
+    if (elementContext) {
+        gst_element_set_context(GST_ELEMENT(message->src), elementContext.get());
+        return true;
+    }
 #endif // USE(GSTREAMER_GL)
 
-#if ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)
-    if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ELEMENT) {
-        const GstStructure* structure = gst_message_get_structure(message);
-        if (gst_structure_has_name(structure, "drm-key-needed")) {
-            GST_DEBUG("handling drm-key-needed message");
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    if (!g_strcmp0(contextType, "drm-preferred-decryption-system-id")) {
+        if (isMainThread()) {
+            GST_ERROR("can't handle drm-preferred-decryption-system-id need context message in the main thread");
+            ASSERT_NOT_REACHED();
+            return false;
+        }
+        GST_DEBUG("handling drm-preferred-decryption-system-id need context message");
+        std::pair<Vector<GRefPtr<GstEvent>>, Vector<String>> streamEncryptionInformation = extractEventsAndSystemsFromMessage(message);
+        GST_TRACE("found %" G_GSIZE_FORMAT " protection events", streamEncryptionInformation.first.size());
+        Vector<uint8_t> concatenatedInitDataChunks;
+        unsigned concatenatedInitDataChunksNumber = 0;
+        String eventKeySystemIdString;
+        for (auto& event : streamEncryptionInformation.first) {
+            GST_TRACE("handling protection event %u", GST_EVENT_SEQNUM(event.get()));
+            const char* eventKeySystemId = nullptr;
+            GstBuffer* data = nullptr;
+            gst_event_parse_protection(event.get(), &eventKeySystemId, &data, nullptr);
 
             // Here we receive the DRM init data from the pipeline: we will emit
             // the needkey event with that data and the browser might create a
@@ -273,24 +325,55 @@ bool MediaPlayerPrivateGStreamerBase::handleSyncMessage(GstMessage* message)
             // DRM challenge to the browser and wait for an update. If on the
             // contrary no session was created we won't wait and let the pipeline
             // error out by itself.
-            GRefPtr<GstBuffer> data;
-            GUniqueOutPtr<gchar> keySystemId;
-            gboolean valid = gst_structure_get(structure, "data", GST_TYPE_BUFFER, &data.outPtr(),
-                "key-system-id", G_TYPE_STRING, &keySystemId.outPtr(), nullptr);
             GstMapInfo mapInfo;
-            if (UNLIKELY(!valid || !gst_buffer_map(data.get(), &mapInfo, GST_MAP_READ)))
-                return false;
+            if (!gst_buffer_map(data, &mapInfo, GST_MAP_READ)) {
+                GST_WARNING("cannot map %s protection data", eventKeySystemId);
+                break;
+            }
+
+            GST_TRACE("appending init data for %s of size %" G_GSIZE_FORMAT, eventKeySystemId, mapInfo.size);
+            GST_MEMDUMP("init data", reinterpret_cast<const unsigned char *>(mapInfo.data), mapInfo.size);
+            concatenatedInitDataChunks.append(mapInfo.data, mapInfo.size);
+            ++concatenatedInitDataChunksNumber;
+            eventKeySystemIdString = eventKeySystemId;
+            if (streamEncryptionInformation.second.contains(eventKeySystemId)) {
+                GST_TRACE("considering init data handled for %s", eventKeySystemId);
+                m_handledProtectionEvents.add(GST_EVENT_SEQNUM(event.get()));
+            }
+            gst_buffer_unmap(data, &mapInfo);
+        }
+
+        if (!concatenatedInitDataChunksNumber)
+            return false;
+
+        if (concatenatedInitDataChunksNumber > 1)
+            eventKeySystemIdString = emptyString();
+
+        RunLoop::main().dispatch([this, eventKeySystemIdString, initData = WTFMove(concatenatedInitDataChunks)] {
+            GST_DEBUG("scheduling keyNeeded event for %s with concatenated init datas size of %" G_GSIZE_FORMAT, eventKeySystemIdString.utf8().data(), initData.size());
+            GST_MEMDUMP("init datas", initData.data(), initData.size());
 
-#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
-            GST_DEBUG("scheduling keyNeeded event");
             // FIXME: Provide a somehow valid sessionId.
-            RefPtr<Uint8Array> initData = Uint8Array::create(reinterpret_cast<const unsigned char *>(mapInfo.data), mapInfo.size);
-            needKey(initData);
-#endif
+            RefPtr<Uint8Array> initDataArray = Uint8Array::create(initData.data(), initData.size());
+            needKey(initDataArray);
+        });
+
+        GST_INFO("waiting for a key request to arrive");
+        LockHolder lock(m_protectionMutex);
+        m_protectionCondition.waitFor(m_protectionMutex, Seconds(4), [this] {
+            return !this->m_lastGenerateKeyRequestKeySystemUuid.isEmpty();
+        });
+        if (!m_lastGenerateKeyRequestKeySystemUuid.isEmpty()) {
+            GST_INFO("got a key request, continuing with %s on %s", m_lastGenerateKeyRequestKeySystemUuid.utf8().data(), GST_MESSAGE_SRC_NAME(message));
+
+            GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-preferred-decryption-system-id", FALSE));
+            GstStructure* contextStructure = gst_context_writable_structure(context.get());
+            gst_structure_set(contextStructure, "decryption-system-id", G_TYPE_STRING, m_lastGenerateKeyRequestKeySystemUuid.utf8().data(), nullptr);
+            gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context.get());
+        } else
+            GST_WARNING("did not get a proper key request");
 
-            gst_buffer_unmap(data.get(), &mapInfo);
-            return true;
-        }
+        return true;
     }
 #endif // ENABLE(LEGACY_ENCRYPTED_MEDIA)
 
@@ -1139,6 +1222,46 @@ void MediaPlayerPrivateGStreamerBase::keyAdded()
 {
 }
 
+void MediaPlayerPrivateGStreamerBase::handleProtectionEvent(GstEvent* event)
+{
+    if (m_handledProtectionEvents.contains(GST_EVENT_SEQNUM(event))) {
+        GST_TRACE("event %u already handled", GST_EVENT_SEQNUM(event));
+        m_handledProtectionEvents.remove(GST_EVENT_SEQNUM(event));
+        return;
+    }
+
+    const gchar* eventKeySystemId = nullptr;
+    GstBuffer* data = nullptr;
+    gst_event_parse_protection(event, &eventKeySystemId, &data, nullptr);
+
+    GstMapInfo mapInfo;
+    if (!gst_buffer_map(data, &mapInfo, GST_MAP_READ)) {
+        GST_WARNING("cannot map %s protection data", eventKeySystemId);
+        return;
+    }
+
+    GST_DEBUG("scheduling keyNeeded event for %s with init data size of %" G_GSIZE_FORMAT, eventKeySystemId, mapInfo.size);
+    GST_MEMDUMP("init datas", mapInfo.data, mapInfo.size);
+    RefPtr<Uint8Array> initDataArray = Uint8Array::create(mapInfo.data, mapInfo.size);
+    needKey(initDataArray);
+    gst_buffer_unmap(data, &mapInfo);
+}
+
+void MediaPlayerPrivateGStreamerBase::receivedGenerateKeyRequest(const String& keySystem)
+{
+    GST_DEBUG("received generate key request for %s", keySystem.utf8().data());
+    m_lastGenerateKeyRequestKeySystemUuid = keySystemIdToUuid(keySystem);
+    m_protectionCondition.notifyOne();
+}
+
+static AtomicString keySystemIdToUuid(const AtomicString& id)
+{
+    if (equalIgnoringASCIICase(id, CLEAR_KEY_PROTECTION_SYSTEM_ID))
+        return AtomicString(CLEAR_KEY_PROTECTION_SYSTEM_UUID);
+
+    return { };
+}
+
 std::unique_ptr<CDMSession> MediaPlayerPrivateGStreamerBase::createSession(const String& keySystem, CDMSessionClient*)
 {
     GST_INFO("Requested CDMSession for KeySystem %s: Returning null.", keySystem.utf8().data());
index 44c3bf0..d9f635f 100644 (file)
@@ -123,9 +123,11 @@ public:
 
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
     void needKey(RefPtr<Uint8Array>);
-    void setCDMSession(CDMSession*);
-    void keyAdded();
+    void setCDMSession(CDMSession*) override;
+    void keyAdded() override;
     virtual void dispatchDecryptionKey(GstBuffer*);
+    void handleProtectionEvent(GstEvent*);
+    void receivedGenerateKeyRequest(const String&);
 #endif
 
     static bool supportsKeySystem(const String& keySystem, const String& mimeType);
@@ -139,6 +141,8 @@ public:
     void setVideoSourceOrientation(const ImageOrientation&);
     GstElement* pipeline() const { return m_pipeline.get(); }
 
+    virtual bool handleSyncMessage(GstMessage*);
+
 protected:
     MediaPlayerPrivateGStreamerBase(MediaPlayer*);
     virtual GstElement* createVideoSink();
@@ -164,8 +168,6 @@ protected:
 
     void setPipeline(GstElement*);
 
-    virtual bool handleSyncMessage(GstMessage*);
-
     void triggerRepaint(GstSample*);
     void repaint();
 
@@ -229,8 +231,12 @@ protected:
 
     ImageOrientation m_videoSourceOrientation;
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
-    std::unique_ptr<CDMSession> createSession(const String&, CDMSessionClient*);
+    std::unique_ptr<CDMSession> createSession(const String&, CDMSessionClient*) override;
     CDMSession* m_cdmSession;
+    Lock m_protectionMutex;
+    Condition m_protectionCondition;
+    String m_lastGenerateKeyRequestKeySystemUuid;
+    HashSet<uint32_t> m_handledProtectionEvents;
 #endif
 #if USE(GSTREAMER_GL)
     std::unique_ptr<VideoTextureCopierGStreamer> m_videoTextureCopier;
index f8ec5ce..dc69708 100644 (file)
@@ -38,7 +38,6 @@ struct _WebKitMediaClearKeyDecryptPrivate {
 };
 
 static void webKitMediaClearKeyDecryptorFinalize(GObject*);
-static void webKitMediaClearKeyDecryptorRequestDecryptionKey(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer*);
 static gboolean webKitMediaClearKeyDecryptorHandleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, GstEvent*);
 static gboolean webKitMediaClearKeyDecryptorSetupCipher(WebKitMediaCommonEncryptionDecrypt*);
 static gboolean webKitMediaClearKeyDecryptorDecrypt(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* iv, GstBuffer* sample, unsigned subSamplesCount, GstBuffer* subSamples);
@@ -47,13 +46,11 @@ static void webKitMediaClearKeyDecryptorReleaseCipher(WebKitMediaCommonEncryptio
 GST_DEBUG_CATEGORY_STATIC(webkit_media_clear_key_decrypt_debug_category);
 #define GST_CAT_DEFAULT webkit_media_clear_key_decrypt_debug_category
 
-#define CLEAR_KEY_PROTECTION_SYSTEM_ID "58147ec8-0423-4659-92e6-f52c5ce8c3cc"
-
 static GstStaticPadTemplate sinkTemplate = GST_STATIC_PAD_TEMPLATE("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS("application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_ID "; "
-    "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_ID));
+    GST_STATIC_CAPS("application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_UUID "; "
+    "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_UUID));
 
 static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src",
     GST_PAD_SRC,
@@ -82,8 +79,7 @@ static void webkit_media_clear_key_decrypt_class_init(WebKitMediaClearKeyDecrypt
         "webkitclearkey", 0, "ClearKey decryptor");
 
     WebKitMediaCommonEncryptionDecryptClass* cencClass = WEBKIT_MEDIA_CENC_DECRYPT_CLASS(klass);
-    cencClass->protectionSystemId = CLEAR_KEY_PROTECTION_SYSTEM_ID;
-    cencClass->requestDecryptionKey = GST_DEBUG_FUNCPTR(webKitMediaClearKeyDecryptorRequestDecryptionKey);
+    cencClass->protectionSystemId = CLEAR_KEY_PROTECTION_SYSTEM_UUID;
     cencClass->handleKeyResponse = GST_DEBUG_FUNCPTR(webKitMediaClearKeyDecryptorHandleKeyResponse);
     cencClass->setupCipher = GST_DEBUG_FUNCPTR(webKitMediaClearKeyDecryptorSetupCipher);
     cencClass->decrypt = GST_DEBUG_FUNCPTR(webKitMediaClearKeyDecryptorDecrypt);
@@ -119,14 +115,6 @@ static void webKitMediaClearKeyDecryptorFinalize(GObject* object)
     GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
 }
 
-static void webKitMediaClearKeyDecryptorRequestDecryptionKey(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* initDataBuffer)
-{
-    gst_element_post_message(GST_ELEMENT(self),
-        gst_message_new_element(GST_OBJECT(self),
-            gst_structure_new("drm-key-needed", "data", GST_TYPE_BUFFER, initDataBuffer,
-                "key-system-id", G_TYPE_STRING, "org.w3.clearkey", nullptr)));
-}
-
 static gboolean webKitMediaClearKeyDecryptorHandleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, GstEvent* event)
 {
     WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
index 7e9790c..30cfa29 100644 (file)
@@ -25,6 +25,9 @@
 
 #include "WebKitCommonEncryptionDecryptorGStreamer.h"
 
+#define CLEAR_KEY_PROTECTION_SYSTEM_UUID "58147ec8-0423-4659-92e6-f52c5ce8c3cc"
+#define CLEAR_KEY_PROTECTION_SYSTEM_ID "org.w3.clearkey"
+
 G_BEGIN_DECLS
 
 #define WEBKIT_TYPE_MEDIA_CK_DECRYPT          (webkit_media_clear_key_decrypt_get_type())
index da351c0..3898080 100644 (file)
@@ -292,30 +292,19 @@ static gboolean webkitMediaCommonEncryptionDecryptSinkEventHandler(GstBaseTransf
 
     switch (GST_EVENT_TYPE(event)) {
     case GST_EVENT_PROTECTION: {
-        const char* systemId;
-        const char* origin;
-        GstBuffer* initDataBuffer;
-
-        GST_DEBUG_OBJECT(self, "received protection event");
-        gst_event_parse_protection(event, &systemId, &initDataBuffer, &origin);
-        GST_DEBUG_OBJECT(self, "systemId: %s", systemId);
-
-        if (!g_str_equal(systemId, klass->protectionSystemId)) {
-            gst_event_unref(event);
-            result = TRUE;
-            break;
-        }
+        const char* systemId = nullptr;
+
+        gst_event_parse_protection(event, &systemId, nullptr, nullptr);
+        GST_TRACE_OBJECT(self, "received protection event for %s", systemId);
 
-        // Keep the event ref around so that the parsed event data
-        // remains valid until the drm-key-need message has been sent.
-        priv->protectionEvent = adoptGRef(event);
-        RunLoop::main().dispatch([self, initDataBuffer] {
-            WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
-            if (klass) {
-                klass->requestDecryptionKey(self, initDataBuffer);
-                self->priv->protectionEvent = nullptr;
-            }});
+        if (!g_strcmp0(systemId, klass->protectionSystemId)) {
+            GST_DEBUG_OBJECT(self, "sending protection event to the pipeline");
+            gst_element_post_message(GST_ELEMENT(self),
+                gst_message_new_element(GST_OBJECT(self),
+                    gst_structure_new("drm-key-needed", "event", GST_TYPE_EVENT, event, nullptr)));
+        }
 
+        gst_event_unref(event);
         result = TRUE;
         break;
     }
index 0255e87..dcae827 100644 (file)
@@ -53,7 +53,6 @@ struct _WebKitMediaCommonEncryptionDecryptClass {
     GstBaseTransformClass parentClass;
 
     const char* protectionSystemId;
-    void (*requestDecryptionKey)(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* initData);
     gboolean (*handleKeyResponse)(WebKitMediaCommonEncryptionDecrypt*, GstEvent* event);
     gboolean (*setupCipher)(WebKitMediaCommonEncryptionDecrypt*);
     gboolean (*decrypt)(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSamplesCount, GstBuffer* subSamplesBuffer);
index 4a84655..ffaf9b2 100644 (file)
@@ -79,9 +79,10 @@ static GstPadProbeReturn appendPipelineDemuxerBlackHolePadProbe(GstPad*, GstPadP
 static GstFlowReturn appendPipelineAppsinkNewSample(GstElement*, AppendPipeline*);
 static void appendPipelineAppsinkEOS(GstElement*, AppendPipeline*);
 
-static void appendPipelineElementMessageCallback(GstBus*, GstMessage* message, AppendPipeline* appendPipeline)
+static void appendPipelineNeedContextMessageCallback(GstBus*, GstMessage* message, AppendPipeline* appendPipeline)
 {
-    appendPipeline->handleElementMessage(message);
+    GST_TRACE("received callback");
+    appendPipeline->handleNeedContextSyncMessage(message);
 }
 
 static void appendPipelineApplicationMessageCallback(GstBus*, GstMessage* message, AppendPipeline* appendPipeline)
@@ -113,7 +114,7 @@ AppendPipeline::AppendPipeline(Ref<MediaSourceClientGStreamerMSE> mediaSourceCli
     gst_bus_add_signal_watch(m_bus.get());
     gst_bus_enable_sync_message_emission(m_bus.get());
 
-    g_signal_connect(m_bus.get(), "sync-message::element", G_CALLBACK(appendPipelineElementMessageCallback), this);
+    g_signal_connect(m_bus.get(), "sync-message::need-context", G_CALLBACK(appendPipelineNeedContextMessageCallback), this);
     g_signal_connect(m_bus.get(), "message::application", G_CALLBACK(appendPipelineApplicationMessageCallback), this);
 
     // We assign the created instances here instead of adoptRef() because gst_bin_add_many()
@@ -213,6 +214,33 @@ AppendPipeline::~AppendPipeline()
     m_demuxerSrcPadCaps = nullptr;
 };
 
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+void AppendPipeline::dispatchPendingDecryptionKey()
+{
+    ASSERT(m_decryptor);
+    ASSERT(m_pendingKey);
+    ASSERT(m_appendState == KeyNegotiation);
+    GST_TRACE("dispatching key to append pipeline %p", this);
+    gst_element_send_event(m_pipeline.get(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB,
+        gst_structure_new("drm-cipher", "key", GST_TYPE_BUFFER, m_pendingKey.get(), nullptr)));
+    m_pendingKey.clear();
+    setAppendState(AppendState::Ongoing);
+}
+
+void AppendPipeline::dispatchDecryptionKey(GstBuffer* buffer)
+{
+    if (m_appendState == AppendState::KeyNegotiation) {
+        GST_TRACE("append pipeline %p in key negotiation", this);
+        m_pendingKey = buffer;
+        if (m_decryptor)
+            dispatchPendingDecryptionKey();
+        else
+            GST_TRACE("no decryptor yet, waiting for it");
+    } else
+        GST_TRACE("append pipeline %p not in key negotiation", this);
+}
+#endif
+
 void AppendPipeline::clearPlayerPrivate()
 {
     ASSERT(WTF::isMainThread());
@@ -240,12 +268,12 @@ void AppendPipeline::clearPlayerPrivate()
         gst_element_set_state(m_pipeline.get(), GST_STATE_NULL);
 }
 
-void AppendPipeline::handleElementMessage(GstMessage* message)
+void AppendPipeline::handleNeedContextSyncMessage(GstMessage* message)
 {
-    ASSERT(WTF::isMainThread());
-
-    const GstStructure* structure = gst_message_get_structure(message);
-    if (gst_structure_has_name(structure, "drm-key-needed"))
+    const gchar* contextType = nullptr;
+    gst_message_parse_context_type(message, &contextType);
+    GST_TRACE("context type: %s", contextType);
+    if (!g_strcmp0(contextType, "drm-preferred-decryption-system-id"))
         setAppendState(AppendPipeline::AppendState::KeyNegotiation);
 
     // MediaPlayerPrivateGStreamerBase will take care of setting up encryption.
@@ -938,6 +966,11 @@ void AppendPipeline::connectDemuxerSrcPadToAppsinkFromAnyThread(GstPad* demuxerS
 
             gst_element_sync_state_with_parent(m_appsink.get());
             gst_element_sync_state_with_parent(m_decryptor.get());
+
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+            if (m_pendingKey)
+                dispatchPendingDecryptionKey();
+#endif
         } else {
 #endif
             gst_pad_link(demuxerSrcPad, appsinkSinkPad.get());
index a72be97..9e14079 100644 (file)
@@ -47,7 +47,7 @@ public:
     AppendPipeline(Ref<MediaSourceClientGStreamerMSE>, Ref<SourceBufferPrivateGStreamer>, MediaPlayerPrivateGStreamerMSE&);
     virtual ~AppendPipeline();
 
-    void handleElementMessage(GstMessage*);
+    void handleNeedContextSyncMessage(GstMessage*);
     void handleApplicationMessage(GstMessage*);
 
     gint id();
@@ -56,6 +56,9 @@ public:
 
     GstFlowReturn handleNewAppsinkSample(GstElement*);
     GstFlowReturn pushNewBuffer(GstBuffer*);
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    void dispatchDecryptionKey(GstBuffer*);
+#endif
 
     // Takes ownership of caps.
     void parseDemuxerSrcPadCaps(GstCaps*);
@@ -91,6 +94,9 @@ private:
     void handleAppsrcNeedDataReceived();
     void removeAppsrcDataLeavingProbe();
     void setAppsrcDataLeavingProbe();
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    void dispatchPendingDecryptionKey();
+#endif
 
 private:
     Ref<MediaSourceClientGStreamerMSE> m_mediaSourceClient;
@@ -146,6 +152,9 @@ private:
     RefPtr<WebCore::TrackPrivateBase> m_track;
 
     GRefPtr<GstBuffer> m_pendingBuffer;
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+    GRefPtr<GstBuffer> m_pendingKey;
+#endif
 };
 
 } // namespace WebCore.
index 97f737e..5f4c123 100644 (file)
@@ -804,15 +804,8 @@ MediaPlayer::SupportsType MediaPlayerPrivateGStreamerMSE::supportsType(const Med
 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
 void MediaPlayerPrivateGStreamerMSE::dispatchDecryptionKey(GstBuffer* buffer)
 {
-    for (auto iterator : m_appendPipelinesMap) {
-        if (iterator.value->appendState() == AppendPipeline::AppendState::KeyNegotiation) {
-            GST_TRACE("append pipeline %p in key negotiation, setting key", iterator.value.get());
-            gst_element_send_event(iterator.value->pipeline(), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB,
-                gst_structure_new("drm-cipher", "key", GST_TYPE_BUFFER, buffer, nullptr)));
-            iterator.value->setAppendState(AppendPipeline::AppendState::Ongoing);
-        } else
-            GST_TRACE("append pipeline %p not in key negotiation", iterator.value.get());
-    }
+    for (auto it : m_appendPipelinesMap)
+        it.value->dispatchDecryptionKey(buffer);
 }
 #endif
 
index 8f61791..bb0c2f5 100644 (file)
@@ -1,3 +1,17 @@
+2017-02-02  Xabier Rodriguez Calvar  <calvaris@igalia.com>
+
+        [EME][GStreamer] Fixed decryptor selection
+        https://bugs.webkit.org/show_bug.cgi?id=167588
+
+        Reviewed by Žan Doberšek.
+
+        This patches are pending at https://bugzilla.gnome.org/show_bug.cgi?id=770107
+
+        * gtk/jhbuild.modules: Added patches to gstreamer and
+        gst-plugins-good packages.
+        * gtk/patches/gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch: Added.
+        * gtk/patches/gstreamer-0001-protection-added-function-to-filter-system-ids.patch: Added.
+
 2017-02-01  Dewei Zhu  <dewei_zhu@apple.com>
 
         Make run-benchmark script supports 'config' key in test plan.
index 4aa40c0..12ac474 100644 (file)
     <branch module="gstreamer/gstreamer-${version}.tar.xz" version="1.8.0"
             repo="gstreamer"
             hash="sha256:947a314a212b5d94985d89b43440dbe66b696e12bbdf9a2f78967b98d74abedc"
-            md5sum="6846d7289ec323c38c49b818171e955a"/>
+            md5sum="6846d7289ec323c38c49b818171e955a">
+      <patch file="gstreamer-0001-protection-added-function-to-filter-system-ids.patch" strip="1"/>
+    </branch>
   </autotools>
 
   <autotools id="gst-plugins-base"
       <patch file="gst-plugins-good-0001-rtpbin-pipeline-gets-an-EOS-when-any-rtpsources-byes.patch" strip="1"/>
       <patch file="gst-plugins-good-0002-rtpbin-avoid-generating-errors-when-rtcp-messages-ar.patch" strip="1"/>
       <patch file="gst-plugins-good-0003-rtpbin-receive-bundle-support.patch" strip="1"/>
+      <patch file="gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch" strip="1"/>
     </branch>
   </autotools>
 
diff --git a/Tools/gtk/patches/gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch b/Tools/gtk/patches/gst-plugins-good-0004-qtdemux-add-context-for-a-preferred-protection.patch
new file mode 100644 (file)
index 0000000..488d52c
--- /dev/null
@@ -0,0 +1,319 @@
+From 5562a03c58a06339df2bf0a55cb39be7321094dd Mon Sep 17 00:00:00 2001
+From: Xabier Rodriguez Calvar <calvaris@igalia.com>
+Date: Fri, 16 Sep 2016 16:08:18 +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 event the events so that the init data is
+accessible.
+---
+ gst/isomp4/qtdemux.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++--
+ gst/isomp4/qtdemux.h |   1 +
+ 2 files changed, 204 insertions(+), 6 deletions(-)
+
+diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c
+index 782ad2f..f56872a 100644
+--- a/gst/isomp4/qtdemux.c
++++ b/gst/isomp4/qtdemux.c
+@@ -477,6 +477,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);
+@@ -569,6 +571,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 ();
+@@ -628,6 +631,7 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
+   qtdemux->cenc_aux_sample_count = 0;
+   qtdemux->protection_system_ids = NULL;
+   qtdemux->always_honor_tfdt = FALSE;
++  qtdemux->preferred_protection_system_id = NULL;
+   g_queue_init (&qtdemux->protection_event_queue);
+   gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
+   qtdemux->flowcombiner = gst_flow_combiner_new ();
+@@ -2023,6 +2027,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
+     g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
+         NULL);
+     g_queue_clear (&qtdemux->protection_event_queue);
++    if (qtdemux->preferred_protection_system_id) {
++      g_free (qtdemux->preferred_protection_system_id);
++      qtdemux->preferred_protection_system_id = NULL;
++    }
+   }
+   qtdemux->offset = 0;
+   gst_adapter_clear (qtdemux->adapter);
+@@ -2454,6 +2462,29 @@ 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 (g_strcmp0 (gst_context_get_context_type (context),
++          "drm-preferred-decryption-system-id") == 0) {
++    const GstStructure *s;
++
++    s = gst_context_get_structure (context);
++    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_TRACE_OBJECT (element, "chaining set_context to superclass %p or %p",
++      GST_ELEMENT_GET_CLASS (element), parent_class);
++  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 */
+@@ -3433,6 +3464,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));
+   }
+@@ -5033,6 +5066,12 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
+     GstEvent *event;
+     while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
++#if (!GST_DISABLE_GST_DEBUG)
++      const gchar *system_id = NULL;
++      gst_event_parse_protection (event, &system_id, NULL, NULL);
++      GST_TRACE_OBJECT (qtdemux, "pushing again protection event for system %s",
++          system_id);
++#endif
+       gst_pad_push_event (stream->pad, event);
+     }
+@@ -6987,11 +7026,148 @@ 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_if_needed (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;
++  }
++
++  GST_TRACE_OBJECT (qtdemux, "currently we have detected %u protection systems",
++      qtdemux->protection_system_ids->len);
++  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);
++  if (filtered_sys_ids == NULL || filtered_sys_ids[0] == NULL) {
++    GST_LOG_OBJECT (qtdemux, "no suitable decryptors found, not issuing the "
++        "context request");
++    g_strfreev (filtered_sys_ids);
++    return;
++  }
++  GST_TRACE_OBJECT (qtdemux, "found suitable decryptors, running the context "
++      "request");
++
++  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 = (GstStructure *) gst_query_get_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);
+@@ -7006,17 +7182,38 @@ 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_if_needed (qtdemux, stream);
++  if (qtdemux->preferred_protection_system_id != NULL) {
++    guint i;
++    for (i = 0; i < qtdemux->protection_system_ids->len; i++) {
++      if (g_strcmp0 (g_ptr_array_index (qtdemux->protection_system_ids, i),
++              qtdemux->preferred_protection_system_id) == 0) {
++        const gchar *preferred_system_array[] =
++            { qtdemux->preferred_protection_system_id, NULL };
++        selected_system = gst_protection_select_system (preferred_system_array);
++        break;
++      }
++    }
++  }
++
++  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 (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 ecf0c63..0c53437 100644
+--- a/gst/isomp4/qtdemux.h
++++ b/gst/isomp4/qtdemux.h
+@@ -153,6 +153,7 @@ struct _GstQTDemux {
+   guint64 cenc_aux_info_offset;
+   guint8 *cenc_aux_info_sizes;
+   guint32 cenc_aux_sample_count;
++  gchar *preferred_protection_system_id;
+   gboolean always_honor_tfdt;
+ };
+-- 
+2.11.0
+
diff --git a/Tools/gtk/patches/gstreamer-0001-protection-added-function-to-filter-system-ids.patch b/Tools/gtk/patches/gstreamer-0001-protection-added-function-to-filter-system-ids.patch
new file mode 100644 (file)
index 0000000..e72ef6c
--- /dev/null
@@ -0,0 +1,77 @@
+From 7772eb350591682b6a315c8a87a58131f731f1d4 Mon Sep 17 00:00:00 2001
+From: Xabier Rodriguez Calvar <calvaris@igalia.com>
+Date: Wed, 19 Oct 2016 16:44:16 +0200
+Subject: [PATCH] protection: added function to filter system ids
+
+gst_protection_filter_systems_by_available_decryptors takes an array of
+strings and returns a new array of strings filtered by the avaible
+decryptors for them so the ones you get are the ones that you should be
+able to decrypt.
+---
+ gst/gstprotection.c | 36 ++++++++++++++++++++++++++++++++++++
+ gst/gstprotection.h |  2 ++
+ 2 files changed, 38 insertions(+)
+
+diff --git a/gst/gstprotection.c b/gst/gstprotection.c
+index 8ee52ea..2d7e5e0 100644
+--- a/gst/gstprotection.c
++++ b/gst/gstprotection.c
+@@ -191,6 +191,42 @@ gst_protection_select_system (const gchar ** system_identifiers)
+   return retval;
+ }
++gchar **
++gst_protection_filter_systems_by_available_decryptors (const gchar **
++    system_identifiers)
++{
++  GList *decryptors, *walk;
++  gchar **retval;
++  guint i = 0, decryptors_number;
++
++  decryptors =
++      gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DECRYPTOR,
++      GST_RANK_MARGINAL);
++
++  decryptors_number = g_list_length (decryptors);
++  retval = g_new (gchar *, decryptors_number + 1);
++
++  GST_TRACE ("found %u decrytors", decryptors_number);
++
++  for (walk = decryptors; walk; walk = g_list_next (walk)) {
++    GstElementFactory *fact = (GstElementFactory *) walk->data;
++
++    const char *found_sys_id =
++        gst_protection_factory_check (fact, system_identifiers);
++    GST_TRACE ("factory %s is valid for %s", GST_OBJECT_NAME (fact),
++        found_sys_id);
++
++    if (found_sys_id) {
++      retval[i++] = g_strdup (found_sys_id);
++    }
++  }
++  retval[i] = NULL;
++
++  gst_plugin_feature_list_free (decryptors);
++
++  return retval;
++}
++
+ static const gchar *
+ gst_protection_factory_check (GstElementFactory * fact,
+     const gchar ** system_identifiers)
+diff --git a/gst/gstprotection.h b/gst/gstprotection.h
+index f2f54c4..95976c5 100644
+--- a/gst/gstprotection.h
++++ b/gst/gstprotection.h
+@@ -66,6 +66,8 @@ GstProtectionMeta *gst_buffer_add_protection_meta (GstBuffer * buffer,
+     GstStructure * info);
+ const gchar *gst_protection_select_system (const gchar ** system_identifiers);
++gchar ** gst_protection_filter_systems_by_available_decryptors (
++    const gchar ** system_identifiers);
+ G_END_DECLS
+ #endif /* __GST_PROTECTION_META_H__ */
+-- 
+2.10.2
+