1255 lines
48 KiB
C
1255 lines
48 KiB
C
// Copyright (C) 2022 Amlogic, Inc. All rights reserved.
|
|
//
|
|
// All information contained herein is Amlogic confidential.
|
|
//
|
|
// This software is provided to you pursuant to Software License
|
|
// Agreement (SLA) with Amlogic Inc ("Amlogic"). This software may be
|
|
// used only in accordance with the terms of this agreement.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification is strictly prohibited without prior written permission
|
|
// from Amlogic.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
|
|
#include <curl/curl.h>
|
|
#include <gst/allocators/gstdmabuf.h>
|
|
#include <gst/base/gstbytereader.h>
|
|
#include <gst/allocators/allocators.h>
|
|
#ifdef SECMEM
|
|
#include <gst/allocators/gstsecmemallocator.h>
|
|
#endif
|
|
#include "gstwlcdmi.h"
|
|
#include "wlcdmi.h"
|
|
#include <assert.h>
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
#define WIDEVINE_SYSTEM_ID "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
|
|
#define PLAYREADY_SYSTEM_ID "9a04f079-9840-4286-ab92-e65be0885f95"
|
|
#define CLEARKEY_SYSTEM_ID "e2719d58-a985-b3c9-781a-b030af78d30e"
|
|
#define VERIMATRIX_SYSTEM_ID "9a27dd82-fde2-4725-8cbc-4234aa06ec09"
|
|
#define PLAYREADY_OLD_SYSTEM_ID "79f0049a-4098-8642-ab92-e65be0885f95"
|
|
|
|
#define IV_SIZE 16
|
|
|
|
#define GST_CAPS_FEATURE_MEMORY_DMABUF "memory:DMABuf"
|
|
|
|
struct _GstWLCdmi {
|
|
GstBaseTransform element;
|
|
WLCdmiSession *session;
|
|
GData *cryptos;
|
|
gchar *license_url;
|
|
gchar *proxy;
|
|
guint key_type;
|
|
gboolean secure;
|
|
gboolean external_allocator;
|
|
GstAllocator *allocator;
|
|
gboolean key_ready;
|
|
gboolean key_error;
|
|
GMutex mutex;
|
|
GCond condition;
|
|
guint audio_valid_type;
|
|
guint scheme;
|
|
gboolean is_vmx;
|
|
guint origin;
|
|
gchar last_iv[IV_SIZE];
|
|
gboolean is_playready;
|
|
gboolean need_parser; // for vp9/av1 link to secure parse
|
|
};
|
|
|
|
struct _GstWLCdmiClass {
|
|
GstBaseTransformClass parent_class;
|
|
|
|
/* Signals */
|
|
GstAllocator* (*need_allocator) (GstWLCdmi *self, gboolean is_4k, uint8_t decoder_format);
|
|
guint* (*custom_event) (GstWLCdmi *self, guint type, gchar *pbEvent, guint cbEvent);
|
|
};
|
|
|
|
|
|
static struct {
|
|
const gchar *system;
|
|
const gchar *scheme;
|
|
} scheme_systems[] = {
|
|
{PLAYREADY_SYSTEM_ID, "application/x-cenc"},
|
|
{WIDEVINE_SYSTEM_ID, "application/x-cenc"},
|
|
{VERIMATRIX_SYSTEM_ID, "application/x-cenc"},
|
|
{PLAYREADY_OLD_SYSTEM_ID, "application/x-cenc"},
|
|
{NULL, "application/x-aes128-cbc"},
|
|
{NULL, "application/x-webm-enc"},
|
|
};
|
|
|
|
static struct {
|
|
const gchar *format;
|
|
const gchar *scheme;
|
|
const gboolean au_byte_stream;
|
|
} scheme_formats[] = {
|
|
{"video/x-h264", "application/x-cenc", FALSE},
|
|
{"video/x-h264", "application/x-aes128-cbc", TRUE},
|
|
{"video/x-h265", "application/x-cenc", FALSE},
|
|
{"video/x-h265", "application/x-aes128-cbc", TRUE},
|
|
{"audio/mpeg", "application/x-cenc", FALSE},
|
|
{"audio/mpeg", "application/x-aes128-cbc", FALSE},
|
|
{"audio/x-ac3", "application/x-aes128-cbc", FALSE},
|
|
{"video/x-vp9", "application/x-webm-enc", FALSE},
|
|
{"video/x-vp9", "application/x-cenc", FALSE},
|
|
{"video/x-av1", "application/x-webm-enc", FALSE},
|
|
{"video/x-av1", "application/x-cenc", FALSE},
|
|
{"video/mpegts", "application/x-aes128-cbc", FALSE},
|
|
{"audio/x-vorbis", "application/x-webm-enc", FALSE},
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_LICENSE_URL,
|
|
PROP_PROXY,
|
|
PROP_KEY_TYPE,
|
|
PROP_EXTERNAL_ALLOCATOR,
|
|
PROP_NEED_PARSER
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_NEED_ALLOCATOR,
|
|
SIGNAL_CUSTOM_EVENT,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_wlcdmi_debug);
|
|
#define GST_CAT_DEFAULT gst_wlcdmi_debug
|
|
|
|
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/mpeg; "
|
|
"audio/x-ac3; "
|
|
"audio/x-vorbis; "
|
|
"video/x-h264; "
|
|
"video/x-h265; "
|
|
"video/x-vp9; "
|
|
"video/x-av1; "
|
|
"video/mpegts; "
|
|
"video/x-h264(" GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY "); "
|
|
"video/x-h265(" GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY "); "
|
|
"video/x-vp9(" GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY "); "
|
|
"video/x-av1(" GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY "); "
|
|
"video/x-vp9(" GST_CAPS_FEATURE_MEMORY_DMABUF "); "
|
|
"video/x-av1(" GST_CAPS_FEATURE_MEMORY_DMABUF "); "
|
|
"video/mpegts(" GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY "); "
|
|
));
|
|
|
|
#define gst_wlcdmi_parent_class parent_class
|
|
G_DEFINE_TYPE(GstWLCdmi, gst_wlcdmi, GST_TYPE_BASE_TRANSFORM);
|
|
|
|
static wlcdmi_callback_t gst_wlcdmi_callback;
|
|
static GstCaps * gst_wlcdmi_supported_caps();
|
|
static void gst_wlcdmi_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_wlcdmi_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_wlcdmi_finalize(GObject *object);
|
|
static gboolean gst_wlcdmi_start(GstBaseTransform *trans);
|
|
static gboolean gst_wlcdmi_stop(GstBaseTransform *trans);
|
|
static GstCaps* gst_wlcdmi_transform_caps(GstBaseTransform *trans,
|
|
GstPadDirection direction,
|
|
GstCaps *caps, GstCaps *filter);
|
|
static gboolean gst_wlcdmi_set_caps(GstBaseTransform *trans, GstCaps *incaps,
|
|
GstCaps *outcaps);
|
|
static GstFlowReturn gst_wlcdmi_transform(GstBaseTransform *trans, GstBuffer *inbuf,
|
|
GstBuffer *outbuf);
|
|
static GstFlowReturn gst_wlcdmi_prepare_output_buffer(GstBaseTransform * trans,
|
|
GstBuffer *input, GstBuffer **outbuf);
|
|
static gboolean gst_wlcdmi_sink_event(GstBaseTransform *trans, GstEvent *event);
|
|
|
|
static guint gst_wlcdmi_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
gst_wlcdmi_class_init (GstWLCdmiClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstElementClass *element_class = (GstElementClass *) klass;
|
|
GstBaseTransformClass *base_class = GST_BASE_TRANSFORM_CLASS(klass);
|
|
|
|
gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_wlcdmi_set_property);
|
|
gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_wlcdmi_get_property);
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_wlcdmi_finalize);
|
|
|
|
base_class->start = GST_DEBUG_FUNCPTR(gst_wlcdmi_start);
|
|
base_class->stop = GST_DEBUG_FUNCPTR(gst_wlcdmi_stop);
|
|
base_class->transform_caps = GST_DEBUG_FUNCPTR(gst_wlcdmi_transform_caps);
|
|
base_class->set_caps = GST_DEBUG_FUNCPTR(gst_wlcdmi_set_caps);
|
|
base_class->transform = GST_DEBUG_FUNCPTR(gst_wlcdmi_transform);
|
|
base_class->prepare_output_buffer = GST_DEBUG_FUNCPTR(gst_wlcdmi_prepare_output_buffer);
|
|
base_class->sink_event = GST_DEBUG_FUNCPTR(gst_wlcdmi_sink_event);
|
|
base_class->passthrough_on_same_caps = FALSE;
|
|
base_class->transform_ip_on_passthrough = FALSE;
|
|
|
|
g_object_class_install_property(gobject_class, PROP_LICENSE_URL,
|
|
g_param_spec_string("license-url", "License url",
|
|
"Set license url",
|
|
NULL, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(gobject_class, PROP_PROXY,
|
|
g_param_spec_string("proxy", "Proxy",
|
|
"Set proxy",
|
|
NULL, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(gobject_class, PROP_KEY_TYPE,
|
|
g_param_spec_uint("key-type", "Key type",
|
|
"Set key type",
|
|
0, 4, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(gobject_class, PROP_EXTERNAL_ALLOCATOR,
|
|
g_param_spec_boolean("external-allocator", "External allocator",
|
|
"Use external allocator",
|
|
FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property(gobject_class, PROP_NEED_PARSER,
|
|
g_param_spec_boolean("need-parser", "Need parser to add head",
|
|
"Whether need to link secure parse for vp9 and av1",
|
|
FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
|
#ifdef SECMEM
|
|
gst_wlcdmi_signals[SIGNAL_NEED_ALLOCATOR] =
|
|
g_signal_new ("need-allocator", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstWLCdmiClass, need_allocator),
|
|
NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_SECMEM_ALLOCATOR,
|
|
2, G_TYPE_BOOLEAN, G_TYPE_UCHAR);
|
|
#endif
|
|
gst_wlcdmi_signals[SIGNAL_CUSTOM_EVENT] =
|
|
g_signal_new ("custom-event", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstWLCdmiClass, custom_event),
|
|
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_UINT,
|
|
3, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT);
|
|
|
|
gst_element_class_set_static_metadata(element_class,
|
|
"Decrypt content encrypted using DRM Encryption",
|
|
GST_ELEMENT_FACTORY_KLASS_DECRYPTOR,
|
|
"Decrypts media that has been encrypted using DRM.",
|
|
"Tao Guo <tao.guo@amlogic.com>");
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&srctemplate));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
gst_wlcdmi_supported_caps ()));
|
|
}
|
|
|
|
static void
|
|
gst_wlcdmi_init(GstWLCdmi * self)
|
|
{
|
|
self->session = NULL;
|
|
self->license_url = NULL;
|
|
self->proxy = NULL;
|
|
self->key_type = 0;
|
|
self->secure = FALSE;
|
|
self->external_allocator = FALSE;
|
|
self->allocator = NULL;
|
|
self->key_ready = FALSE;
|
|
self->key_error = FALSE;
|
|
self->audio_valid_type = 0;
|
|
self->scheme = WLCDMI_SCHEME_DEFAULT;
|
|
self->is_vmx = FALSE;
|
|
self->is_playready = FALSE;
|
|
self->need_parser = FALSE;
|
|
memset(self->last_iv, 0, sizeof(self->last_iv));
|
|
g_mutex_init(&self->mutex);
|
|
g_cond_init(&self->condition);
|
|
g_datalist_init (&self->cryptos);
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(object);
|
|
GST_DEBUG_OBJECT(self, "%s", __func__);
|
|
switch (prop_id) {
|
|
case PROP_LICENSE_URL:
|
|
GST_OBJECT_LOCK(self);
|
|
self->license_url = g_value_dup_string(value);
|
|
GST_INFO("self->license_url %s", self->license_url);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_PROXY:
|
|
GST_OBJECT_LOCK(self);
|
|
self->proxy = g_value_dup_string(value);
|
|
GST_INFO("self->proxy %s", self->proxy);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_KEY_TYPE:
|
|
GST_OBJECT_LOCK(self);
|
|
self->key_type = g_value_get_uint(value);
|
|
GST_INFO("self->key_type %d", self->key_type);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_EXTERNAL_ALLOCATOR:
|
|
GST_OBJECT_LOCK(self);
|
|
self->external_allocator = g_value_get_boolean(value);
|
|
GST_INFO("self->external_allocator %d", self->external_allocator);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_NEED_PARSER:
|
|
GST_OBJECT_LOCK(self);
|
|
self->need_parser = g_value_get_boolean(value);
|
|
GST_INFO("self->need_parser %d", self->need_parser);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(object);
|
|
GST_DEBUG_OBJECT(self, "%s", __func__);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LICENSE_URL:
|
|
GST_OBJECT_LOCK(self);
|
|
g_value_set_string(value, self->license_url);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_PROXY:
|
|
GST_OBJECT_LOCK(self);
|
|
g_value_set_string(value, self->proxy);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_KEY_TYPE:
|
|
GST_OBJECT_LOCK(self);
|
|
g_value_set_uint(value, self->key_type);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_EXTERNAL_ALLOCATOR:
|
|
GST_OBJECT_LOCK(self);
|
|
g_value_set_boolean(value, self->external_allocator);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
case PROP_NEED_PARSER:
|
|
GST_OBJECT_LOCK(self);
|
|
g_value_set_boolean(value, self->need_parser);
|
|
GST_OBJECT_UNLOCK(self);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_finalize(GObject *object)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(object);
|
|
GST_DEBUG_OBJECT(self, "%s", __func__);
|
|
g_free(self->license_url);
|
|
self->license_url = NULL;
|
|
g_free(self->proxy);
|
|
self->proxy = NULL;
|
|
}
|
|
|
|
gboolean
|
|
gst_wlcdmi_start(GstBaseTransform *trans)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
GST_DEBUG_OBJECT(self, "%s", __func__);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_wlcdmi_stop(GstBaseTransform *trans)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
GST_DEBUG_OBJECT(self, "%s", __func__);
|
|
g_datalist_clear(&self->cryptos);
|
|
if (self->session)
|
|
wlcdmi_session_close(self->session);
|
|
if (self->allocator && !self->external_allocator)
|
|
gst_object_unref(self->allocator);
|
|
g_cond_clear(&self->condition);
|
|
g_mutex_clear(&self->mutex);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_wlcdmi_crypto_destroy(gpointer data)
|
|
{
|
|
WLCdmiCrypto *crypto = (WLCdmiCrypto *)data;
|
|
GST_INFO("destroy crypto");
|
|
wlcdmi_crypto_close(crypto);
|
|
}
|
|
|
|
static void
|
|
gst_wlcdmi_caps_add_unique_structure(GstCaps *caps, GstStructure *in)
|
|
{
|
|
guint size = gst_caps_get_size(caps);
|
|
gboolean dup = FALSE;
|
|
for (guint i = 0; i < size; i++) {
|
|
GstStructure *structure = gst_caps_get_structure(caps, i);
|
|
if (gst_structure_is_equal(structure, in)) {
|
|
dup = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (dup) {
|
|
gst_structure_free(in);
|
|
} else {
|
|
gst_caps_append_structure(caps, in);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_wlcdmi_caps_add_protect_system(GstCaps *caps, GstStructure *in)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(scheme_systems); i++) {
|
|
if (strstr(scheme_systems[i].scheme, gst_structure_get_name(in))) {
|
|
GstStructure *tmp = gst_structure_copy(in);
|
|
if (scheme_systems[i].system) {
|
|
gst_structure_set (tmp,
|
|
"protection-system", G_TYPE_STRING, scheme_systems[i].system, NULL);
|
|
}
|
|
gst_wlcdmi_caps_add_unique_structure(caps, tmp);
|
|
}
|
|
}
|
|
gst_structure_free(in);
|
|
}
|
|
|
|
static void
|
|
gst_wlcdmi_caps_add_format(GstCaps *caps, GstStructure *in)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(scheme_formats); i++) {
|
|
if (!strcmp(scheme_formats[i].format, gst_structure_get_name (in))) {
|
|
GstStructure *tmp = gst_structure_copy(in);
|
|
gst_structure_set (tmp,
|
|
"original-media-type", G_TYPE_STRING, scheme_formats[i].format,
|
|
NULL);
|
|
if (scheme_formats[i].au_byte_stream) {
|
|
gst_structure_set (tmp,
|
|
"stream-format", G_TYPE_STRING, "byte-stream",
|
|
"alignment", G_TYPE_STRING, "au",
|
|
"parsed", G_TYPE_BOOLEAN, TRUE,
|
|
NULL);
|
|
}
|
|
gst_structure_set_name(tmp, scheme_formats[i].scheme);
|
|
gst_wlcdmi_caps_add_protect_system(caps, tmp);
|
|
}
|
|
}
|
|
gst_structure_free(in);
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_wlcdmi_supported_caps()
|
|
{
|
|
GstCaps* caps = gst_caps_new_empty();
|
|
for (int i = 0; i < ARRAY_SIZE(scheme_formats); i++) {
|
|
GstStructure *tmp = gst_structure_new(scheme_formats[i].scheme,
|
|
"original-media-type", G_TYPE_STRING, scheme_formats[i].format,
|
|
NULL);
|
|
if (scheme_formats[i].au_byte_stream) {
|
|
gst_structure_set (tmp,
|
|
"stream-format", G_TYPE_STRING, "byte-stream",
|
|
"alignment", G_TYPE_STRING, "au",
|
|
"parsed", G_TYPE_BOOLEAN, TRUE,
|
|
NULL);
|
|
}
|
|
gst_wlcdmi_caps_add_protect_system(caps, tmp);
|
|
}
|
|
return caps;
|
|
}
|
|
|
|
GstCaps*
|
|
gst_wlcdmi_transform_caps(GstBaseTransform *trans,
|
|
GstPadDirection direction,
|
|
GstCaps *caps, GstCaps *filter)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
#if 1
|
|
GstCaps *srccaps = NULL, *sinkcaps = NULL;
|
|
srccaps = gst_pad_get_pad_template_caps(GST_BASE_TRANSFORM_SRC_PAD(trans));
|
|
sinkcaps = gst_pad_get_pad_template_caps(GST_BASE_TRANSFORM_SINK_PAD(trans));
|
|
#endif
|
|
GstCaps* transformed_caps = gst_caps_new_empty();
|
|
|
|
GST_DEBUG_OBJECT (self, "transform_caps direction:%d caps:%" GST_PTR_FORMAT
|
|
" filter: %" GST_PTR_FORMAT, direction, caps, filter);
|
|
|
|
guint size = gst_caps_get_size(caps);
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
GstStructure *in = gst_caps_get_structure(caps, i);
|
|
GstStructure *out = NULL;
|
|
if (direction == GST_PAD_SINK) {
|
|
if (!gst_structure_has_field(in, "original-media-type"))
|
|
continue;
|
|
out = gst_structure_copy(in);
|
|
gst_structure_set_name(out, gst_structure_get_string(out, "original-media-type"));
|
|
gst_structure_remove_fields(out, "original-media-type", "protection-system",
|
|
"encryption-algorithm", "cipher-mode", "encoding-scope", NULL);
|
|
gst_wlcdmi_caps_add_unique_structure(transformed_caps, out);
|
|
} else if (direction == GST_PAD_SRC) {
|
|
out = gst_structure_copy(in);
|
|
gst_structure_remove_fields(out, "base-profile", "profile", "codec_data", "rate",
|
|
"height", "width", "framerate", "level", "pixel-aspect-ratio",
|
|
"mpegversion", "channels", "framed", "stream-format", NULL);
|
|
gst_wlcdmi_caps_add_format(transformed_caps, out);
|
|
}
|
|
}
|
|
|
|
#ifdef SECMEM
|
|
if (direction == GST_PAD_SINK) {
|
|
guint nb_caps = gst_caps_get_size(transformed_caps);
|
|
for (guint i = 0; i < nb_caps; ++i) {
|
|
GstStructure *st = gst_caps_get_structure(transformed_caps, i);
|
|
const gchar *name = gst_structure_get_name(st);
|
|
if (g_str_has_prefix (name, "video/")) {
|
|
if ((g_str_has_prefix(name, "video/x-vp9") || g_str_has_prefix(name, "video/x-av1"))
|
|
&& !self->need_parser) {
|
|
gst_caps_set_features(transformed_caps, i,
|
|
gst_caps_features_from_string(GST_CAPS_FEATURE_MEMORY_DMABUF));
|
|
} else {
|
|
gst_caps_set_features(transformed_caps, i,
|
|
gst_caps_features_from_string(GST_CAPS_FEATURE_MEMORY_SECMEM_MEMORY));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if 1
|
|
if (gst_caps_is_empty(caps)) {
|
|
if (direction == GST_PAD_SINK)
|
|
gst_caps_replace(&transformed_caps, gst_caps_copy(srccaps));
|
|
else if (direction == GST_PAD_SRC)
|
|
gst_caps_replace(&transformed_caps, gst_caps_copy(sinkcaps));
|
|
}
|
|
#endif
|
|
|
|
if (filter) {
|
|
GstCaps *intersection = NULL;
|
|
GST_LOG_OBJECT(self, "Using filter caps %" GST_PTR_FORMAT, filter);
|
|
intersection = gst_caps_intersect_full(transformed_caps, filter, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_replace(&transformed_caps, intersection);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "transformed_caps direction:%d return:%" GST_PTR_FORMAT,
|
|
direction, transformed_caps);
|
|
return transformed_caps;
|
|
}
|
|
|
|
gboolean
|
|
gst_wlcdmi_set_caps(GstBaseTransform *trans, GstCaps *incaps,
|
|
GstCaps *outcaps)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
GST_INFO_OBJECT (self, "set_caps incaps:%" GST_PTR_FORMAT
|
|
" outcaps:%" GST_PTR_FORMAT, incaps, outcaps);
|
|
|
|
guint size = gst_caps_get_size(incaps);
|
|
for (int i = 0; i < size; i++) {
|
|
GstStructure *in_structure = gst_caps_get_structure(incaps, i);
|
|
if (!gst_structure_has_field(in_structure, "original-media-type"))
|
|
continue;
|
|
const gchar* media_type = gst_structure_get_string(in_structure, "original-media-type");
|
|
|
|
if (!gst_structure_has_field(in_structure, "cipher-mode")) {
|
|
GST_WARNING_OBJECT (self, "no cipher-mode");
|
|
} else {
|
|
gchar* pbCipherMode = (gchar *)gst_structure_get_string(in_structure, "cipher-mode");
|
|
if (pbCipherMode) {
|
|
if (strcasecmp(pbCipherMode, "cenc") == 0)
|
|
self->scheme = WLCDMI_SCHEME_CENC;
|
|
else if (strcasecmp(pbCipherMode, "cens") == 0)
|
|
self->scheme = WLCDMI_SCHEME_CENS;
|
|
else if (strcasecmp(pbCipherMode, "cbc1") == 0)
|
|
self->scheme = WLCDMI_SCHEME_CBC1;
|
|
else if (strcasecmp(pbCipherMode, "cbcs") == 0)
|
|
self->scheme = WLCDMI_SCHEME_CBCS;
|
|
else
|
|
self->scheme = WLCDMI_SCHEME_DEFAULT;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(self, "scheme %d", self->scheme);
|
|
#ifdef SECMEM
|
|
if (media_type && g_str_has_prefix (media_type, "audio/")) {
|
|
|
|
if (g_str_has_prefix (media_type, "audio/mpeg")) {
|
|
self->audio_valid_type = AUD_VALID_TYPE_AAC_ADTS;
|
|
} else if (g_str_has_prefix (media_type, "audio/x-ac3")) {
|
|
self->audio_valid_type = AUD_VALID_TYPE_AC3;
|
|
}
|
|
GST_DEBUG_OBJECT (self, "audio_valid_type:%d ", self->audio_valid_type);
|
|
}
|
|
uint8_t format = SECMEM_CODEC_DEFAULT;
|
|
if (media_type && g_str_has_prefix (media_type, "video/")) {
|
|
self->secure = TRUE;
|
|
if (g_str_has_prefix (media_type, "video/x-vp9")) {
|
|
format = SECMEM_DECODER_VP9;
|
|
} else if (g_str_has_prefix (media_type, "video/x-av1")) {
|
|
format = SECMEM_DECODER_AV1;
|
|
} else if (g_str_has_prefix (media_type, "video/mpegts")) {
|
|
format = SECMEM_DECODER_MPEGTS;
|
|
}
|
|
}
|
|
if (self->secure && !self->allocator) {
|
|
gboolean is_4k = FALSE;
|
|
int width = 0, height = 0;
|
|
gst_structure_get_int(in_structure, "width", &width);
|
|
gst_structure_get_int(in_structure, "height", &height);
|
|
if ((width * height) > (1920 * 1088))
|
|
is_4k = TRUE;
|
|
GST_DEBUG_OBJECT(self, "allocator %d", self->scheme);
|
|
if (self->external_allocator) {
|
|
g_signal_emit (self, gst_wlcdmi_signals[SIGNAL_NEED_ALLOCATOR], 0, is_4k, format, &self->allocator);
|
|
} else {
|
|
self->allocator = gst_secmem_allocator_new(is_4k, format);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_wlcdmi_transform(GstBaseTransform *trans, GstBuffer *inbuf,
|
|
GstBuffer *outbuf)
|
|
{
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
GstProtectionMeta *meta = NULL;
|
|
|
|
if (!inbuf) {
|
|
GST_DEBUG_OBJECT(self, "Failed to get writable buffer");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
meta = gst_buffer_get_protection_meta(inbuf);
|
|
if (meta) {
|
|
guint iv_size = 0;
|
|
gboolean encrypted = FALSE;
|
|
guint subsample_count = 0;
|
|
const GValue *value = NULL;
|
|
GstBuffer *kid = NULL;
|
|
GstBuffer *iv_buffer = NULL;
|
|
GstBuffer *subsamples = NULL;
|
|
GstMemory *mem = NULL;
|
|
int mem_fd = -1;
|
|
|
|
if (self->allocator) {
|
|
mem = gst_buffer_peek_memory(outbuf, 0);
|
|
mem_fd = gst_dmabuf_memory_get_fd(mem);
|
|
if (mem_fd < 0) {
|
|
GST_ERROR_OBJECT(self, "got invalid fd %d mem %p", mem_fd, mem);
|
|
result = GST_FLOW_NOT_SUPPORTED;
|
|
goto beach;
|
|
}
|
|
}
|
|
// GST_INFO_OBJECT(self, gst_structure_to_string(meta->info));
|
|
if (!gst_structure_get_uint(meta->info, "iv_size", &iv_size)) {
|
|
GST_ERROR_OBJECT(self, "failed to get iv_size");
|
|
result = GST_FLOW_NOT_SUPPORTED;
|
|
goto beach;
|
|
}
|
|
|
|
if (!gst_structure_get_boolean(meta->info, "encrypted", &encrypted)) {
|
|
GST_ERROR_OBJECT(self, "failed to get encrypted flag");
|
|
result = GST_FLOW_NOT_SUPPORTED;
|
|
goto beach;
|
|
}
|
|
if (!encrypted || (iv_size != 8 && iv_size != 16)) {
|
|
GST_ERROR_OBJECT(self, "incorrect encrypted flag %d invalid iv_size %d ", encrypted, iv_size);
|
|
goto beach;
|
|
}
|
|
|
|
if (!self->key_ready) {
|
|
gint64 end_time = -1;
|
|
g_mutex_lock(&self->mutex);
|
|
|
|
end_time = g_get_monotonic_time() + 10 * G_TIME_SPAN_SECOND;
|
|
if (!g_cond_wait_until(&self->condition, &self->mutex, end_time))
|
|
{
|
|
g_mutex_unlock(&self->mutex);
|
|
GST_ERROR_OBJECT(self, "failed to get key ready status");
|
|
result = GST_FLOW_ERROR;
|
|
goto beach;
|
|
}
|
|
g_mutex_unlock(&self->mutex);
|
|
}
|
|
|
|
if (self->key_error) {
|
|
GST_ERROR_OBJECT(self, "decrypt failed: key error");
|
|
result = GST_FLOW_ERROR;
|
|
goto beach;
|
|
}
|
|
|
|
value = gst_structure_get_value(meta->info, "kid");
|
|
if (!value) {
|
|
GST_ERROR_OBJECT(self, "failed to get kid for sample");
|
|
result = GST_FLOW_NOT_SUPPORTED;
|
|
goto beach;
|
|
}
|
|
kid = gst_value_get_buffer(value);
|
|
|
|
value = gst_structure_get_value(meta->info, "iv");
|
|
if (!value) {
|
|
GST_ERROR_OBJECT(self, "failed to get IV for sample");
|
|
result = GST_FLOW_NOT_SUPPORTED;
|
|
goto beach;
|
|
}
|
|
iv_buffer = gst_value_get_buffer(value);
|
|
|
|
if (!gst_structure_get_uint(meta->info, "subsample_count", &subsample_count)) {
|
|
subsample_count = 0;
|
|
}
|
|
if (subsample_count > 0 ) {
|
|
value = gst_structure_get_value(meta->info, "subsamples");
|
|
if (value) {
|
|
subsamples = gst_value_get_buffer(value);
|
|
} else {
|
|
GST_INFO_OBJECT(self, "no value");
|
|
}
|
|
} else {
|
|
GST_DEBUG_OBJECT(self, "single subsample");
|
|
subsample_count = 1;
|
|
}
|
|
|
|
GstMapInfo map_data = GST_MAP_INFO_INIT;
|
|
uint32_t *subsample_array = NULL;
|
|
|
|
do {
|
|
GstMapInfo map_iv = GST_MAP_INFO_INIT;
|
|
GstMapInfo map_kid = GST_MAP_INFO_INIT;
|
|
gst_buffer_map(inbuf, &map_data, GST_MAP_READWRITE);
|
|
subsample_array = malloc(subsample_count * sizeof(uint32_t) * 2);
|
|
if (!subsample_array) {
|
|
GST_ERROR_OBJECT(self, "alloc subsample array failed");
|
|
break;
|
|
}
|
|
|
|
if (subsamples) {
|
|
GstMapInfo map_subsample = GST_MAP_INFO_INIT;
|
|
guint16 clear = 0;
|
|
guint32 enc = 0;
|
|
|
|
gst_buffer_map(subsamples, &map_subsample, GST_MAP_READ);
|
|
GstByteReader* reader = gst_byte_reader_new(map_subsample.data, map_subsample.size);
|
|
for (guint i = 0; i < subsample_count; i++) {
|
|
gst_byte_reader_get_uint16_be(reader, &clear);
|
|
gst_byte_reader_get_uint32_be(reader, &enc);
|
|
GST_DEBUG_OBJECT(self, "subsample %d %d", clear, enc);
|
|
subsample_array[i * 2] = clear;
|
|
subsample_array[i * 2 + 1] = enc;
|
|
}
|
|
gst_buffer_unmap(subsamples, &map_subsample);
|
|
} else {
|
|
GST_DEBUG_OBJECT(self, "single subsample");
|
|
subsample_array[0] = 0;
|
|
subsample_array[1] = map_data.size;
|
|
}
|
|
|
|
uint32_t pattern_array[2] = {0};
|
|
if (!gst_structure_get_uint(meta->info, "crypt_byte_block", &pattern_array[0])) {
|
|
GST_DEBUG_OBJECT(self, "use default crypt block 0");
|
|
}
|
|
if (!gst_structure_get_uint(meta->info, "skip_byte_block", &pattern_array[1])) {
|
|
GST_DEBUG_OBJECT(self, "use default skip block 0");
|
|
}
|
|
GST_DEBUG_OBJECT(self, "pattern %d %d", pattern_array[0], pattern_array[1]);
|
|
do {
|
|
gst_buffer_map(kid, &map_kid, GST_MAP_READ);
|
|
gchar *key = g_base64_encode(map_kid.data, map_kid.size);
|
|
GQuark quark = g_quark_from_string(key);
|
|
|
|
WLCdmiCrypto *crypto = g_datalist_id_get_data(&self->cryptos, quark);
|
|
|
|
guint64 offset = GST_BUFFER_OFFSET(inbuf);
|
|
gst_buffer_map(iv_buffer, &map_iv, GST_MAP_READ);
|
|
GST_DEBUG_OBJECT(self, "buffer offset %" PRId64 ", ivsize=%zd, datasize=%zd, data %p.", offset, map_iv.size, map_data.size, map_data.data);
|
|
GstCaps *sinkcaps = gst_pad_get_current_caps (trans->sinkpad);
|
|
GstStructure *structure = gst_caps_get_structure (sinkcaps, 0);
|
|
if (self->scheme == WLCDMI_SCHEME_CBC1 && gst_structure_has_name (structure, "application/x-aes128-cbc")) {
|
|
if (offset == 0)
|
|
memcpy(self->last_iv, map_iv.data, map_iv.size);
|
|
} else {
|
|
memcpy(self->last_iv, map_iv.data, map_iv.size);
|
|
}
|
|
|
|
gchar tmp_iv[IV_SIZE];
|
|
if (self->scheme == WLCDMI_SCHEME_CBC1 && gst_structure_has_name (structure, "application/x-aes128-cbc")) {
|
|
memcpy(tmp_iv, map_data.data + map_data.size - map_iv.size, map_iv.size);
|
|
}
|
|
|
|
if (crypto) {
|
|
wlcdmi_crypto_decrypt_pattern(crypto, map_iv.size, (uint8_t *)self->last_iv, subsample_count,
|
|
subsample_array, pattern_array, map_data.size, (uint8_t *)map_data.data, mem_fd);
|
|
} else {
|
|
GST_WARNING_OBJECT(self, "crypto not exist in datalist, create new now");
|
|
crypto = wlcdmi_session_open_crypto(self->session, map_kid.data, map_kid.size, self->secure);
|
|
g_datalist_id_set_data_full(&self->cryptos, quark, crypto, gst_wlcdmi_crypto_destroy);
|
|
wlcdmi_crypto_decrypt_pattern(crypto, map_iv.size, (uint8_t *)self->last_iv, subsample_count,
|
|
subsample_array, pattern_array, map_data.size, (uint8_t *)map_data.data, mem_fd);
|
|
}
|
|
if (self->scheme == WLCDMI_SCHEME_CBC1 && gst_structure_has_name (structure, "application/x-aes128-cbc")) {
|
|
memcpy(self->last_iv, tmp_iv, IV_SIZE);
|
|
}
|
|
gst_buffer_unmap(iv_buffer, &map_iv);
|
|
|
|
g_free(key);
|
|
gst_buffer_unmap(kid, &map_kid);
|
|
} while(0);
|
|
|
|
} while (0);
|
|
|
|
if (subsample_array)
|
|
free(subsample_array);
|
|
gst_buffer_unmap(inbuf, &map_data);
|
|
|
|
} else {
|
|
GST_LOG_OBJECT(self, "no meta");
|
|
#ifdef SECMEM
|
|
if (self->allocator)
|
|
gst_buffer_copy_to_secmem(outbuf, inbuf);
|
|
#endif
|
|
}
|
|
beach:
|
|
return result;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_wlcdmi_prepare_output_buffer(GstBaseTransform * trans,
|
|
GstBuffer *input, GstBuffer **outbuf)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
if (self->allocator) {
|
|
*outbuf = gst_buffer_new_allocate(self->allocator, gst_buffer_get_size(input), NULL);
|
|
g_return_val_if_fail(*outbuf != NULL, GST_FLOW_ERROR);
|
|
GST_DEBUG_OBJECT(self, "alloc from %p size:%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT "",
|
|
self->allocator, gst_buffer_get_size(input), gst_buffer_get_size(*outbuf));
|
|
GST_BASE_TRANSFORM_CLASS(parent_class)->copy_metadata (trans, input, *outbuf);
|
|
} else if (self->secure) {
|
|
GST_ERROR_OBJECT(self, "no secure allocator");
|
|
return GST_FLOW_ERROR;
|
|
} else {
|
|
*outbuf = input;
|
|
}
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_set_parameter(GstBaseTransform * trans)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
if (self->session) {
|
|
GST_INFO_OBJECT(self, "set parameter audio_valid_type=%d,license_url=%s ",
|
|
self->audio_valid_type, self->license_url);
|
|
if (self->audio_valid_type)
|
|
wlcdmi_session_set_parameter(self->session, WLCDMI_PARAM_AUDIO_CODEC_TYPE,
|
|
(uint8_t *)&self->audio_valid_type, sizeof(guint));
|
|
|
|
if (self->license_url) {
|
|
if (self->is_vmx) {
|
|
wlcdmi_session_set_parameter(self->session, WLCDMI_PARAM_VMX_SERVER_ADDRESS,
|
|
(uint8_t *)self->license_url, strlen(self->license_url) + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_wlcdmi_sink_event(GstBaseTransform *trans, GstEvent *event)
|
|
{
|
|
gboolean result = FALSE;
|
|
GstWLCdmi *self = GST_WLCDMI(trans);
|
|
const gchar *system_id = NULL;
|
|
GstBuffer *data = NULL;
|
|
const gchar *origin = NULL;
|
|
|
|
switch (GST_EVENT_TYPE(event)) {
|
|
case GST_EVENT_PROTECTION:
|
|
gst_event_parse_protection (event, &system_id, &data, &origin);
|
|
GST_INFO_OBJECT(self, "received protection event: system id=%s, origin=%s", system_id, origin);
|
|
gchar *system_id_lower = NULL;
|
|
if (system_id) {
|
|
system_id_lower = g_ascii_strdown(system_id, -1);
|
|
system_id = system_id_lower;
|
|
}
|
|
// gst_util_dump_buffer(data);
|
|
if (!self->session) {
|
|
GstMapInfo map = GST_MAP_INFO_INIT;
|
|
gst_buffer_map(data, &map, GST_MAP_READ);
|
|
if (system_id != NULL) {
|
|
if (!strcmp(system_id, VERIMATRIX_SYSTEM_ID)) {
|
|
self->is_vmx = TRUE;
|
|
} else if (!strcmp(system_id, PLAYREADY_SYSTEM_ID) || !strcmp(system_id, PLAYREADY_OLD_SYSTEM_ID)) {
|
|
self->is_playready = TRUE;
|
|
system_id = "playready";
|
|
}
|
|
}
|
|
|
|
if (!strcmp(origin, "dash/mpd")) {
|
|
const char *init_data_start = NULL, *init_data_end = NULL;
|
|
init_data_start = strstr((const char *)map.data, "<cenc:pssh>");
|
|
init_data_end = strstr((const char *)map.data, "</cenc:pssh>");
|
|
if (init_data_start && init_data_end) {
|
|
gsize init_data_length = 0;
|
|
char *init_data = g_strndup(init_data_start + 11,
|
|
init_data_end - init_data_start - 11);
|
|
|
|
if (init_data) {
|
|
g_base64_decode_inplace(init_data, &init_data_length);
|
|
self->session = wlcdmi_open_session(system_id, self->key_type, self->scheme,
|
|
(uint8_t *)init_data, init_data_length, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
g_free(init_data);
|
|
}
|
|
} else {
|
|
init_data_start = strstr((const char *)map.data, "<mspr:pro>");
|
|
init_data_end = strstr((const char *)map.data, "</mspr:pro>");
|
|
if (init_data_start && init_data_end) {
|
|
gsize init_data_length = 0;
|
|
char *init_data = g_strndup(init_data_start + 10, init_data_end - init_data_start - 10);
|
|
if (init_data) {
|
|
g_base64_decode_inplace(init_data, &init_data_length);
|
|
self->session = wlcdmi_open_session(system_id, self->key_type, self->scheme,
|
|
(uint8_t *)init_data, init_data_length, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
g_free(init_data);
|
|
}
|
|
}
|
|
}
|
|
} else if (!strcmp(origin, "isobmff/moov")) {
|
|
self->session = wlcdmi_open_session(system_id, self->key_type, self->scheme,
|
|
(uint8_t *)map.data, map.size, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
} else if (!strcmp(origin, "hls/sample-aes")) {
|
|
const char *init_data_start = NULL, *init_data_end = NULL;
|
|
init_data_start = strstr((const char *)map.data, "data:text/plain;base64,");
|
|
if (init_data_start)
|
|
init_data_end = strchr(init_data_start, '"');
|
|
if (init_data_start && init_data_end) {
|
|
gsize init_data_length = 0;
|
|
char *init_data = g_strndup(init_data_start + 23,
|
|
init_data_end - init_data_start - 23);
|
|
|
|
if (init_data) {
|
|
g_base64_decode_inplace(init_data, &init_data_length);
|
|
self->session = wlcdmi_open_session(system_id, self->key_type, self->scheme,
|
|
(uint8_t *)init_data, init_data_length, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
g_free(init_data);
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT(self, "sampleAES:null protection system id: use default vmx system");
|
|
self->is_vmx = TRUE;
|
|
self->session = wlcdmi_open_session("verimatrix", self->key_type, self->scheme,
|
|
(uint8_t *)map.data, map.size, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
}
|
|
} else if (strcmp(origin, "hls/aes-128") == 0) {
|
|
GST_INFO_OBJECT(self, "aes128:null protection system id: use default vmx system");
|
|
self->is_vmx = TRUE;
|
|
self->session = wlcdmi_open_session("verimatrix", self->key_type, self->scheme,
|
|
(uint8_t *)map.data, map.size, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
} else if (!strcmp(origin, "matroskademux")) {
|
|
GST_WARNING_OBJECT(self, "widevine matroskademux");
|
|
self->session = wlcdmi_open_session("widevine", self->key_type, self->scheme,
|
|
(uint8_t *)map.data, map.size, NULL, 0, &gst_wlcdmi_callback, self);
|
|
gst_wlcdmi_set_parameter(trans);
|
|
wlcdmi_session_run(self->session);
|
|
}
|
|
gst_buffer_unmap(data, &map);
|
|
}
|
|
gst_event_unref(event);
|
|
if (system_id_lower) {
|
|
g_free(system_id_lower);
|
|
}
|
|
result = TRUE;
|
|
break;
|
|
case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
|
|
default:
|
|
result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static size_t gst_wlcdmi_curl_data_callback(void *contents, size_t size, size_t nmemb, GstBuffer *response)
|
|
{
|
|
size_t newLength = size * nmemb;
|
|
GstBuffer *chunk = gst_buffer_new_and_alloc(newLength);
|
|
|
|
if (chunk) {
|
|
gst_buffer_fill(chunk, 0, contents, newLength);
|
|
gst_buffer_append(response, chunk);
|
|
}
|
|
return newLength;
|
|
}
|
|
|
|
static gpointer gst_wlcdmi_key_message_async_task(gpointer data)
|
|
{
|
|
CURL *curl = curl_easy_init();
|
|
CURLcode code = CURLE_OK;
|
|
GstMapInfo map = GST_MAP_INFO_INIT;
|
|
WLCdmiSession *session = NULL;
|
|
GstBuffer *request = NULL;
|
|
GstStructure *structure = (GstStructure *)data;
|
|
GstBuffer *response = gst_buffer_new();
|
|
struct curl_slist *headers = NULL;
|
|
const gchar *url = gst_structure_get_string(structure, "license-url");
|
|
const gchar *proxy = gst_structure_get_string(structure, "proxy");
|
|
const GValue *value = gst_structure_get_value(structure, "request");
|
|
gboolean playready;
|
|
|
|
if (!value) {
|
|
GST_ERROR("no request");
|
|
goto beach;
|
|
}
|
|
|
|
request = gst_value_get_buffer(value);
|
|
value = gst_structure_get_value(structure, "session");
|
|
if (!value) {
|
|
GST_ERROR("no session");
|
|
goto beach;
|
|
}
|
|
session = g_value_get_object(value);
|
|
|
|
if (!curl) {
|
|
GST_ERROR("curl is none");
|
|
goto beach;
|
|
}
|
|
|
|
gst_buffer_map(request, &map, GST_MAP_READ);
|
|
if (gst_structure_get_boolean(structure, "playready", &playready) && playready) {
|
|
headers = curl_slist_append(headers, "Content-Type: text/xml");
|
|
headers = curl_slist_append(headers, "SOAPAction: http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense");
|
|
} else {
|
|
headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, map.data);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, map.size);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, gst_wlcdmi_curl_data_callback);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
|
|
// timeout
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
|
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
|
|
|
|
if (proxy && strlen(proxy))
|
|
curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
|
|
|
|
GST_INFO("send key request url %s\n", url);
|
|
if (proxy)
|
|
GST_INFO("send key request proxy %s\n", proxy);
|
|
|
|
GST_INFO("send key request map.size %d map.data %s\n", map.size, g_base64_encode(map.data, map.size));
|
|
|
|
code = curl_easy_perform(curl);
|
|
gst_buffer_unmap(request, &map);
|
|
if (code != CURLE_OK) {
|
|
if (code == CURLE_OPERATION_TIMEDOUT) {
|
|
GST_ERROR("curl timeout when requesting \"%s\"", url);
|
|
} else if (code == CURLE_COULDNT_CONNECT) {
|
|
GST_ERROR("curl connect timeout \"%s\"", url);
|
|
} else {
|
|
GST_ERROR("curl failed \"%s\", code: %d, msg: %s", url, code, curl_easy_strerror(code));
|
|
}
|
|
goto beach;
|
|
}
|
|
|
|
if (gst_buffer_get_size(response) == 0) {
|
|
GST_ERROR("error response");
|
|
goto beach;
|
|
}
|
|
|
|
gst_buffer_map(response, &map, GST_MAP_READ);
|
|
GST_INFO("process key response map.size %d \t %s\n", map.size, g_base64_encode(map.data, map.size));
|
|
wlcdmi_session_update(session, map.data, map.size);
|
|
gst_buffer_unmap(response, &map);
|
|
|
|
beach:
|
|
gst_structure_free(structure);
|
|
if (curl)
|
|
curl_easy_cleanup(curl);
|
|
if (headers)
|
|
curl_slist_free_all(headers);
|
|
if (session)
|
|
g_object_unref(session);
|
|
if (request)
|
|
gst_buffer_unref(request);
|
|
gst_buffer_unref(response);
|
|
g_thread_exit(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
#if (GST_VERSION_MAJOR <= 1 && GST_VERSION_MINOR < 20)
|
|
GstBuffer *
|
|
gst_buffer_new_memdup (gconstpointer data, gsize size)
|
|
{
|
|
gpointer data2 = g_memdup (data, size);
|
|
|
|
return gst_buffer_new_wrapped_full (0, data2, size, 0, size, data2, g_free);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
gst_wlcdmi_key_message(WLCdmiSession *session,
|
|
void* pUserData,
|
|
const char *pbUrl,
|
|
const uint8_t *pbChallenge,
|
|
const uint32_t cbChallenge)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(pUserData);
|
|
const gchar *url = NULL;
|
|
|
|
GST_INFO("handle key request");
|
|
|
|
if (self->is_vmx)
|
|
goto beach;
|
|
|
|
if (self->license_url && strlen(self->license_url)) {
|
|
url = self->license_url;
|
|
} else if (pbUrl && strlen(pbUrl)) {
|
|
url = pbUrl;
|
|
} else {
|
|
GST_ERROR("no license url");
|
|
goto beach;
|
|
}
|
|
|
|
GstStructure *data = gst_structure_new_empty("key-request");
|
|
GstBuffer *request = gst_buffer_new_memdup(pbChallenge, cbChallenge);
|
|
|
|
gst_structure_set(data,
|
|
"license-url", G_TYPE_STRING, url,
|
|
"proxy", G_TYPE_STRING, self->proxy,
|
|
"session", G_TYPE_OBJECT, g_object_ref(session),
|
|
"request", GST_TYPE_BUFFER, request,
|
|
"playready", G_TYPE_BOOLEAN, self->is_playready,
|
|
NULL);
|
|
|
|
g_thread_new("fetch_key", gst_wlcdmi_key_message_async_task, data);
|
|
|
|
beach:
|
|
return;
|
|
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_key_status(WLCdmiSession *session,
|
|
void* pUserData,
|
|
const char *pbStatus,
|
|
const uint8_t *pbKeyId,
|
|
const uint8_t cbKeyId)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(pUserData);
|
|
gchar *key = g_base64_encode(pbKeyId, cbKeyId);
|
|
|
|
GQuark quark = g_quark_from_string(key);
|
|
WLCdmiCrypto *crypto = g_datalist_id_get_data(&self->cryptos, quark);
|
|
if (!strcmp("KeyUsable", pbStatus)) {
|
|
if (!crypto) {
|
|
crypto = wlcdmi_session_open_crypto(session, pbKeyId, cbKeyId, self->secure);
|
|
g_datalist_id_set_data_full(&self->cryptos, quark, crypto, gst_wlcdmi_crypto_destroy);
|
|
}
|
|
} else if (!strcmp("KeyReleased", pbStatus)) {
|
|
if (crypto) {
|
|
g_datalist_id_remove_data(&self->cryptos, quark);
|
|
wlcdmi_crypto_close(crypto);
|
|
}
|
|
}
|
|
g_free(key);
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_key_ready(WLCdmiSession *session,
|
|
void* pUserData)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(pUserData);
|
|
|
|
g_mutex_lock(&self->mutex);
|
|
self->key_ready = TRUE;
|
|
g_cond_signal(&self->condition);
|
|
g_mutex_unlock(&self->mutex);
|
|
|
|
GST_INFO_OBJECT(self, "key ready");
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_key_error(WLCdmiSession *session,
|
|
void* pUserData,
|
|
uint32_t dwError,
|
|
const char *pbMessage)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(pUserData);
|
|
g_mutex_lock(&self->mutex);
|
|
self->key_error = TRUE;
|
|
g_cond_signal(&self->condition);
|
|
g_mutex_unlock(&self->mutex);
|
|
GST_ERROR_OBJECT(self, "key error %d %s", dwError, pbMessage);
|
|
}
|
|
|
|
void
|
|
gst_wlcdmi_custom_event(WLCdmiSession *session,
|
|
void* pUserData,
|
|
uint32_t type,
|
|
const char *pbEvent,
|
|
const uint32_t cbEvent)
|
|
{
|
|
GstWLCdmi *self = GST_WLCDMI(pUserData);
|
|
GST_INFO("type %d pbEvent %p cbEvent %d \n", type, pbEvent, cbEvent);
|
|
guint ret;
|
|
g_signal_emit (self, gst_wlcdmi_signals[SIGNAL_CUSTOM_EVENT], 0, type, pbEvent, cbEvent, &ret);
|
|
}
|
|
|
|
static wlcdmi_callback_t gst_wlcdmi_callback = {
|
|
gst_wlcdmi_key_message,
|
|
gst_wlcdmi_key_status,
|
|
gst_wlcdmi_key_ready,
|
|
gst_wlcdmi_key_error,
|
|
gst_wlcdmi_custom_event
|
|
};
|
|
|
|
static gboolean
|
|
wlcdmi_init (GstPlugin * wlcdmi)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT(gst_wlcdmi_debug, "wlcdmi", 0, GST_PACKAGE_NAME);
|
|
|
|
return gst_element_register(wlcdmi, "wlcdmi", GST_RANK_PRIMARY + 1, GST_TYPE_WLCDMI);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE(
|
|
GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
wlcdmi,
|
|
GST_PACKAGE_NAME,
|
|
wlcdmi_init,
|
|
PACKAGE_VERSION,
|
|
GST_LICENSE,
|
|
PACKAGE,
|
|
GST_PACKAGE_ORIGIN
|
|
)
|
|
|