449 lines
18 KiB
C
449 lines
18 KiB
C
/*
|
|
* 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 <gst/gst.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <gst/app/gstappsrc.h>
|
|
#include <gst/app/gstappsink.h>
|
|
#include "video_transcoding.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC(video_transcoding);
|
|
#define GST_CAT_DEFAULT video_transcoding
|
|
|
|
|
|
|
|
//GstFlowReturn video_transcoding_pulldata_callback(GstElement *sink, CustomData *data);
|
|
static void *video_transcoding_workloop(HANDLE *handle);
|
|
|
|
static GstFlowReturn appsink_pull_data_callback(GstElement *sink, CustomData *data );
|
|
void send_eos_event_to_app (GstElement* sink, CustomData *data);
|
|
|
|
HANDLE video_transcoding_init( video_transcoding_param *param, int argc, char **argv){
|
|
GstCaps* src_caps = NULL;
|
|
GstCaps* sink_caps = NULL;
|
|
|
|
|
|
CustomData *data = (CustomData *)malloc(sizeof(CustomData));
|
|
if (NULL == data)
|
|
{
|
|
GST_ERROR("%s malloc CustomData error\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
/* init the logsystem */
|
|
GST_DEBUG_CATEGORY_EXTERN(video_transcoding);
|
|
GST_DEBUG_CATEGORY_INIT(video_transcoding, "video_transcoding", 0, "libtranscoding log");
|
|
|
|
GST_DEBUG("%s src_width=%d src_height=%d src_codec=%d src_framerate=%d/1\n \
|
|
dst_width=%d dst_height=%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);
|
|
|
|
printf("%s src_width=%d src_height=%d src_codec=%d src_framerate=%d/1\n \
|
|
dst_width=%d dst_height=%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);
|
|
|
|
/* Initialize cumstom data structure */
|
|
memset (data, 0, sizeof (CustomData));
|
|
|
|
/* Initialize GStreamer */
|
|
gst_init (&argc, &argv);
|
|
|
|
/* switch the decoder codec */
|
|
switch (param->src_codec)
|
|
{
|
|
case AV1:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2av1dec", "transcoding-v4l2_av1_dec");
|
|
break;
|
|
case AVS2:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2avs2dec", "transcoding-v4l2_avs2_dec");
|
|
break;
|
|
case AVS3:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2avs3dec", "transcoding-v4l2_avs3_dec");
|
|
break;
|
|
case AVS:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2avsdec", "transcoding-v4l2_avs_dec");
|
|
break;
|
|
case H264:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2h264dec", "transcoding-v4l2_h264_dec");
|
|
src_caps = gst_caps_new_simple("video/x-h264",
|
|
"width", G_TYPE_INT, param->src_size.width,
|
|
"height", G_TYPE_INT, param->src_size.height,
|
|
//"framerate", GST_TYPE_FRACTION, param->src_framerate,1,
|
|
NULL);
|
|
break;
|
|
case H265:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2h265dec", "transcoding-v4l2_h265_dec");
|
|
src_caps = gst_caps_new_simple("video/x-h265",
|
|
"width", G_TYPE_INT, param->src_size.width,
|
|
"height", G_TYPE_INT, param->src_size.height,
|
|
//"framerate", GST_TYPE_FRACTION, param->src_framerate,1,
|
|
NULL);
|
|
break;
|
|
case JPEG:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2jpegdec", "transcoding-v4l2_jpeg_dec");
|
|
break;
|
|
case MPEG4:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2mpeg4dec", "transcoding-v4l2_mpeg4_dec");
|
|
src_caps = gst_caps_new_simple("video/mpeg",
|
|
"mpegversion", G_TYPE_INT, 2,
|
|
"systemstream", G_TYPE_BOOLEAN, FALSE,
|
|
// "width", G_TYPE_INT, param->src_size.width,
|
|
// "height", G_TYPE_INT, param->src_size.height,
|
|
//"framerate", GST_TYPE_FRACTION, param->src_framerate,1,
|
|
NULL);
|
|
|
|
//src_caps = gst_caps_new_simple("video/mpeg",NULL);
|
|
//src_caps = gst_caps_new_any();
|
|
break;
|
|
case VC1:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2vc1dec", "transcoding-v4l2_vc1_dec");
|
|
break;
|
|
case VP9:
|
|
data->v4l2_dec = gst_element_factory_make ("amlv4l2vp9dec", "transcoding-v4l2_vp9_dec");
|
|
break;
|
|
default:
|
|
GST_ERROR("%s No match dec codec : %d\n", __func__, param->src_codec);
|
|
break;
|
|
}
|
|
|
|
//const gchar *dec_name = gst_element_get_name(data->v4l2_dec);
|
|
GST_DEBUG("%s dec plugin name : %s\n", __func__, gst_element_get_name(data->v4l2_dec));
|
|
|
|
/* switch the encoder codec */
|
|
switch (param->dst_codec)
|
|
{
|
|
case JPEG:
|
|
data->video_enc = gst_element_factory_make ("amljpegenc", "transcoding-video_jpeg_enc");
|
|
break;
|
|
case H264:
|
|
data->video_enc = gst_element_factory_make ("amlvenc", "transcoding-video_h264_enc");
|
|
//sink_caps = gst_caps_new_simple("video/x-raw",
|
|
sink_caps = gst_caps_new_simple("video/x-h264",
|
|
"width", G_TYPE_INT, param->dst_size.width,
|
|
"height", G_TYPE_INT, param->dst_size.height,
|
|
"framerate", GST_TYPE_FRACTION, param->dst_framerate,1,
|
|
NULL);
|
|
break;
|
|
case H265:
|
|
data->video_enc = gst_element_factory_make ("amlvenc", "transcoding-video_h265_enc");
|
|
sink_caps = gst_caps_new_simple("video/x-h265",
|
|
"width", G_TYPE_INT, param->dst_size.width,
|
|
"height", G_TYPE_INT, param->dst_size.height,
|
|
"framerate", GST_TYPE_FRACTION, param->dst_framerate,1,
|
|
NULL);
|
|
break;
|
|
default:
|
|
GST_ERROR("No match enc codec : %d\n", param->dst_codec);
|
|
break;
|
|
}
|
|
|
|
//const gchar *enc_name = gst_element_get_name(data->video_enc);
|
|
GST_DEBUG("%s encoder plugin name : %s\n", __func__, gst_element_get_name(data->video_enc));
|
|
|
|
/* Create the elements */
|
|
// data->app_queue = gst_element_factory_make ("queue", "app_queue");
|
|
data->app_sink = gst_element_factory_make ("appsink", "transcoding-app_sink");
|
|
data->app_source = gst_element_factory_make ("appsrc", "transcoding-app_source");
|
|
// data->ts_demux = gst_element_factory_make ("tsdemux", "ts_demux");
|
|
data->video_convert = gst_element_factory_make ("amlvconv", "transcoding-video_convert");
|
|
// data->src_parse = gst_element_factory_make ("mpegvideoparse", "src_parse");
|
|
// data->video_queue = gst_element_factory_make ("queue", "video_queue");
|
|
//data->video_rate = gst_element_factory_make ("videorate", "video_rate");
|
|
|
|
/* Create the empty pipeline */
|
|
data->pipeline = gst_pipeline_new ("transcoding-pipeline");
|
|
|
|
if (!data->pipeline || !data->app_sink || !data->app_source || !data->v4l2_dec || !data->video_convert || !data->video_enc) {
|
|
GST_ERROR("%s Not all elements could be created.\n", __func__);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* configure the appsrc */
|
|
g_object_set(data->app_source, "caps", src_caps, "block", TRUE, NULL);
|
|
GST_DEBUG("%s sink_caps we set : %s\n",__func__, gst_caps_to_string(src_caps));
|
|
gst_caps_unref(src_caps);
|
|
|
|
//g_signal_connect (data->app_source, "need-data", G_CALLBACK (start_feed), &data);
|
|
//g_signal_connect (data->app_source, "enough-data", G_CALLBACK (stop_feed), &data);
|
|
|
|
/* configure the amlvenc */
|
|
g_object_set(data->video_enc,"bitrate", param->bitrate_kb, "gop", param->gop_size, NULL);
|
|
|
|
/*configure the app sink */
|
|
g_object_set (data->app_sink, "emit-signals", TRUE, "caps", sink_caps, "sync", FALSE, NULL);
|
|
g_signal_connect (data->app_sink, "eos", G_CALLBACK (send_eos_event_to_app), data);
|
|
|
|
//"async", FALSE,
|
|
GST_DEBUG("%s sink_caps we set : %s\n",__func__, gst_caps_to_string(sink_caps));
|
|
gst_caps_unref(sink_caps);
|
|
//g_signal_connect (data->app_sink, "new-sample", G_CALLBACK (appsink_pull_data_callback), data);
|
|
|
|
/* Add all plugins to the pipeline */
|
|
gst_bin_add_many (GST_BIN (data->pipeline), data->app_source, data->v4l2_dec, data->video_convert, data->video_enc,data->app_sink ,NULL);
|
|
if (gst_element_link_many (data->app_source, data->v4l2_dec, data->video_convert, data->video_enc, data->app_sink, NULL) != TRUE ) {
|
|
GST_ERROR("%s Elements could not be linked.\n", __func__);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Create a GLib Main Loop and set it to run */
|
|
data->main_loop = g_main_loop_new (NULL, FALSE);
|
|
data->init = TRUE;
|
|
data->playing = FALSE;
|
|
data->cus_pull_data_callback = NULL;
|
|
|
|
// start thread
|
|
pthread_create(&data->tid, NULL, video_transcoding_workloop, (HANDLE)data);
|
|
|
|
return (HANDLE)data;
|
|
|
|
cleanup:
|
|
if (data->pipeline) gst_object_unref(data->pipeline);
|
|
if (data->app_source) gst_object_unref(data->app_source);
|
|
if (data->v4l2_dec) gst_object_unref(data->v4l2_dec);
|
|
if (data->video_convert) gst_object_unref(data->video_convert);
|
|
if (data->video_enc) gst_object_unref(data->video_enc);
|
|
if (data->app_sink) gst_object_unref(data->app_sink);
|
|
|
|
free(data);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
void video_transcoding_deinit(HANDLE *handle){
|
|
/* chech input param */
|
|
if (NULL == handle) {
|
|
GST_ERROR("%s invalid param!\n", __func__);
|
|
return;
|
|
}
|
|
CustomData *data = (CustomData *)handle;
|
|
/* free resources */
|
|
data->init = FALSE;
|
|
data->playing = FALSE;
|
|
g_main_loop_quit(data->main_loop);
|
|
if (pthread_join(data->tid, NULL) ==0) {
|
|
printf("workloop thread exited successful\n");
|
|
} else {
|
|
printf("workloop thread exited fail\n");
|
|
}
|
|
g_main_loop_unref(data->main_loop);
|
|
gst_object_unref(data->pipeline);
|
|
free(data);
|
|
|
|
return;
|
|
}
|
|
|
|
/* This function is called when an error message is posted on the bus */
|
|
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *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:
|
|
GST_DEBUG("%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))
|
|
{
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, \
|
|
g_strdup_printf("libtranscoding_dot.%s_%s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state)));
|
|
data->playing = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* We want to keep receiving messages */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void *video_transcoding_workloop(HANDLE *handle) {
|
|
/* chech input param */
|
|
if (NULL == handle) {
|
|
GST_ERROR("%s invalid param!\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
CustomData *data = (CustomData *)handle;
|
|
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
|
|
// GstBus *bus = gst_element_get_bus (data->pipeline);
|
|
// gst_bus_add_signal_watch (bus);
|
|
// g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_callback, &data);
|
|
|
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(data->pipeline));
|
|
gst_bus_add_watch(bus, (GstBusFunc)handle_message, data);
|
|
gst_object_unref(bus);
|
|
g_main_loop_run(data->main_loop);
|
|
|
|
GST_DEBUG("%s work loop start work\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int video_transcoding_writeData(HANDLE handle, void* buffer, gint buff_size){
|
|
GstFlowReturn ret;
|
|
|
|
/* chech input param */
|
|
if (NULL == handle) {
|
|
GST_ERROR("%s invalid param!\n", __func__);
|
|
return -1;
|
|
}
|
|
CustomData *data = (CustomData *)handle;
|
|
/* push sample to appsrc */
|
|
//g_signal_emit_by_name(data->app_source, "push-sample", &sample);
|
|
/* for not size set */
|
|
//gst_buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, buffer, buff_size, 0 ,buff_size, NULL, NULL);
|
|
GstBuffer *gst_buffer = gst_buffer_copy((GstBuffer *)buffer);
|
|
ret = gst_app_src_push_buffer(data->app_source, gst_buffer);
|
|
if (ret != GST_FLOW_OK) {
|
|
/* We got some error, stop sending data */
|
|
GST_ERROR("%s write data error\n", __func__);
|
|
return -1;
|
|
}
|
|
GST_DEBUG("%s write data addr: %p wirite data size: %d\n", __func__, buffer, buff_size);
|
|
return 0;
|
|
}
|
|
|
|
static GstFlowReturn appsink_pull_data_callback(GstElement *sink, CustomData *data ){
|
|
GstSample *sample;
|
|
GstBuffer *out_buffer;
|
|
gint out_size;
|
|
//HANDLE *handle = (HANDLE *)data;
|
|
/* chech input param */
|
|
if (NULL == data) {
|
|
GST_ERROR("%s invalid param!\n", __func__);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
/* Retrieve the buffer */
|
|
g_signal_emit_by_name (sink, "pull-sample", &sample);
|
|
if (NULL != sample) {
|
|
out_buffer = gst_sample_get_buffer(sample);
|
|
out_size = gst_buffer_get_size(out_buffer);
|
|
/* The only thing we do in this example is print a * to indicate a received buffer */
|
|
GST_DEBUG ("%s out_buffer addr: %p out_buffer size: %d\n", __func__, out_buffer, out_size);
|
|
//video_transcoding_pulldata_callback(handle, buffer, out_size);
|
|
|
|
/* app pull data callback */
|
|
data->cus_pull_data_callback(out_buffer, out_size);
|
|
gst_sample_unref (sample);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
int video_transcoding_setSinkCallback(HANDLE *handle, video_transcoding_callback video_transcoding_pulldata_callback) {
|
|
//GstSample* sample = NULL;
|
|
/* chech input param */
|
|
if (NULL == handle) {
|
|
GST_ERROR("%s invalid param!\n", __func__);
|
|
return -1;
|
|
}
|
|
CustomData *data = (CustomData *)handle;
|
|
/* get sample from appsink */
|
|
//g_signal_emit_by_name (data->app_sink, "pull-sample", &sample);
|
|
data->cus_pull_data_callback = video_transcoding_pulldata_callback;
|
|
//printf("video_transcoding_pulldata_callback address : %p\n", data->cus_pull_data_callback)
|
|
g_signal_connect (data->app_sink, "new-sample", G_CALLBACK (appsink_pull_data_callback), data);
|
|
GST_DEBUG("%s get data done\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
int video_transcoding_start(HANDLE *handle) {
|
|
GstStateChangeReturn ret;
|
|
/* check input param */
|
|
if (NULL == handle)
|
|
{
|
|
GST_ERROR("%s input param error\n", __func__ );
|
|
return -1;
|
|
}
|
|
CustomData *data = (CustomData *)handle;
|
|
/* start playing */
|
|
ret = gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
GST_ERROR("%s Unable to set the pipeline to the PLAYING state.\n", __func__);
|
|
gst_object_unref(data->pipeline);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int video_transcoding_stop(HANDLE *handle) {
|
|
GstStateChangeReturn ret;
|
|
/* check input param */
|
|
if (NULL == handle)
|
|
{
|
|
GST_ERROR("%s input param error\n", __func__ );
|
|
return -1;
|
|
}
|
|
CustomData *data = (CustomData *)handle;
|
|
|
|
ret = gst_element_set_state(data->pipeline, GST_STATE_NULL);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
GST_ERROR("%s Unable to set the pipeline to the NULL state.\n", __func__);
|
|
gst_object_unref(data->pipeline);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void send_eos_event_to_app (GstElement* sink, CustomData *data){
|
|
printf("%s we got eos event\n",__func__);
|
|
data->cus_pull_data_callback(NULL, -1);
|
|
return;
|
|
}
|
|
|
|
|
|
|