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

442 lines
15 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.
#include "glib.h"
#include <unistd.h>
#include <stdio.h>
#include <inttypes.h>
#include <getopt.h>
#include <gst/gst.h>
#ifdef SECMEM
#include <gst/allocators/gstsecmemallocator.h>
#endif
typedef struct {
GstElement *playbin;
GstElement *uridecodebin;
GstElement *decbin;
GstElement *parsebin;
GstElement *videobin;
GstElement *audiobin;
GMainLoop *main_loop;
char *uri;
char *license_url;
char *proxy;
int key_type;
gboolean need_parser;
gint64 pos;
GMutex lock;
GstAllocator* allocator;
} Context;
typedef struct {
char BestEffort_Enabled; // Use best efforts on each setting, but not mandatory
int ccACP_Level; // 0=off, 1, 2, 3
char DwightCavendish_Enabled;
char HDCP_Enabled; // ON = use highest ver available
int ccCGMS_A_Level; // 0=Freely, No More, Once, Never
char CIT_Analog_Enabled; // HD must be downres on Analog output
char CIT_Digital_Enabled; // HD must be downres if no HDCP
char DOT_Enabled; // Doesn't allow playback on Analog
char Anti_Mirroring_Enabled; // Disable mirroring (e.g. Airplay on iOS)
int HDCP_Version; // 0=not required, 1=HDCP 1.4, 2=HDCP 2.2
} OutputControlSettings_t;
typedef struct {
char bGlobal; // true if the data is Global Operator Data
char data[257]; //256 max plus a char for \0
unsigned int dataSize;
} OperatorDataSettings_t;
typedef struct {
int status;
int urlLen;
char *url;
} KeyRetrievalStatus_t;
enum CUSTOM_EVENT_TYPE
{
CUSTOM_EVENT_UNUSED = 0,
CUSTOM_EVENT_KEY_RETRIEVAL_STATUS,
CUSTOM_EVENT_OUTPUT_CONTROL,
CUSTOM_EVENT_OPERATOR_DATA,
};
static GstAllocator* handle_need_allocator(GstElement *wlcdmi, gboolean is_4k, uint8_t decoder_format, Context *main_ctx)
{
g_mutex_lock(&main_ctx->lock);
if (!main_ctx->allocator) {
#ifdef SECMEM
main_ctx->allocator = gst_secmem_allocator_new(is_4k, decoder_format);
#else
main_ctx->allocator = NULL;
#endif
}
g_mutex_unlock(&main_ctx->lock);
return main_ctx->allocator;
}
static guint handle_custom_event(GstElement *wlcdmi, uint32_t type, char *pbEvent, uint32_t cbEvent, Context *main_ctx)
{
g_mutex_lock(&main_ctx->lock);
GST_INFO("%s type %d pbEvent %p cbEvent %d\n", __func__, type, pbEvent, cbEvent);
KeyRetrievalStatus_t KR;
OutputControlSettings_t *OPC;
OperatorDataSettings_t *OD;
switch (type) {
case CUSTOM_EVENT_KEY_RETRIEVAL_STATUS:
KR.status = *(int *)(pbEvent);
KR.urlLen = *(int *)(pbEvent + 4);
KR.url = (char *)(pbEvent + 8);
GST_INFO("status %d \n", KR.status);
GST_INFO("urlLen %d \n", KR.urlLen);
GST_INFO("url %s \n", KR.url);
break;
case CUSTOM_EVENT_OUTPUT_CONTROL:
OPC = (OutputControlSettings_t *)pbEvent;
GST_INFO("BestEffort_Enabled %d \n", OPC->BestEffort_Enabled);
GST_INFO("ccACP_Level %d \n", OPC->ccACP_Level);
GST_INFO("DwightCavendish_Enabled %d \n", OPC->DwightCavendish_Enabled);
GST_INFO("HDCP_Enabled %d \n", OPC->HDCP_Enabled);
GST_INFO("ccCGMS_A_Level %d \n", OPC->ccCGMS_A_Level);
GST_INFO("CIT_Analog_Enabled %d \n", OPC->CIT_Analog_Enabled);
GST_INFO("CIT_Digital_Enabled %d \n", OPC->CIT_Digital_Enabled);
GST_INFO("DOT_Enabled %d \n", OPC->DOT_Enabled);
GST_INFO("Anti_Mirroring_Enabled %d \n", OPC->Anti_Mirroring_Enabled);
GST_INFO("HDCP_Version %d \n", OPC->HDCP_Version);
break;
case CUSTOM_EVENT_OPERATOR_DATA:
OD = (OperatorDataSettings_t *)pbEvent;
GST_INFO("bGlobal %d \n", OD->bGlobal);
GST_INFO("data %s \n", OD->data);
GST_INFO("dataSize %d \n", OD->dataSize);
break;
default:
break;
}
g_mutex_unlock(&main_ctx->lock);
return 0;
}
static void on_element_added(GstBin *bin, GstElement *element,
gpointer user_data) {
Context *ctx;
const gchar *name = GST_ELEMENT_NAME(element);
const gchar *type = G_OBJECT_TYPE_NAME(element);
if (NULL == user_data) {
GST_ERROR("user_data is null.");
return;
}
ctx = (Context *)user_data;
GST_DEBUG("New element added to %s : %s", GST_ELEMENT_NAME(bin), name);
if (g_strrstr(name, "uridecodebin")) {
if (ctx->uridecodebin != element) {
g_signal_connect(element, "element-added", G_CALLBACK(on_element_added), ctx);
}
ctx->uridecodebin = element;
} else if (g_strrstr(name, "decodebin")) {
if (ctx->decbin != element) {
g_signal_connect(element, "element-added", G_CALLBACK(on_element_added), ctx);
}
ctx->decbin = element;
// serial property case hls dv case frame dropped when doing resolution
// changed g_object_set (play->db, "serial-switch", TRUE, NULL);
} else if (g_strrstr(name, "parsebin")) {
if (ctx->parsebin != element) {
g_signal_connect(element, "element-added", G_CALLBACK(on_element_added), ctx);
}
ctx->parsebin = element;
} else if (g_strrstr(name, "vbin")) {
if (ctx->videobin != element) {
g_signal_connect(element, "element-added", G_CALLBACK(on_element_added), ctx);
}
ctx->videobin = element;
} else if (g_strrstr(name, "abin")) {
if (ctx->audiobin != element) {
g_signal_connect(element, "element-added", G_CALLBACK(on_element_added), ctx);
}
ctx->audiobin = element;
}
}
static void on_element_setup (GstElement *playbin, GstElement *element, Context *main_ctx)
{
GstElementFactory *f = gst_element_get_factory(element);
if (!f)
return;
if (!strcmp(GST_OBJECT_NAME (f), "wlcdmi")) {
g_signal_connect (G_OBJECT(element), "need-allocator", (GCallback) handle_need_allocator, main_ctx);
{
GValue value = G_VALUE_INIT;
g_value_init(&value, G_TYPE_BOOLEAN);
g_value_set_boolean(&value, TRUE);
g_object_set_property(G_OBJECT(element), "external-allocator", &value);
g_value_unset(&value);
}
g_signal_connect (G_OBJECT(element), "custom-event", (GCallback) handle_custom_event, main_ctx);
if (main_ctx->license_url) {
GValue value = G_VALUE_INIT;
GST_INFO("set license url %s\n", main_ctx->license_url);
g_value_init(&value, G_TYPE_STRING);
g_value_set_string(&value, main_ctx->license_url);
g_object_set_property(G_OBJECT(element), "license-url", &value);
g_value_unset(&value);
}
if (main_ctx->proxy) {
GValue value = G_VALUE_INIT;
GST_INFO("set proxy %s\n", main_ctx->proxy);
g_value_init(&value, G_TYPE_STRING);
g_value_set_string(&value, main_ctx->proxy);
g_object_set_property(G_OBJECT(element), "proxy", &value);
g_value_unset (&value);
}
if (main_ctx->key_type) {
GValue value = G_VALUE_INIT;
GST_INFO("set key_type %d\n", main_ctx->key_type);
g_value_init(&value, G_TYPE_UINT);
g_value_set_uint(&value, main_ctx->key_type);
g_object_set_property(G_OBJECT(element), "key-type", &value);
g_value_unset(&value);
}
if (main_ctx->need_parser) {
GValue value = G_VALUE_INIT;
GST_INFO("set need_parser TRUE\n");
g_value_init(&value, G_TYPE_BOOLEAN);
g_value_set_boolean(&value, TRUE);
g_object_set_property(G_OBJECT(element), "need-parser", &value);
g_value_unset(&value);
}
} else if (!strcmp(GST_OBJECT_NAME (f), "hlsdemux")) {
GValue value = G_VALUE_INIT;
g_value_init(&value, G_TYPE_BOOLEAN);
g_value_set_boolean(&value, TRUE);
g_object_set_property(G_OBJECT(element), "use-hw-decrypt", &value);
g_value_unset(&value);
} else if (g_strrstr(GST_ELEMENT_NAME(element), "dec") && g_strrstr(GST_ELEMENT_NAME(element), "v4l2")) {
g_object_set (element, "enable-nr", TRUE, NULL);
g_object_set (element, "local-buffer-pool", TRUE, NULL);
}
}
static void handle_message (GstBus *bus, GstMessage *msg, Context *main_ctx)
{
GError *err = NULL;
gchar *debug_info = NULL;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ASYNC_DONE:
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (main_ctx->playbin),
GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.async-done");
break;
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &debug_info);
GST_ERROR("Error received from element %s: %s\n",
GST_OBJECT_NAME(msg->src), err->message);
GST_ERROR("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error(&err);
g_free(debug_info);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (main_ctx->playbin),
GST_DEBUG_GRAPH_SHOW_ALL, "gst-play.error");
g_main_loop_quit (main_ctx->main_loop);
break;
case GST_MESSAGE_EOS:
GST_ERROR("EOS");
g_main_loop_quit (main_ctx->main_loop);
break;
default:
break;
}
}
#ifdef SECMEM
static gboolean print_free_secmem(gpointer user_data)
{
Context *main_ctx = (Context *)user_data;
gint free_size = gst_secmem_check_free_buf_size(main_ctx->allocator);
GST_DEBUG("free secmem: %d\n", free_size);
}
#endif
static gboolean playback_timer (gpointer user_data)
{
Context *main_ctx = (Context *)user_data;
gst_element_query_position (main_ctx->playbin, GST_FORMAT_TIME, &main_ctx->pos);
return TRUE;
}
static void videosink_buffer_underflow(Context *ctx)
{
(void *)ctx;
GST_WARNING("buffer underflow\n");
}
static void videosink_first_frame(Context *ctx)
{
(void *)ctx;
GST_WARNING("first video frame\n");
}
int playback(Context *main_ctx)
{
int ret = -1;
GstStateChangeReturn res;
GstElement *audiosink;
guint watcher = 0;
guint timer = 0;
GstElement *videosink;
const gchar *sinkname = NULL;
const char *video_sink = "westerossink";
main_ctx->pos = GST_CLOCK_TIME_NONE;
main_ctx->playbin = gst_element_factory_make ("playbin", "playbin");
if (!main_ctx->playbin) {
GST_ERROR("playbin create failed");
goto exit;
}
sinkname = g_getenv ("GST_CFG_VIDEO_SINK");
if (sinkname)
{
if (strstr(sinkname, "westerossink"))
video_sink = "westerossink";
else if(strstr(sinkname, "amlvideosink"))
video_sink = "amlvideosink";
else if(strstr(sinkname, "clutterautovideosink"))
video_sink = "clutterautovideosink";
else
video_sink = "westerossink";
}
videosink = gst_element_factory_make (video_sink, "video-sink");
if (!videosink) {
GST_ERROR("videosink create failed");
goto exit;
}
g_signal_connect_swapped(videosink, "buffer-underflow-callback", G_CALLBACK(videosink_buffer_underflow), main_ctx);
g_signal_connect_swapped(videosink, "first-video-frame-callback", G_CALLBACK(videosink_first_frame), main_ctx);
g_object_set (main_ctx->playbin, "video-sink", videosink, NULL);
audiosink = gst_element_factory_make ("audiosink", "audio-sink");
// g_object_set (audiosink, "sync", TRUE, NULL);
g_object_set (main_ctx->playbin, "audio-sink", audiosink, NULL);
g_signal_connect(main_ctx->playbin, "element-added", G_CALLBACK(on_element_added), main_ctx);
g_signal_connect (main_ctx->playbin, "element-setup", G_CALLBACK (on_element_setup), main_ctx);
g_object_set (main_ctx->playbin, "uri", main_ctx->uri, NULL);
watcher = gst_bus_add_watch (GST_ELEMENT_BUS (main_ctx->playbin), (GstBusFunc)handle_message, main_ctx);
res = gst_element_set_state(main_ctx->playbin, GST_STATE_PLAYING);
if (res == GST_STATE_CHANGE_FAILURE) {
GST_ERROR("playback failed");
gst_object_unref(main_ctx->playbin);
goto exit;
}
main_ctx->main_loop = g_main_loop_new (NULL, FALSE);
timer = g_timeout_add (100, playback_timer, main_ctx);
#ifdef SECMEM
g_timeout_add(1000, print_free_secmem, main_ctx);
#endif
g_main_loop_run (main_ctx->main_loop);
ret = 0;
exit:
if (main_ctx->main_loop) {
g_main_loop_unref(main_ctx->main_loop);
main_ctx->main_loop = NULL;
}
if (main_ctx->playbin) {
gst_element_set_state(main_ctx->playbin, GST_STATE_NULL);
g_source_remove(watcher);
gst_object_unref(main_ctx->playbin);
main_ctx->playbin = NULL;
}
if (main_ctx->allocator) {
gst_object_unref(main_ctx->allocator);
main_ctx->allocator = NULL;
}
if (timer)
g_source_remove(timer);
return ret;
}
int main(int argc, char *argv[])
{
Context main_ctx = {0};
int opt;
gboolean endurance_test = FALSE;
g_mutex_init(&main_ctx.lock);
gst_init (&argc, &argv);
for (int i = 0; i < argc; i++) {
gst_print("%s\n", argv[i]);
}
while ((opt = getopt(argc, argv, "l:p:t:e")) != -1) {
switch (opt) {
case 'l':
main_ctx.license_url = optarg;
break;
case 'p':
main_ctx.proxy = optarg;
break;
case 't':
main_ctx.key_type = atoi(optarg);
break;
case 'e':
endurance_test = TRUE;
break;
case 'r':
main_ctx.need_parser = TRUE;
break;
case '?':
default:
gst_print("Usage: %s [-e] [-l license_server] [-p proxy] [-t key_type] [-r need_parser] uri\n", argv[0]);
exit(EXIT_FAILURE);
}
}
if (optind == argc) {
gst_print("No file to play\n");
exit(EXIT_FAILURE);
} else {
main_ctx.uri = argv[optind];
}
do {
main_ctx.pos = 0;
playback(&main_ctx);
if (GST_CLOCK_TIME_IS_VALID(main_ctx.pos)) {
gst_print("played %" PRIu64 ".%09" PRIu64 "\n", main_ctx.pos / GST_SECOND, main_ctx.pos % GST_SECOND);
} else {
gst_print("played 0\n");
}
} while(endurance_test);
return 0;
}