diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/gstreamer/eme')
4 files changed, 743 insertions, 0 deletions
diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp new file mode 100644 index 000000000..dc697089f --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp @@ -0,0 +1,260 @@ +/* GStreamer ClearKey common encryption decryptor + * + * Copyright (C) 2016 Metrological + * Copyright (C) 2016 Igalia S.L + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "config.h" +#include "WebKitClearKeyDecryptorGStreamer.h" + +#if (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) + +#include "GRefPtrGStreamer.h" +#include <gcrypt.h> +#include <gst/base/gstbytereader.h> +#include <wtf/RunLoop.h> + +#define CLEARKEY_SIZE 16 + +#define WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CK_DECRYPT, WebKitMediaClearKeyDecryptPrivate)) +struct _WebKitMediaClearKeyDecryptPrivate { + GRefPtr<GstBuffer> key; + gcry_cipher_hd_t handle; +}; + +static void webKitMediaClearKeyDecryptorFinalize(GObject*); +static gboolean webKitMediaClearKeyDecryptorHandleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, GstEvent*); +static gboolean webKitMediaClearKeyDecryptorSetupCipher(WebKitMediaCommonEncryptionDecrypt*); +static gboolean webKitMediaClearKeyDecryptorDecrypt(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* iv, GstBuffer* sample, unsigned subSamplesCount, GstBuffer* subSamples); +static void webKitMediaClearKeyDecryptorReleaseCipher(WebKitMediaCommonEncryptionDecrypt*); + +GST_DEBUG_CATEGORY_STATIC(webkit_media_clear_key_decrypt_debug_category); +#define GST_CAT_DEFAULT webkit_media_clear_key_decrypt_debug_category + +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_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, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264; audio/mpeg")); + +#define webkit_media_clear_key_decrypt_parent_class parent_class +G_DEFINE_TYPE(WebKitMediaClearKeyDecrypt, webkit_media_clear_key_decrypt, WEBKIT_TYPE_MEDIA_CENC_DECRYPT); + +static void webkit_media_clear_key_decrypt_class_init(WebKitMediaClearKeyDecryptClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + gobjectClass->finalize = webKitMediaClearKeyDecryptorFinalize; + + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate)); + gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&srcTemplate)); + + gst_element_class_set_static_metadata(elementClass, + "Decrypt content encrypted using ISOBMFF ClearKey Common Encryption", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts media that has been encrypted using ISOBMFF ClearKey Common Encryption.", + "Philippe Normand <philn@igalia.com>"); + + GST_DEBUG_CATEGORY_INIT(webkit_media_clear_key_decrypt_debug_category, + "webkitclearkey", 0, "ClearKey decryptor"); + + WebKitMediaCommonEncryptionDecryptClass* cencClass = WEBKIT_MEDIA_CENC_DECRYPT_CLASS(klass); + 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); + cencClass->releaseCipher = GST_DEBUG_FUNCPTR(webKitMediaClearKeyDecryptorReleaseCipher); + + g_type_class_add_private(klass, sizeof(WebKitMediaClearKeyDecryptPrivate)); +} + +static void webkit_media_clear_key_decrypt_init(WebKitMediaClearKeyDecrypt* self) +{ + WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(self); + + if (!gcry_check_version(GCRYPT_VERSION)) + GST_ERROR_OBJECT(self, "Libgcrypt failed to initialize"); + + // Allocate a pool of 16k secure memory. This make the secure memory + // available and also drops privileges where needed. + gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0); + + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + self->priv = priv; + new (priv) WebKitMediaClearKeyDecryptPrivate(); +} + +static void webKitMediaClearKeyDecryptorFinalize(GObject* object) +{ + WebKitMediaClearKeyDecrypt* self = WEBKIT_MEDIA_CK_DECRYPT(object); + WebKitMediaClearKeyDecryptPrivate* priv = self->priv; + + priv->~WebKitMediaClearKeyDecryptPrivate(); + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static gboolean webKitMediaClearKeyDecryptorHandleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, GstEvent* event) +{ + WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self)); + const GstStructure* structure = gst_event_get_structure(event); + + if (!gst_structure_has_name(structure, "drm-cipher")) + return FALSE; + + const GValue* value = gst_structure_get_value(structure, "key"); + priv->key.clear(); + priv->key = adoptGRef(gst_buffer_copy(gst_value_get_buffer(value))); + return TRUE; +} + +static gboolean webKitMediaClearKeyDecryptorSetupCipher(WebKitMediaCommonEncryptionDecrypt* self) +{ + WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self)); + gcry_error_t error; + + ASSERT(priv->key); + if (!priv->key) { + GST_ERROR_OBJECT(self, "Decryption key not provided"); + return false; + } + + error = gcry_cipher_open(&(priv->handle), GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); + if (error) { + GST_ERROR_OBJECT(self, "Failed to create AES 128 CTR cipher handle: %s", gpg_strerror(error)); + return false; + } + + GstMapInfo keyMap; + if (!gst_buffer_map(priv->key.get(), &keyMap, GST_MAP_READ)) { + GST_ERROR_OBJECT(self, "Failed to map decryption key"); + return false; + } + + ASSERT(keyMap.size == CLEARKEY_SIZE); + error = gcry_cipher_setkey(priv->handle, keyMap.data, keyMap.size); + gst_buffer_unmap(priv->key.get(), &keyMap); + if (error) { + GST_ERROR_OBJECT(self, "gcry_cipher_setkey failed: %s", gpg_strerror(error)); + return false; + } + + return true; +} + +static gboolean webKitMediaClearKeyDecryptorDecrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer) +{ + GstMapInfo ivMap; + if (!gst_buffer_map(ivBuffer, &ivMap, GST_MAP_READ)) { + GST_ERROR_OBJECT(self, "Failed to map IV"); + return false; + } + + uint8_t ctr[CLEARKEY_SIZE]; + if (ivMap.size == 8) { + memset(ctr + 8, 0, 8); + memcpy(ctr, ivMap.data, 8); + } else { + ASSERT(ivMap.size == CLEARKEY_SIZE); + memcpy(ctr, ivMap.data, CLEARKEY_SIZE); + } + gst_buffer_unmap(ivBuffer, &ivMap); + + WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self)); + gcry_error_t error = gcry_cipher_setctr(priv->handle, ctr, CLEARKEY_SIZE); + if (error) { + GST_ERROR_OBJECT(self, "gcry_cipher_setctr failed: %s", gpg_strerror(error)); + return false; + } + + GstMapInfo map; + gboolean bufferMapped = gst_buffer_map(buffer, &map, static_cast<GstMapFlags>(GST_MAP_READWRITE)); + if (!bufferMapped) { + GST_ERROR_OBJECT(self, "Failed to map buffer"); + return false; + } + + GstMapInfo subSamplesMap; + gboolean subsamplesBufferMapped = gst_buffer_map(subSamplesBuffer, &subSamplesMap, GST_MAP_READ); + if (!subsamplesBufferMapped) { + GST_ERROR_OBJECT(self, "Failed to map subsample buffer"); + gst_buffer_unmap(buffer, &map); + return false; + } + + GstByteReader* reader = gst_byte_reader_new(subSamplesMap.data, subSamplesMap.size); + unsigned position = 0; + unsigned sampleIndex = 0; + + GST_DEBUG_OBJECT(self, "position: %d, size: %zu", position, map.size); + + while (position < map.size) { + guint16 nBytesClear = 0; + guint32 nBytesEncrypted = 0; + + if (sampleIndex < subSampleCount) { + if (!gst_byte_reader_get_uint16_be(reader, &nBytesClear) + || !gst_byte_reader_get_uint32_be(reader, &nBytesEncrypted)) { + GST_DEBUG_OBJECT(self, "unsupported"); + gst_byte_reader_free(reader); + gst_buffer_unmap(buffer, &map); + gst_buffer_unmap(subSamplesBuffer, &subSamplesMap); + return false; + } + + sampleIndex++; + } else { + nBytesClear = 0; + nBytesEncrypted = map.size - position; + } + + GST_TRACE_OBJECT(self, "%d bytes clear (todo=%zu)", nBytesClear, map.size - position); + position += nBytesClear; + if (nBytesEncrypted) { + GST_TRACE_OBJECT(self, "%d bytes encrypted (todo=%zu)", nBytesEncrypted, map.size - position); + error = gcry_cipher_decrypt(priv->handle, map.data + position, nBytesEncrypted, 0, 0); + if (error) { + GST_ERROR_OBJECT(self, "decryption failed: %s", gpg_strerror(error)); + gst_byte_reader_free(reader); + gst_buffer_unmap(buffer, &map); + gst_buffer_unmap(subSamplesBuffer, &subSamplesMap); + return false; + } + position += nBytesEncrypted; + } + } + + gst_byte_reader_free(reader); + gst_buffer_unmap(buffer, &map); + gst_buffer_unmap(subSamplesBuffer, &subSamplesMap); + return true; +} + +static void webKitMediaClearKeyDecryptorReleaseCipher(WebKitMediaCommonEncryptionDecrypt* self) +{ + WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self)); + gcry_cipher_close(priv->handle); +} + +#endif // (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h new file mode 100644 index 000000000..30cfa299b --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.h @@ -0,0 +1,57 @@ +/* GStreamer ClearKey common encryption decryptor + * + * Copyright (C) 2016 Metrological + * Copyright (C) 2016 Igalia S.L + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) + +#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()) +#define WEBKIT_MEDIA_CK_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_MEDIA_CK_DECRYPT, WebKitMediaClearKeyDecrypt)) +#define WEBKIT_MEDIA_CK_DECRYPT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_TYPE_MEDIA_CK_DECRYPT, WebKitMediaClearKeyDecryptClass)) +#define WEBKIT_IS_MEDIA_CK_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_MEDIA_CK_DECRYPT)) +#define WEBKIT_IS_MEDIA_CK_DECRYPT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_TYPE_MEDIA_CK_DECRYPT)) + +typedef struct _WebKitMediaClearKeyDecrypt WebKitMediaClearKeyDecrypt; +typedef struct _WebKitMediaClearKeyDecryptClass WebKitMediaClearKeyDecryptClass; +typedef struct _WebKitMediaClearKeyDecryptPrivate WebKitMediaClearKeyDecryptPrivate; + +GType webkit_media_clear_key_decrypt_get_type(void); + +struct _WebKitMediaClearKeyDecrypt { + WebKitMediaCommonEncryptionDecrypt parent; + + WebKitMediaClearKeyDecryptPrivate* priv; +}; + +struct _WebKitMediaClearKeyDecryptClass { + WebKitMediaCommonEncryptionDecryptClass parentClass; +}; + +G_END_DECLS + +#endif // (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp new file mode 100644 index 000000000..389808050 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp @@ -0,0 +1,362 @@ +/* GStreamer ClearKey common encryption decryptor + * + * Copyright (C) 2013 YouView TV Ltd. <alex.ashley@youview.com> + * Copyright (C) 2016 Metrological + * Copyright (C) 2016 Igalia S.L + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "config.h" +#include "WebKitCommonEncryptionDecryptorGStreamer.h" + +#if (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) + +#include "GRefPtrGStreamer.h" +#include <wtf/Condition.h> +#include <wtf/RunLoop.h> + +#define WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptPrivate)) +struct _WebKitMediaCommonEncryptionDecryptPrivate { + GRefPtr<GstEvent> protectionEvent; + + bool keyReceived; + Lock mutex; + Condition condition; +}; + +static GstStateChangeReturn webKitMediaCommonEncryptionDecryptorChangeState(GstElement*, GstStateChange transition); +static void webKitMediaCommonEncryptionDecryptorFinalize(GObject*); +static GstCaps* webkitMediaCommonEncryptionDecryptTransformCaps(GstBaseTransform*, GstPadDirection, GstCaps*, GstCaps*); +static GstFlowReturn webkitMediaCommonEncryptionDecryptTransformInPlace(GstBaseTransform*, GstBuffer*); +static gboolean webkitMediaCommonEncryptionDecryptSinkEventHandler(GstBaseTransform*, GstEvent*); + +static gboolean webKitMediaCommonEncryptionDecryptDefaultSetupCipher(WebKitMediaCommonEncryptionDecrypt*); +static void webKitMediaCommonEncryptionDecryptDefaultReleaseCipher(WebKitMediaCommonEncryptionDecrypt*); + +GST_DEBUG_CATEGORY_STATIC(webkit_media_common_encryption_decrypt_debug_category); +#define GST_CAT_DEFAULT webkit_media_common_encryption_decrypt_debug_category + +#define webkit_media_common_encryption_decrypt_parent_class parent_class +G_DEFINE_TYPE(WebKitMediaCommonEncryptionDecrypt, webkit_media_common_encryption_decrypt, GST_TYPE_BASE_TRANSFORM); + +static void webkit_media_common_encryption_decrypt_class_init(WebKitMediaCommonEncryptionDecryptClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + gobjectClass->finalize = webKitMediaCommonEncryptionDecryptorFinalize; + + GST_DEBUG_CATEGORY_INIT(webkit_media_common_encryption_decrypt_debug_category, + "webkitcenc", 0, "Common Encryption base class"); + + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + elementClass->change_state = GST_DEBUG_FUNCPTR(webKitMediaCommonEncryptionDecryptorChangeState); + + GstBaseTransformClass* baseTransformClass = GST_BASE_TRANSFORM_CLASS(klass); + baseTransformClass->transform_ip = GST_DEBUG_FUNCPTR(webkitMediaCommonEncryptionDecryptTransformInPlace); + baseTransformClass->transform_caps = GST_DEBUG_FUNCPTR(webkitMediaCommonEncryptionDecryptTransformCaps); + baseTransformClass->transform_ip_on_passthrough = FALSE; + baseTransformClass->sink_event = GST_DEBUG_FUNCPTR(webkitMediaCommonEncryptionDecryptSinkEventHandler); + + klass->setupCipher = GST_DEBUG_FUNCPTR(webKitMediaCommonEncryptionDecryptDefaultSetupCipher); + klass->releaseCipher = GST_DEBUG_FUNCPTR(webKitMediaCommonEncryptionDecryptDefaultReleaseCipher); + + g_type_class_add_private(klass, sizeof(WebKitMediaCommonEncryptionDecryptPrivate)); +} + +static void webkit_media_common_encryption_decrypt_init(WebKitMediaCommonEncryptionDecrypt* self) +{ + WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); + + self->priv = priv; + new (priv) WebKitMediaCommonEncryptionDecryptPrivate(); + + GstBaseTransform* base = GST_BASE_TRANSFORM(self); + gst_base_transform_set_in_place(base, TRUE); + gst_base_transform_set_passthrough(base, FALSE); + gst_base_transform_set_gap_aware(base, FALSE); +} + +static void webKitMediaCommonEncryptionDecryptorFinalize(GObject* object) +{ + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(object); + WebKitMediaCommonEncryptionDecryptPrivate* priv = self->priv; + + priv->~WebKitMediaCommonEncryptionDecryptPrivate(); + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static GstCaps* webkitMediaCommonEncryptionDecryptTransformCaps(GstBaseTransform* base, GstPadDirection direction, GstCaps* caps, GstCaps* filter) +{ + if (direction == GST_PAD_UNKNOWN) + return nullptr; + + GST_DEBUG_OBJECT(base, "direction: %s, caps: %" GST_PTR_FORMAT " filter: %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "src" : "sink", caps, filter); + + GstCaps* transformedCaps = gst_caps_new_empty(); + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base); + WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self); + + unsigned size = gst_caps_get_size(caps); + for (unsigned i = 0; i < size; ++i) { + GstStructure* incomingStructure = gst_caps_get_structure(caps, i); + GRefPtr<GstStructure> outgoingStructure = nullptr; + + if (direction == GST_PAD_SINK) { + if (!gst_structure_has_field(incomingStructure, "original-media-type")) + continue; + + outgoingStructure = adoptGRef(gst_structure_copy(incomingStructure)); + gst_structure_set_name(outgoingStructure.get(), gst_structure_get_string(outgoingStructure.get(), "original-media-type")); + + // Filter out the DRM related fields from the down-stream caps. + for (int j = 0; j < gst_structure_n_fields(incomingStructure); ++j) { + const gchar* fieldName = gst_structure_nth_field_name(incomingStructure, j); + + if (g_str_has_prefix(fieldName, "protection-system") + || g_str_has_prefix(fieldName, "original-media-type")) + gst_structure_remove_field(outgoingStructure.get(), fieldName); + } + } else { + outgoingStructure = adoptGRef(gst_structure_copy(incomingStructure)); + // Filter out the video related fields from the up-stream caps, + // because they are not relevant to the input caps of this element and + // can cause caps negotiation failures with adaptive bitrate streams. + for (int index = gst_structure_n_fields(outgoingStructure.get()) - 1; index >= 0; --index) { + const gchar* fieldName = gst_structure_nth_field_name(outgoingStructure.get(), index); + GST_TRACE("Check field \"%s\" for removal", fieldName); + + if (!g_strcmp0(fieldName, "base-profile") + || !g_strcmp0(fieldName, "codec_data") + || !g_strcmp0(fieldName, "height") + || !g_strcmp0(fieldName, "framerate") + || !g_strcmp0(fieldName, "level") + || !g_strcmp0(fieldName, "pixel-aspect-ratio") + || !g_strcmp0(fieldName, "profile") + || !g_strcmp0(fieldName, "rate") + || !g_strcmp0(fieldName, "width")) { + gst_structure_remove_field(outgoingStructure.get(), fieldName); + GST_TRACE("Removing field %s", fieldName); + } + } + + gst_structure_set(outgoingStructure.get(), "protection-system", G_TYPE_STRING, klass->protectionSystemId, + "original-media-type", G_TYPE_STRING, gst_structure_get_name(incomingStructure), nullptr); + + gst_structure_set_name(outgoingStructure.get(), "application/x-cenc"); + } + + bool duplicate = false; + unsigned size = gst_caps_get_size(transformedCaps); + + for (unsigned index = 0; !duplicate && index < size; ++index) { + GstStructure* structure = gst_caps_get_structure(transformedCaps, index); + if (gst_structure_is_equal(structure, outgoingStructure.get())) + duplicate = true; + } + + if (!duplicate) + gst_caps_append_structure(transformedCaps, outgoingStructure.leakRef()); + } + + if (filter) { + GstCaps* intersection; + + GST_DEBUG_OBJECT(base, "Using filter caps %" GST_PTR_FORMAT, filter); + intersection = gst_caps_intersect_full(transformedCaps, filter, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref(transformedCaps); + transformedCaps = intersection; + } + + GST_DEBUG_OBJECT(base, "returning %" GST_PTR_FORMAT, transformedCaps); + return transformedCaps; +} + +static GstFlowReturn webkitMediaCommonEncryptionDecryptTransformInPlace(GstBaseTransform* base, GstBuffer* buffer) +{ + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base); + WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); + LockHolder locker(priv->mutex); + + // The key might not have been received yet. Wait for it. + if (!priv->keyReceived) { + GST_DEBUG_OBJECT(self, "key not available yet, waiting for it"); + if (GST_STATE(GST_ELEMENT(self)) < GST_STATE_PAUSED || (GST_STATE_TARGET(GST_ELEMENT(self)) != GST_STATE_VOID_PENDING && GST_STATE_TARGET(GST_ELEMENT(self)) < GST_STATE_PAUSED)) { + GST_ERROR_OBJECT(self, "can't process key requests in less than PAUSED state"); + return GST_FLOW_NOT_SUPPORTED; + } + priv->condition.waitFor(priv->mutex, Seconds(5), [priv] { + return priv->keyReceived; + }); + if (!priv->keyReceived) { + GST_ERROR_OBJECT(self, "key not available"); + return GST_FLOW_NOT_SUPPORTED; + } + GST_DEBUG_OBJECT(self, "key received, continuing"); + } + + GstProtectionMeta* protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer)); + if (!protectionMeta) { + GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer); + return GST_FLOW_NOT_SUPPORTED; + } + + unsigned ivSize; + if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) { + GST_ERROR_OBJECT(self, "Failed to get iv_size"); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + gboolean encrypted; + if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", &encrypted)) { + GST_ERROR_OBJECT(self, "Failed to get encrypted flag"); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + if (!ivSize || !encrypted) { + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_OK; + } + + GST_DEBUG_OBJECT(base, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info); + + unsigned subSampleCount; + if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", &subSampleCount)) { + GST_ERROR_OBJECT(self, "Failed to get subsample_count"); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + const GValue* value; + GstBuffer* subSamplesBuffer = nullptr; + if (subSampleCount) { + value = gst_structure_get_value(protectionMeta->info, "subsamples"); + if (!value) { + GST_ERROR_OBJECT(self, "Failed to get subsamples"); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + subSamplesBuffer = gst_value_get_buffer(value); + } + + WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self); + if (!klass->setupCipher(self)) { + GST_ERROR_OBJECT(self, "Failed to configure cipher"); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + value = gst_structure_get_value(protectionMeta->info, "iv"); + if (!value) { + GST_ERROR_OBJECT(self, "Failed to get IV for sample"); + klass->releaseCipher(self); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + GstBuffer* ivBuffer = gst_value_get_buffer(value); + GST_TRACE_OBJECT(self, "decrypting"); + if (!klass->decrypt(self, ivBuffer, buffer, subSampleCount, subSamplesBuffer)) { + GST_ERROR_OBJECT(self, "Decryption failed"); + klass->releaseCipher(self); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_NOT_SUPPORTED; + } + + klass->releaseCipher(self); + gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); + return GST_FLOW_OK; +} + + +static gboolean webkitMediaCommonEncryptionDecryptSinkEventHandler(GstBaseTransform* trans, GstEvent* event) +{ + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(trans); + WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); + WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self); + gboolean result = FALSE; + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_PROTECTION: { + const char* systemId = nullptr; + + gst_event_parse_protection(event, &systemId, nullptr, nullptr); + GST_TRACE_OBJECT(self, "received protection event for %s", systemId); + + 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; + } + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: { + if (klass->handleKeyResponse(self, event)) { + GST_DEBUG_OBJECT(self, "key received"); + priv->keyReceived = true; + priv->condition.notifyOne(); + } + + gst_event_unref(event); + result = TRUE; + break; + } + default: + result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event); + break; + } + + return result; +} + +static GstStateChangeReturn webKitMediaCommonEncryptionDecryptorChangeState(GstElement* element, GstStateChange transition) +{ + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element); + WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT(self, "PAUSED->READY"); + priv->condition.notifyOne(); + break; + default: + break; + } + + GstStateChangeReturn result = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + + // Add post-transition code here. + + return result; +} + + +static gboolean webKitMediaCommonEncryptionDecryptDefaultSetupCipher(WebKitMediaCommonEncryptionDecrypt*) +{ + return true; +} + + +static void webKitMediaCommonEncryptionDecryptDefaultReleaseCipher(WebKitMediaCommonEncryptionDecrypt*) +{ +} + +#endif // (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h new file mode 100644 index 000000000..dcae82790 --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h @@ -0,0 +1,64 @@ +/* GStreamer ClearKey common encryption decryptor + * + * Copyright (C) 2013 YouView TV Ltd. <alex.ashley@youview.com> + * Copyright (C) 2016 Metrological + * Copyright (C) 2016 Igalia S.L + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) + +#include <gst/base/gstbasetransform.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_MEDIA_CENC_DECRYPT (webkit_media_common_encryption_decrypt_get_type()) +#define WEBKIT_MEDIA_CENC_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecrypt)) +#define WEBKIT_MEDIA_CENC_DECRYPT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptClass)) +#define WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptClass)) + +#define WEBKIT_IS_MEDIA_CENC_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT)) +#define WEBKIT_IS_MEDIA_CENC_DECRYPT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_TYPE_MEDIA_CENC_DECRYPT)) + +typedef struct _WebKitMediaCommonEncryptionDecrypt WebKitMediaCommonEncryptionDecrypt; +typedef struct _WebKitMediaCommonEncryptionDecryptClass WebKitMediaCommonEncryptionDecryptClass; +typedef struct _WebKitMediaCommonEncryptionDecryptPrivate WebKitMediaCommonEncryptionDecryptPrivate; + +GType webkit_media_common_encryption_decrypt_get_type(void); + +struct _WebKitMediaCommonEncryptionDecrypt { + GstBaseTransform parent; + + WebKitMediaCommonEncryptionDecryptPrivate* priv; +}; + +struct _WebKitMediaCommonEncryptionDecryptClass { + GstBaseTransformClass parentClass; + + const char* protectionSystemId; + gboolean (*handleKeyResponse)(WebKitMediaCommonEncryptionDecrypt*, GstEvent* event); + gboolean (*setupCipher)(WebKitMediaCommonEncryptionDecrypt*); + gboolean (*decrypt)(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSamplesCount, GstBuffer* subSamplesBuffer); + void (*releaseCipher)(WebKitMediaCommonEncryptionDecrypt*); +}; + +G_END_DECLS + +#endif // (ENABLE(LEGACY_ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA)) && USE(GSTREAMER) |