gstreamer/gst/mpegtsdemux/mpegtsparse.c
Jan Alexander Steffens (heftig) 9b2ed3a3fc mpegtsdemux: Close a buffer leak and simplify input_done
tsparse leaked input buffers quite badly:

    GST_TRACERS=leaks GST_DEBUG=GST_TRACER:9 gst-launch-1.0 audiotestsrc num-buffers=3 ! avenc_aac ! mpegtsmux ! tsparse ! fakesink

The input_done vfunc was passed the input buffer, which it had to
consume. For this reason, the base class takes a reference on the buffer
if and only if input_done is not NULL.

Before 34af8ed66a, input_done was used in
tsparse to pass on the input buffer on the "src" pad. That commit
changed the code to packetize for that pad as well and removed the use
of input_done.

Afterwards, 0d2e908523 set input_done
again in order to handle automatic alignment of the output buffers to
the input buffers. However, it ignored the provided buffer and did not
even unref it, causing a leak.

Since no code makes use of the buffer provided with input_done, just
remove the argument in order to simplify things a bit.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1274>
2020-05-18 14:11:40 +00:00

1200 lines
36 KiB
C

/*
* mpegtsparse.c -
* Copyright (C) 2007 Alessandro Decina
*
* Authors:
* Alessandro Decina <alessandro@nnva.org>
* Zaheer Abbas Merali <zaheerabbas at merali dot org>
*
* 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 St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mpegtsbase.h"
#include "mpegtsparse.h"
#include "gstmpegdesc.h"
/* latency in mseconds is maximum 100 ms between PCR */
#define TS_LATENCY 100
#define TABLE_ID_UNSET 0xFF
#define RUNNING_STATUS_RUNNING 4
#define SYNC_BYTE 0x47
GST_DEBUG_CATEGORY_STATIC (mpegts_parse_debug);
#define GST_CAT_DEFAULT mpegts_parse_debug
typedef struct _MpegTSParsePad MpegTSParsePad;
typedef struct
{
MpegTSBaseProgram program;
MpegTSParsePad *tspad;
} MpegTSParseProgram;
struct _MpegTSParsePad
{
GstPad *pad;
/* the program number that the peer wants on this pad */
gint program_number;
MpegTSParseProgram *program;
/* set to FALSE before a push and TRUE after */
gboolean pushed;
/* the return of the latest push */
GstFlowReturn flow_return;
MpegTSParse2Adapter ts_adapter;
};
static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ")
);
static GstStaticPadTemplate program_template =
GST_STATIC_PAD_TEMPLATE ("program_%u", GST_PAD_SRC,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("video/mpegts, " "systemstream = (boolean) true ")
);
enum
{
PROP_0,
PROP_SET_TIMESTAMPS,
PROP_SMOOTHING_LATENCY,
PROP_PCR_PID,
PROP_ALIGNMENT,
PROP_SPLIT_ON_RAI,
/* FILL ME */
};
static void mpegts_parse_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void mpegts_parse_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void
mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program);
static void
mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program);
static GstFlowReturn
mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet,
GstMpegtsSection * section);
static void mpegts_parse_inspect_packet (MpegTSBase * base,
MpegTSPacketizerPacket * packet);
static GstFlowReturn mpegts_parse_have_buffer (MpegTSBase * base,
GstBuffer * buffer);
static MpegTSParsePad *mpegts_parse_create_tspad (MpegTSParse2 * parse,
const gchar * name);
static void mpegts_parse_destroy_tspad (MpegTSParse2 * parse,
MpegTSParsePad * tspad);
static void mpegts_parse_pad_removed (GstElement * element, GstPad * pad);
static GstPad *mpegts_parse_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
static void mpegts_parse_release_pad (GstElement * element, GstPad * pad);
static gboolean mpegts_parse_src_pad_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean push_event (MpegTSBase * base, GstEvent * event);
#define mpegts_parse_parent_class parent_class
G_DEFINE_TYPE (MpegTSParse2, mpegts_parse, GST_TYPE_MPEGTS_BASE);
static void mpegts_parse_reset (MpegTSBase * base);
static GstFlowReturn mpegts_parse_input_done (MpegTSBase * base);
static GstFlowReturn
drain_pending_buffers (MpegTSParse2 * parse, gboolean drain_all);
static void
mpegts_parse_finalize (GObject * object)
{
MpegTSParse2 *parse = (MpegTSParse2 *) object;
gst_flow_combiner_free (parse->flowcombiner);
gst_adapter_clear (parse->ts_adapter.adapter);
g_object_unref (parse->ts_adapter.adapter);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
mpegts_parse_class_init (MpegTSParse2Class * klass)
{
GObjectClass *gobject_class = (GObjectClass *) (klass);
GstElementClass *element_class;
MpegTSBaseClass *ts_class;
gobject_class->set_property = mpegts_parse_set_property;
gobject_class->get_property = mpegts_parse_get_property;
gobject_class->finalize = mpegts_parse_finalize;
g_object_class_install_property (gobject_class, PROP_SET_TIMESTAMPS,
g_param_spec_boolean ("set-timestamps",
"Timestamp (or re-timestamp) the output stream",
"If set, timestamps will be set on the output buffers using "
"PCRs and smoothed over the smoothing-latency period", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SMOOTHING_LATENCY,
g_param_spec_uint ("smoothing-latency", "Smoothing Latency",
"Additional latency in microseconds for smoothing jitter in input timestamps on live capture",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PCR_PID,
g_param_spec_int ("pcr-pid", "PID containing PCR",
"Set the PID to use for PCR values (-1 for auto)",
-1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ALIGNMENT,
g_param_spec_uint ("alignment", "Alignment",
"Number of packets per buffer (padded with dummy packets on EOS) (0 = auto)",
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SPLIT_ON_RAI,
g_param_spec_boolean ("split-on-rai", "Split on RAI",
"If set, buffers sized smaller than the alignment will be sent "
"so that RAI packets are at the start of a new buffer", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
element_class = GST_ELEMENT_CLASS (klass);
element_class->pad_removed = mpegts_parse_pad_removed;
element_class->request_new_pad = mpegts_parse_request_new_pad;
element_class->release_pad = mpegts_parse_release_pad;
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_add_static_pad_template (element_class, &program_template);
gst_element_class_set_static_metadata (element_class,
"MPEG transport stream parser", "Codec/Parser",
"Parses MPEG2 transport streams",
"Alessandro Decina <alessandro@nnva.org>, "
"Zaheer Abbas Merali <zaheerabbas at merali dot org>");
ts_class = GST_MPEGTS_BASE_CLASS (klass);
ts_class->push = GST_DEBUG_FUNCPTR (mpegts_parse_push);
ts_class->push_event = GST_DEBUG_FUNCPTR (push_event);
ts_class->program_started = GST_DEBUG_FUNCPTR (mpegts_parse_program_started);
ts_class->program_stopped = GST_DEBUG_FUNCPTR (mpegts_parse_program_stopped);
ts_class->reset = GST_DEBUG_FUNCPTR (mpegts_parse_reset);
ts_class->input_done = GST_DEBUG_FUNCPTR (mpegts_parse_input_done);
ts_class->inspect_packet = GST_DEBUG_FUNCPTR (mpegts_parse_inspect_packet);
}
static void
mpegts_parse_init (MpegTSParse2 * parse)
{
MpegTSBase *base = (MpegTSBase *) parse;
base->program_size = sizeof (MpegTSParseProgram);
base->push_data = TRUE;
base->push_section = TRUE;
parse->user_pcr_pid = parse->pcr_pid = -1;
parse->flowcombiner = gst_flow_combiner_new ();
parse->srcpad = gst_pad_new_from_static_template (&src_template, "src");
gst_flow_combiner_add_pad (parse->flowcombiner, parse->srcpad);
parse->first = TRUE;
gst_pad_set_query_function (parse->srcpad,
GST_DEBUG_FUNCPTR (mpegts_parse_src_pad_query));
gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad);
parse->have_group_id = FALSE;
parse->group_id = G_MAXUINT;
parse->ts_adapter.adapter = gst_adapter_new ();
parse->ts_adapter.packets_in_adapter = 0;
parse->ts_adapter.first_is_keyframe = TRUE;
parse->alignment = 0;
parse->is_eos = FALSE;
parse->header = 0;
parse->split_on_rai = FALSE;
}
static void
mpegts_parse_reset (MpegTSBase * base)
{
MpegTSParse2 *parse = (MpegTSParse2 *) base;
/* Set the various know PIDs we are interested in */
/* CAT */
MPEGTS_BIT_SET (base->known_psi, 1);
/* NIT, ST */
MPEGTS_BIT_SET (base->known_psi, 0x10);
/* SDT, BAT, ST */
MPEGTS_BIT_SET (base->known_psi, 0x11);
/* EIT, ST, CIT (TS 102 323) */
MPEGTS_BIT_SET (base->known_psi, 0x12);
/* RST, ST */
MPEGTS_BIT_SET (base->known_psi, 0x13);
/* RNT (TS 102 323) */
MPEGTS_BIT_SET (base->known_psi, 0x16);
/* inband signalling */
MPEGTS_BIT_SET (base->known_psi, 0x1c);
/* measurement */
MPEGTS_BIT_SET (base->known_psi, 0x1d);
/* DIT */
MPEGTS_BIT_SET (base->known_psi, 0x1e);
/* SIT */
MPEGTS_BIT_SET (base->known_psi, 0x1f);
parse->first = TRUE;
parse->have_group_id = FALSE;
parse->group_id = G_MAXUINT;
g_list_free_full (parse->pending_buffers, (GDestroyNotify) gst_buffer_unref);
parse->pending_buffers = NULL;
parse->current_pcr = GST_CLOCK_TIME_NONE;
parse->previous_pcr = GST_CLOCK_TIME_NONE;
parse->base_pcr = GST_CLOCK_TIME_NONE;
parse->bytes_since_pcr = 0;
parse->pcr_pid = parse->user_pcr_pid;
parse->ts_offset = 0;
gst_adapter_clear (parse->ts_adapter.adapter);
parse->ts_adapter.packets_in_adapter = 0;
parse->ts_adapter.first_is_keyframe = TRUE;
parse->is_eos = FALSE;
parse->header = 0;
}
static void
mpegts_parse_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
MpegTSParse2 *parse = (MpegTSParse2 *) object;
switch (prop_id) {
case PROP_SET_TIMESTAMPS:
parse->set_timestamps = g_value_get_boolean (value);
break;
case PROP_SMOOTHING_LATENCY:
parse->smoothing_latency = GST_USECOND * g_value_get_uint (value);
mpegts_packetizer_set_pcr_discont_threshold (GST_MPEGTS_BASE
(parse)->packetizer, parse->smoothing_latency);
break;
case PROP_PCR_PID:
parse->pcr_pid = parse->user_pcr_pid = g_value_get_int (value);
break;
case PROP_ALIGNMENT:
parse->alignment = g_value_get_uint (value);
break;
case PROP_SPLIT_ON_RAI:
parse->split_on_rai = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
mpegts_parse_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
MpegTSParse2 *parse = (MpegTSParse2 *) object;
switch (prop_id) {
case PROP_SET_TIMESTAMPS:
g_value_set_boolean (value, parse->set_timestamps);
break;
case PROP_SMOOTHING_LATENCY:
g_value_set_uint (value, parse->smoothing_latency / GST_USECOND);
break;
case PROP_PCR_PID:
g_value_set_int (value, parse->pcr_pid);
break;
case PROP_ALIGNMENT:
g_value_set_uint (value, parse->alignment);
break;
case PROP_SPLIT_ON_RAI:
g_value_set_boolean (value, parse->split_on_rai);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static gboolean
prepare_src_pad (MpegTSBase * base, MpegTSParse2 * parse)
{
GstEvent *event;
gchar *stream_id;
GstCaps *caps;
if (!parse->first)
return TRUE;
/* If there's no packet_size yet, we can't set caps yet */
if (G_UNLIKELY (base->packetizer->packet_size == 0))
return FALSE;
stream_id =
gst_pad_create_stream_id (parse->srcpad, GST_ELEMENT_CAST (base),
"multi-program");
event =
gst_pad_get_sticky_event (parse->parent.sinkpad, GST_EVENT_STREAM_START,
0);
if (event) {
if (gst_event_parse_group_id (event, &parse->group_id))
parse->have_group_id = TRUE;
else
parse->have_group_id = FALSE;
gst_event_unref (event);
} else if (!parse->have_group_id) {
parse->have_group_id = TRUE;
parse->group_id = gst_util_group_id_next ();
}
event = gst_event_new_stream_start (stream_id);
if (parse->have_group_id)
gst_event_set_group_id (event, parse->group_id);
gst_pad_push_event (parse->srcpad, event);
g_free (stream_id);
caps = gst_caps_new_simple ("video/mpegts",
"systemstream", G_TYPE_BOOLEAN, TRUE,
"packetsize", G_TYPE_INT, base->packetizer->packet_size, NULL);
gst_pad_set_caps (parse->srcpad, caps);
gst_caps_unref (caps);
/* If setting output timestamps, ensure that the output segment is TIME */
if (parse->set_timestamps == FALSE || base->segment.format == GST_FORMAT_TIME)
/* Just use the upstream segment */
base->out_segment = base->segment;
else {
GstSegment *seg = &base->out_segment;
gst_segment_init (seg, GST_FORMAT_TIME);
GST_DEBUG_OBJECT (parse,
"Generating time output segment %" GST_SEGMENT_FORMAT, seg);
}
gst_pad_push_event (parse->srcpad,
gst_event_new_segment (&base->out_segment));
parse->first = FALSE;
return TRUE;
}
static gboolean
push_event (MpegTSBase * base, GstEvent * event)
{
MpegTSParse2 *parse = (MpegTSParse2 *) base;
GList *tmp;
if (G_UNLIKELY (parse->first)) {
/* We will send the segment when really starting */
if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT)) {
gst_event_unref (event);
return TRUE;
}
prepare_src_pad (base, parse);
}
if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_EOS)) {
parse->is_eos = TRUE;
if (parse->alignment > 0 && parse->ts_adapter.packets_in_adapter > 0
&& parse->ts_adapter.packets_in_adapter < parse->alignment) {
GstBuffer *buf;
GstMapInfo map;
guint8 *data;
gint missing_packets =
parse->alignment - parse->ts_adapter.packets_in_adapter;
gint i = missing_packets;
gsize packet_size = base->packetizer->packet_size;
GST_DEBUG_OBJECT (parse, "Adding %d dummy packets", missing_packets);
buf = gst_buffer_new_and_alloc (missing_packets * packet_size);
gst_buffer_map (buf, &map, GST_MAP_READWRITE);
data = map.data;
g_assert (packet_size > 0);
for (; i > 0; i--) {
gint offset;
if (packet_size > MPEGTS_NORMAL_PACKETSIZE) {
parse->header++;
GST_WRITE_UINT32_BE (data, parse->header);
offset = 4;
} else {
offset = 0;
}
GST_WRITE_UINT8 (data + offset, SYNC_BYTE);
/* null packet PID */
GST_WRITE_UINT16_BE (data + offset + 1, 0x1FFF);
/* no adaptation field exists | continuity counter undefined */
GST_WRITE_UINT8 (data + offset + 3, 0x10);
/* payload */
memset (data + offset + 4, 0, MPEGTS_NORMAL_PACKETSIZE - 4);
data += packet_size;
}
gst_buffer_unmap (buf, &map);
gst_adapter_push (parse->ts_adapter.adapter, buf);
parse->ts_adapter.packets_in_adapter += missing_packets;
}
drain_pending_buffers (parse, TRUE);
}
if (G_UNLIKELY (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT))
parse->ts_offset = 0;
for (tmp = parse->srcpads; tmp; tmp = tmp->next) {
GstPad *pad = (GstPad *) tmp->data;
if (pad) {
gst_event_ref (event);
gst_pad_push_event (pad, event);
}
}
gst_pad_push_event (parse->srcpad, event);
return TRUE;
}
static MpegTSParsePad *
mpegts_parse_create_tspad (MpegTSParse2 * parse, const gchar * pad_name)
{
GstPad *pad;
MpegTSParsePad *tspad;
pad = gst_pad_new_from_static_template (&program_template, pad_name);
gst_pad_set_query_function (pad,
GST_DEBUG_FUNCPTR (mpegts_parse_src_pad_query));
/* create our wrapper */
tspad = g_new0 (MpegTSParsePad, 1);
tspad->pad = pad;
tspad->program_number = -1;
tspad->program = NULL;
tspad->pushed = FALSE;
tspad->flow_return = GST_FLOW_NOT_LINKED;
tspad->ts_adapter.adapter = gst_adapter_new ();
tspad->ts_adapter.packets_in_adapter = 0;
tspad->ts_adapter.first_is_keyframe = TRUE;
gst_pad_set_element_private (pad, tspad);
gst_flow_combiner_add_pad (parse->flowcombiner, pad);
return tspad;
}
static void
mpegts_parse_destroy_tspad (MpegTSParse2 * parse, MpegTSParsePad * tspad)
{
gst_adapter_clear (tspad->ts_adapter.adapter);
g_object_unref (tspad->ts_adapter.adapter);
/* free the wrapper */
g_free (tspad);
}
static void
mpegts_parse_pad_removed (GstElement * element, GstPad * pad)
{
MpegTSParsePad *tspad;
MpegTSParse2 *parse = GST_MPEGTS_PARSE (element);
if (gst_pad_get_direction (pad) == GST_PAD_SINK)
return;
tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad);
if (tspad) {
mpegts_parse_destroy_tspad (parse, tspad);
parse->srcpads = g_list_remove_all (parse->srcpads, pad);
}
if (GST_ELEMENT_CLASS (parent_class)->pad_removed)
GST_ELEMENT_CLASS (parent_class)->pad_removed (element, pad);
}
static GstPad *
mpegts_parse_request_new_pad (GstElement * element, GstPadTemplate * template,
const gchar * padname, const GstCaps * caps)
{
MpegTSParse2 *parse;
MpegTSParsePad *tspad;
MpegTSParseProgram *parseprogram;
GstPad *pad;
gint program_num = -1;
GstEvent *event;
gchar *stream_id;
g_return_val_if_fail (template != NULL, NULL);
g_return_val_if_fail (GST_IS_MPEGTS_PARSE (element), NULL);
g_return_val_if_fail (padname != NULL, NULL);
sscanf (padname + 8, "%d", &program_num);
GST_DEBUG_OBJECT (element, "padname:%s, program:%d", padname, program_num);
parse = GST_MPEGTS_PARSE (element);
tspad = mpegts_parse_create_tspad (parse, padname);
tspad->program_number = program_num;
/* Find if the program is already active */
parseprogram =
(MpegTSParseProgram *) mpegts_base_get_program (GST_MPEGTS_BASE (parse),
program_num);
if (parseprogram) {
tspad->program = parseprogram;
parseprogram->tspad = tspad;
}
pad = tspad->pad;
parse->srcpads = g_list_append (parse->srcpads, pad);
gst_pad_set_active (pad, TRUE);
stream_id = gst_pad_create_stream_id (pad, element, padname + 8);
event =
gst_pad_get_sticky_event (parse->parent.sinkpad, GST_EVENT_STREAM_START,
0);
if (event) {
if (gst_event_parse_group_id (event, &parse->group_id))
parse->have_group_id = TRUE;
else
parse->have_group_id = FALSE;
gst_event_unref (event);
} else if (!parse->have_group_id) {
parse->have_group_id = TRUE;
parse->group_id = gst_util_group_id_next ();
}
event = gst_event_new_stream_start (stream_id);
if (parse->have_group_id)
gst_event_set_group_id (event, parse->group_id);
gst_pad_push_event (pad, event);
g_free (stream_id);
gst_element_add_pad (element, pad);
return pad;
}
static GstBuffer *
mpegts_packet_to_buffer (MpegTSPacketizerPacket * packet)
{
GstBuffer *buf =
gst_buffer_new_and_alloc (packet->data_end - packet->data_start);
gst_buffer_fill (buf, 0, packet->data_start,
packet->data_end - packet->data_start);
return buf;
}
static void
mpegts_parse_release_pad (GstElement * element, GstPad * pad)
{
MpegTSParse2 *parse = (MpegTSParse2 *) element;
gst_pad_set_active (pad, FALSE);
/* we do the cleanup in GstElement::pad-removed */
gst_flow_combiner_remove_pad (parse->flowcombiner, pad);
gst_element_remove_pad (element, pad);
}
static GstFlowReturn
empty_adapter_into_pad (MpegTSParse2Adapter * ts_adapter, GstPad * pad)
{
GstAdapter *adapter = ts_adapter->adapter;
GstBuffer *buf = NULL;
GstClockTime ts = gst_adapter_prev_pts (adapter, NULL);
gsize avail = gst_adapter_available (adapter);
GstFlowReturn ret = GST_FLOW_OK;
if (avail > 0)
buf = gst_adapter_take_buffer (adapter, avail);
ts_adapter->packets_in_adapter = 0;
if (buf) {
GST_BUFFER_PTS (buf) = ts;
if (!ts_adapter->first_is_keyframe)
gst_buffer_set_flags (buf, GST_BUFFER_FLAG_DELTA_UNIT);
ret = gst_pad_push (pad, buf);
}
return ret;
}
static GstFlowReturn
enqueue_and_maybe_push_buffer (MpegTSParse2 * parse, GstPad * pad,
MpegTSParse2Adapter * ts_adapter, GstBuffer * buffer)
{
GstFlowReturn ret = GST_FLOW_OK;
if (buffer != NULL) {
if (parse->alignment == 1) {
ret = gst_pad_push (pad, buffer);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
} else {
if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)
&& parse->split_on_rai) {
ret = empty_adapter_into_pad (ts_adapter, pad);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
}
gst_adapter_push (ts_adapter->adapter, buffer);
ts_adapter->packets_in_adapter++;
if (ts_adapter->packets_in_adapter == 1 && parse->split_on_rai) {
ts_adapter->first_is_keyframe =
!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
}
if (ts_adapter->packets_in_adapter == parse->alignment
&& ts_adapter->packets_in_adapter > 0) {
ret = empty_adapter_into_pad (ts_adapter, pad);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
}
}
}
return ret;
}
static GstFlowReturn
mpegts_parse_tspad_push_section (MpegTSParse2 * parse, MpegTSParsePad * tspad,
GstMpegtsSection * section, MpegTSPacketizerPacket * packet)
{
GstFlowReturn ret = GST_FLOW_OK;
gboolean to_push = TRUE;
if (tspad->program_number != -1) {
if (tspad->program) {
/* we push all sections to all pads except PMTs which we
* only push to pads meant to receive that program number */
if (section->table_id == 0x02) {
/* PMT */
if (section->subtable_extension != tspad->program_number)
to_push = FALSE;
}
} else if (section->table_id != 0x00) {
/* there's a program filter on the pad but the PMT for the program has not
* been parsed yet, ignore the pad until we get a PMT.
* But we always allow PAT to go through */
to_push = FALSE;
}
}
GST_DEBUG_OBJECT (parse,
"pushing section: %d program number: %d table_id: %d", to_push,
tspad->program_number, section->table_id);
if (to_push) {
GstBuffer *buf = mpegts_packet_to_buffer (packet);
ret =
enqueue_and_maybe_push_buffer (parse, tspad->pad, &tspad->ts_adapter,
buf);
}
GST_LOG_OBJECT (parse, "Returning %s", gst_flow_get_name (ret));
return ret;
}
static GstFlowReturn
mpegts_parse_tspad_push (MpegTSParse2 * parse, MpegTSParsePad * tspad,
MpegTSPacketizerPacket * packet)
{
GstFlowReturn ret = GST_FLOW_OK;
MpegTSBaseProgram *bp = NULL;
if (tspad->program_number != -1) {
if (tspad->program)
bp = (MpegTSBaseProgram *) tspad->program;
else
bp = mpegts_base_get_program ((MpegTSBase *) parse,
tspad->program_number);
}
if (bp) {
if (packet->pid == bp->pmt_pid || bp->streams == NULL
|| bp->streams[packet->pid]) {
GstBuffer *buf = mpegts_packet_to_buffer (packet);
/* push if there's no filter or if the pid is in the filter */
ret = gst_pad_push (tspad->pad, buf);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
}
}
GST_DEBUG_OBJECT (parse, "Returning %s", gst_flow_get_name (ret));
return ret;
}
static void
pad_clear_for_push (GstPad * pad, MpegTSParse2 * parse)
{
MpegTSParsePad *tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad);
tspad->flow_return = GST_FLOW_NOT_LINKED;
tspad->pushed = FALSE;
}
static GstFlowReturn
mpegts_parse_push (MpegTSBase * base, MpegTSPacketizerPacket * packet,
GstMpegtsSection * section)
{
MpegTSParse2 *parse = (MpegTSParse2 *) base;
guint32 pads_cookie;
gboolean done = FALSE;
GstPad *pad = NULL;
MpegTSParsePad *tspad;
GstFlowReturn ret;
GList *srcpads;
GstBuffer *buf;
GST_OBJECT_LOCK (parse);
srcpads = parse->srcpads;
/* clear tspad->pushed on pads */
g_list_foreach (srcpads, (GFunc) pad_clear_for_push, parse);
if (srcpads)
ret = GST_FLOW_NOT_LINKED;
else
ret = GST_FLOW_OK;
/* Get cookie and source pads list */
pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie;
if (G_LIKELY (srcpads)) {
pad = GST_PAD_CAST (srcpads->data);
g_object_ref (pad);
}
GST_OBJECT_UNLOCK (parse);
buf = mpegts_packet_to_buffer (packet);
if (parse->split_on_rai
&& !(packet->afc_flags & MPEGTS_AFC_RANDOM_ACCESS_FLAG)) {
gst_buffer_set_flags (buf, GST_BUFFER_FLAG_DELTA_UNIT);
}
ret = mpegts_parse_have_buffer (base, buf);
while (pad && !done) {
tspad = gst_pad_get_element_private (pad);
if (G_LIKELY (!tspad->pushed)) {
if (section) {
tspad->flow_return =
mpegts_parse_tspad_push_section (parse, tspad, section, packet);
} else {
tspad->flow_return = mpegts_parse_tspad_push (parse, tspad, packet);
}
tspad->pushed = TRUE;
if (G_UNLIKELY (tspad->flow_return != GST_FLOW_OK
&& tspad->flow_return != GST_FLOW_NOT_LINKED)) {
/* return the error upstream */
ret = tspad->flow_return;
done = TRUE;
}
}
if (ret == GST_FLOW_NOT_LINKED)
ret = tspad->flow_return;
g_object_unref (pad);
if (G_UNLIKELY (!done)) {
GST_OBJECT_LOCK (parse);
if (G_UNLIKELY (pads_cookie != GST_ELEMENT_CAST (parse)->pads_cookie)) {
/* resync */
GST_DEBUG ("resync");
pads_cookie = GST_ELEMENT_CAST (parse)->pads_cookie;
srcpads = parse->srcpads;
} else {
GST_DEBUG ("getting next pad");
/* Get next pad */
srcpads = g_list_next (srcpads);
}
if (srcpads) {
pad = GST_PAD_CAST (srcpads->data);
g_object_ref (pad);
} else
done = TRUE;
GST_OBJECT_UNLOCK (parse);
}
}
return ret;
}
static void
mpegts_parse_inspect_packet (MpegTSBase * base, MpegTSPacketizerPacket * packet)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (base);
GST_LOG ("pid 0x%04x pusi:%d, afc:%d, cont:%d, payload:%p PCR %"
G_GUINT64_FORMAT, packet->pid, packet->payload_unit_start_indicator,
packet->scram_afc_cc & 0x30,
FLAGS_CONTINUITY_COUNTER (packet->scram_afc_cc), packet->payload,
packet->pcr);
/* Store the PCR if desired */
if (parse->current_pcr == GST_CLOCK_TIME_NONE &&
packet->afc_flags & MPEGTS_AFC_PCR_FLAG) {
/* Take this as the pcr_pid if set to auto-select */
if (parse->pcr_pid == -1)
parse->pcr_pid = packet->pid;
/* Check the PCR-PID matches the program we want for multiple programs */
if (parse->pcr_pid == packet->pid) {
parse->current_pcr = mpegts_packetizer_pts_to_ts (base->packetizer,
PCRTIME_TO_GSTTIME (packet->pcr), parse->pcr_pid);
GST_DEBUG ("Got new PCR %" GST_TIME_FORMAT " raw %" G_GUINT64_FORMAT,
GST_TIME_ARGS (parse->current_pcr), packet->pcr);
if (parse->base_pcr == GST_CLOCK_TIME_NONE) {
parse->base_pcr = parse->current_pcr;
}
}
}
}
static GstClockTime
get_pending_timestamp_diff (MpegTSParse2 * parse)
{
GList *l;
GstClockTime first_ts, last_ts;
if (parse->pending_buffers == NULL)
return GST_CLOCK_TIME_NONE;
l = g_list_last (parse->pending_buffers);
first_ts = GST_BUFFER_PTS (l->data);
if (first_ts == GST_CLOCK_TIME_NONE)
return GST_CLOCK_TIME_NONE;
l = g_list_first (parse->pending_buffers);
last_ts = GST_BUFFER_PTS (l->data);
if (last_ts == GST_CLOCK_TIME_NONE)
return GST_CLOCK_TIME_NONE;
return last_ts - first_ts;
}
static GstFlowReturn
drain_pending_buffers (MpegTSParse2 * parse, gboolean drain_all)
{
GstFlowReturn ret = GST_FLOW_OK;
GstClockTime start_ts;
GstClockTime pcr = GST_CLOCK_TIME_NONE;
GstClockTime pcr_diff = 0;
gsize pcr_bytes, bytes_since_pcr, pos;
GstBuffer *buffer;
GList *l, *end = NULL;
if (parse->pending_buffers == NULL)
return GST_FLOW_OK; /* Nothing to push */
/*
* There are 4 cases:
* 1 We get a buffer with no PCR -> it's the head of the list
* -> Do nothing, unless it's EOS
* 2 We get a buffer with a PCR, it's the first PCR we've seen, and belongs
* to the buffer at the head of the list
* -> Push any buffers in the list except the head,
* using a smoothing of their timestamps to land at the PCR
* -> store new PCR as the previous PCR, bytes_since_pcr = sizeof (buffer);
* 3 It's EOS (drain_all == TRUE, current_pcr == NONE)
* -> Push any buffers in the list using a smoothing of their timestamps
* starting at the previous PCR or first TS
* 4 We get a buffer with a PCR, and have a previous PCR
* -> If distance > smoothing_latency,
* output buffers except the last in the pending queue using
* piecewise-linear timestamps
* -> store new PCR as the previous PCR, bytes_since_pcr = sizeof (buffer);
*/
/* Case 1 */
if (!GST_CLOCK_TIME_IS_VALID (parse->current_pcr) && !drain_all)
return GST_FLOW_OK;
if (GST_CLOCK_TIME_IS_VALID (parse->current_pcr)) {
pcr = parse->current_pcr;
parse->current_pcr = GST_CLOCK_TIME_NONE;
}
/* The bytes of the last buffer are after the PCR */
buffer = GST_BUFFER (g_list_nth_data (parse->pending_buffers, 0));
bytes_since_pcr = gst_buffer_get_size (buffer);
pcr_bytes = parse->bytes_since_pcr - bytes_since_pcr;
if (!drain_all)
end = g_list_first (parse->pending_buffers);
/* Case 2 */
if (!GST_CLOCK_TIME_IS_VALID (parse->previous_pcr)) {
pcr_diff = get_pending_timestamp_diff (parse);
/* Calculate the start_ts that ends at the end timestamp */
start_ts = GST_CLOCK_TIME_NONE;
if (end) {
start_ts = GST_BUFFER_PTS (GST_BUFFER (end->data));
if (start_ts > pcr_diff)
start_ts -= pcr_diff;
}
} else if (drain_all) { /* Case 3 */
start_ts = parse->previous_pcr;
pcr_diff = get_pending_timestamp_diff (parse);
} else { /* Case 4 */
start_ts = parse->previous_pcr;
if (GST_CLOCK_TIME_IS_VALID (pcr) && pcr > start_ts)
pcr_diff = GST_CLOCK_DIFF (start_ts, pcr);
/* Make sure PCR observations are sufficiently far apart */
if (drain_all == FALSE && pcr_diff < parse->smoothing_latency)
return GST_FLOW_OK;
}
GST_INFO_OBJECT (parse, "Pushing buffers - startTS %" GST_TIME_FORMAT
" duration %" GST_TIME_FORMAT " %" G_GSIZE_FORMAT " bytes",
GST_TIME_ARGS (start_ts), GST_TIME_ARGS (pcr_diff), pcr_bytes);
/* Now, push buffers out pacing timestamps over pcr_diff time and pcr_bytes */
pos = 0;
l = g_list_last (parse->pending_buffers);
while (l != end) {
GList *p;
GstClockTime out_ts = start_ts;
buffer = gst_buffer_make_writable (GST_BUFFER (l->data));
if (out_ts != GST_CLOCK_TIME_NONE && pcr_diff != GST_CLOCK_TIME_NONE &&
pcr_bytes && pos)
out_ts += gst_util_uint64_scale (pcr_diff, pos, pcr_bytes);
pos += gst_buffer_get_size (buffer);
GST_DEBUG_OBJECT (parse,
"InputTS %" GST_TIME_FORMAT " out %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), GST_TIME_ARGS (out_ts));
GST_BUFFER_PTS (buffer) = out_ts + parse->ts_offset;
GST_BUFFER_DTS (buffer) = out_ts + parse->ts_offset;
if (ret == GST_FLOW_OK) {
ret =
enqueue_and_maybe_push_buffer (parse, parse->srcpad,
&parse->ts_adapter, buffer);
} else {
gst_buffer_unref (buffer);
}
/* Free this list node and move to the next */
p = g_list_previous (l);
parse->pending_buffers = g_list_delete_link (parse->pending_buffers, l);
l = p;
}
if (parse->is_eos) {
empty_adapter_into_pad (&parse->ts_adapter, parse->srcpad);
}
parse->pending_buffers = end;
parse->bytes_since_pcr = bytes_since_pcr;
parse->previous_pcr = pcr;
return ret;
}
static GstFlowReturn
mpegts_parse_have_buffer (MpegTSBase * base, GstBuffer * buffer)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (base);
GstFlowReturn ret = GST_FLOW_OK;
GST_LOG_OBJECT (parse, "Received buffer %" GST_PTR_FORMAT, buffer);
/* Assume all packets have equal size */
if (parse->alignment > 0 &&
base->packetizer->packet_size != MPEGTS_NORMAL_PACKETSIZE) {
GstMapInfo map;
guint8 *data;
gst_buffer_map (buffer, &map, GST_MAP_READ);
data = map.data;
parse->header = GST_READ_UINT32_BE (data);
gst_buffer_unmap (buffer, &map);
}
if (parse->current_pcr != GST_CLOCK_TIME_NONE) {
GST_DEBUG_OBJECT (parse,
"InputTS %" GST_TIME_FORMAT " PCR %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_PTS (buffer)),
GST_TIME_ARGS (parse->current_pcr));
}
if (parse->set_timestamps || parse->first) {
parse->pending_buffers = g_list_prepend (parse->pending_buffers, buffer);
parse->bytes_since_pcr += gst_buffer_get_size (buffer);
buffer = NULL;
}
if (!prepare_src_pad (base, parse))
return GST_FLOW_OK;
if (parse->pending_buffers != NULL) {
/* Don't keep pending_buffers if not setting output timestamps */
gboolean drain_all = (parse->set_timestamps == FALSE);
ret = drain_pending_buffers (parse, drain_all);
if (ret != GST_FLOW_OK) {
if (buffer)
gst_buffer_unref (buffer);
return ret;
}
}
ret =
enqueue_and_maybe_push_buffer (parse, parse->srcpad, &parse->ts_adapter,
buffer);
return ret;
}
static void
empty_pad (GstPad * pad, MpegTSParse2 * parse)
{
MpegTSParsePad *tspad = (MpegTSParsePad *) gst_pad_get_element_private (pad);
GstFlowReturn ret;
ret = empty_adapter_into_pad (&tspad->ts_adapter, tspad->pad);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
}
static GstFlowReturn
mpegts_parse_input_done (MpegTSBase * base)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (base);
GstFlowReturn ret = GST_FLOW_OK;
if (!prepare_src_pad (base, parse))
return GST_FLOW_OK;
if (parse->alignment == 0) {
ret = empty_adapter_into_pad (&parse->ts_adapter, parse->srcpad);
ret = gst_flow_combiner_update_flow (parse->flowcombiner, ret);
g_list_foreach (parse->srcpads, (GFunc) empty_pad, parse);
}
return ret;
}
static MpegTSParsePad *
find_pad_for_program (MpegTSParse2 * parse, guint program_number)
{
GList *tmp;
for (tmp = parse->srcpads; tmp; tmp = tmp->next) {
MpegTSParsePad *tspad = gst_pad_get_element_private ((GstPad *) tmp->data);
if (tspad->program_number == program_number)
return tspad;
}
return NULL;
}
static void
mpegts_parse_program_started (MpegTSBase * base, MpegTSBaseProgram * program)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (base);
MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program;
MpegTSParsePad *tspad;
/* If we have a request pad for that program, activate it */
tspad = find_pad_for_program (parse, program->program_number);
if (tspad) {
tspad->program = parseprogram;
parseprogram->tspad = tspad;
}
}
static void
mpegts_parse_program_stopped (MpegTSBase * base, MpegTSBaseProgram * program)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (base);
MpegTSParseProgram *parseprogram = (MpegTSParseProgram *) program;
MpegTSParsePad *tspad;
/* If we have a request pad for that program, activate it */
tspad = find_pad_for_program (parse, program->program_number);
if (tspad) {
tspad->program = NULL;
parseprogram->tspad = NULL;
}
parse->pcr_pid = -1;
parse->ts_offset += parse->current_pcr - parse->base_pcr;
parse->base_pcr = GST_CLOCK_TIME_NONE;
}
static gboolean
mpegts_parse_src_pad_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
MpegTSParse2 *parse = GST_MPEGTS_PARSE (parent);
gboolean res;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_LATENCY:
{
if ((res = gst_pad_peer_query (((MpegTSBase *) parse)->sinkpad, query))) {
gboolean is_live;
GstClockTime min_latency, max_latency;
gst_query_parse_latency (query, &is_live, &min_latency, &max_latency);
if (is_live) {
GstClockTime extra_latency = TS_LATENCY * GST_MSECOND;
if (parse->set_timestamps) {
extra_latency = MAX (extra_latency, parse->smoothing_latency);
}
min_latency += extra_latency;
if (max_latency != GST_CLOCK_TIME_NONE)
max_latency += extra_latency;
}
gst_query_set_latency (query, is_live, min_latency, max_latency);
}
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
}
return res;
}
gboolean
gst_mpegtsparse_plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (mpegts_parse_debug, "tsparse", 0,
"MPEG transport stream parser");
return gst_element_register (plugin, "tsparse",
GST_RANK_NONE, GST_TYPE_MPEGTS_PARSE);
}