1/* GStreamer ClearKey common encryption decryptor
2 *
3 * Copyright (C) 2013 YouView TV Ltd. <alex.ashley@youview.com>
4 * Copyright (C) 2016 Metrological
5 * Copyright (C) 2016 Igalia S.L
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
20 * Boston, MA 02110-1335, USA.
21 */
22
23#include "config.h"
24#include "WebKitCommonEncryptionDecryptorGStreamer.h"
25
26#if ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
27
28#include "GStreamerCommon.h"
29#include "GStreamerEMEUtilities.h"
30#include <wtf/Condition.h>
31#include <wtf/PrintStream.h>
32#include <wtf/RunLoop.h>
33
34using WebCore::CDMInstance;
35
36#define WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptPrivate))
37struct _WebKitMediaCommonEncryptionDecryptPrivate {
38 GRefPtr<GstEvent> protectionEvent;
39 RefPtr<CDMInstance> cdmInstance;
40 bool keyReceived { false };
41 bool waitingForKey { false };
42 Lock mutex;
43 Condition condition;
44};
45
46static GstStateChangeReturn changeState(GstElement*, GstStateChange transition);
47static void finalize(GObject*);
48static GstCaps* transformCaps(GstBaseTransform*, GstPadDirection, GstCaps*, GstCaps*);
49static GstFlowReturn transformInPlace(GstBaseTransform*, GstBuffer*);
50static gboolean sinkEventHandler(GstBaseTransform*, GstEvent*);
51static gboolean queryHandler(GstBaseTransform*, GstPadDirection, GstQuery*);
52static bool isCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt*);
53static void setContext(GstElement*, GstContext*);
54
55
56GST_DEBUG_CATEGORY_STATIC(webkit_media_common_encryption_decrypt_debug_category);
57#define GST_CAT_DEFAULT webkit_media_common_encryption_decrypt_debug_category
58
59#define webkit_media_common_encryption_decrypt_parent_class parent_class
60G_DEFINE_TYPE(WebKitMediaCommonEncryptionDecrypt, webkit_media_common_encryption_decrypt, GST_TYPE_BASE_TRANSFORM);
61
62static void webkit_media_common_encryption_decrypt_class_init(WebKitMediaCommonEncryptionDecryptClass* klass)
63{
64 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
65 gobjectClass->finalize = finalize;
66
67 GST_DEBUG_CATEGORY_INIT(webkit_media_common_encryption_decrypt_debug_category,
68 "webkitcenc", 0, "Common Encryption base class");
69
70 GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
71 elementClass->change_state = GST_DEBUG_FUNCPTR(changeState);
72 elementClass->set_context = GST_DEBUG_FUNCPTR(setContext);
73
74 GstBaseTransformClass* baseTransformClass = GST_BASE_TRANSFORM_CLASS(klass);
75 baseTransformClass->transform_ip = GST_DEBUG_FUNCPTR(transformInPlace);
76 baseTransformClass->transform_caps = GST_DEBUG_FUNCPTR(transformCaps);
77 baseTransformClass->transform_ip_on_passthrough = FALSE;
78 baseTransformClass->sink_event = GST_DEBUG_FUNCPTR(sinkEventHandler);
79 baseTransformClass->query = GST_DEBUG_FUNCPTR(queryHandler);
80
81 g_type_class_add_private(klass, sizeof(WebKitMediaCommonEncryptionDecryptPrivate));
82}
83
84static void webkit_media_common_encryption_decrypt_init(WebKitMediaCommonEncryptionDecrypt* self)
85{
86 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
87
88 self->priv = priv;
89 new (priv) WebKitMediaCommonEncryptionDecryptPrivate();
90
91 GstBaseTransform* base = GST_BASE_TRANSFORM(self);
92 gst_base_transform_set_in_place(base, TRUE);
93 gst_base_transform_set_passthrough(base, FALSE);
94 gst_base_transform_set_gap_aware(base, FALSE);
95}
96
97static void finalize(GObject* object)
98{
99 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(object);
100 WebKitMediaCommonEncryptionDecryptPrivate* priv = self->priv;
101
102 priv->~WebKitMediaCommonEncryptionDecryptPrivate();
103 GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
104}
105
106static GstCaps* transformCaps(GstBaseTransform* base, GstPadDirection direction, GstCaps* caps, GstCaps* filter)
107{
108 if (direction == GST_PAD_UNKNOWN)
109 return nullptr;
110
111 GST_DEBUG_OBJECT(base, "direction: %s, caps: %" GST_PTR_FORMAT " filter: %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "src" : "sink", caps, filter);
112
113 GstCaps* transformedCaps = gst_caps_new_empty();
114 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base);
115 WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
116
117 unsigned size = gst_caps_get_size(caps);
118 for (unsigned i = 0; i < size; ++i) {
119 GstStructure* incomingStructure = gst_caps_get_structure(caps, i);
120 GUniquePtr<GstStructure> outgoingStructure = nullptr;
121
122 if (direction == GST_PAD_SINK) {
123 if (!gst_structure_has_field(incomingStructure, "original-media-type"))
124 continue;
125
126 outgoingStructure = GUniquePtr<GstStructure>(gst_structure_copy(incomingStructure));
127 gst_structure_set_name(outgoingStructure.get(), gst_structure_get_string(outgoingStructure.get(), "original-media-type"));
128
129 // Filter out the DRM related fields from the down-stream caps.
130 gst_structure_remove_fields(outgoingStructure.get(), "protection-system", "original-media-type", "encryption-algorithm", "encoding-scope", "cipher-mode", nullptr);
131 } else {
132 outgoingStructure = GUniquePtr<GstStructure>(gst_structure_copy(incomingStructure));
133 // Filter out the video related fields from the up-stream caps,
134 // because they are not relevant to the input caps of this element and
135 // can cause caps negotiation failures with adaptive bitrate streams.
136 gst_structure_remove_fields(outgoingStructure.get(), "base-profile", "codec_data", "height", "framerate", "level", "pixel-aspect-ratio", "profile", "rate", "width", nullptr);
137
138 gst_structure_set(outgoingStructure.get(), "protection-system", G_TYPE_STRING, klass->protectionSystemId,
139 "original-media-type", G_TYPE_STRING, gst_structure_get_name(incomingStructure), nullptr);
140
141 // GST_PROTECTION_UNSPECIFIED_SYSTEM_ID was added in the GStreamer
142 // developement git master which will ship as version 1.16.0.
143 gst_structure_set_name(outgoingStructure.get(),
144#if GST_CHECK_VERSION(1, 15, 0)
145 !g_strcmp0(klass->protectionSystemId, GST_PROTECTION_UNSPECIFIED_SYSTEM_ID) ? "application/x-webm-enc" :
146#endif
147 "application/x-cenc");
148 }
149
150 bool duplicate = false;
151 unsigned size = gst_caps_get_size(transformedCaps);
152
153 for (unsigned index = 0; !duplicate && index < size; ++index) {
154 GstStructure* structure = gst_caps_get_structure(transformedCaps, index);
155 if (gst_structure_is_equal(structure, outgoingStructure.get()))
156 duplicate = true;
157 }
158
159 if (!duplicate)
160 gst_caps_append_structure(transformedCaps, outgoingStructure.release());
161 }
162
163 if (filter) {
164 GstCaps* intersection;
165
166 GST_DEBUG_OBJECT(base, "Using filter caps %" GST_PTR_FORMAT, filter);
167 intersection = gst_caps_intersect_full(transformedCaps, filter, GST_CAPS_INTERSECT_FIRST);
168 gst_caps_unref(transformedCaps);
169 transformedCaps = intersection;
170 }
171
172 GST_DEBUG_OBJECT(base, "returning %" GST_PTR_FORMAT, transformedCaps);
173 return transformedCaps;
174}
175
176static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
177{
178 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base);
179 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
180 LockHolder locker(priv->mutex);
181
182 // The key might not have been received yet. Wait for it.
183 if (!priv->keyReceived) {
184 GST_DEBUG_OBJECT(self, "key not available yet, waiting for it");
185 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)) {
186 GST_ERROR_OBJECT(self, "can't process key requests in less than PAUSED state");
187 return GST_FLOW_NOT_SUPPORTED;
188 }
189 // Send "drm-cdm-instance-needed" message to the player to resend the CDMInstance if available and inform we are waiting for key.
190 priv->waitingForKey = true;
191 gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-waiting-for-key")));
192
193 priv->condition.waitFor(priv->mutex, Seconds(5), [priv] {
194 return priv->keyReceived;
195 });
196 if (!priv->keyReceived) {
197 GST_ERROR_OBJECT(self, "key not available");
198 return GST_FLOW_NOT_SUPPORTED;
199 }
200 GST_DEBUG_OBJECT(self, "key received, continuing");
201 priv->waitingForKey = false;
202 gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-key-received")));
203 }
204
205 GstProtectionMeta* protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer));
206 if (!protectionMeta) {
207 GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer);
208 return GST_FLOW_NOT_SUPPORTED;
209 }
210
211 unsigned ivSize;
212 if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) {
213 GST_ERROR_OBJECT(self, "Failed to get iv_size");
214 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
215 return GST_FLOW_NOT_SUPPORTED;
216 }
217
218 gboolean encrypted;
219 if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", &encrypted)) {
220 GST_ERROR_OBJECT(self, "Failed to get encrypted flag");
221 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
222 return GST_FLOW_NOT_SUPPORTED;
223 }
224
225 if (!ivSize || !encrypted) {
226 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
227 return GST_FLOW_OK;
228 }
229
230 GST_DEBUG_OBJECT(base, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info);
231
232 unsigned subSampleCount;
233 if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", &subSampleCount)) {
234 GST_ERROR_OBJECT(self, "Failed to get subsample_count");
235 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
236 return GST_FLOW_NOT_SUPPORTED;
237 }
238
239 const GValue* value;
240 GstBuffer* subSamplesBuffer = nullptr;
241 if (subSampleCount) {
242 value = gst_structure_get_value(protectionMeta->info, "subsamples");
243 if (!value) {
244 GST_ERROR_OBJECT(self, "Failed to get subsamples");
245 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
246 return GST_FLOW_NOT_SUPPORTED;
247 }
248 subSamplesBuffer = gst_value_get_buffer(value);
249 }
250
251 value = gst_structure_get_value(protectionMeta->info, "kid");
252
253 if (!value) {
254 GST_ERROR_OBJECT(self, "Failed to get key id for buffer");
255 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
256 return GST_FLOW_NOT_SUPPORTED;
257 }
258 GstBuffer* keyIDBuffer = gst_value_get_buffer(value);
259
260 value = gst_structure_get_value(protectionMeta->info, "iv");
261 if (!value) {
262 GST_ERROR_OBJECT(self, "Failed to get IV for sample");
263 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
264 return GST_FLOW_NOT_SUPPORTED;
265 }
266
267 GstBuffer* ivBuffer = gst_value_get_buffer(value);
268 WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
269
270 GST_TRACE_OBJECT(self, "decrypting");
271 if (!klass->decrypt(self, ivBuffer, keyIDBuffer, buffer, subSampleCount, subSamplesBuffer)) {
272 GST_ERROR_OBJECT(self, "Decryption failed");
273 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
274 return GST_FLOW_NOT_SUPPORTED;
275 }
276
277 gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
278 return GST_FLOW_OK;
279}
280
281static bool isCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt* self)
282{
283 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
284
285 ASSERT(priv->mutex.isLocked());
286
287 if (!priv->cdmInstance) {
288 GRefPtr<GstContext> context = adoptGRef(gst_element_get_context(GST_ELEMENT(self), "drm-cdm-instance"));
289 // According to the GStreamer documentation, if we can't find the context, we should run a downstream query, then an upstream one and then send a bus
290 // message. In this case that does not make a lot of sense since only the app (player) answers it, meaning that no query is going to solve it. A message
291 // could be helpful but the player sets the context as soon as it gets the CDMInstance and if it does not have it, we have no way of asking for one as it is
292 // something provided by crossplatform code. This means that we won't be able to answer the bus request in any way either. Summing up, neither queries nor bus
293 // requests are useful here.
294 if (context) {
295 const GValue* value = gst_structure_get_value(gst_context_get_structure(context.get()), "cdm-instance");
296 // Capture the CDMInstance into a separate variable to avoid missing a refcount.
297 CDMInstance* instance = value ? reinterpret_cast<CDMInstance*>(g_value_get_pointer(value)) : nullptr;
298 // ... And force a refcount bump using operator=.
299 priv->cdmInstance = instance;
300 if (priv->cdmInstance)
301 GST_DEBUG_OBJECT(self, "received a new CDM instance %p, refcount %u", priv->cdmInstance.get(), priv->cdmInstance->refCount());
302 else
303 GST_TRACE_OBJECT(self, "CDM instance was detached");
304 }
305 }
306
307 GST_TRACE_OBJECT(self, "CDM instance available %s", boolForPrinting(priv->cdmInstance.get()));
308 return priv->cdmInstance;
309}
310
311static gboolean sinkEventHandler(GstBaseTransform* trans, GstEvent* event)
312{
313 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(trans);
314 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
315 WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
316 gboolean result = FALSE;
317
318 switch (GST_EVENT_TYPE(event)) {
319 case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
320 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=191355
321 // We should be handling protection events in this class in
322 // addition to out-of-band data. In regular playback, after a
323 // preferred system ID context is set, any future protection
324 // events will not be handled by the demuxer, so the must be
325 // handled in here.
326 LockHolder locker(priv->mutex);
327 if (!isCDMInstanceAvailable(self)) {
328 GST_ERROR_OBJECT(self, "No CDM instance available");
329 result = FALSE;
330 break;
331 }
332
333 if (klass->handleKeyResponse(self, priv->cdmInstance)) {
334 GST_DEBUG_OBJECT(self, "key received");
335 priv->keyReceived = true;
336 priv->condition.notifyOne();
337 }
338
339 gst_event_unref(event);
340 result = TRUE;
341 break;
342 }
343 default:
344 result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event);
345 break;
346 }
347
348 return result;
349}
350
351static gboolean queryHandler(GstBaseTransform* trans, GstPadDirection direction, GstQuery* query)
352{
353 if (gst_structure_has_name(gst_query_get_structure(query), "any-decryptor-waiting-for-key")) {
354 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(trans);
355 GST_TRACE_OBJECT(trans, "any-decryptor-waiting-for-key query, waitingforkey %s", boolForPrinting(priv->waitingForKey));
356 return priv->waitingForKey;
357 }
358 return GST_BASE_TRANSFORM_CLASS(parent_class)->query(trans, direction, query);
359}
360
361static GstStateChangeReturn changeState(GstElement* element, GstStateChange transition)
362{
363 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element);
364 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
365
366 switch (transition) {
367 case GST_STATE_CHANGE_PAUSED_TO_READY:
368 GST_DEBUG_OBJECT(self, "PAUSED->READY");
369 priv->condition.notifyOne();
370 break;
371 default:
372 break;
373 }
374
375 GstStateChangeReturn result = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
376
377 // Add post-transition code here.
378
379 return result;
380}
381
382static void setContext(GstElement* element, GstContext* context)
383{
384 WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element);
385 WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
386
387 if (gst_context_has_context_type(context, "drm-cdm-instance")) {
388 const GValue* value = gst_structure_get_value(gst_context_get_structure(context), "cdm-instance");
389 LockHolder locker(priv->mutex);
390 priv->cdmInstance = value ? reinterpret_cast<CDMInstance*>(g_value_get_pointer(value)) : nullptr;
391 GST_DEBUG_OBJECT(self, "received new CDMInstance %p", priv->cdmInstance.get());
392 return;
393 }
394
395 GST_ELEMENT_CLASS(parent_class)->set_context(element, context);
396}
397
398#endif // ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
399