/* GStreamer * Copyright (C) 2021 zgj * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, * Boston, MA 02110-1335, USA. */ /** * SECTION:element-gstamltspvsink * * The amltspvsink element does FIXME stuff. * * * Example launch line * |[ * gst-launch-1.0 -v fakesrc ! amltspvsink * ]| * FIXME Describe what the pipeline does. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gstamltspvsink.h" #include #include #include #include #include #include #include "video_adaptor.h" #include "gstamlsysctl.h" #define GST_USE_UNSTABLE_API 1 #include #include #ifndef UNUSED #define UNUSED(x) (void)(x) #endif #define DUMP_TO_FILE 1 #define SAVE_FREE(p) \ if (p) \ { \ free(p); \ (p) = NULL; \ } GST_DEBUG_CATEGORY_STATIC(gst_amltspvsink_debug_category); #define GST_CAT_DEFAULT gst_amltspvsink_debug_category #define gst_amltspvsink_parent_class parent_class #define COMMON_VIDEO_CAPS \ "width = (int) [ 16, 1920 ], " \ "height = (int) [ 16, 1920 ] " #define PTS_90K 90000 typedef enum { ED_TYPE_INVALID = -1, ED_TYPE_H264 = 0, ED_TYPE_H265 } eExtraDataType; /* h264/h265 extradata struct. */ typedef struct _ExtraData { char *vps; /* vps ptr, only for h265 */ int vps_size; /* vps nal size, only for h265 */ char *sps; /* sps ptr, for h264/h265 */ int sps_size; char *pps; /* pps ptr, for h264/h265 */ int pps_size; } ExtraData; /* private */ struct _GstAmltspvsinkPrivate { GstAmltspvsink *sink; /* element status */ gboolean paused; gboolean received_eos; gboolean eos; guint32 seqnum; /* for eos */ /* eos wating thread */ gboolean quit_eos_wait; GThread *eos_wait_thread; guint64 final_vpts; /* es dimension */ gint32 es_w; gint32 es_h; /* es framerate */ gdouble fr; /* frame rate */ /* display position and dimension */ gboolean setwindow; gint32 disp_x; gint32 disp_y; gint32 disp_w; gint32 disp_h; /* render angle settings */ gboolean setangle; gint32 angle; /* time */ gint64 pts; gint64 duration; gboolean keeposd; /* extradata inject */ eExtraDataType extradata_type; gboolean extradata_got; ExtraData extradata; gboolean extradata_injected; }; enum { PROP_0, PROP_WINDOW_SET, PROP_KEEPOSD, PROP_RENDER_ANGLE }; enum { SIGNAL_FIRSTFRAME, MAX_SIGNAL }; static guint g_signals[MAX_SIGNAL] = {0}; /* prototypes */ static void gst_amltspvsink_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gst_amltspvsink_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gst_amltspvsink_dispose(GObject *object); static void gst_amltspvsink_finalize(GObject *object); static GstStateChangeReturn gst_amltspvsink_change_state(GstElement *element, GstStateChange transition); static GstCaps *gst_amltspvsink_get_caps(GstBaseSink *sink, GstCaps *filter); static gboolean gst_amltspvsink_set_caps(GstBaseSink *sink, GstCaps *caps); static gboolean gst_amltspvsink_start(GstBaseSink *sink); static gboolean gst_amltspvsink_stop(GstBaseSink *sink); #if 0 static void gst_amltspvsink_get_times (GstBaseSink * sink, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); #endif static gboolean gst_amltspvsink_query(GstBaseSink *sink, GstQuery *query); static gboolean gst_amltspvsink_event(GstBaseSink *sink, GstEvent *event); static GstFlowReturn gst_amltspvsink_render(GstBaseSink *sink, GstBuffer *buffer); static void keeposd(gboolean blank); static void dump(const char *path, const uint8_t *data, int size, gboolean vp9, int frame_cnt); /* pad templates */ /* support h264/h265/mjpeg/mpeg1/mpeg2 */ static GstStaticPadTemplate gst_amltspvsink_sink_template = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS( "video/x-h264, " "stream-format={ byte-stream }, " "alignment={ au }, " COMMON_VIDEO_CAPS "; " "video/x-h265, " "stream-format={ byte-stream }, " "alignment={ au }, " COMMON_VIDEO_CAPS "; " "video/mpeg, " "mpegversion = (int) { 1, 2 }, " "systemstream = (boolean) false, " COMMON_VIDEO_CAPS "; " "image/jpeg, " COMMON_VIDEO_CAPS)); /* class initialization */ G_DEFINE_TYPE_WITH_CODE(GstAmltspvsink, gst_amltspvsink, GST_TYPE_BASE_SINK, GST_DEBUG_CATEGORY_INIT(gst_amltspvsink_debug_category, "amltspvsink", 0, "debug category for amltspvsink element"); G_ADD_PRIVATE(GstAmltspvsink)); /******************************utils start*****************************/ static void keeposd(gboolean blank) { static int fb0_enable = -1; static int fb1_enable = -1; static gboolean first = TRUE; static gboolean pre_state = TRUE; if (TRUE == first) { fb0_enable = get_osd0_status(); fb1_enable = get_osd1_status(); } if ((TRUE == first) || (blank != pre_state)) { first = FALSE; pre_state = blank; if (TRUE == blank) { if (1 == fb0_enable) { set_fb0_blank(0); } if (1 == fb1_enable) { set_fb1_blank(0); } } else { set_fb0_blank(1); set_fb1_blank(1); } } return; } /* video_adaptor callback base on AmTsPlayer */ void video_callback(void *user_data, am_tsplayer_event *event) { GstAmltspvsink *amltspvsink = (GstAmltspvsink *)user_data; GST_INFO_OBJECT(amltspvsink, "video_callback type %d\n", event ? event->type : 0); switch (event->type) { case AM_TSPLAYER_EVENT_TYPE_VIDEO_CHANGED: { GST_INFO_OBJECT(amltspvsink, "[evt] AM_TSPLAYER_EVENT_TYPE_VIDEO_CHANGED: %d x %d @%d [%d]\n", event->event.video_format.frame_width, event->event.video_format.frame_height, event->event.video_format.frame_rate, event->event.video_format.frame_aspectratio); break; } case AM_TSPLAYER_EVENT_TYPE_USERDATA_AFD: case AM_TSPLAYER_EVENT_TYPE_USERDATA_CC: { uint8_t *pbuf = event->event.mpeg_user_data.data; uint32_t size = event->event.mpeg_user_data.len; GST_INFO_OBJECT(amltspvsink, "[evt] USERDATA [%d] : %x-%x-%x-%x %x-%x-%x-%x ,size %d\n", event->type, pbuf[0], pbuf[1], pbuf[2], pbuf[3], pbuf[4], pbuf[5], pbuf[6], pbuf[7], size); break; } case AM_TSPLAYER_EVENT_TYPE_FIRST_FRAME: { GST_INFO_OBJECT(amltspvsink, "[evt] AM_TSPLAYER_EVENT_TYPE_FIRST_FRAME\n"); g_signal_emit(G_OBJECT(amltspvsink), g_signals[SIGNAL_FIRSTFRAME], 0, 2, NULL); keeposd(amltspvsink->priv->keeposd); break; } case AM_TSPLAYER_EVENT_TYPE_DECODE_FIRST_FRAME_VIDEO: { GST_INFO_OBJECT(amltspvsink, "[evt] AM_TSPLAYER_EVENT_TYPE_DECODE_FIRST_FRAME_VIDEO\n"); break; } case AM_TSPLAYER_EVENT_TYPE_DECODE_FIRST_FRAME_AUDIO: { GST_INFO_OBJECT(amltspvsink, "[evt] AM_TSPLAYER_EVENT_TYPE_DECODE_FIRST_FRAME_AUDIO\n"); break; } case AM_TSPLAYER_EVENT_TYPE_AV_SYNC_DONE: { GST_INFO_OBJECT(amltspvsink, "[evt] AM_TSPLAYER_EVENT_TYPE_AV_SYNC_DONE\n"); break; } default: break; } } #ifdef DUMP_TO_FILE static uint8_t ivf_header[32] = { 'D', 'K', 'I', 'F', 0x00, 0x00, 0x20, 0x00, 'V', 'P', '9', '0', 0x80, 0x07, 0x38, 0x04, /* 1920 x 1080 */ 0x30, 0x76, 0x00, 0x00, /* frame rate */ 0xe8, 0x03, 0x00, 0x00, /* time scale */ 0x00, 0x00, 0xff, 0xff, /* # of frames */ 0x00, 0x00, 0x00, 0x00 /* unused */ }; static void dump(const char *path, const uint8_t *data, int size, gboolean vp9, int frame_cnt) { char name[50]; uint8_t frame_header[12] = {0}; FILE *fd; if (!data) return; sprintf(name, "%s%d.dat", path, 0); fd = fopen(name, "ab"); if (!fd) return; if (vp9) { if (!frame_cnt) fwrite(ivf_header, 1, 32, fd); frame_header[0] = size & 0xff; frame_header[1] = (size >> 8) & 0xff; frame_header[2] = (size >> 16) & 0xff; frame_header[3] = (size >> 24) & 0xff; fwrite(frame_header, 1, 12, fd); } fwrite(data, 1, size, fd); fclose(fd); } #endif /* wait video eos thread */ static gpointer video_eos_thread(gpointer data) { GstAmltspvsink *amltspvsink = (GstAmltspvsink *)data; GstAmltspvsinkPrivate *priv = amltspvsink->priv; uint32_t count = 30; /* 3s timeout */ uint64_t last_vpts = 0; /* used to save the last vpts obtained */ prctl(PR_SET_NAME, "amltspvsink_eos_t"); GST_INFO("enter"); while (!priv->quit_eos_wait) { uint64_t curr_vpts = 0; /* * EOS judgment basis: * 1.When priv->final_vpts is less than or equal to curr_vpts; * 2.When curr_vpts equals last_vpts, curr_vpts will not change; */ video_get_pts(&curr_vpts); GST_INFO("final_vpts:%llu, curr_vpts:%llu", priv->final_vpts, curr_vpts); if (priv->final_vpts <= curr_vpts || curr_vpts == last_vpts) { priv->eos = TRUE; } last_vpts = curr_vpts; /* priv->eos=TRUE or timeout */ if (priv->eos || !count) { GstMessage *message; if (!count) { GST_WARNING_OBJECT(amltspvsink, "EOS timeout"); } /* Posting EOS */ GST_WARNING_OBJECT(amltspvsink, "Posting EOS"); message = gst_message_new_eos(GST_OBJECT_CAST(amltspvsink)); gst_message_set_seqnum(message, priv->seqnum); gst_element_post_message(GST_ELEMENT_CAST(amltspvsink), message); break; } g_usleep(100000); count--; } GST_INFO("quit"); return NULL; } /* start wait video eos thread */ static int start_eos_thread(GstAmltspvsink *amltspvsink) { GstAmltspvsinkPrivate *priv = amltspvsink->priv; priv->eos_wait_thread = g_thread_new("video eos thread", video_eos_thread, amltspvsink); if (!priv->eos_wait_thread) { GST_ERROR_OBJECT(amltspvsink, "fail to create thread"); return -1; } return 0; } /* start wait video eos thread */ static int stop_eos_thread(GstAmltspvsink *amltspvsink) { GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_OBJECT_LOCK(amltspvsink); priv->quit_eos_wait = TRUE; if (priv->eos_wait_thread) { GST_OBJECT_UNLOCK(amltspvsink); g_thread_join(priv->eos_wait_thread); priv->eos_wait_thread = NULL; return 0; } GST_OBJECT_UNLOCK(amltspvsink); return 0; } /* h264/265 extradata parser, like vps/sps/pps */ static int h264_extradata_parser(const unsigned char *in_buf, int in_size, ExtraData *extra_data) { GstH264ParserResult res = GST_H264_PARSER_OK; GstH264NalParser *h264parser = gst_h264_nal_parser_new(); unsigned int offset = 0; do { GstH264NalUnit nal = {0}; res = gst_h264_parser_identify_nalu(h264parser, in_buf, offset, in_size, &nal); offset = nal.offset + nal.size; GST_DEBUG("nal info, type:%d, size:%d, offset:%d, sc_offset:%d; res:%d!", nal.type, nal.size, nal.offset, nal.sc_offset, res); if ((GST_H264_PARSER_OK != res) && (GST_H264_PARSER_NO_NAL_END != res)) { GST_ERROR("h264 nal parse error!"); goto error; } switch (nal.type) { case (GST_H264_NAL_SPS): { if (NULL == extra_data->sps) { extra_data->sps_size = nal.size + nal.offset - nal.sc_offset; extra_data->sps = malloc(extra_data->sps_size); if (NULL == extra_data->sps) goto error; memcpy(extra_data->sps, nal.data + nal.sc_offset, extra_data->sps_size); } break; } case (GST_H264_NAL_PPS): { if (NULL == extra_data->pps) { extra_data->pps_size = nal.size + nal.offset - nal.sc_offset; extra_data->pps = malloc(extra_data->pps_size); if (NULL == extra_data->pps) goto error; memcpy(extra_data->pps, nal.data + nal.sc_offset, extra_data->pps_size); } break; } default: break; } } while (GST_H264_PARSER_NO_NAL_END != res); if ((NULL == extra_data->sps) || (NULL == extra_data->pps)) goto error; GST_DEBUG("h264 extradata parse finish!"); gst_h264_nal_parser_free(h264parser); return 0; error: GST_ERROR("h264 extradata parse error!"); gst_h264_nal_parser_free(h264parser); SAVE_FREE(extra_data->sps); SAVE_FREE(extra_data->pps); return -1; } static int h265_extradata_parser(const unsigned char *in_buf, int in_size, ExtraData *extra_data) { GstH265ParserResult res = GST_H265_PARSER_OK; GstH265Parser *h265parser = gst_h265_parser_new(); unsigned int offset = 0; do { GstH265NalUnit nal = {0}; res = gst_h265_parser_identify_nalu(h265parser, in_buf, offset, in_size, &nal); offset = nal.offset + nal.size; GST_DEBUG("nal info, type:%d, size:%d, offset:%d, sc_offset:%d; res:%d!", nal.type, nal.size, nal.offset, nal.sc_offset, res); if ((GST_H265_PARSER_OK != res) && (GST_H265_PARSER_NO_NAL_END != res)) { GST_ERROR("h265 nal parse error!"); goto error; } switch (nal.type) { case (GST_H265_NAL_VPS): { if (NULL == extra_data->vps) { extra_data->vps_size = nal.size + nal.offset - nal.sc_offset; extra_data->vps = malloc(extra_data->vps_size); if (NULL == extra_data->vps) goto error; memcpy(extra_data->vps, nal.data + nal.sc_offset, extra_data->vps_size); } break; } case (GST_H265_NAL_SPS): { if (NULL == extra_data->sps) { extra_data->sps_size = nal.size + nal.offset - nal.sc_offset; extra_data->sps = malloc(extra_data->sps_size); if (NULL == extra_data->sps) goto error; memcpy(extra_data->sps, nal.data + nal.sc_offset, extra_data->sps_size); } break; } case (GST_H265_NAL_PPS): { if (NULL == extra_data->pps) { extra_data->pps_size = nal.size + nal.offset - nal.sc_offset; extra_data->pps = malloc(extra_data->pps_size); if (NULL == extra_data->pps) goto error; memcpy(extra_data->pps, nal.data + nal.sc_offset, extra_data->pps_size); } break; } default: break; } } while (GST_H265_PARSER_NO_NAL_END != res); if ((NULL == extra_data->vps) || (NULL == extra_data->sps) || (NULL == extra_data->pps)) goto error; GST_DEBUG("h265 extradata parse finish!"); gst_h265_parser_free(h265parser); return 0; error: GST_ERROR("h265 extradata parse error!"); gst_h265_parser_free(h265parser); SAVE_FREE(extra_data->vps); SAVE_FREE(extra_data->sps); SAVE_FREE(extra_data->pps); return -1; } static int extradata_get(eExtraDataType type, const unsigned char *in_buf, int in_size, ExtraData *extra_data) { int ret = 0; ExtraData tmp_extra_data = {0}; if ((ED_TYPE_INVALID == type) || (NULL == in_buf) || (0 >= in_size) || (NULL == extra_data)) { return -1; } if (ED_TYPE_H264 == type) { ret = h264_extradata_parser(in_buf, in_size, &tmp_extra_data); } else { ret = h265_extradata_parser(in_buf, in_size, &tmp_extra_data); } if (0 == ret) { SAVE_FREE(extra_data->vps); SAVE_FREE(extra_data->sps); SAVE_FREE(extra_data->pps); memcpy(extra_data, &tmp_extra_data, sizeof(ExtraData)); } return ret; } static void extradata_release(ExtraData *extra_data) { if (NULL == extra_data) { return; } SAVE_FREE(extra_data->vps); SAVE_FREE(extra_data->sps); SAVE_FREE(extra_data->pps); return; } /*******************************utils end******************************/ /* gst-api */ static void gst_amltspvsink_class_init(GstAmltspvsinkClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); GstElementClass *element_class = GST_ELEMENT_CLASS(klass); GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS(klass); /* Setting up pads and setting metadata should be moved to base_class_init if you intend to subclass this class. */ gst_element_class_add_static_pad_template(element_class, &gst_amltspvsink_sink_template); gst_element_class_set_static_metadata(element_class, "Video Decoder on Tsplayer", "Codec/Decoder/Video", "Video Decoder and Render on Tsplayer", "Guangjun Zhu "); gobject_class->set_property = gst_amltspvsink_set_property; gobject_class->get_property = gst_amltspvsink_get_property; gobject_class->dispose = gst_amltspvsink_dispose; gobject_class->finalize = gst_amltspvsink_finalize; element_class->change_state = GST_DEBUG_FUNCPTR(gst_amltspvsink_change_state); base_sink_class->get_caps = GST_DEBUG_FUNCPTR(gst_amltspvsink_get_caps); base_sink_class->set_caps = GST_DEBUG_FUNCPTR(gst_amltspvsink_set_caps); base_sink_class->start = GST_DEBUG_FUNCPTR(gst_amltspvsink_start); base_sink_class->stop = GST_DEBUG_FUNCPTR(gst_amltspvsink_stop); base_sink_class->query = GST_DEBUG_FUNCPTR(gst_amltspvsink_query); base_sink_class->event = GST_DEBUG_FUNCPTR(gst_amltspvsink_event); base_sink_class->render = GST_DEBUG_FUNCPTR(gst_amltspvsink_render); g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_WINDOW_SET, g_param_spec_string("rectangle", "rectangle", "Window Set Format: x,y,width,height", NULL, G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_KEEPOSD, g_param_spec_boolean("keeposd", "keeposd", "Whether to keep OSD during playback", FALSE, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_RENDER_ANGLE, g_param_spec_int("render-angle", "render-angle", "Render angle settings:0/90/180/270", 0, 270, 0, G_PARAM_READWRITE)); g_signals[SIGNAL_FIRSTFRAME] = g_signal_new("first-video-frame-callback", G_TYPE_FROM_CLASS(GST_ELEMENT_CLASS(klass)), (GSignalFlags)(G_SIGNAL_RUN_LAST), 0, /* class offset */ NULL, /* accumulator */ NULL, /* accu data */ g_cclosure_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); return; } static void gst_amltspvsink_init(GstAmltspvsink *amltspvsink) { GstAmltspvsinkPrivate *priv = (GstAmltspvsinkPrivate *)gst_amltspvsink_get_instance_private(amltspvsink); GST_FIXME_OBJECT(amltspvsink, "build time:%s,%s", __DATE__, __TIME__); memset(priv, 0, sizeof(GstAmltspvsinkPrivate)); amltspvsink->priv = priv; priv->sink = amltspvsink; priv->paused = FALSE; priv->received_eos = FALSE; priv->eos = FALSE; priv->setwindow = FALSE; priv->keeposd = FALSE; priv->extradata_type = ED_TYPE_INVALID; priv->extradata_got = FALSE; priv->extradata_injected = TRUE; return; } void gst_amltspvsink_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(object); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_DEBUG_OBJECT(amltspvsink, "set_property, id:%d!", property_id); switch (property_id) { case PROP_WINDOW_SET: { const gchar *str = g_value_get_string(value); gchar **parts = g_strsplit(str, ",", 4); if (!parts[0] || !parts[1] || !parts[2] || !parts[3]) { GST_ERROR("Bad window properties string"); } else { priv->disp_x = atoi(parts[0]); priv->disp_y = atoi(parts[1]); priv->disp_w = atoi(parts[2]); priv->disp_h = atoi(parts[3]); priv->setwindow = TRUE; video_set_region(priv->disp_x, priv->disp_y, priv->disp_w, priv->disp_h); GST_INFO("set window rect (%d,%d,%d,%d)\n", priv->disp_x, priv->disp_y, priv->disp_w, priv->disp_h); } g_strfreev(parts); break; } case PROP_KEEPOSD: { priv->keeposd = g_value_get_boolean(value); keeposd(priv->keeposd); break; } case PROP_RENDER_ANGLE: { int angle = g_value_get_int(value); if (0 == angle || 90 == angle || 180 == angle || 270 == angle) { priv->angle = angle; if (ERROR_CODE_OK != video_set_angle(priv->angle)) { priv->setangle = TRUE; } GST_INFO("set render angle, %d", angle); } else { GST_ERROR("Bad render angle value, %d", angle); } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } void gst_amltspvsink_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(object); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_DEBUG_OBJECT(amltspvsink, "get_property, id:%d!", property_id); switch (property_id) { case PROP_KEEPOSD: { g_value_set_boolean(value, priv->keeposd); break; } case PROP_RENDER_ANGLE: { g_value_set_int(value, priv->angle); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } void gst_amltspvsink_dispose(GObject *object) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(object); GST_DEBUG_OBJECT(amltspvsink, "dispose"); /* clean up as possible. may be called multiple times */ G_OBJECT_CLASS(gst_amltspvsink_parent_class)->dispose(object); } void gst_amltspvsink_finalize(GObject *object) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(object); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_DEBUG_OBJECT(amltspvsink, "finalize"); /* clean up object here */ GST_OBJECT_LOCK(amltspvsink); if ((ED_TYPE_INVALID != priv->extradata_type) && (TRUE == priv->extradata_got)) { GST_INFO("release extradata!"); extradata_release(&priv->extradata); } GST_OBJECT_UNLOCK(amltspvsink); G_OBJECT_CLASS(gst_amltspvsink_parent_class)->finalize(object); } static GstStateChangeReturn gst_amltspvsink_change_state(GstElement *element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(element); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_FIXME_OBJECT(amltspvsink, "change_state--%s!", gst_state_change_get_name(transition)); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: { GST_OBJECT_LOCK(amltspvsink); if (ERROR_CODE_OK != video_init()) { GST_ERROR_OBJECT(amltspvsink, "video_init failed!"); GST_OBJECT_UNLOCK(amltspvsink); return GST_STATE_CHANGE_FAILURE; } if (ERROR_CODE_OK != video_register_callback(video_callback, (void *)amltspvsink)) { GST_ERROR_OBJECT(amltspvsink, "video_register_callback failed!"); GST_OBJECT_UNLOCK(amltspvsink); return GST_STATE_CHANGE_FAILURE; } if (TRUE == priv->setwindow) { video_set_region(priv->disp_x, priv->disp_y, priv->disp_w, priv->disp_h); priv->setwindow = FALSE; } if (TRUE == priv->setangle) { video_set_angle(priv->angle); priv->setangle = FALSE; } GST_OBJECT_UNLOCK(amltspvsink); break; } case GST_STATE_CHANGE_READY_TO_PAUSED: { break; } case GST_STATE_CHANGE_PAUSED_TO_PLAYING: { GST_OBJECT_LOCK(amltspvsink); if (TRUE == priv->paused) { video_resume(); } priv->paused = FALSE; GST_OBJECT_UNLOCK(amltspvsink); break; } case GST_STATE_CHANGE_PLAYING_TO_PAUSED: { GST_OBJECT_LOCK(amltspvsink); priv->paused = TRUE; video_pause(); GST_OBJECT_UNLOCK(amltspvsink); break; } case GST_STATE_CHANGE_PAUSED_TO_READY: { keeposd(TRUE); break; } default: break; } ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); switch (transition) { case GST_STATE_CHANGE_READY_TO_NULL: { GST_OBJECT_LOCK(amltspvsink); video_stop(); video_deinit(); GST_OBJECT_UNLOCK(amltspvsink); break; } default: break; } return ret; } static GstCaps * gst_amltspvsink_get_caps(GstBaseSink *sink, GstCaps *filter) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GstCaps *caps = gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(sink)); GST_DEBUG_OBJECT(amltspvsink, "get_caps, filter: %s", filter ? gst_structure_get_name(gst_caps_get_structure(filter, 0)) : "null"); if (filter) { GstCaps *intersection = gst_caps_intersect_full(filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref(caps); caps = intersection; } GST_DEBUG_OBJECT(amltspvsink, "returning caps: %" GST_PTR_FORMAT, caps); return caps; } /* notify subclass of new caps */ static gboolean gst_amltspvsink_set_caps(GstBaseSink *sink, GstCaps *caps) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GstStructure *structure; const gchar *mime; gint version = 0; gint len = 0; gint num = 0; gint denom = 0; gint width = 0; gint height = 0; gchar *str = gst_caps_to_string(caps); GST_DEBUG_OBJECT(amltspvsink, "caps:%s!", str); g_free(str); structure = gst_caps_get_structure(caps, 0); if (!structure) return FALSE; mime = gst_structure_get_name(structure); if (!mime) return FALSE; /* format */ len = strlen(mime); if (len == 12 && !strncmp("video/x-h264", mime, len)) { /* alignment check */ if (gst_structure_has_field(structure, "alignment")) { const char *alignment = gst_structure_get_string(structure, "alignment"); if (strncmp("au", alignment, strlen(alignment))) { GST_ERROR_OBJECT(amltspvsink, "aligment:%s!", alignment); goto error; } priv->extradata_type = ED_TYPE_H264; } } else if (len == 12 && !strncmp("video/x-h265", mime, len)) { /* alignment check */ if (gst_structure_has_field(structure, "alignment")) { const char *alignment = gst_structure_get_string(structure, "alignment"); if (strncmp("au", alignment, strlen(alignment))) { GST_ERROR_OBJECT(amltspvsink, "aligment:%s!", alignment); goto error; } priv->extradata_type = ED_TYPE_H265; } } else if (len == 10 && !strncmp("video/mpeg", mime, len)) { if (!gst_structure_get_int(structure, "mpegversion", &version)) { goto error; } } else if (len == 10 && !strncmp("image/jpeg", mime, len)) { /* do nothing */ } else { GST_ERROR("not accepting format(%s)", mime); goto error; } video_set_codec(mime, version); video_start(); /* frame rate */ if (gst_structure_get_fraction(structure, "framerate", &num, &denom)) { if (denom == 0) denom = 1; priv->fr = (double)num / (double)denom; if (priv->fr <= 0.0) { g_print("assume 60 fps\n"); priv->fr = 60.0; } } /* dimension */ if (gst_structure_get_int(structure, "width", &width)) priv->es_w = width; else priv->es_w = -1; if (gst_structure_get_int(structure, "height", &height)) priv->es_h = height; else priv->es_h = -1; return TRUE; error: return FALSE; } /* start and stop processing, ideal for opening/closing the resource */ static gboolean gst_amltspvsink_start(GstBaseSink *sink) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GST_DEBUG_OBJECT(amltspvsink, "start"); return TRUE; } static gboolean gst_amltspvsink_stop(GstBaseSink *sink) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GST_DEBUG_OBJECT(amltspvsink, "stop"); return TRUE; } /* notify subclass of query */ static gboolean gst_amltspvsink_query(GstBaseSink *sink, GstQuery *query) { gboolean res = TRUE; GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GST_FIXME_OBJECT(amltspvsink, "query--%s", GST_QUERY_TYPE_NAME(query)); res = GST_BASE_SINK_CLASS(parent_class)->query(sink, query); return res; } /* notify subclass of event */ static gboolean gst_amltspvsink_event(GstBaseSink *sink, GstEvent *event) { gboolean res = TRUE; GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GST_FIXME_OBJECT(amltspvsink, "event--%s", GST_EVENT_TYPE_NAME(event)); switch (GST_EVENT_TYPE(event)) { case GST_EVENT_EOS: { gboolean eof = TRUE; GST_OBJECT_LOCK(sink); priv->received_eos = TRUE; priv->eos = FALSE; priv->seqnum = gst_event_get_seqnum(event); GST_WARNING_OBJECT(amltspvsink, "EOS received seqnum %d", priv->seqnum); /* start wait video eos thread */ start_eos_thread(amltspvsink); /* notify tsplayer EOF */ video_set_param(AM_TSPLAYER_KEY_SET_STREAM_EOF, &eof); GST_OBJECT_UNLOCK(sink); return TRUE; } case GST_EVENT_FLUSH_START: { break; } case GST_EVENT_FLUSH_STOP: { video_flush(); GST_OBJECT_LOCK(sink); priv->extradata_injected = FALSE; GST_OBJECT_UNLOCK(sink); break; } case GST_EVENT_SEGMENT: { GstSegment segment; gst_event_copy_segment(event, &segment); GST_FIXME_OBJECT(amltspvsink, "rate--%f", segment.rate); video_set_rate(segment.rate); break; } default: break; } res = GST_BASE_SINK_CLASS(parent_class)->event(sink, event); return res; } static GstFlowReturn gst_amltspvsink_render(GstBaseSink *sink, GstBuffer *buffer) { GstAmltspvsink *amltspvsink = GST_AMLTSPVSINK(sink); GstAmltspvsinkPrivate *priv = amltspvsink->priv; GstClockTime time = 0; guint64 pts = 0; time = GST_BUFFER_TIMESTAMP(buffer); if (GST_BUFFER_PTS_IS_VALID(buffer)) { pts = time * 9 / 100000; } /* staging max vpts */ priv->final_vpts = (pts > priv->final_vpts) ? pts : priv->final_vpts; GST_OBJECT_LOCK(sink); { GstMapInfo map; gst_buffer_map(buffer, &map, (GstMapFlags)GST_MAP_READ); GST_DEBUG_OBJECT(amltspvsink, "render---size: 0x%zx, vpts:%llu!", map.size, pts); /* get extradata when "priv->extradata_type!=ED_TYPE_INVALID" and "priv->extradata_got==false" */ if ((ED_TYPE_INVALID != priv->extradata_type) && (FALSE == priv->extradata_got)) { if (0 == extradata_get(priv->extradata_type, (const unsigned char *)map.data, map.size, &priv->extradata)) { GST_INFO("got extradata!"); priv->extradata_got = TRUE; } } /* inject extradata when "priv->extradata_injected==false" */ if (priv->extradata_got && !priv->extradata_injected) { GST_INFO("injected extradata!"); priv->extradata_injected = TRUE; if (priv->extradata.vps) /* only for h265 */ video_write_frame(priv->extradata.vps, priv->extradata.vps_size, (uint64_t)pts); if (priv->extradata.sps) video_write_frame(priv->extradata.sps, priv->extradata.sps_size, (uint64_t)pts); if (priv->extradata.pps) video_write_frame(priv->extradata.pps, priv->extradata.pps_size, (uint64_t)pts); #ifdef DUMP_TO_FILE if (getenv("AMLTSPVSINK_ES_DUMP")) { dump("/tmp/ss", (const uint8_t *)priv->extradata.vps, priv->extradata.vps_size, FALSE, 0); dump("/tmp/ss", (const uint8_t *)priv->extradata.sps, priv->extradata.sps_size, FALSE, 0); dump("/tmp/ss", (const uint8_t *)priv->extradata.pps, priv->extradata.pps_size, FALSE, 0); dump("/tmp/ss", map.data, map.size, FALSE, 0); } #endif } video_write_frame(map.data, (int32_t)map.size, (uint64_t)pts); #ifdef DUMP_TO_FILE if (getenv("AMLTSPVSINK_ES_DUMP")) { dump("/tmp/es", map.data, map.size, FALSE, 0); } #endif gst_buffer_unmap(buffer, &map); } GST_OBJECT_UNLOCK(sink); return GST_FLOW_OK; } static gboolean plugin_init(GstPlugin *plugin) { /* FIXME Remember to set the rank if it's an element that is meant to be autoplugged by decodebin. */ return gst_element_register(plugin, "amltspvsink", GST_RANK_PRIMARY - 1, GST_TYPE_AMLTSPVSINK); } /* FIXME: these are normally defined by the GStreamer build system. If you are creating an element to be included in gst-plugins-*, remove these, as they're always defined. Otherwise, edit as appropriate for your external plugin package. */ #ifndef VERSION #define VERSION "1.0.0" #endif #ifndef PACKAGE #define PACKAGE "aml_package" #endif #ifndef PACKAGE_NAME #define PACKAGE_NAME "aml_media" #endif #ifndef GST_PACKAGE_ORIGIN #define GST_PACKAGE_ORIGIN "http://amlogic.com" #endif GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, amltspvsink, "amltspvsink", plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)