/* * Copyright (C) 2014-2019 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 #include #include #include #include #include "video_transcoding.h" CodecType tset_code = MPEG4; /* Structure to contain all our information, so we can pass it to callbacks */ typedef struct _CustomData_App { GMainLoop *main_loop; GstElement *pipeline_demux; GstElement *pipeline_mux; GstElement *file_source; GstElement *demuxer; GstElement *video_queue; GstElement *audio_queue; GstElement *video_mpegparser; GstElement *audio_parser; GstElement *videosink; GstElement *audiosink; GstElement *app_source_video; GstElement *app_source_audio; GstElement *video_h26xparser; GstElement *audio_parser_mux; GstElement *muxer; GstElement *filesink; } CustomData_App; // init the global parmam video_transcoding_param global_video_transcoding_param; /* library handle */ HANDLE *handle = NULL; /* app handle */ HANDLE *handle_app = NULL; CodecType mapStringToCodec(const char *codec) { if (strcmp(codec, "AV1") == 0) { return AV1; } else if (strcmp(codec, "AVS2") == 0) { return AVS2; } else if (strcmp(codec, "AVS3") == 0) { return AVS3; } else if (strcmp(codec, "AVS") == 0) { return AVS; } else if (strcmp(codec, "H264") == 0) { return H264; } else if (strcmp(codec, "H265") == 0) { return H265; } else if (strcmp(codec, "JPEG") == 0) { return JPEG; } else if (strcmp(codec, "MPEG4") == 0) { return MPEG4; } else if (strcmp(codec, "VC1") == 0) { return VC1; } else if (strcmp(codec, "VP9") == 0) { return VP9; } g_printerr("%s no match input codec %s\n", __func__, codec); return INVALID_CODEC; } void parseConfigFile(const char *filename, video_transcoding_param *param){ //char codecStr[20]; char src_codec_value[20]; char dst_codec_value[20]; FILE *file = fopen(filename,"r"); if (file == NULL) { g_printerr("Error opening config file %s\n", filename); param->dst_size.width = 640; param->dst_size.height = 480; param->dst_codec = H264; param->dst_framerate = 30; param->bitrate_kb = 3000; param->gop_size = 60; g_print("%s set the default value\n dst_width=%d dst_height=%d dst_codec=%d dst_framerate=%d/1\n \ bitrate_kb=%d gop_size=%d\n", __func__, param->dst_size.width,param->dst_size.height,param->dst_codec, param->dst_framerate,param->bitrate_kb,param->gop_size); return; } // Parse the config file if (fscanf(file, "src_size={%d, %d}\n", ¶m->src_size.width, ¶m->src_size.height) != 2) { g_printerr("Error reading src_size from config file %s\n", filename); fclose(file); return; } if (fscanf(file, "src_codec=%s\n", src_codec_value) != 1) { g_printerr("Error reading src_codec from config file %s\n", filename); fclose(file); return; } param->src_codec = mapStringToCodec(src_codec_value); if (fscanf(file, "src_framerate=%d\n", ¶m->src_framerate) != 1) { g_printerr("Error reading src_framerate from config file %s\n", filename); fclose(file); return; } if (fscanf(file, "dst_size={%d, %d}\n", ¶m->dst_size.width, ¶m->dst_size.height) != 2) { g_printerr("Error reading dst_size from config file %s\n", filename); fclose(file); return; } if (fscanf(file, "dst_codec=%s\n", dst_codec_value) != 1) { g_printerr("Error reading dst_codec from config file %s\n", filename); fclose(file); return; } param->dst_codec = mapStringToCodec(dst_codec_value); if (fscanf(file, "dst_framerate=%d\n", ¶m->dst_framerate) != 1) { g_printerr("Error reading dst_framerate from config file %s\n", filename); fclose(file); return; } if (fscanf(file, "bitrate_kb=%d\n", ¶m->bitrate_kb) != 1) { g_printerr("Error reading bitrate_kb from config file %s\n", filename); fclose(file); return; } if (fscanf(file, "gop_size=%d\n", ¶m->gop_size) != 1) { g_printerr("Error reading gop_size from config file %s\n", filename); fclose(file); return; } fclose(file); g_print("%s: Successfully parsed config file\n src_size=%d,%d src_codec=%d src_framerate=%d/1\n \ dst_size=%d,%d dst_codec=%d dst_framerate=%d/1\n bitrate_kb=%d gop_size=%d\n", __func__, param->src_size.width, param->src_size.height, param->src_codec, param->src_framerate, param->dst_size.width, param->dst_size.height, param->dst_codec, param->dst_framerate, param->bitrate_kb, param->gop_size); } static void SignalHandler(int signum){ if ( NULL == handle ) { return; } CustomData_App *data = (CustomData_App *)handle_app; video_transcoding_stop(handle); video_transcoding_deinit(handle); gst_element_set_state(data->pipeline_demux, GST_STATE_NULL); gst_object_unref(data->pipeline_demux); gst_element_set_state(data->pipeline_mux, GST_STATE_NULL); gst_object_unref(data->pipeline_mux); free(data); exit(1); } static GstFlowReturn new_sample_video(GstElement *appsink, gpointer data) { GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(appsink)); if (sample) { GstBuffer *buffer = gst_sample_get_buffer(sample); if (buffer) { gsize size = gst_buffer_get_size(buffer); //g_print("Received video ES data: %d\n", (int)size); // save the out data video_transcoding_writeData(handle, buffer, size); gst_sample_unref(sample); return GST_FLOW_OK; } } return GST_FLOW_ERROR; } static GstFlowReturn new_sample_audio(GstElement *appsink, gpointer data) { GstPadLinkReturn ret; GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(appsink)); CustomData_App *data_app = (CustomData_App *)handle_app; if (sample) { GstBuffer *buffer = gst_sample_get_buffer(sample); if (buffer) { gsize size = gst_buffer_get_size(buffer); //g_print("Received audio ES data: %d\n", (int)size); // save the out data GstBuffer *gst_buffer = gst_buffer_copy((GstBuffer *)buffer); ret = gst_app_src_push_buffer(data_app->app_source_audio, gst_buffer); if (ret != GST_FLOW_OK) { /* We got some error, stop sending data */ printf("write data error\n"); return -1; } gst_sample_unref(sample); return GST_FLOW_OK; } } return GST_FLOW_ERROR; } /* This function will be called by the pad-added signal */ static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData_App *data) { GstPad *sink_pad = NULL; GstPadLinkReturn ret; GstCaps *new_pad_caps = NULL; GstStructure *new_pad_struct = NULL; const gchar *new_pad_type = NULL; g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src)); // chech the pad is the source pad if (GST_PAD_DIRECTION(new_pad) != GST_PAD_SRC) return; /* Check the new pad's type */ new_pad_caps = gst_pad_get_current_caps (new_pad); new_pad_struct = gst_caps_get_structure (new_pad_caps, 0); new_pad_type = gst_structure_get_name (new_pad_struct); // determine audio or video based on caps if (g_strrstr(new_pad_type, "video")) { g_print("New pad is a video pad.\n"); sink_pad = gst_element_get_static_pad (data->video_queue, "sink"); // connect to video processing flow } else if (g_strrstr(new_pad_type, "audio")) { sink_pad = gst_element_get_static_pad (data->audio_queue, "sink"); // connect to audio processing flow } else { g_print("%s New pad is of unknown type.\n",new_pad_type); return; } /* If our converter is already linked, we have nothing to do here */ if (gst_pad_is_linked (sink_pad)) { g_print ("We are already linked. Ignoring.\n"); goto exit; } /* Attempt the link */ ret = gst_pad_link (new_pad, sink_pad); if (GST_PAD_LINK_FAILED (ret)) { g_print ("Type is '%s' but link failed.\n", new_pad_type); } else { g_print ("Link succeeded (type '%s').\n", new_pad_type); } exit: /* Unreference the new pad's caps, if we got them */ if (new_pad_caps != NULL) gst_caps_unref (new_pad_caps); /* Unreference the sink pad */ gst_object_unref (sink_pad); } int video_transcoding_pulldata_callback(void *buffer, gint size){ CustomData_App *data = (CustomData_App *)handle_app; GstStateChangeReturn ret=GST_FLOW_OK; if (NULL != buffer) { /* The only thing we do in this example is print a * to indicate a received buffer */ //g_print ("%s : ****** \n", __func__); GstBuffer *gst_buffer = gst_buffer_copy((GstBuffer *)buffer); ret = gst_app_src_push_buffer(data->app_source_video, gst_buffer); if (ret != GST_FLOW_OK) { /* We got some error, stop sending data */ printf("write data error\n"); return -1; } //printf("%s : write video data func done\n", __func__); return 0; } gst_app_src_end_of_stream(GST_APP_SRC(data->app_source_video)); gst_app_src_end_of_stream(GST_APP_SRC(data->app_source_audio)); return 0; } void send_eos_event (GstElement* appsink, HANDLE handle){ printf("%s we send eos enent to lib\n",__func__); CustomData *data = (CustomData *)handle; gst_element_send_event(data->app_source, gst_event_new_eos()); return; } /* This function is called when an error message is posted on the bus */ static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData_App *data) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, &debug_info); GST_ERROR("%s Error received from element %s: %s\n",__func__, 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); g_main_loop_quit(data->main_loop); break; case GST_MESSAGE_EOS: printf("%s End-Of-Stream reached.\n", __func__); g_main_loop_quit(data->main_loop); break; case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state, pending_state; gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); GST_DEBUG("%s: state changed from %s to %s\n", GST_MESSAGE_SRC_NAME(msg), gst_element_state_get_name(old_state), gst_element_state_get_name(new_state)); if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data->pipeline_mux)) { GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->pipeline_mux), GST_DEBUG_GRAPH_SHOW_ALL, \ g_strdup_printf("app_dot.%s_%s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state))); } if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data->pipeline_demux)) { GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->pipeline_demux), GST_DEBUG_GRAPH_SHOW_ALL, \ g_strdup_printf("demuxtranscodin_dot.%s_%s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state))); } break; } default: break; } return TRUE; } int main(int argc, char *argv[]) { CustomData_App *data = (CustomData_App *)malloc(sizeof(CustomData_App)); GstMessage *msg; GstStateChangeReturn ret; GstCaps* src_caps = NULL; const char *mediaFile = argv[1]; const char *configFile = argv[2]; /* check input param */ if (argc !=3 ) { g_printerr("Usage: %s \n", argv[0]); return -1; } signal(SIGINT,SignalHandler); /* read param from config file */ parseConfigFile(configFile, &global_video_transcoding_param); /* init the gstreamer library */ gst_init(&argc, &argv); data->pipeline_demux = gst_pipeline_new("demo-pipeline"); data->pipeline_mux = gst_pipeline_new("demo-pipeline"); data->file_source = gst_element_factory_make("filesrc", "demo-file-source"); g_object_set(G_OBJECT(data->file_source), "location", argv[1], NULL); data->demuxer = gst_element_factory_make("tsdemux", "demo-ts-demuxer"); data->video_mpegparser = gst_element_factory_make("mpegvideoparse", "demo-mpegvideo-parser"); data->video_queue = gst_element_factory_make ("queue", "demo-video_queue"); data->audio_queue = gst_element_factory_make ("queue", "demo-audio_queue"); data->videosink = gst_element_factory_make("appsink", "demo-video-sink"); data->audiosink = gst_element_factory_make("appsink", "demo-audio-sink"); data->audio_parser = gst_element_factory_make("ac3parse", "demo-ac3audio-parser"); data->app_source_video = gst_element_factory_make ("appsrc", "demo-app_source_video"); data->app_source_audio = gst_element_factory_make ("appsrc", "demo-app_source_audio"); data->audio_parser_mux = gst_element_factory_make("ac3parse", "demo-ac3audio_parser_mux"); data->filesink = gst_element_factory_make("filesink", "demo-my-filesink"); g_object_set(G_OBJECT(data->filesink), "location", "/data/test.ts", NULL); data->muxer = gst_element_factory_make("mpegtsmux", "demo-ts-muxer"); data->video_h26xparser = gst_element_factory_make("h265parse", "demo-h265parse-parser"); switch (global_video_transcoding_param.dst_codec) { case H264: data->video_h26xparser = gst_element_factory_make("h264parse", "demo-h264parse-parser"); src_caps = gst_caps_new_simple("video/x-h264", "stream-format", G_TYPE_STRING, "byte-stream", "alignment",G_TYPE_STRING,"au", //"width", G_TYPE_INT, 720, //"height", G_TYPE_INT, 420, // "framerate", GST_TYPE_FRACTION, param->src_framerate, NULL); break; case H265: data->video_h26xparser = gst_element_factory_make("h265parse", "demo-h265parse-parser"); src_caps = gst_caps_new_simple("video/x-h265", "stream-format", G_TYPE_STRING, "byte-stream", "alignment",G_TYPE_STRING,"au", //"width", G_TYPE_INT, 720, //"height", G_TYPE_INT, 420, // "framerate", GST_TYPE_FRACTION, param->src_framerate, NULL); break; default: break; } /* configure the appsrc */ g_object_set (data->app_source_video, "caps", src_caps, "block", TRUE, NULL); gst_caps_unref(src_caps); GstCaps* src_caps_audio = gst_caps_new_simple("audio/x-ac3", NULL); g_object_set (data->app_source_audio, "caps", src_caps_audio, "block", TRUE, NULL); gst_caps_unref(src_caps_audio); /* Check if all elements were successfully created */ if (!data->pipeline_demux || !data->pipeline_mux || !data->file_source || !data->demuxer || !data->video_queue || !data->audio_queue || !data->video_mpegparser || !data->audio_parser || !data->videosink || !data->audiosink || !data->app_source_video || !data->app_source_audio || !data->video_h26xparser || !data->audio_parser_mux || !data->muxer || !data->filesink) { g_print("One or more elements could not be created. Exiting.\n"); return -1; } /* Add elements to the pipeline */ gst_bin_add(GST_BIN(data->pipeline_demux), data->file_source); gst_bin_add(GST_BIN(data->pipeline_demux), data->demuxer); gst_bin_add(GST_BIN(data->pipeline_demux), data->video_queue); gst_bin_add(GST_BIN(data->pipeline_demux), data->audio_queue); gst_bin_add(GST_BIN(data->pipeline_demux), data->video_mpegparser); gst_bin_add(GST_BIN(data->pipeline_demux), data->audio_parser); gst_bin_add(GST_BIN(data->pipeline_demux), data->videosink); gst_bin_add(GST_BIN(data->pipeline_demux), data->audiosink); gst_bin_add(GST_BIN(data->pipeline_mux), data->app_source_video); gst_bin_add(GST_BIN(data->pipeline_mux), data->app_source_audio); gst_bin_add(GST_BIN(data->pipeline_mux), data->video_h26xparser); gst_bin_add(GST_BIN(data->pipeline_mux), data->audio_parser_mux); gst_bin_add(GST_BIN(data->pipeline_mux), data->muxer); gst_bin_add(GST_BIN(data->pipeline_mux), data->filesink); /* pipeline_demux link elements */ if (!gst_element_link(data->file_source, data->demuxer) || !gst_element_link_many(data->video_queue, data->video_mpegparser, data->videosink, NULL) || !gst_element_link_many(data->audio_queue, data->audio_parser, data->audiosink, NULL)) { g_print("pipeline_demux Elements could not be linked. Exiting.\n"); return -1; } /* Set appsink */ g_object_set(G_OBJECT(data->videosink), "emit-signals", TRUE, "sync", FALSE, NULL); g_signal_connect(G_OBJECT(data->videosink), "new-sample", G_CALLBACK(new_sample_video), NULL); /* Set appsink */ g_object_set(G_OBJECT(data->audiosink), "emit-signals", TRUE, "sync", FALSE, NULL); g_signal_connect(G_OBJECT(data->audiosink), "new-sample", G_CALLBACK(new_sample_audio), NULL); /* Connect the "pad-added" signal handler */ g_signal_connect(data->demuxer, "pad-added", G_CALLBACK(pad_added_handler), data); /* pipeline_mux link elements */ if (!gst_element_link_many(data->app_source_video, data->video_h26xparser, NULL) ) { g_print("app_source_video Elements could not be linked. Exiting.\n"); return -1; } /* pipeline_mux link elements */ if (!gst_element_link_many(data->app_source_audio, data->audio_parser_mux, NULL) ) { g_print("app_source_audio Elements could not be linked. Exiting.\n"); return -1; } /* pipeline_mux link elements */ if ( !gst_element_link_many(data->video_h26xparser, data->muxer, NULL) ) { g_print("video_h26xparser Elements could not be linked. Exiting.\n"); return -1; } if ( !gst_element_link_many(data->audio_parser_mux, data->muxer, NULL) ) { g_print("audio_parser_mux Elements could not be linked. Exiting.\n"); return -1; } if (!gst_element_link(data->muxer, data->filesink) ) { g_print("muxer and filesink Elements could not be linked. Exiting.\n"); return -1; } handle_app=(HANDLE)data; handle = video_transcoding_init(&global_video_transcoding_param, argc, NULL); int ret_lib=video_transcoding_start(handle); if (ret_lib == -1) { g_printerr("lib pip: Unable to set the pipeline to the playing state.\n"); gst_object_unref(data->pipeline_demux); return -1; } /* send eos event */ g_signal_connect(G_OBJECT(data->videosink), "eos", G_CALLBACK(send_eos_event), handle); /* Set pipeline status to playing */ ret = gst_element_set_state(data->pipeline_demux, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("Unable to set the pipeline_demux to the playing state.\n"); gst_object_unref(data->pipeline_demux); return -1; } /* Set pipeline status to playing */ ret = gst_element_set_state(data->pipeline_mux, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("Unable to set the pipeline_mux to the playing state.\n"); gst_object_unref(data->pipeline_mux); return -1; } video_transcoding_callback pull_data_callback = video_transcoding_pulldata_callback; video_transcoding_setSinkCallback(handle, pull_data_callback); data->main_loop = g_main_loop_new(NULL, FALSE); GstBus *bus = gst_element_get_bus(data->pipeline_mux); gst_bus_add_watch(bus, (GstBusFunc)handle_message, data); gst_object_unref(bus); g_main_loop_run(data->main_loop); printf("release the source demo to do\n"); video_transcoding_stop(handle); video_transcoding_deinit(handle); /* release source */ gst_element_set_state(data->pipeline_demux, GST_STATE_NULL); gst_object_unref(data->pipeline_demux); gst_element_set_state(data->pipeline_mux, GST_STATE_NULL); gst_object_unref(data->pipeline_mux); free(data); return 0; }