blob: 40a62a567e3984f6ddd76ac50e5988a600e629eb [file] [log] [blame]
/*
* 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");
}