// 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 #include #include #include #include #ifdef SECMEM #include #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; }