static gboolean webkitMediaPlayReadyDecryptSinkEventHandler(GstBaseTransform* trans, GstEvent* event) { gboolean result = FALSE; WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(trans); switch (GST_EVENT_TYPE(event)) { case GST_EVENT_PROTECTION: { const gchar* systemId; const gchar* origin; GstBuffer* initdatabuffer; GST_INFO_OBJECT(self, "received protection event"); gst_event_parse_protection(event, &systemId, &initdatabuffer, &origin); GST_DEBUG_OBJECT(self, "systemId: %s", systemId); // Ignore protection events about content we cannot handle. if (!g_str_equal(systemId, PLAYREADY_PROTECTION_SYSTEM_ID)) { gst_event_unref(event); result = TRUE; break; } // Keep the event ref around so that the parsed event data // remains valid until the drm-key-need message has been sent. self->protectionEvent = event; self->initDataBuffer = initdatabuffer; g_timeout_add(0, requestKey, self); result = TRUE; break; } case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: { GST_INFO_OBJECT(self, "received OOB event"); g_mutex_lock(&self->mutex); const GstStructure* structure = gst_event_get_structure(event); if (gst_structure_has_name(structure, "dxdrm-session")) { GST_INFO_OBJECT(self, "received dxdrm session"); const GValue* value = gst_structure_get_value(structure, "session"); self->sessionMetaData = reinterpret_cast<WebCore::DiscretixSession*>(g_value_get_pointer(value)); self->streamReceived = TRUE; g_cond_signal(&self->condition); } g_mutex_unlock(&self->mutex); gst_event_unref(event); result = TRUE; break; } default: result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event); break; } return result; }
static void webkit_media_playready_decrypt_dispose(GObject* object) { WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(object); if (self->protectionEvent) { gst_event_unref(self->protectionEvent); self->protectionEvent = nullptr; } g_mutex_clear(&self->mutex); g_cond_clear(&self->condition); G_OBJECT_CLASS(parent_class)->dispose(object); }
static gboolean requestKey(gpointer userData) { if (!WEBKIT_IS_MEDIA_PLAYREADY_DECRYPT(userData)) return G_SOURCE_REMOVE; WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(userData); GST_DEBUG_OBJECT(self, "posting drm-key-needed message"); gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new("drm-key-needed", "data", GST_TYPE_BUFFER, self->initDataBuffer, "key-system-id", G_TYPE_STRING, "com.microsoft.playready", nullptr))); if (self->protectionEvent) { gst_event_unref(self->protectionEvent); self->protectionEvent = nullptr; } return G_SOURCE_REMOVE; }
static GstStateChangeReturn webKitMediaPlayReadyDecryptChangeState(GstElement* element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(element); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: GST_DEBUG_OBJECT(self, "PAUSED->READY"); g_mutex_lock(&self->mutex); g_cond_signal(&self->condition); g_mutex_unlock(&self->mutex); break; default: break; } ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); // Add post-transition code here. return ret; }
static GstFlowReturn webkitMediaPlayReadyDecryptTransformInPlace(GstBaseTransform* base, GstBuffer* buffer) { WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(base); GstFlowReturn result = GST_FLOW_OK; GstMapInfo map; const GValue* value; guint sampleIndex = 0; int errorCode; uint32_t trackID = 0; GstPad* pad; GstCaps* caps; GstMapInfo boxMap; GstBuffer* box = nullptr; GstProtectionMeta* protectionMeta = 0; gboolean boxMapped = FALSE; gboolean bufferMapped = FALSE; GST_TRACE_OBJECT(self, "Processing buffer"); g_mutex_lock(&self->mutex); GST_TRACE_OBJECT(self, "Mutex acquired, stream received: %s", self->streamReceived ? "yes":"no"); // The key might not have been received yet. Wait for it. if (!self->streamReceived) g_cond_wait(&self->condition, &self->mutex); if (!self->streamReceived) { GST_DEBUG_OBJECT(self, "Condition signaled from state change transition. Aborting."); result = GST_FLOW_NOT_SUPPORTED; goto beach; } GST_TRACE_OBJECT(self, "Proceeding with decryption"); protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer)); if (!protectionMeta || !buffer) { if (!protectionMeta) GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer); if (!buffer) GST_ERROR_OBJECT(self, "Failed to get writable buffer"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } bufferMapped = gst_buffer_map(buffer, &map, static_cast<GstMapFlags>(GST_MAP_READWRITE)); if (!bufferMapped) { GST_ERROR_OBJECT(self, "Failed to map buffer"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } pad = gst_element_get_static_pad(GST_ELEMENT(self), "src"); caps = gst_pad_get_current_caps(pad); if (g_str_has_prefix(gst_structure_get_name(gst_caps_get_structure(caps, 0)), "video/")) trackID = 1; else trackID = 2; gst_caps_unref(caps); gst_object_unref(pad); GST_TRACE_OBJECT(self, "Protection meta: %" GST_PTR_FORMAT, protectionMeta->info); if (gst_structure_get_uint(protectionMeta->info, "sample-index", &sampleIndex)) { // Process the PIFF box. value = gst_structure_get_value(protectionMeta->info, "box"); if (!value) { GST_ERROR_OBJECT(self, "Failed to get encryption box for sample"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } box = gst_value_get_buffer(value); boxMapped = gst_buffer_map(box, &boxMap, GST_MAP_READ); if (!boxMapped) { GST_ERROR_OBJECT(self, "Failed to map encryption box"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } GST_TRACE_OBJECT(self, "decrypt sample %u", sampleIndex); if ((errorCode = self->sessionMetaData->decrypt(static_cast<void*>(map.data), static_cast<uint32_t>(map.size), static_cast<void*>(boxMap.data), static_cast<uint32_t>(boxMap.size), static_cast<uint32_t>(sampleIndex), trackID))) { GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode); GST_MEMDUMP_OBJECT(self, "box", boxMap.data, boxMap.size); result = GST_FLOW_ERROR; goto beach; } } else { // Process CENC data. guint ivSize; gboolean encrypted; GstBuffer* ivBuffer = nullptr; GstMapInfo ivMap; unsigned position = 0; unsigned sampleIndex = 0; guint subSampleCount; GstBuffer* subsamplesBuffer = nullptr; GstMapInfo subSamplesMap; GstByteReader* reader = nullptr; if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) { GST_ERROR_OBJECT(self, "failed to get iv_size"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", &encrypted)) { GST_ERROR_OBJECT(self, "failed to get encrypted flag"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } // Unencrypted sample. if (!ivSize || !encrypted) goto beach; if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", &subSampleCount)) { GST_ERROR_OBJECT(self, "failed to get subsample_count"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } value = gst_structure_get_value(protectionMeta->info, "iv"); if (!value) { GST_ERROR_OBJECT(self, "Failed to get IV for sample"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } ivBuffer = gst_value_get_buffer(value); if (!gst_buffer_map(ivBuffer, &ivMap, GST_MAP_READ)) { GST_ERROR_OBJECT(self, "Failed to map IV"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } if (subSampleCount) { value = gst_structure_get_value(protectionMeta->info, "subsamples"); if (!value) { GST_ERROR_OBJECT(self, "Failed to get subsamples"); result = GST_FLOW_NOT_SUPPORTED; goto release; } subsamplesBuffer = gst_value_get_buffer(value); if (!gst_buffer_map(subsamplesBuffer, &subSamplesMap, GST_MAP_READ)) { GST_ERROR_OBJECT(self, "Failed to map subsample buffer"); result = GST_FLOW_NOT_SUPPORTED; goto release; } } reader = gst_byte_reader_new(subSamplesMap.data, subSamplesMap.size); if (!reader) { GST_ERROR_OBJECT(self, "Failed to allocate subsample reader"); result = GST_FLOW_NOT_SUPPORTED; goto release; } GST_DEBUG_OBJECT(self, "position: %d, size: %d", 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)) { result = GST_FLOW_NOT_SUPPORTED; GST_DEBUG_OBJECT(self, "unsupported"); goto release; } sampleIndex++; } else { nBytesClear = 0; nBytesEncrypted = map.size - position; } GST_TRACE_OBJECT(self, "%d bytes clear (todo=%d)", nBytesClear, map.size - position); position += nBytesClear; if (nBytesEncrypted) { GST_TRACE_OBJECT(self, "%d bytes encrypted (todo=%d)", nBytesEncrypted, map.size - position); if ((errorCode = self->sessionMetaData->processPayload(trackID, static_cast<const void*>(ivMap.data), static_cast<uint32_t>(ivMap.size), static_cast<void*>(map.data + position), static_cast<uint32_t>(nBytesEncrypted)))) { GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode); result = GST_FLOW_ERROR; goto release; } position += nBytesEncrypted; } } release: gst_buffer_unmap(ivBuffer, &ivMap); if (reader) gst_byte_reader_free(reader); if (subsamplesBuffer) gst_buffer_unmap(subsamplesBuffer, &subSamplesMap); } beach: if (boxMapped) gst_buffer_unmap(box, &boxMap); if (bufferMapped) gst_buffer_unmap(buffer, &map); if (protectionMeta) gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); GST_TRACE_OBJECT(self, "Unlocking mutex"); g_mutex_unlock(&self->mutex); return result; }
static GstFlowReturn webkitMediaPlayReadyDecryptTransformInPlace(GstBaseTransform* base, GstBuffer* buffer) { WebKitMediaPlayReadyDecrypt* self = WEBKIT_MEDIA_PLAYREADY_DECRYPT(base); GstFlowReturn result = GST_FLOW_OK; GstMapInfo map; const GValue* value; guint sampleIndex = 0; int errorCode; uint32_t trackID = 0; GstPad* pad; GstCaps* caps; GstMapInfo boxMap; GstBuffer* box = nullptr; GstProtectionMeta* protectionMeta = 0; gboolean boxMapped = FALSE; gboolean bufferMapped = FALSE; GST_TRACE_OBJECT(self, "Processing buffer"); g_mutex_lock(&self->mutex); GST_TRACE_OBJECT(self, "Mutex acquired, stream received: %s", self->streamReceived ? "yes":"no"); // The key might not have been received yet. Wait for it. if (!self->streamReceived) g_cond_wait(&self->condition, &self->mutex); if (!self->streamReceived) { GST_DEBUG_OBJECT(self, "Condition signaled from state change transition. Aborting."); result = GST_FLOW_NOT_SUPPORTED; goto beach; } GST_TRACE_OBJECT(self, "Proceeding with decryption"); protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer)); if (!protectionMeta || !buffer) { if (!protectionMeta) GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer); if (!buffer) GST_ERROR_OBJECT(self, "Failed to get writable buffer"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } bufferMapped = gst_buffer_map(buffer, &map, static_cast<GstMapFlags>(GST_MAP_READWRITE)); if (!bufferMapped) { GST_ERROR_OBJECT(self, "Failed to map buffer"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } pad = gst_element_get_static_pad(GST_ELEMENT(self), "src"); caps = gst_pad_get_current_caps(pad); if (g_str_has_prefix(gst_structure_get_name(gst_caps_get_structure(caps, 0)), "video/")) trackID = 1; else trackID = 2; gst_caps_unref(caps); gst_object_unref(pad); if (!gst_structure_get_uint(protectionMeta->info, "sample-index", &sampleIndex)) { GST_ERROR_OBJECT(self, "failed to get sample-index"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } value = gst_structure_get_value(protectionMeta->info, "box"); if (!value) { GST_ERROR_OBJECT(self, "Failed to get encryption box for sample"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } box = gst_value_get_buffer(value); boxMapped = gst_buffer_map(box, &boxMap, GST_MAP_READ); if (!boxMapped) { GST_ERROR_OBJECT(self, "Failed to map encryption box"); result = GST_FLOW_NOT_SUPPORTED; goto beach; } GST_TRACE_OBJECT(self, "decrypt sample %u", sampleIndex); if ((errorCode = self->sessionMetaData->decrypt(static_cast<void*>(map.data), static_cast<uint32_t>(map.size), static_cast<void*>(boxMap.data), static_cast<uint32_t>(boxMap.size), static_cast<uint32_t>(sampleIndex), trackID))) { GST_WARNING_OBJECT(self, "ERROR - packet decryption failed [%d]", errorCode); GST_MEMDUMP_OBJECT(self, "box", boxMap.data, boxMap.size); result = GST_FLOW_ERROR; goto beach; } beach: if (boxMapped) gst_buffer_unmap(box, &boxMap); if (bufferMapped) gst_buffer_unmap(buffer, &map); if (protectionMeta) gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta)); GST_TRACE_OBJECT(self, "Unlocking mutex"); g_mutex_unlock(&self->mutex); return result; }