Files
2026-08-24 10:03:24 +08:00

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 <[email protected]>");
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
)