| /* |
| * Copyright (c) 2014, Freescale Semiconductor, Inc. All rights reserved. |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| #include <gst/tag/tag.h> |
| #include "gstavbmpegtssink.h" |
| #include "avtp.h" |
| #include "cip.h" |
| |
| #define MPEGTS_PACKET_SIZE (188) |
| #define MPEGTS_MAX_PACKAGE_IN_CIP (7)//count of 188 package in one avb package |
| #define MPEGTS_TIMESTAMP_SIZE (4) |
| #define MPEGTS_DEFAULT_DBS (6)//according to IEEE61883-4, set dbs to 6 |
| |
| #define CLOCK_BASE 9LL |
| |
| #define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ |
| GST_MSECOND/10, CLOCK_BASE)) |
| #define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ |
| CLOCK_BASE, GST_MSECOND/10)) |
| #define PCRTIME_TO_GSTTIME(t) ((t) * 1000 / 27) |
| |
| |
| GST_DEBUG_CATEGORY_STATIC (avb_mpegts_sink_debug); |
| #define GST_CAT_DEFAULT avb_mpegts_sink_debug |
| |
| #define gst_avb_mpegts_sink_parent_class parent_class |
| G_DEFINE_TYPE (GstAvbMpegtsSink, gst_avb_mpegts_sink, GST_TYPE_AVBSINK); |
| |
| #define MPEGTS_CAPS \ |
| "video/mpegts, "\ |
| "systemstream = (boolean)true, " \ |
| "packetsize = (int)188" |
| |
| static GstStaticPadTemplate sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (MPEGTS_CAPS)); |
| |
| static gstsutils_property avbsink_property[]= |
| { |
| {"latency", G_TYPE_UINT, gst_avbsink_set_latency}, |
| {"use_ptp_time", G_TYPE_BOOLEAN, gst_avbsink_set_use_ptp_time}, |
| {"package_count",G_TYPE_UINT,gst_avbsink_set_package_count} |
| }; |
| static gstsutils_property basesink_property[]= |
| { |
| {"sync", G_TYPE_BOOLEAN, gst_base_sink_set_sync} |
| }; |
| |
| static guint8 gst_avb_mpegts_sink_get_dbs(GstAvbSink * sink) |
| { |
| GstAvbMpegtsSink *mpegtssink; |
| mpegtssink = GST_AVB_MPEGTS_SINK(sink); |
| return mpegtssink->dbs; |
| } |
| static guint8 gst_avb_mpegts_sink_get_fn(GstAvbSink * sink) |
| { |
| return 3; |
| } |
| static guint8 gst_avb_mpegts_sink_get_sph(GstAvbSink * sink) |
| { |
| return 1; |
| } |
| static guint8 gst_avb_mpegts_sink_get_dbc(GstAvbSink * sink,guint32 payloadLen) |
| { |
| GstAvbMpegtsSink *mpegtssink; |
| mpegtssink = GST_AVB_MPEGTS_SINK(sink); |
| return payloadLen/mpegtssink->dbs/4; |
| } |
| static guint8 gst_avb_mpegts_sink_get_fmt(GstAvbSink * sink) |
| { |
| return CIP_FMT_MPEGTS; |
| } |
| static guint8 gst_avb_mpegts_sink_get_fdf(GstAvbSink * sink) |
| { |
| //this may need a property to config |
| //(1 << 7) time-shifted, 0 non-time-shifted |
| return 0; |
| } |
| static guint16 gst_avb_mpegts_sink_get_syt(GstAvbSink * sink) |
| { |
| return 0; |
| } |
| |
| static guint32 gst_avb_mpegts_sink_get_packet_len(GstAvbSink * sink,guint32 leftsize) |
| { |
| guint32 count = 0; |
| guint32 max_count = 0; |
| |
| max_count = gst_avbsink_get_package_count(sink); |
| count = leftsize/MPEGTS_PACKET_SIZE; |
| |
| if(count > max_count) |
| count = max_count; |
| |
| if(count == 0) |
| count = 1; |
| |
| return (count * MPEGTS_PACKET_SIZE+MPEGTS_TIMESTAMP_SIZE); |
| } |
| static gboolean |
| gst_avb_mpegts_sink_set_caps (GstBaseSink * basesink, GstCaps * caps) |
| { |
| gboolean ret; |
| GstAvbMpegtsSink *sink; |
| const gchar *mimetype; |
| GstStructure *structure; |
| |
| sink = GST_AVB_MPEGTS_SINK (basesink); |
| |
| if (G_UNLIKELY (sink->caps && gst_caps_is_equal (sink->caps, caps))) { |
| GST_DEBUG_OBJECT (sink, |
| "caps haven't changed, skipping reconfiguration"); |
| return TRUE; |
| } |
| |
| structure = gst_caps_get_structure (caps, 0); |
| GST_DEBUG_OBJECT (sink,"gst_avb_mpegts_sink_set_caps %"GST_PTR_FORMAT,caps); |
| |
| /* we have to differentiate between int and float formats */ |
| mimetype = gst_structure_get_name (structure); |
| |
| if (!g_str_equal (mimetype, "video/mpegts")) |
| return FALSE; |
| |
| sink->caps = caps; |
| |
| return TRUE; |
| } |
| static GstCaps * |
| gst_avb_mpegts_sink_get_caps (GstBaseSink * basesink, GstCaps * filter) |
| { |
| GstCaps *caps; |
| GstAvbMpegtsSink *sink = GST_AVB_MPEGTS_SINK (basesink); |
| if ((caps = sink->caps)) { |
| if (filter) |
| caps = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); |
| else |
| gst_caps_ref (caps); |
| } |
| GST_DEBUG_OBJECT (sink, "got caps %" GST_PTR_FORMAT, caps); |
| |
| return caps; |
| } |
| |
| static gboolean |
| gst_avb_mpegts_sink_process_buffer(GstAvbSink * sink, guint8 *srcBuffer, |
| guint32 * offset, guint32 descSize, guint8 * descBuffer, guint64 * outDur) |
| { |
| GstAvbMpegtsSink *mpegtssink; |
| guint32 usedSize = 0; |
| |
| if(sink == NULL || srcBuffer == NULL || offset == NULL || descBuffer == NULL) |
| return FALSE; |
| |
| mpegtssink = GST_AVB_MPEGTS_SINK(sink); |
| usedSize = *offset; |
| |
| memcpy(descBuffer+MPEGTS_TIMESTAMP_SIZE,srcBuffer+usedSize,descSize-MPEGTS_TIMESTAMP_SIZE); |
| usedSize += (descSize-MPEGTS_TIMESTAMP_SIZE); |
| *outDur = 0; |
| *offset = usedSize; |
| return TRUE; |
| } |
| static gboolean write_ts_time(guint8 *inBuffer,guint64 time) |
| { |
| guint8 start = 0; |
| |
| if(inBuffer == NULL) |
| return FALSE; |
| |
| if((inBuffer[0] & 0x01) != 0x01) |
| return FALSE; |
| if((inBuffer[2] & 0x01) != 0x01) |
| return FALSE; |
| if((inBuffer[4] & 0x01) != 0x01) |
| return FALSE; |
| |
| inBuffer[0] = inBuffer[0] & 0xF0; |
| |
| inBuffer[0] |= (time >> 29) & 0x7; |
| inBuffer[0] |= 0x1; |
| |
| inBuffer[1] = (time >> 22) & 0xFF; |
| inBuffer[2] = 0x1; |
| inBuffer[2] |= (time >> 14) & 0xFE; |
| |
| inBuffer[3] = (time >> 7) & 0xFF; |
| inBuffer[4] = 0x1; |
| inBuffer[4] |= (time << 1) & 0xFE; |
| |
| return TRUE; |
| } |
| static gboolean read_ts_time(guint8 *inBuffer,guint64 * time) |
| { |
| guint64 out_time = 0; |
| |
| if(inBuffer == NULL || time == NULL) |
| return FALSE; |
| |
| if((inBuffer[0] & 0x01) != 0x01) |
| return FALSE; |
| if((inBuffer[2] & 0x01) != 0x01) |
| return FALSE; |
| if((inBuffer[4] & 0x01) != 0x01) |
| return FALSE; |
| |
| out_time = ((guint64)(inBuffer[0] & 0x0E) << 29); |
| out_time |= ((guint64)inBuffer[1] << 22); |
| out_time |= ((guint64)(inBuffer[2]& 0xFE) << 14); |
| out_time |= ((guint64)inBuffer[3] << 7); |
| out_time |= ((guint64)(inBuffer[4]& 0xFE) >> 1); |
| |
| *time = out_time; |
| return TRUE; |
| } |
| static inline guint64 |
| get_mpegts_pcr (guint8 * data) |
| { |
| guint32 pcr1; |
| guint16 pcr2; |
| guint64 pcr, pcr_ext; |
| |
| pcr1 = GST_READ_UINT32_BE (data); |
| pcr2 = GST_READ_UINT16_BE (data + 4); |
| pcr = ((guint64) pcr1) << 1; |
| pcr |= (pcr2 & 0x8000) >> 15; |
| pcr_ext = (pcr2 & 0x01ff); |
| return pcr * 300 + pcr_ext % 300; |
| } |
| |
| static gboolean |
| gst_avb_mpegts_sink_rewrite_time(GstAvbSink * sink, guint8 *inBuffer, guint32 bufferSize, guint64 ptp_ts, guint64 ts) |
| { |
| GstAvbMpegtsSink *mpegtssink; |
| int i = 0; |
| guint32 packageCount = 0; |
| guint32 in_ts; |
| #if 1 |
| guint32 offset = 0; |
| guint32 latency = 0; |
| guint8 * pes_buffer = NULL; |
| guint8 adaptation = 0; |
| guint8 start_indicator = 0; |
| guint8 adaptation_len = 0; |
| guint8 has_payload = 0; |
| guint8 strema_id = 0; |
| guint8 flag = 0; |
| guint64 pts = 0; |
| guint64 desc_pts = 0; |
| guint64 test_pts = 0; |
| guint64 dts = 0; |
| gboolean write = FALSE; |
| guint8 adaptation_flag = 0; |
| #endif |
| if(sink == NULL || inBuffer == NULL) |
| return FALSE; |
| |
| mpegtssink = GST_AVB_MPEGTS_SINK(sink); |
| |
| |
| |
| //copy ptp time to header mpeg ts header |
| |
| //todo: convert PTP time |
| in_ts = AVTPDU_GET_U32_TS(ptp_ts); |
| memcpy(inBuffer,&in_ts,MPEGTS_TIMESTAMP_SIZE); |
| |
| GST_LOG_OBJECT(mpegtssink,"gst_avb_mpegts_sink_rewrite_time ptp_ts=%"GST_TIME_FORMAT",u23=%"GST_TIME_FORMAT, |
| GST_TIME_ARGS (ptp_ts),GST_TIME_ARGS (in_ts)); |
| |
| //write latency into pst time in pes header |
| #if 0 |
| offset = MPEGTS_TIMESTAMP_SIZE; |
| |
| packageCount = (bufferSize-MPEGTS_TIMESTAMP_SIZE)/MPEGTS_PACKET_SIZE; |
| |
| for(i = 0; i < packageCount; i++){ |
| offset = MPEGTS_TIMESTAMP_SIZE + i*MPEGTS_PACKET_SIZE; |
| |
| if(inBuffer[offset] != 0x47){ |
| GST_LOG_OBJECT(mpegtssink,"no sync byte"); |
| continue; |
| } |
| |
| offset ++; |
| start_indicator = inBuffer[offset] & 0x40; |
| |
| if(start_indicator == 0){ |
| GST_LOG_OBJECT(mpegtssink,"no start_indicator"); |
| continue; |
| } |
| |
| offset +=2; |
| adaptation = ((inBuffer[offset] & 0x30) >> 4); |
| has_payload = adaptation & 0x1; |
| |
| if(has_payload == 0){ |
| GST_LOG_OBJECT(mpegtssink,"no has_payload"); |
| continue; |
| } |
| |
| offset ++; |
| |
| if(adaptation > 1){ |
| adaptation_len = inBuffer[offset]; |
| |
| adaptation_flag = inBuffer[offset+1]; |
| |
| if(adaptation_flag & 0x10){ |
| guint64 pcr_gst; |
| pcr_gst = PCRTIME_TO_GSTTIME(get_mpegts_pcr(&inBuffer[offset+2])); |
| mpegtssink->pcr = GSTTIME_TO_MPEGTIME(pcr_gst); |
| GST_DEBUG_OBJECT(mpegtssink,"pcr=%"GST_TIME_FORMAT,GST_TIME_ARGS(mpegtssink->pcr)); |
| } |
| offset += adaptation_len+1; |
| } |
| |
| if(offset > bufferSize){ |
| GST_LOG_OBJECT(mpegtssink,"payload offset is wrong"); |
| continue; |
| } |
| |
| pes_buffer = inBuffer+offset; |
| offset = 0; |
| |
| |
| if(pes_buffer[offset] != 0 || pes_buffer[offset+1] != 0 || pes_buffer[offset+2] != 0x1){ |
| GST_LOG_OBJECT(mpegtssink,"pes start code is wrong"); |
| continue; |
| } |
| |
| |
| offset += 3; |
| strema_id = pes_buffer[offset]; |
| offset ++; |
| |
| #if 0 |
| if(strema_id == 0xbc || strema_id == 0xbe |
| || strema_id == 0xbf || (strema_id >= 0xf0 |
| && strema_id <= 0xf2) || strema_id == 0xff |
| || strema_id == 0xf8) |
| return FALSE; |
| #endif |
| |
| //skip pes length |
| offset += 2; |
| |
| if((pes_buffer[offset] >> 6) != 0x2){//first 2 bit is 10 |
| GST_LOG_OBJECT(mpegtssink,"pes buffer is wrong"); |
| continue; |
| } |
| |
| offset ++; |
| |
| flag = pes_buffer[offset]; |
| offset +=2; |
| |
| GST_LOG_OBJECT(mpegtssink,"flag=%x,offset=%d",flag,offset); |
| |
| if((flag & 0x80) == 0x80){ |
| if(read_ts_time(&pes_buffer[offset],&pts)){ |
| if(mpegtssink->pcr_offset == 0 && mpegtssink->pcr > 0 && mpegtssink->pcr < pts){ |
| mpegtssink->pcr_offset = pts - mpegtssink->pcr; |
| GST_DEBUG_OBJECT(mpegtssink,"pcr=%"GST_TIME_FORMAT",pcr_offset=%"GST_TIME_FORMAT, |
| GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (mpegtssink->pcr)),GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (mpegtssink->pcr_offset))); |
| } |
| |
| |
| desc_pts = GSTTIME_TO_MPEGTIME(ts) + mpegtssink->pcr/* + GSTTIME_TO_MPEGTIME(sink->latency)*/; |
| write_ts_time(&pes_buffer[offset],desc_pts); |
| read_ts_time(&pes_buffer[offset],&test_pts);//read again to check the value |
| GST_DEBUG_OBJECT(mpegtssink,"read PTS=%"GST_TIME_FORMAT",write PTS=%"GST_TIME_FORMAT" inTime=%"GST_TIME_FORMAT, |
| GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pts)),GST_TIME_ARGS(MPEGTIME_TO_GSTTIME(desc_pts)), |
| GST_TIME_ARGS(MPEGTIME_TO_GSTTIME(test_pts))); |
| offset +=5; |
| write = TRUE; |
| } |
| } |
| if((flag & 0x40) == 0x40) { |
| if(read_ts_time(&pes_buffer[offset],&dts)){ |
| |
| //pts = MPEGTIME_TO_GSTTIME(pts); |
| desc_pts = GSTTIME_TO_MPEGTIME(ts) + mpegtssink->pcr; |
| GST_DEBUG_OBJECT(mpegtssink,"read DTS=%"GST_TIME_FORMAT",write DTS=%"GST_TIME_FORMAT, |
| GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (dts)),GST_TIME_ARGS(MPEGTIME_TO_GSTTIME(desc_pts))); |
| write_ts_time(&pes_buffer[offset],desc_pts); |
| offset +=5; |
| } |
| } |
| if(write) |
| break; |
| } |
| #endif |
| return TRUE; |
| } |
| |
| static void |
| gst_avb_mpegts_sink_class_init (GstAvbMpegtsSinkClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| GstBaseSinkClass *gstbasesink_class; |
| GstAvbSinkClass *gstavbsink_class; |
| |
| gobject_class = G_OBJECT_CLASS(klass); |
| gstelement_class = GST_ELEMENT_CLASS(klass); |
| gstbasesink_class = GST_BASE_SINK_CLASS(klass); |
| gstavbsink_class = GST_AVBSINK_CLASS(klass); |
| |
| gstbasesink_class->set_caps = gst_avb_mpegts_sink_set_caps; |
| gstbasesink_class->get_caps = gst_avb_mpegts_sink_get_caps; |
| |
| gstavbsink_class->get_dbs = gst_avb_mpegts_sink_get_dbs; |
| gstavbsink_class->get_fn = gst_avb_mpegts_sink_get_fn; |
| gstavbsink_class->get_sph = gst_avb_mpegts_sink_get_sph; |
| gstavbsink_class->get_dbc = gst_avb_mpegts_sink_get_dbc; |
| gstavbsink_class->get_fmt = gst_avb_mpegts_sink_get_fmt; |
| gstavbsink_class->get_fdf = gst_avb_mpegts_sink_get_fdf; |
| gstavbsink_class->get_syt = gst_avb_mpegts_sink_get_syt; |
| |
| gstavbsink_class->get_packet_len = gst_avb_mpegts_sink_get_packet_len; |
| gstavbsink_class->process_buffer = gst_avb_mpegts_sink_process_buffer; |
| gstavbsink_class->rewrite_time = gst_avb_mpegts_sink_rewrite_time; |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&sink_template)); |
| |
| GST_DEBUG_CATEGORY_INIT (avb_mpegts_sink_debug, "avbmpegtssink", 0, "freescale avb mpegts sink"); |
| |
| gst_element_class_set_static_metadata (gstelement_class,"avb mpegts sink", |
| "Sink/Network", |
| "Send mpegts data over 1722 network", |
| IMX_GST_PLUGIN_AUTHOR); |
| |
| |
| GST_LOG_OBJECT (gobject_class,"gst_avb_mpegts_sink_class_init"); |
| |
| } |
| |
| static void gst_avb_mpegts_sink_init (GstAvbMpegtsSink * sink) |
| { |
| GstBaseSink *basesink; |
| GstAvbSink *avbsink; |
| |
| sink->caps = NULL; |
| |
| sink->dbs = MPEGTS_DEFAULT_DBS; |
| |
| sink->pcr = 0; |
| sink->pcr_offset = 0; |
| |
| basesink = GST_BASE_SINK(sink); |
| avbsink = GST_AVBSINK(sink); |
| |
| gst_avbsink_set_package_count(avbsink,MPEGTS_MAX_PACKAGE_IN_CIP); |
| |
| gstsutils_load_default_property(basesink_property,basesink, |
| FSL_GST_CONF_DEFAULT_FILENAME,"avbmpegtssink"); |
| gstsutils_load_default_property(avbsink_property,avbsink, |
| FSL_GST_CONF_DEFAULT_FILENAME,"avbmpegtssink"); |
| |
| sink->latency = gst_avbsink_get_latency(avbsink); |
| GST_LOG_OBJECT (sink,"gst_avb_mpegts_sink_init"); |
| } |
| |