diff --git a/ChangeLog b/ChangeLog index b96ae08902..2eb584064b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-11-13 Stefan Kost + + * configure.ac: + * gst/aacparse/Makefile.am: + * gst/aacparse/gstaacparse.c: + * gst/aacparse/gstaacparse.h: + * gst/aacparse/gstbaseparse.c: + * gst/aacparse/gstbaseparse.h: + * gst/amrparse/Makefile.am: + * gst/amrparse/gstamrparse.c: + * gst/amrparse/gstamrparse.h: + * gst/amrparse/gstbaseparse.c: + * gst/amrparse/gstbaseparse.h: + Add two new baseparse based parsers (aac and amr) from Bug #518857. + 2008-11-12 Thijs Vermeir * gst/librfb/rfbdecoder.c: diff --git a/configure.ac b/configure.ac index 2a9f956f68..452797685e 100644 --- a/configure.ac +++ b/configure.ac @@ -232,7 +232,9 @@ AG_GST_SET_LEVEL_DEFAULT($GST_CVS) dnl *** plug-ins to include *** dnl these are all the gst plug-ins, compilable without additional libs +AG_GST_CHECK_PLUGIN(aacparse) AG_GST_CHECK_PLUGIN(aiffparse) +AG_GST_CHECK_PLUGIN(amrparse) AG_GST_CHECK_PLUGIN(app) AG_GST_CHECK_PLUGIN(bayer) AG_GST_CHECK_PLUGIN(cdxaparse) @@ -1385,7 +1387,9 @@ AC_CONFIG_FILES( Makefile gst-plugins-bad.spec gst/Makefile +gst/aacparse/Makefile gst/aiffparse/Makefile +gst/amrparse/Makefile gst/app/Makefile gst/bayer/Makefile gst/cdxaparse/Makefile diff --git a/gst/aacparse/Makefile.am b/gst/aacparse/Makefile.am new file mode 100644 index 0000000000..1341e8dd67 --- /dev/null +++ b/gst/aacparse/Makefile.am @@ -0,0 +1,11 @@ +plugin_LTLIBRARIES = libgstaacparse.la + +libgstaacparse_la_SOURCES = \ + gstaacparse.c gstbaseparse.c + +libgstaacparse_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) +libgstaacparse_la_LIBADD = $(GST_BASE_LIBS) +libgstaacparse_la_LDFLAGS = $(PACKAGE_LIBS) $(GST_PLUGIN_LDFLAGS) +libgstaacparse_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstaacparse.h gstbaseparse.h diff --git a/gst/aacparse/gstaacparse.c b/gst/aacparse/gstaacparse.c new file mode 100644 index 0000000000..f7946f4249 --- /dev/null +++ b/gst/aacparse/gstaacparse.c @@ -0,0 +1,857 @@ +/* GStreamer AAC parser plugin + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +/** + * SECTION:gstaacparse + * @short_description: AAC parser + * @see_also: #GstAmrParse + * + * + * + * This is an AAC parser. It can handle both ADIF and ADTS stream formats. + * The parser inherits from #GstBaseParse and therefore in only needs to + * implement AAC-specific functionality. + * + * + * As ADIF format is not framed, it is not seekable. From the same reason + * stream duration cannot be calculated either. Instead, AAC clips that are + * in ADTS format can be seeked, and parser also is able to calculate their + * playback position and clip duration. + * + * Example launch line + * + * + * gst-launch filesrc location=abc.aac ! aacparse ! faad ! audioresample ! audioconvert ! alsasink + * + * + * + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstaacparse.h" + + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, " + "framed = (boolean) true, " "mpegversion = (int) { 2, 4 };")); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, " + "framed = (boolean) false, " "mpegversion = (int) { 2, 4 };")); + +GST_DEBUG_CATEGORY_STATIC (gst_aacparse_debug); +#define GST_CAT_DEFAULT gst_aacparse_debug + + +static const guint aac_sample_rates[] = { + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000 +}; + + +#define ADIF_MAX_SIZE 40 /* Should be enough */ +#define ADTS_MAX_SIZE 10 /* Should be enough */ + + +#define AAC_FRAME_DURATION(parse) (GST_SECOND/parse->frames_per_sec) + +static void gst_aacparse_finalize (GObject * object); + +gboolean gst_aacparse_start (GstBaseParse * parse); +gboolean gst_aacparse_stop (GstBaseParse * parse); + +static gboolean gst_aacparse_sink_setcaps (GstBaseParse * parse, + GstCaps * caps); + +gboolean gst_aacparse_check_valid_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * size, gint * skipsize); + +GstFlowReturn gst_aacparse_parse_frame (GstBaseParse * parse, + GstBuffer * buffer); + +gboolean gst_aacparse_convert (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, GstFormat dest_format, gint64 * dest_value); + +gboolean gst_aacparse_is_seekable (GstBaseParse * parse); + +gboolean gst_aacparse_event (GstBaseParse * parse, GstEvent * event); + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (gst_aacparse_debug, "aacparse", 0, \ + "AAC audio stream parser"); + +GST_BOILERPLATE_FULL (GstAacParse, gst_aacparse, GstBaseParse, + GST_TYPE_BASE_PARSE, _do_init); + + +/** + * gst_aacparse_base_init: + * @klass: #GstElementClass. + * + */ +static void +gst_aacparse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstElementDetails details = GST_ELEMENT_DETAILS ("AAC audio stream parser", + "Codec/Parser/Audio", + "Advanced Audio Coding parser", + "Stefan Kost "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details (element_class, &details); +} + + +/** + * gst_aacparse_class_init: + * @klass: #GstAacParseClass. + * + */ +static void +gst_aacparse_class_init (GstAacParseClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + object_class->finalize = gst_aacparse_finalize; + + parse_class->start = GST_DEBUG_FUNCPTR (gst_aacparse_start); + parse_class->stop = GST_DEBUG_FUNCPTR (gst_aacparse_stop); + parse_class->event = GST_DEBUG_FUNCPTR (gst_aacparse_event); + parse_class->convert = GST_DEBUG_FUNCPTR (gst_aacparse_convert); + parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_aacparse_sink_setcaps); + parse_class->is_seekable = GST_DEBUG_FUNCPTR (gst_aacparse_is_seekable); + parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_aacparse_parse_frame); + parse_class->check_valid_frame = + GST_DEBUG_FUNCPTR (gst_aacparse_check_valid_frame); +} + + +/** + * gst_aacparse_init: + * @aacparse: #GstAacParse. + * @klass: #GstAacParseClass. + * + */ +static void +gst_aacparse_init (GstAacParse * aacparse, GstAacParseClass * klass) +{ + /* init rest */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), 1024); + aacparse->ts = 0; + GST_DEBUG ("initialized"); +} + + +/** + * gst_aacparse_finalize: + * @object: + * + */ +static void +gst_aacparse_finalize (GObject * object) +{ + GstAacParse *aacparse; + + aacparse = GST_AACPARSE (object); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/** + * gst_aacparse_set_src_caps: + * @aacparse: #GstAacParse. + * + * Set source pad caps according to current knowledge about the + * audio stream. + * + * Returns: TRUE if caps were successfully set. + */ +static gboolean +gst_aacparse_set_src_caps (GstAacParse * aacparse) +{ + GstCaps *src_caps = NULL; + gchar *caps_str = NULL; + gboolean res = FALSE; + + src_caps = gst_caps_new_simple ("audio/mpeg", + "framed", G_TYPE_BOOLEAN, TRUE, + "mpegversion", G_TYPE_INT, aacparse->mpegversion, NULL); + + caps_str = gst_caps_to_string (src_caps); + GST_DEBUG_OBJECT (aacparse, "setting srcpad caps: %s", caps_str); + g_free (caps_str); + + gst_pad_use_fixed_caps (GST_BASE_PARSE (aacparse)->srcpad); + res = gst_pad_set_caps (GST_BASE_PARSE (aacparse)->srcpad, src_caps); + gst_pad_fixate_caps (GST_BASE_PARSE (aacparse)->srcpad, src_caps); + gst_caps_unref (src_caps); + return res; +} + + +/** + * gst_aacparse_sink_setcaps: + * @sinkpad: GstPad + * @caps: GstCaps + * + * Implementation of "set_sink_caps" vmethod in #GstBaseParse class. + * + * Returns: TRUE on success. + */ +static gboolean +gst_aacparse_sink_setcaps (GstBaseParse * parse, GstCaps * caps) +{ + GstAacParse *aacparse; + GstStructure *structure; + gchar *caps_str; + + aacparse = GST_AACPARSE (parse); + structure = gst_caps_get_structure (caps, 0); + caps_str = gst_caps_to_string (caps); + + GST_DEBUG_OBJECT (aacparse, "setcaps: %s", caps_str); + g_free (caps_str); + + // This is needed at least in case of RTP + // Parses the codec_data information to get ObjectType, + // number of channels and samplerate + if (gst_structure_has_field (structure, "codec_data")) { + + const GValue *value = gst_structure_get_value (structure, "codec_data"); + + if (value) { + GstBuffer *buf = gst_value_get_buffer (value); + const guint8 *buffer = GST_BUFFER_DATA (buf); + aacparse->object_type = (buffer[0] & 0xf8) >> 3; + aacparse->sample_rate = ((buffer[0] & 0x07) << 1) | + ((buffer[1] & 0x80) >> 7); + aacparse->channels = (buffer[1] & 0x78) >> 3; + aacparse->header_type = DSPAAC_HEADER_NONE; + aacparse->mpegversion = 4; + } else + return FALSE; + } + + return TRUE; +} + + +/** + * gst_aacparse_update_duration: + * @aacparse: #GstAacParse. + * + */ +static void +gst_aacparse_update_duration (GstAacParse * aacparse) +{ + GstPad *peer; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (aacparse); + + /* Cannot estimate duration. No data has been passed to us yet */ + if (!aacparse->framecount || !aacparse->frames_per_sec) { + return; + } + // info->length = (int)((filelength_filestream(file)/(((info->bitrate*8)/1024)*16))*1000); + + peer = gst_pad_get_peer (parse->sinkpad); + if (peer) { + GstFormat pformat = GST_FORMAT_BYTES; + guint64 bpf = aacparse->bytecount / aacparse->framecount; + gboolean qres = FALSE; + gint64 ptot; + + qres = gst_pad_query_duration (peer, &pformat, &ptot); + gst_object_unref (GST_OBJECT (peer)); + if (qres && bpf) { + gst_base_parse_set_duration (parse, GST_FORMAT_TIME, + AAC_FRAME_DURATION (aacparse) * ptot / bpf); + } + } +} + + +/** + * gst_aacparse_adts_get_frame_len: + * @data: block of data containing an ADTS header. + * + * This function calculates ADTS frame length from the given header. + * + * Returns: size of the ADTS frame. + */ +static inline guint +gst_aacparse_adts_get_frame_len (const guint8 * data) +{ + return ((data[3] & 0x03) << 11) | (data[4] << 3) | ((data[5] & 0xe0) >> 5); +} + + +/** + * gst_aacparse_check_adts_frame: + * @aacparse: #GstAacParse. + * @data: Data to be checked. + * @avail: Amount of data passed. + * @framesize: If valid ADTS frame was found, this will be set to tell the + * found frame size in bytes. + * @needed_data: If frame was not found, this may be set to tell how much + * more data is needed in the next round to detect the frame + * reliably. This may happen when a frame header candidate + * is found but it cannot be guaranteed to be the header without + * peeking the following data. + * + * Check if the given data contains contains ADTS frame. The algorithm + * will examine ADTS frame header and calculate the frame size. Also, another + * consecutive ADTS frame header need to be present after the found frame. + * Otherwise the data is not considered as a valid ADTS frame. However, this + * "extra check" is omitted when EOS has been received. In this case it is + * enough when data[0] contains a valid ADTS header. + * + * This function may set the #needed_data to indicate that a possible frame + * candidate has been found, but more data (#needed_data bytes) is needed to + * be absolutely sure. When this situation occurs, FALSE will be returned. + * + * When a valid frame is detected, this function will use + * gst_base_parse_set_min_frame_size() function from #GstBaseParse class + * to set the needed bytes for next frame.This way next data chunk is already + * of correct size. + * + * Returns: TRUE if the given data contains a valid ADTS header. + */ +static gboolean +gst_aacparse_check_adts_frame (GstAacParse * aacparse, + const guint8 * data, + const guint avail, guint * framesize, guint * needed_data) +{ + if ((data[0] == 0xff) && ((data[1] & 0xf6) == 0xf0)) { + *framesize = gst_aacparse_adts_get_frame_len (data); + + /* In EOS mode this is enough. No need to examine the data further */ + if (aacparse->eos) { + return TRUE; + } + + if (*framesize + ADTS_MAX_SIZE > avail) { + /* We have found a possible frame header candidate, but can't be + sure since we don't have enough data to check the next frame */ + GST_DEBUG ("NEED MORE DATA: we need %d, available %d", + *framesize + ADTS_MAX_SIZE, avail); + *needed_data = *framesize + ADTS_MAX_SIZE; + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), + *framesize + ADTS_MAX_SIZE); + return FALSE; + } + + if ((data[*framesize] == 0xff) && ((data[*framesize + 1] & 0xf6) == 0xf0)) { + guint nextlen = gst_aacparse_adts_get_frame_len (data + (*framesize)); + + GST_LOG ("ADTS frame found, len: %d bytes", *framesize); + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), + nextlen + ADTS_MAX_SIZE); + return TRUE; + } + } + aacparse->sync = FALSE; + return FALSE; +} + + +/** + * gst_aacparse_detect_stream: + * @aacparse: #GstAacParse. + * @data: A block of data that needs to be examined for stream characteristics. + * @avail: Size of the given datablock. + * @framesize: If valid stream was found, this will be set to tell the + * first frame size in bytes. + * @skipsize: If valid stream was found, this will be set to tell the first + * audio frame position within the given data. + * + * Examines the given piece of data and try to detect the format of it. It + * checks for "ADIF" header (in the beginning of the clip) and ADTS frame + * header. If the stream is detected, TRUE will be returned and #framesize + * is set to indicate the found frame size. Additionally, #skipsize might + * be set to indicate the number of bytes that need to be skipped, a.k.a. the + * position of the frame inside given data chunk. + * + * Returns: TRUE on success. + */ +static gboolean +gst_aacparse_detect_stream (GstAacParse * aacparse, + const guint8 * data, const guint avail, guint * framesize, gint * skipsize) +{ + gboolean found = FALSE; + guint need_data = 0; + guint i = 0; + + GST_DEBUG_OBJECT (aacparse, "Parsing header data"); + + /* FIXME: No need to check for ADIF if we are not in the beginning of the + stream */ + + /* Can we even parse the header? */ + if (avail < ADTS_MAX_SIZE) + return FALSE; + + for (i = 0; i < avail - 4; i++) { + if (((data[i] == 0xff) && ((data[i + 1] & 0xf6) == 0xf0)) || + strncmp ((char *) data + i, "ADIF", 4) == 0) { + found = TRUE; + + if (i) { + /* Trick: tell the parent class that we didn't find the frame yet, + but make it skip 'i' amount of bytes. Next time we arrive + here we have full frame in the beginning of the data. */ + *skipsize = i; + return FALSE; + } + break; + } + } + if (!found) { + if (i) + *skipsize = i; + return FALSE; + } + + if (gst_aacparse_check_adts_frame (aacparse, data, avail, + framesize, &need_data)) { + gint sr_idx; + GST_INFO ("ADTS ID: %d, framesize: %d", (data[1] & 0x08) >> 3, *framesize); + + aacparse->header_type = DSPAAC_HEADER_ADTS; + sr_idx = (data[2] & 0x3c) >> 2; + + aacparse->sample_rate = aac_sample_rates[sr_idx]; + aacparse->mpegversion = (data[1] & 0x08) ? 2 : 4; + aacparse->object_type = (data[2] & 0xc0) >> 6; + aacparse->channels = ((data[2] & 0x01) << 2) | ((data[3] & 0xc0) >> 6); + aacparse->bitrate = ((data[5] & 0x1f) << 6) | ((data[6] & 0xfc) >> 2); + + aacparse->frames_per_sec = aac_sample_rates[sr_idx] / 1024.f; + + GST_DEBUG ("ADTS: samplerate %d, channels %d, bitrate %d, objtype %d, " + "fps %f", aacparse->sample_rate, aacparse->channels, + aacparse->bitrate, aacparse->object_type, aacparse->frames_per_sec); + + aacparse->sync = TRUE; + return TRUE; + } else if (need_data) { + /* This tells the parent class not to skip any data */ + *skipsize = 0; + return FALSE; + } + + if (avail < ADIF_MAX_SIZE) + return FALSE; + + if (memcmp (data + i, "ADIF", 4) == 0) { + const guint8 *adif; + int skip_size = 0; + int bitstream_type; + int sr_idx; + + aacparse->header_type = DSPAAC_HEADER_ADIF; + aacparse->mpegversion = 4; + + // Skip the "ADIF" bytes + adif = data + i + 4; + + /* copyright string */ + if (adif[0] & 0x80) + skip_size += 9; /* skip 9 bytes */ + + bitstream_type = adif[0 + skip_size] & 0x10; + aacparse->bitrate = + ((unsigned int) (adif[0 + skip_size] & 0x0f) << 19) | + ((unsigned int) adif[1 + skip_size] << 11) | + ((unsigned int) adif[2 + skip_size] << 3) | + ((unsigned int) adif[3 + skip_size] & 0xe0); + + /* CBR */ + if (bitstream_type == 0) { +#if 0 + /* Buffer fullness parsing. Currently not needed... */ + guint num_elems = 0; + guint fullness = 0; + + num_elems = (adif[3 + skip_size] & 0x1e); + GST_INFO ("ADIF num_config_elems: %d", num_elems); + + fullness = ((unsigned int) (adif[3 + skip_size] & 0x01) << 19) | + ((unsigned int) adif[4 + skip_size] << 11) | + ((unsigned int) adif[5 + skip_size] << 3) | + ((unsigned int) (adif[6 + skip_size] & 0xe0) >> 5); + + GST_INFO ("ADIF buffer fullness: %d", fullness); +#endif + aacparse->object_type = ((adif[6 + skip_size] & 0x01) << 1) | + ((adif[7 + skip_size] & 0x80) >> 7); + sr_idx = (adif[7 + skip_size] & 0x78) >> 3; + } + /* VBR */ + else { + aacparse->object_type = (adif[4 + skip_size] & 0x18) >> 3; + sr_idx = ((adif[4 + skip_size] & 0x07) << 1) | + ((adif[5 + skip_size] & 0x80) >> 7); + } + + /* FIXME: This gives totally wrong results. Duration calculation cannot + be based on this */ + aacparse->sample_rate = aac_sample_rates[sr_idx]; + + aacparse->frames_per_sec = aac_sample_rates[sr_idx] / 1024.f; + GST_INFO ("ADIF fps: %f", aacparse->frames_per_sec); + + // FIXME: Can we assume this? + aacparse->channels = 2; + + GST_INFO ("ADIF: br=%d, samplerate=%d, objtype=%d", + aacparse->bitrate, aacparse->sample_rate, aacparse->object_type); + + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), 512); + + *framesize = avail; + aacparse->sync = TRUE; + return TRUE; + } + + /* This should never happen */ + return FALSE; +} + + +/** + * gst_aacparse_check_valid_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * @framesize: If the buffer contains a valid frame, its size will be put here + * @skipsize: How much data parent class should skip in order to find the + * frame header. + * + * Implementation of "check_valid_frame" vmethod in #GstBaseParse class. + * + * Returns: TRUE if buffer contains a valid frame. + */ +gboolean +gst_aacparse_check_valid_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize) +{ + const guint8 *data; + GstAacParse *aacparse; + guint needed_data = 1024; + gboolean ret = FALSE; + + aacparse = GST_AACPARSE (parse); + data = GST_BUFFER_DATA (buffer); + + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + /* Discontinuous stream -> drop the sync */ + aacparse->sync = FALSE; + } + + if (aacparse->header_type == DSPAAC_HEADER_ADIF || + aacparse->header_type == DSPAAC_HEADER_NONE) { + /* There is nothing to parse */ + *framesize = GST_BUFFER_SIZE (buffer); + ret = TRUE; + } + + else if (aacparse->header_type == DSPAAC_HEADER_NOT_PARSED || + aacparse->sync == FALSE) { + ret = gst_aacparse_detect_stream (aacparse, data, GST_BUFFER_SIZE (buffer), + framesize, skipsize); + } else if (aacparse->header_type == DSPAAC_HEADER_ADTS) { + ret = gst_aacparse_check_adts_frame (aacparse, data, + GST_BUFFER_SIZE (buffer), framesize, &needed_data); + } + + if (!ret) { + /* Increase the block size, we want to find the header by ourselves */ + GST_DEBUG ("buffer didn't contain valid frame, skip = %d", *skipsize); + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), needed_data); + } + return ret; +} + + +/** + * gst_aacparse_parse_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * + * Implementation of "parse_frame" vmethod in #GstBaseParse class. + * + * Returns: GST_FLOW_OK if frame was successfully parsed and can be pushed + * forward. Otherwise appropriate error is returned. + */ +GstFlowReturn +gst_aacparse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) +{ + GstAacParse *aacparse; + GstFlowReturn ret = GST_FLOW_OK; + + aacparse = GST_AACPARSE (parse); + + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + gint64 btime; + gboolean r = gst_aacparse_convert (parse, GST_FORMAT_BYTES, + GST_BUFFER_OFFSET (buffer), + GST_FORMAT_TIME, &btime); + if (r) { + /* FIXME: What to do if the conversion fails? */ + aacparse->ts = btime; + } + } + + GST_BUFFER_DURATION (buffer) = AAC_FRAME_DURATION (aacparse); + GST_BUFFER_TIMESTAMP (buffer) = aacparse->ts; + + if (GST_CLOCK_TIME_IS_VALID (aacparse->ts)) + aacparse->ts += GST_BUFFER_DURATION (buffer); + + if (!(++aacparse->framecount % 50)) { + gst_aacparse_update_duration (aacparse); + } + aacparse->bytecount += GST_BUFFER_SIZE (buffer); + + if (!aacparse->src_caps_set) { + if (!gst_aacparse_set_src_caps (aacparse)) { + /* If linking fails, we need to return appropriate error */ + ret = GST_FLOW_NOT_LINKED; + } + aacparse->src_caps_set = TRUE; + } + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + return ret; +} + + +/** + * gst_aacparse_start: + * @parse: #GstBaseParse. + * + * Implementation of "start" vmethod in #GstBaseParse class. + * + * Returns: TRUE if startup succeeded. + */ +gboolean +gst_aacparse_start (GstBaseParse * parse) +{ + GstAacParse *aacparse; + + aacparse = GST_AACPARSE (parse); + GST_DEBUG ("start"); + aacparse->src_caps_set = FALSE; + aacparse->framecount = 0; + aacparse->bytecount = 0; + aacparse->ts = 0; + aacparse->sync = FALSE; + aacparse->eos = FALSE; + return TRUE; +} + + +/** + * gst_aacparse_stop: + * @parse: #GstBaseParse. + * + * Implementation of "stop" vmethod in #GstBaseParse class. + * + * Returns: TRUE is stopping succeeded. + */ +gboolean +gst_aacparse_stop (GstBaseParse * parse) +{ + GstAacParse *aacparse; + + aacparse = GST_AACPARSE (parse); + GST_DEBUG ("stop"); + aacparse->ts = -1; + return TRUE; +} + + +/** + * gst_aacparse_event: + * @parse: #GstBaseParse. + * @event: #GstEvent. + * + * Implementation of "event" vmethod in #GstBaseParse class. + * + * Returns: TRUE if the event was handled and can be dropped. + */ +gboolean +gst_aacparse_event (GstBaseParse * parse, GstEvent * event) +{ + GstAacParse *aacparse; + + aacparse = GST_AACPARSE (parse); + GST_DEBUG ("event"); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + aacparse->eos = TRUE; + GST_DEBUG ("EOS event"); + break; + default: + break; + } + + return parent_class->event (parse, event); +} + + +/** + * gst_aacparse_convert: + * @parse: #GstBaseParse. + * @src_format: #GstFormat describing the source format. + * @src_value: Source value to be converted. + * @dest_format: #GstFormat defining the converted format. + * @dest_value: Pointer where the conversion result will be put. + * + * Implementation of "convert" vmethod in #GstBaseParse class. + * + * Returns: TRUE if conversion was successful. + */ +gboolean +gst_aacparse_convert (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, GstFormat dest_format, gint64 * dest_value) +{ + gboolean ret = FALSE; + GstAacParse *aacparse; + gfloat bpf; + + aacparse = GST_AACPARSE (parse); + + /* We are not able to do any estimations until some data has been passed */ + if (!aacparse->framecount) + return FALSE; + + bpf = (gfloat) aacparse->bytecount / aacparse->framecount; + + if (src_format == GST_FORMAT_BYTES) { + if (dest_format == GST_FORMAT_TIME) { + /* BYTES -> TIME conversion */ + GST_DEBUG ("converting bytes -> time"); + + if (aacparse->framecount && aacparse->frames_per_sec) { + *dest_value = AAC_FRAME_DURATION (aacparse) * src_value / bpf; + GST_DEBUG ("conversion result: %lld ms", *dest_value / GST_MSECOND); + ret = TRUE; + } + } else if (dest_format == GST_FORMAT_BYTES) { + /* Parent class may ask us to convert from BYTES to BYTES */ + *dest_value = src_value; + ret = TRUE; + } + } else if (src_format == GST_FORMAT_TIME) { + GST_DEBUG ("converting time -> bytes"); + if (dest_format == GST_FORMAT_BYTES) { + if (aacparse->framecount && aacparse->frames_per_sec) { + *dest_value = bpf * src_value / AAC_FRAME_DURATION (aacparse); + GST_DEBUG ("time %lld ms in bytes = %lld", src_value / GST_MSECOND, + *dest_value); + ret = TRUE; + } + } + } else if (src_format == GST_FORMAT_DEFAULT) { + /* DEFAULT == frame-based */ + if (dest_format == GST_FORMAT_TIME && aacparse->frames_per_sec) { + *dest_value = src_value * AAC_FRAME_DURATION (aacparse); + ret = TRUE; + } else if (dest_format == GST_FORMAT_BYTES) { + } + } + + return ret; +} + + +/** + * gst_aacparse_is_seekable: + * @parse: #GstBaseParse. + * + * Implementation of "is_seekable" vmethod in #GstBaseParse class. + * + * Returns: TRUE if the current stream is seekable. + */ +gboolean +gst_aacparse_is_seekable (GstBaseParse * parse) +{ + GstAacParse *aacparse; + + aacparse = GST_AACPARSE (parse); + GST_DEBUG_OBJECT (aacparse, "IS_SEEKABLE: %d", + aacparse->header_type != DSPAAC_HEADER_ADIF); + + /* Not seekable if ADIF header is found */ + return (aacparse->header_type != DSPAAC_HEADER_ADIF); +} + + +/** + * plugin_init: + * @plugin: GstPlugin + * + * Returns: TRUE on success. + */ +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "aacparse", + GST_RANK_PRIMARY + 1, GST_TYPE_AACPARSE); +} + + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "aacparse", + "Advanced Audio Coding Parser", + plugin_init, VERSION, GST_LICENSE_UNKNOWN, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN); diff --git a/gst/aacparse/gstaacparse.h b/gst/aacparse/gstaacparse.h new file mode 100644 index 0000000000..bbdbebcf90 --- /dev/null +++ b/gst/aacparse/gstaacparse.h @@ -0,0 +1,119 @@ +/* GStreamer AAC parser + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +#ifndef __GST_AACPARSE_H__ +#define __GST_AACPARSE_H__ + +#include +#include "gstbaseparse.h" + +G_BEGIN_DECLS + +#define GST_TYPE_AACPARSE \ + (gst_aacparse_get_type()) +#define GST_AACPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AACPARSE, GstAacParse)) +#define GST_AACPARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AACPARSE, GstAacParseClass)) +#define GST_IS_AACPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AACPARSE)) +#define GST_IS_AACPARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AACPARSE)) + + +/** + * GstAacHeaderType: + * @DSPAAC_HEADER_NOT_PARSED: Header not parsed yet. + * @DSPAAC_HEADER_UNKNOWN: Unknown (not recognized) header. + * @DSPAAC_HEADER_ADIF: ADIF header found. + * @DSPAAC_HEADER_ADTS: ADTS header found. + * @DSPAAC_HEADER_NONE: Raw stream, no header. + * + * Type header enumeration set in #header_type. + */ +typedef enum { + DSPAAC_HEADER_NOT_PARSED, + DSPAAC_HEADER_UNKNOWN, + DSPAAC_HEADER_ADIF, + DSPAAC_HEADER_ADTS, + DSPAAC_HEADER_NONE +} GstAacHeaderType; + + +typedef struct _GstAacParse GstAacParse; +typedef struct _GstAacParseClass GstAacParseClass; + +/** + * GstAacParse: + * @element: the parent element. + * @object_type: AAC object type of the stream. + * @bitrate: Current media bitrate. + * @sample_rate: Current media samplerate. + * @channels: Current media channel count. + * @frames_per_sec: FPS value of the current stream. + * @header_type: #GstAacHeaderType indicating the current stream type. + * @framecount: The amount of frames that has been processed this far. + * @bytecount: The amount of bytes that has been processed this far. + * @sync: Tells whether the parser is in sync (a.k.a. not searching for header) + * @eos: End-of-Stream indicator. Set when EOS event arrives. + * @duration: Duration of the current stream. + * @ts: Current stream timestamp. + * + * The opaque GstAacParse data structure. + */ +struct _GstAacParse { + GstBaseParse element; + + /* Stream type -related info */ + gint object_type; + gint bitrate; + gint sample_rate; + gint channels; + gint mpegversion; + gfloat frames_per_sec; + + GstAacHeaderType header_type; + + guint64 framecount; + guint64 bytecount; + gboolean src_caps_set; + gboolean sync; + gboolean eos; + + GstClockTime duration; + GstClockTime ts; +}; + +/** + * GstAacParseClass: + * @parent_class: Element parent class. + * + * The opaque GstAacParseClass data structure. + */ +struct _GstAacParseClass { + GstBaseParseClass parent_class; +}; + +GType gst_aacparse_get_type (void); + +G_END_DECLS + +#endif /* __GST_AACPARSE_H__ */ diff --git a/gst/aacparse/gstbaseparse.c b/gst/aacparse/gstbaseparse.c new file mode 100644 index 0000000000..18b396c3bf --- /dev/null +++ b/gst/aacparse/gstbaseparse.c @@ -0,0 +1,1741 @@ +/* GStreamer + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2008 Sebastian Dröge . + * + * 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. + */ + +/** + * SECTION:gstbaseparse + * @short_description: Base class for stream parsers + * @see_also: #GstBaseTransform + * + * This base class is for parser elements that process data and splits it + * into separate audio/video/whatever frames. + * + * It provides for: + * + * One sinkpad and one srcpad + * Handles state changes + * Does flushing + * Push mode + * Pull mode + * Handles events (NEWSEGMENT/EOS/FLUSH) + * Handles seeking in both modes + * + * Handles POSITION/DURATION/SEEKING/FORMAT/CONVERT queries + * + * + * + * The purpose of this base class is to provide a basic functionality of + * a parser and share a lot of rather complex code. + * + * Description of the parsing mechanism: + * + * + * Set-up phase + * + * GstBaseParse class calls @set_sink_caps to inform the subclass about + * incoming sinkpad caps. Subclass should set the srcpad caps accordingly. + * + * + * GstBaseParse calls @start to inform subclass that data processing is + * about to start now. + * + * + * At least in this point subclass needs to tell the GstBaseParse class + * how big data chunks it wants to receive (min_frame_size). It can do + * this with @gst_base_parse_set_min_frame_size. + * + * + * GstBaseParse class sets up appropriate data passing mode (pull/push) + * and starts to process the data. + * + * + * + * + * + * Parsing phase + * + * GstBaseParse gathers at least min_frame_size bytes of data either + * by pulling it from upstream or collecting buffers into internal + * #GstAdapter. + * + * + * A buffer of min_frame_size bytes is passed to subclass with + * @check_valid_frame. Subclass checks the contents and returns TRUE + * if the buffer contains a valid frame. It also needs to set the + * @framesize according to the detected frame size. If buffer didn't + * contain a valid frame, this call must return FALSE and optionally + * set the @skipsize value to inform base class that how many bytes + * it needs to skip in order to find a valid frame. The passed buffer + * is read-only. + * + * + * After valid frame is found, it will be passed again to subclass with + * @parse_frame call. Now subclass is responsible for parsing the + * frame contents and setting the buffer timestamp, duration and caps. + * + * + * Finally the buffer can be pushed downstream and parsing loop starts + * over again. + * + * + * During the parsing process GstBaseClass will handle both srcpad and + * sinkpad events. They will be passed to subclass if @event or + * @src_event callbacks have been provided. + * + * + * + * + * Shutdown phase + * + * GstBaseParse class calls @stop to inform the subclass that data + * parsing will be stopped. + * + * + * + * + * + * Subclass is responsible for providing pad template caps for + * source and sink pads. The pads need to be named "sink" and "src". It also + * needs to set the fixed caps on srcpad, when the format is ensured (e.g. + * when base class calls subclass' @set_sink_caps function). + * + * This base class uses GST_FORMAT_DEFAULT as a meaning of frames. So, + * subclass conversion routine needs to know that conversion from + * GST_FORMAT_TIME to GST_FORMAT_DEFAULT must return the + * frame number that can be found from the given byte position. + * + * GstBaseParse uses subclasses conversion methods also for seeking. If + * subclass doesn't provide @convert function, seeking will get disabled. + * + * Subclass @start and @stop functions will be called to inform the beginning + * and end of data processing. + * + * Things that subclass need to take care of: + * + * Provide pad templates + * + * Fixate the source pad caps when appropriate + * + * + * Inform base class how big data chunks should be retrieved. This is + * done with @gst_base_parse_set_min_frame_size function. + * + * + * Examine data chunks passed to subclass with @check_valid_frame + * and tell if they contain a valid frame + * + * + * Set the caps and timestamp to frame that is passed to subclass with + * @parse_frame function. + * + * Provide conversion functions + * + * Update the duration information with @gst_base_parse_set_duration + * + * + * + */ + +/* TODO: + * - Better segment handling: + * - NEWSEGMENT for gaps + * - Not NEWSEGMENT starting at 0 but at first frame timestamp + * - GstIndex support + * - Seek table generation and subclass seek entry injection + * - Accurate seeking + * - In push mode provide a queue of adapter-"queued" buffers for upstream + * buffer metadata + * - Timestamp tracking and setting + * - Handle upstream timestamps + * - Queue buffers/events until caps are set + * - Bitrate tracking => inaccurate seeking, inaccurate duration calculation + * - Let subclass decide if frames outside the segment should be dropped + * - Send queries upstream + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "gstbaseparse.h" + +GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug); +#define GST_CAT_DEFAULT gst_base_parse_debug + +/* Supported formats */ +static GstFormat fmtlist[] = { + GST_FORMAT_DEFAULT, + GST_FORMAT_BYTES, + GST_FORMAT_TIME, + 0 +}; + +#define GST_BASE_PARSE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_PARSE, GstBaseParsePrivate)) + +struct _GstBaseParsePrivate +{ + GstActivateMode pad_mode; + + gint64 duration; + GstFormat duration_fmt; + + guint min_frame_size; + + gboolean discont; + gboolean flushing; + + gint64 offset; + gint64 pending_offset; + + GList *pending_events; + + GstBuffer *cache; +}; + +struct _GstBaseParseClassPrivate +{ + gpointer _padding; +}; + +static GstElementClass *parent_class = NULL; + +static void gst_base_parse_base_init (gpointer g_class); +static void gst_base_parse_base_finalize (gpointer g_class); +static void gst_base_parse_class_init (GstBaseParseClass * klass); +static void gst_base_parse_init (GstBaseParse * parse, + GstBaseParseClass * klass); + +GType +gst_base_parse_get_type (void) +{ + static GType base_parse_type = 0; + + if (!base_parse_type) { + static const GTypeInfo base_parse_info = { + sizeof (GstBaseParseClass), + (GBaseInitFunc) gst_base_parse_base_init, + (GBaseFinalizeFunc) gst_base_parse_base_finalize, + (GClassInitFunc) gst_base_parse_class_init, + NULL, + NULL, + sizeof (GstBaseParse), + 0, + (GInstanceInitFunc) gst_base_parse_init, + }; + + base_parse_type = g_type_register_static (GST_TYPE_ELEMENT, + "GstFlacBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT); + } + return base_parse_type; +} + +static void gst_base_parse_finalize (GObject * object); + +static gboolean gst_base_parse_sink_activate (GstPad * sinkpad); +static gboolean gst_base_parse_sink_activate_push (GstPad * pad, + gboolean active); +static gboolean gst_base_parse_sink_activate_pull (GstPad * pad, + gboolean active); +static gboolean gst_base_parse_handle_seek (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_base_parse_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_base_parse_query (GstPad * pad, GstQuery * query); +static gboolean gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps); +static const GstQueryType *gst_base_parse_get_querytypes (GstPad * pad); + +static GstFlowReturn gst_base_parse_chain (GstPad * pad, GstBuffer * buffer); +static void gst_base_parse_loop (GstPad * pad); + +static gboolean gst_base_parse_check_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize); + +static gboolean gst_base_parse_parse_frame (GstBaseParse * parse, + GstBuffer * buffer); + +static gboolean gst_base_parse_sink_eventfunc (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_src_eventfunc (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_is_seekable (GstBaseParse * parse); + +static void gst_base_parse_drain (GstBaseParse * parse); + +static void +gst_base_parse_base_init (gpointer g_class) +{ + GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class); + GstBaseParseClassPrivate *priv; + + GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "flacbaseparse", 0, + "baseparse element"); + + /* TODO: Remove this once GObject supports class private data */ + priv = g_slice_new0 (GstBaseParseClassPrivate); + if (klass->priv) + memcpy (priv, klass->priv, sizeof (GstBaseParseClassPrivate)); + klass->priv = priv; +} + +static void +gst_base_parse_base_finalize (gpointer g_class) +{ + GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class); + + g_slice_free (GstBaseParseClassPrivate, klass->priv); + klass->priv = NULL; +} + +static void +gst_base_parse_finalize (GObject * object) +{ + GstBaseParse *parse = GST_BASE_PARSE (object); + + g_mutex_free (parse->parse_lock); + g_object_unref (parse->adapter); + + if (parse->pending_segment) { + gst_event_replace (&parse->pending_segment, NULL); + } + if (parse->close_segment) { + gst_event_replace (&parse->close_segment, NULL); + } + + if (parse->priv->cache) { + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_base_parse_class_init (GstBaseParseClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + g_type_class_add_private (klass, sizeof (GstBaseParsePrivate)); + parent_class = g_type_class_peek_parent (klass); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_base_parse_finalize); + + /* Default handlers */ + klass->check_valid_frame = gst_base_parse_check_frame; + klass->parse_frame = gst_base_parse_parse_frame; + klass->event = gst_base_parse_sink_eventfunc; + klass->src_event = gst_base_parse_src_eventfunc; + klass->is_seekable = gst_base_parse_is_seekable; +} + +static void +gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass) +{ + GstPadTemplate *pad_template; + + GST_DEBUG_OBJECT (parse, "gst_base_parse_init"); + + parse->priv = GST_BASE_PARSE_GET_PRIVATE (parse); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink"); + g_return_if_fail (pad_template != NULL); + parse->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_event_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_event)); + gst_pad_set_setcaps_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_setcaps)); + gst_pad_set_chain_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_chain)); + gst_pad_set_activate_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate)); + gst_pad_set_activatepush_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_push)); + gst_pad_set_activatepull_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_pull)); + gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); + + GST_DEBUG_OBJECT (parse, "sinkpad created"); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); + g_return_if_fail (pad_template != NULL); + parse->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_set_event_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_src_event)); + gst_pad_set_query_type_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_get_querytypes)); + gst_pad_set_query_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_query)); + gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); + GST_DEBUG_OBJECT (parse, "src created"); + + parse->parse_lock = g_mutex_new (); + parse->adapter = gst_adapter_new (); + parse->pending_segment = NULL; + parse->close_segment = NULL; + + parse->priv->pad_mode = GST_ACTIVATE_NONE; + parse->priv->duration = -1; + parse->priv->min_frame_size = 1; + parse->priv->discont = FALSE; + parse->priv->flushing = FALSE; + parse->priv->offset = 0; + GST_DEBUG_OBJECT (parse, "init ok"); +} + + + +/** + * gst_base_parse_check_frame: + * @parse: #GstBaseParse. + * @buffer: GstBuffer. + * @framesize: This will be set to tell the found frame size in bytes. + * @skipsize: Output parameter that tells how much data needs to be skipped + * in order to find the following frame header. + * + * Default callback for check_valid_frame. + * + * Returns: Always TRUE. + */ +static gboolean +gst_base_parse_check_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize) +{ + *framesize = GST_BUFFER_SIZE (buffer); + *skipsize = 0; + return TRUE; +} + + +/** + * gst_base_parse_parse_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * + * Default callback for parse_frame. + */ +static gboolean +gst_base_parse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) +{ + /* FIXME: Could we even _try_ to do something clever here? */ + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + return TRUE; +} + + +/** + * gst_base_parse_bytepos_to_time: + * @parse: #GstBaseParse. + * @bytepos: Position (in bytes) to be converted. + * @pos_in_time: #GstClockTime pointer where the result is set. + * + * Convert given byte position into #GstClockTime format. + * + * Returns: TRUE if conversion succeeded. + */ +static gboolean +gst_base_parse_bytepos_to_time (GstBaseParse * parse, gint64 bytepos, + GstClockTime * pos_in_time) +{ + GstBaseParseClass *klass; + gboolean res = FALSE; + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (klass->convert) { + res = klass->convert (parse, GST_FORMAT_BYTES, bytepos, + GST_FORMAT_TIME, (gint64 *) pos_in_time); + } + return res; +} + + +/** + * gst_base_parse_sink_event: + * @pad: #GstPad that received the event. + * @event: #GstEvent to be handled. + * + * Handler for sink pad events. + * + * Returns: TRUE if the event was handled. + */ +static gboolean +gst_base_parse_sink_event (GstPad * pad, GstEvent * event) +{ + GstBaseParse *parse; + GstBaseParseClass *bclass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "handling event %d", GST_EVENT_TYPE (event)); + + /* Cache all events except EOS, NEWSEGMENT and FLUSH_STOP if we have a + * pending segment */ + if (parse->pending_segment && GST_EVENT_TYPE (event) != GST_EVENT_EOS + && GST_EVENT_TYPE (event) != GST_EVENT_NEWSEGMENT + && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_START + && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP) { + parse->priv->pending_events = + g_list_append (parse->priv->pending_events, event); + ret = TRUE; + } else { + + if (bclass->event) + handled = bclass->event (parse, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + } + + gst_object_unref (parse); + GST_DEBUG_OBJECT (parse, "event handled"); + return ret; +} + + +/** + * gst_base_parse_sink_eventfunc: + * @parse: #GstBaseParse. + * @event: #GstEvent to be handled. + * + * Element-level event handler function. + * + * Returns: TRUE if the event was handled and not need forwarding. + */ +static gboolean +gst_base_parse_sink_eventfunc (GstBaseParse * parse, GstEvent * event) +{ + gboolean handled = FALSE; + GstEvent **eventp; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, pos; + gboolean update; + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &pos); + + + if (format == GST_FORMAT_BYTES) { + GstClockTime seg_start, seg_stop, seg_pos; + + /* stop time is allowed to be open-ended, but not start & pos */ + seg_stop = GST_CLOCK_TIME_NONE; + parse->priv->pending_offset = pos; + + if (gst_base_parse_bytepos_to_time (parse, start, &seg_start) && + gst_base_parse_bytepos_to_time (parse, pos, &seg_pos)) { + gst_event_unref (event); + event = gst_event_new_new_segment_full (update, rate, applied_rate, + GST_FORMAT_TIME, seg_start, seg_stop, seg_pos); + format = GST_FORMAT_TIME; + GST_DEBUG_OBJECT (parse, "Converted incoming segment to TIME. " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, GST_TIME_ARGS (seg_start), + GST_TIME_ARGS (seg_stop), GST_TIME_ARGS (seg_pos)); + } + } + + if (format != GST_FORMAT_TIME) { + /* Unknown incoming segment format. Output a default open-ended + * TIME segment */ + gst_event_unref (event); + event = gst_event_new_new_segment_full (update, rate, applied_rate, + GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0); + } + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &pos); + + gst_segment_set_newsegment_full (&parse->segment, update, rate, + applied_rate, format, start, stop, pos); + + GST_DEBUG_OBJECT (parse, "Created newseg rate %g, applied rate %g, " + "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (pos)); + + /* save the segment for later, right before we push a new buffer so that + * the caps are fixed and the next linked element can receive + * the segment. */ + eventp = &parse->pending_segment; + gst_event_replace (eventp, event); + gst_event_unref (event); + handled = TRUE; + break; + } + + case GST_EVENT_FLUSH_START: + parse->priv->flushing = TRUE; + /* Wait for _chain() to exit by taking the srcpad STREAM_LOCK */ + GST_PAD_STREAM_LOCK (parse->srcpad); + handled = gst_pad_push_event (parse->srcpad, event); + GST_PAD_STREAM_UNLOCK (parse->srcpad); + break; + + case GST_EVENT_FLUSH_STOP: + gst_adapter_clear (parse->adapter); + parse->priv->flushing = FALSE; + parse->priv->discont = TRUE; + break; + + case GST_EVENT_EOS: + gst_base_parse_drain (parse); + break; + + default: + break; + } + + return handled; +} + + +/** + * gst_base_parse_src_event: + * @pad: #GstPad that received the event. + * @event: #GstEvent that was received. + * + * Handler for source pad events. + * + * Returns: TRUE if the event was handled. + */ +static gboolean +gst_base_parse_src_event (GstPad * pad, GstEvent * event) +{ + GstBaseParse *parse; + GstBaseParseClass *bclass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + if (bclass->src_event) + handled = bclass->src_event (parse, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + + gst_object_unref (parse); + return ret; +} + + +/** + * gst_base_parse_src_eventfunc: + * @parse: #GstBaseParse. + * @event: #GstEvent that was received. + * + * Default srcpad event handler. + * + * Returns: TRUE if the event was handled and can be dropped. + */ +static gboolean +gst_base_parse_src_eventfunc (GstBaseParse * parse, GstEvent * event) +{ + gboolean handled = FALSE; + GstBaseParseClass *bclass; + + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + if (bclass->is_seekable (parse)) { + handled = gst_base_parse_handle_seek (parse, event); + gst_event_unref (event); + } + break; + } + default: + break; + } + return handled; +} + + +/** + * gst_base_parse_is_seekable: + * @parse: #GstBaseParse. + * + * Default handler for is_seekable. + * + * Returns: Always TRUE. + */ +static gboolean +gst_base_parse_is_seekable (GstBaseParse * parse) +{ + return TRUE; +} + + +/** + * gst_base_parse_handle_and_push_buffer: + * @parse: #GstBaseParse. + * @klass: #GstBaseParseClass. + * @buffer: #GstBuffer. + * + * Parses the frame from given buffer and pushes it forward. Also performs + * timestamp handling and checks the segment limits. + * + * This is called with srcpad STREAM_LOCK held. + * + * Returns: #GstFlowReturn + */ +static GstFlowReturn +gst_base_parse_handle_and_push_buffer (GstBaseParse * parse, + GstBaseParseClass * klass, GstBuffer * buffer) +{ + GstFlowReturn ret; + GstClockTime last_stop = GST_CLOCK_TIME_NONE; + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + parse->priv->discont = FALSE; + } + + ret = klass->parse_frame (parse, buffer); + + /* FIXME: Check the output buffer for any missing metadata, + * keep track of timestamp and calculate everything possible + * if not set already */ + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + last_stop = GST_BUFFER_TIMESTAMP (buffer); + if (last_stop != GST_CLOCK_TIME_NONE && GST_BUFFER_DURATION_IS_VALID (buffer)) + last_stop += GST_BUFFER_DURATION (buffer); + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + + /* TODO: Add to seek table */ + + if (ret == GST_FLOW_OK) { + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && + GST_CLOCK_TIME_IS_VALID (parse->segment.stop) && + GST_BUFFER_TIMESTAMP (buffer) > parse->segment.stop) { + GST_LOG_OBJECT (parse, "Dropped frame, after segment"); + gst_buffer_unref (buffer); + } else if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && + GST_BUFFER_DURATION_IS_VALID (buffer) && + GST_CLOCK_TIME_IS_VALID (parse->segment.start) && + GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION_IS_VALID (buffer) + < parse->segment.start) { + /* FIXME: subclass needs way to override the start as downstream might + * need frames before for proper decoding */ + GST_LOG_OBJECT (parse, "Dropped frame, before segment"); + gst_buffer_unref (buffer); + } else { + ret = gst_pad_push (parse->srcpad, buffer); + GST_LOG_OBJECT (parse, "frame (%d bytes) pushed: %d", + GST_BUFFER_SIZE (buffer), ret); + } + } else { + gst_buffer_unref (buffer); + } + + /* Update current running segment position */ + if (ret == GST_FLOW_OK && last_stop != GST_CLOCK_TIME_NONE) + gst_segment_set_last_stop (&parse->segment, GST_FORMAT_TIME, last_stop); + + /* convert internal flow to OK and mark discont for the next buffer. */ + if (ret == GST_BASE_PARSE_FLOW_DROPPED) { + parse->priv->discont = TRUE; + ret = GST_FLOW_OK; + } + return ret; +} + + +/** + * gst_base_parse_drain: + * @parse: #GstBaseParse. + * + * Drains the adapter until it is empty. It decreases the min_frame_size to + * match the current adapter size and calls chain method until the adapter + * is emptied or chain returns with error. + */ +static void +gst_base_parse_drain (GstBaseParse * parse) +{ + guint avail; + + for (;;) { + avail = gst_adapter_available (parse->adapter); + if (!avail) + break; + + gst_base_parse_set_min_frame_size (parse, avail); + if (gst_base_parse_chain (parse->sinkpad, NULL) != GST_FLOW_OK) { + break; + } + } +} + + +/** + * gst_base_parse_chain: + * @pad: #GstPad. + * @buffer: #GstBuffer. + * + * Returns: #GstFlowReturn. + */ +static GstFlowReturn +gst_base_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstBaseParseClass *bclass; + GstBaseParse *parse; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *outbuf = NULL; + GstBuffer *tmpbuf = NULL; + guint fsize = 0; + gint skip = -1; + const guint8 *data; + guint min_size; + + parse = GST_BASE_PARSE (GST_OBJECT_PARENT (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + if (parse->pending_segment) { + GST_DEBUG_OBJECT (parse, "chain pushing a pending segment"); + gst_pad_push_event (parse->srcpad, parse->pending_segment); + parse->pending_segment = NULL; + parse->priv->offset = parse->priv->pending_offset; + + /* Make sure that adapter doesn't have any old data after + newsegment has been pushed */ + + /* FIXME: when non-flushing seek occurs, chain is still processing the + data from old segment. If this processing loop is then interrupted + (e.g. paused), chain function exists and next time it gets called + all this old data gets lost and playback continues from new segment */ + gst_adapter_clear (parse->adapter); + } + + if (parse->priv->pending_events) { + GList *l; + + for (l = parse->priv->pending_events; l != NULL; l = l->next) { + gst_pad_push_event (parse->srcpad, GST_EVENT (l->data)); + } + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + } + + if (buffer) { + GST_LOG_OBJECT (parse, "buffer size: %d, offset = %lld", + GST_BUFFER_SIZE (buffer), GST_BUFFER_OFFSET (buffer)); + + + gst_adapter_push (parse->adapter, buffer); + } + + /* Parse and push as many frames as possible */ + /* Stop either when adapter is empty or we are flushing */ + while (!parse->priv->flushing) { + tmpbuf = gst_buffer_new (); + + GST_BASE_PARSE_LOCK (parse); + min_size = parse->priv->min_frame_size; + GST_BASE_PARSE_UNLOCK (parse); + + /* Synchronization loop */ + for (;;) { + /* Collect at least min_frame_size bytes */ + if (gst_adapter_available (parse->adapter) < min_size) { + GST_DEBUG_OBJECT (parse, "not enough data available (only %d bytes)", + gst_adapter_available (parse->adapter)); + gst_buffer_unref (tmpbuf); + goto done; + } + + data = gst_adapter_peek (parse->adapter, min_size); + GST_BUFFER_DATA (tmpbuf) = (guint8 *) data; + GST_BUFFER_SIZE (tmpbuf) = min_size; + GST_BUFFER_OFFSET (tmpbuf) = parse->priv->offset; + GST_BUFFER_FLAG_SET (tmpbuf, GST_MINI_OBJECT_FLAG_READONLY); + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + + skip = -1; + if (bclass->check_valid_frame (parse, tmpbuf, &fsize, &skip)) { + break; + } + if (skip > 0) { + GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip); + gst_adapter_flush (parse->adapter, skip); + parse->priv->offset += skip; + } else if (skip == -1) { + /* subclass didn't touch this value. By default we skip 1 byte */ + GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte"); + gst_adapter_flush (parse->adapter, 1); + parse->priv->offset++; + } + /* There is a possibility that subclass set the skip value to zero. + This means that it has probably found a frame but wants to ask + more data (by increasing the min_size) to be sure of this. */ + } + gst_buffer_unref (tmpbuf); + tmpbuf = NULL; + + if (skip > 0) { + /* Subclass found the sync, but still wants to skip some data */ + GST_LOG_OBJECT (parse, "skipping %d bytes", skip); + gst_adapter_flush (parse->adapter, skip); + parse->priv->offset += skip; + } + + /* Grab lock to prevent a race with FLUSH_START handler */ + GST_PAD_STREAM_LOCK (parse->srcpad); + + /* FLUSH_START event causes the "flushing" flag to be set. In this + * case we can leave the frame pushing loop */ + if (parse->priv->flushing) { + GST_PAD_STREAM_UNLOCK (parse->srcpad); + break; + } + + /* FIXME: Would it be more efficient to make a subbuffer instead? */ + outbuf = gst_adapter_take_buffer (parse->adapter, fsize); + + /* Subclass may want to know the data offset */ + GST_BUFFER_OFFSET (outbuf) = parse->priv->offset; + parse->priv->offset += fsize; + + ret = gst_base_parse_handle_and_push_buffer (parse, bclass, outbuf); + GST_PAD_STREAM_UNLOCK (parse->srcpad); + + if (ret != GST_FLOW_OK) { + GST_LOG_OBJECT (parse, "push returned %d", ret); + break; + } + } + +done: + GST_LOG_OBJECT (parse, "chain leaving"); + return ret; +} + +static GstFlowReturn +gst_base_parse_pull_range (GstBaseParse * parse, guint size, + GstBuffer ** buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + /* Caching here actually makes much less difference than one would expect. + * We do it mainly to avoid pulling buffers of 1 byte all the time */ + if (parse->priv->cache) { + guint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache); + guint cache_size = GST_BUFFER_SIZE (parse->priv->cache); + + if (cache_offset <= parse->priv->offset && + (parse->priv->offset + size) < (cache_offset + cache_size)) { + *buffer = gst_buffer_create_sub (parse->priv->cache, + parse->priv->offset - cache_offset, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + return GST_FLOW_OK; + } + /* not enough data in the cache, free cache and get a new one */ + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + /* refill the cache */ + ret = + gst_pad_pull_range (parse->sinkpad, parse->priv->offset, MAX (size, + 64 * 1024), &parse->priv->cache); + if (ret != GST_FLOW_OK) { + parse->priv->cache = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->priv->cache) >= size) { + *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + return GST_FLOW_OK; + } + + /* Not possible to get enough data, try a last time with + * requesting exactly the size we need */ + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + + ret = gst_pad_pull_range (parse->sinkpad, parse->priv->offset, size, + &parse->priv->cache); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "pull_range returned %d", ret); + *buffer = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->priv->cache) < size) { + GST_WARNING_OBJECT (parse, "Dropping short buffer at offset %" + G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", parse->priv->offset, + size, GST_BUFFER_SIZE (parse->priv->cache)); + + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + + *buffer = NULL; + return GST_FLOW_UNEXPECTED; + } + + *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + + return GST_FLOW_OK; +} + +/** + * gst_base_parse_loop: + * @pad: GstPad + * + * Loop that is used in pull mode to retrieve data from upstream. + */ +static void +gst_base_parse_loop (GstPad * pad) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + GstBuffer *buffer, *outbuf; + gboolean ret = FALSE; + guint fsize = 0, min_size; + gint skip = 0; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (parse->close_segment) { + GST_DEBUG_OBJECT (parse, "loop sending close segment"); + gst_pad_push_event (parse->srcpad, parse->close_segment); + parse->close_segment = NULL; + } + + if (parse->pending_segment) { + GST_DEBUG_OBJECT (parse, "loop push pending segment"); + gst_pad_push_event (parse->srcpad, parse->pending_segment); + parse->pending_segment = NULL; + } + + /* TODO: Check if we reach segment stop limit */ + + while (TRUE) { + + GST_BASE_PARSE_LOCK (parse); + min_size = parse->priv->min_frame_size; + GST_BASE_PARSE_UNLOCK (parse); + + ret = gst_base_parse_pull_range (parse, min_size, &buffer); + + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + else if (ret != GST_FLOW_OK) + goto need_pause; + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + + skip = -1; + if (klass->check_valid_frame (parse, buffer, &fsize, &skip)) { + break; + } + if (skip > 0) { + GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip); + parse->priv->offset += skip; + } else if (skip == -1) { + GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte"); + parse->priv->offset++; + } + GST_DEBUG_OBJECT (parse, "finding sync..."); + gst_buffer_unref (buffer); + } + + if (fsize <= GST_BUFFER_SIZE (buffer)) { + outbuf = gst_buffer_create_sub (buffer, 0, fsize); + GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buffer); + gst_buffer_unref (buffer); + } else { + gst_buffer_unref (buffer); + ret = gst_base_parse_pull_range (parse, fsize, &outbuf); + + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + else if (ret != GST_FLOW_OK) + goto need_pause; + } + + parse->priv->offset += fsize; + + /* Does the subclass want to skip too? */ + if (skip > 0) + parse->priv->offset += skip; + + /* This always unrefs the outbuf, even if error occurs */ + ret = gst_base_parse_handle_and_push_buffer (parse, klass, outbuf); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "flow: %s", gst_flow_get_name (ret)); + if (GST_FLOW_IS_FATAL (ret)) { + GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL), + ("streaming task paused, reason: %s", gst_flow_get_name (ret))); + gst_pad_push_event (parse->srcpad, gst_event_new_eos ()); + } + goto need_pause; + } + + gst_object_unref (parse); + return; + +need_pause: + { + GST_LOG_OBJECT (parse, "pausing task"); + gst_pad_pause_task (pad); + gst_object_unref (parse); + return; + } +eos: + { + GST_LOG_OBJECT (parse, "pausing task %d", ret); + gst_pad_push_event (parse->srcpad, gst_event_new_eos ()); + gst_pad_pause_task (pad); + gst_object_unref (parse); + return; + } +} + + +/** + * gst_base_parse_sink_activate: + * @sinkpad: #GstPad to be activated. + * + * Returns: TRUE if activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate (GstPad * sinkpad) +{ + GstBaseParse *parse; + gboolean result = TRUE; + + parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad)); + + GST_DEBUG_OBJECT (parse, "sink activate"); + + if (gst_pad_check_pull_range (sinkpad)) { + GST_DEBUG_OBJECT (parse, "trying to activate in pull mode"); + result = gst_pad_activate_pull (sinkpad, TRUE); + } else { + GST_DEBUG_OBJECT (parse, "trying to activate in push mode"); + result = gst_pad_activate_push (sinkpad, TRUE); + } + + GST_DEBUG_OBJECT (parse, "sink activate return %d", result); + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_activate: + * @parse: #GstBaseParse. + * @active: TRUE if element will be activated, FALSE if disactivated. + * + * Returns: TRUE if the operation succeeded. + */ +static gboolean +gst_base_parse_activate (GstBaseParse * parse, gboolean active) +{ + GstBaseParseClass *klass; + gboolean result = FALSE; + + GST_DEBUG_OBJECT (parse, "activate"); + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (active) { + if (parse->priv->pad_mode == GST_ACTIVATE_NONE && klass->start) + result = klass->start (parse); + + GST_OBJECT_LOCK (parse); + gst_segment_init (&parse->segment, GST_FORMAT_TIME); + parse->priv->duration = -1; + parse->priv->discont = FALSE; + parse->priv->flushing = FALSE; + parse->priv->offset = 0; + + if (parse->pending_segment) + gst_event_unref (parse->pending_segment); + + parse->pending_segment = + gst_event_new_new_segment (FALSE, parse->segment.rate, + parse->segment.format, + parse->segment.start, parse->segment.stop, parse->segment.last_stop); + + GST_OBJECT_UNLOCK (parse); + } else { + /* We must make sure streaming has finished before resetting things + * and calling the ::stop vfunc */ + GST_PAD_STREAM_LOCK (parse->sinkpad); + GST_PAD_STREAM_UNLOCK (parse->sinkpad); + + if (parse->priv->pad_mode != GST_ACTIVATE_NONE && klass->stop) + result = klass->stop (parse); + + g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + + if (parse->priv->cache) { + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + parse->priv->pad_mode = GST_ACTIVATE_NONE; + } + GST_DEBUG_OBJECT (parse, "activate: %d", result); + return result; +} + + +/** + * gst_base_parse_sink_activate_push: + * @pad: #GstPad to be (de)activated. + * @active: TRUE when activating, FALSE when deactivating. + * + * Returns: TRUE if (de)activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = TRUE; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (parse, "sink activate push"); + + result = gst_base_parse_activate (parse, active); + + if (result) + parse->priv->pad_mode = active ? GST_ACTIVATE_PUSH : GST_ACTIVATE_NONE; + + GST_DEBUG_OBJECT (parse, "sink activate push: %d", result); + + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_sink_activate_pull: + * @sinkpad: #GstPad to be (de)activated. + * @active: TRUE when activating, FALSE when deactivating. + * + * Returns: TRUE if (de)activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate_pull (GstPad * sinkpad, gboolean active) +{ + gboolean result = FALSE; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad)); + + GST_DEBUG_OBJECT (parse, "activate pull"); + + result = gst_base_parse_activate (parse, active); + + if (result) { + if (active) { + result &= gst_pad_start_task (sinkpad, + (GstTaskFunction) gst_base_parse_loop, sinkpad); + } else { + result &= gst_pad_stop_task (sinkpad); + } + } + + if (result) + parse->priv->pad_mode = active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE; + + GST_DEBUG_OBJECT (parse, "sink activate pull: %d", result); + + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_set_duration: + * @parse: #GstBaseParse. + * @fmt: #GstFormat. + * @duration: duration value. + * + * Sets the duration of the currently playing media. Subclass can use this + * when it notices a change in the media duration. + */ +void +gst_base_parse_set_duration (GstBaseParse * parse, + GstFormat fmt, gint64 duration) +{ + g_return_if_fail (parse != NULL); + + GST_BASE_PARSE_LOCK (parse); + if (duration != parse->priv->duration) { + GstMessage *m; + + m = gst_message_new_duration (GST_OBJECT (parse), fmt, duration); + gst_element_post_message (GST_ELEMENT (parse), m); + + /* TODO: what about duration tag? */ + } + parse->priv->duration = duration; + parse->priv->duration_fmt = fmt; + GST_DEBUG_OBJECT (parse, "set duration: %lld", duration); + GST_BASE_PARSE_UNLOCK (parse); +} + + +/** + * gst_base_parse_set_min_frame_size: + * @parse: #GstBaseParse. + * @min_size: Minimum size of the data that this base class should give to + * subclass. + * + * Subclass can use this function to tell the base class that it needs to + * give at least #min_size buffers. + */ +void +gst_base_parse_set_min_frame_size (GstBaseParse * parse, guint min_size) +{ + g_return_if_fail (parse != NULL); + + GST_BASE_PARSE_LOCK (parse); + parse->priv->min_frame_size = min_size; + GST_LOG_OBJECT (parse, "set frame_min_size: %d", min_size); + GST_BASE_PARSE_UNLOCK (parse); +} + + +/** + * gst_base_parse_get_querytypes: + * @pad: GstPad + * + * Returns: A table of #GstQueryType items describing supported query types. + */ +static const GstQueryType * +gst_base_parse_get_querytypes (GstPad * pad) +{ + static const GstQueryType list[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_FORMATS, + GST_QUERY_SEEKING, + GST_QUERY_CONVERT, + 0 + }; + + return list; +} + + +/** + * gst_base_parse_query: + * @pad: #GstPad. + * @query: #GstQuery. + * + * Returns: TRUE on success. + */ +static gboolean +gst_base_parse_query (GstPad * pad, GstQuery * query) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + gboolean res = FALSE; + + parse = GST_BASE_PARSE (GST_PAD_PARENT (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + /* If subclass doesn't provide conversion function we can't reply + to the query either */ + if (!klass->convert) { + return FALSE; + } + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + gint64 dest_value; + GstFormat format; + + GST_DEBUG_OBJECT (parse, "position query"); + + gst_query_parse_position (query, &format, NULL); + + g_mutex_lock (parse->parse_lock); + + if (format == GST_FORMAT_BYTES) { + dest_value = parse->priv->offset; + res = TRUE; + } else if (format == parse->segment.format && + GST_CLOCK_TIME_IS_VALID (parse->segment.last_stop)) { + dest_value = parse->segment.last_stop; + res = TRUE; + } else { + /* priv->offset is updated in both PUSH/PULL modes */ + res = klass->convert (parse, GST_FORMAT_BYTES, parse->priv->offset, + format, &dest_value); + } + g_mutex_unlock (parse->parse_lock); + + if (res) + gst_query_set_position (query, format, dest_value); + else + res = gst_pad_query_default (pad, query); + + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 dest_value; + + GST_DEBUG_OBJECT (parse, "duration query"); + + gst_query_parse_duration (query, &format, NULL); + + g_mutex_lock (parse->parse_lock); + + if (format == GST_FORMAT_BYTES) { + res = gst_pad_query_peer_duration (parse->sinkpad, &format, + &dest_value); + } else if (parse->priv->duration != -1 && + format == parse->priv->duration_fmt) { + dest_value = parse->priv->duration; + res = TRUE; + } else if (parse->priv->duration != -1) { + res = klass->convert (parse, parse->priv->duration_fmt, + parse->priv->duration, format, &dest_value); + } + + g_mutex_unlock (parse->parse_lock); + + if (res) + gst_query_set_duration (query, format, dest_value); + else + res = gst_pad_query_default (pad, query); + break; + } + case GST_QUERY_SEEKING: + { + GstFormat fmt; + gboolean seekable = FALSE; + + GST_DEBUG_OBJECT (parse, "seeking query"); + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + + if (fmt != GST_FORMAT_TIME) { + return gst_pad_query_default (pad, query); + } + + seekable = klass->is_seekable (parse); + + /* TODO: could this duration be calculated/converted if subclass + hasn't given it? */ + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, + (parse->priv->duration == -1) ? + GST_CLOCK_TIME_NONE : parse->priv->duration); + + GST_DEBUG_OBJECT (parse, "seekable: %d", seekable); + res = TRUE; + break; + } + case GST_QUERY_FORMATS: + gst_query_set_formatsv (query, 3, fmtlist); + res = TRUE; + break; + + case GST_QUERY_CONVERT: + { + GstFormat src_format, dest_format; + gint64 src_value, dest_value; + + gst_query_parse_convert (query, &src_format, &src_value, + &dest_format, &dest_value); + + /* FIXME: hm? doesn't make sense + * We require all those values to be given + if (src_format && src_value && dest_format && dest_value ) { */ + res = klass->convert (parse, src_format, src_value, + dest_format, &dest_value); + if (res) { + gst_query_set_convert (query, src_format, src_value, + dest_format, dest_value); + } + /*} */ + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + return res; +} + + +/** + * gst_base_parse_handle_seek: + * @parse: #GstBaseParse. + * @event: #GstEvent. + * + * Returns: TRUE if seek succeeded. + */ +static gboolean +gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) +{ + GstBaseParseClass *klass; + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type; + gboolean flush, update, res = TRUE; + gint64 cur, stop, seekpos; + GstSegment seeksegment = { 0, }; + GstFormat dstformat; + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + /* no negative rates yet */ + if (rate < 0.0) + goto negative_rate; + + if (cur_type != GST_SEEK_TYPE_SET) + goto wrong_type; + + /* For any format other than TIME, see if upstream handles + * it directly or fail. For TIME, try upstream, but do it ourselves if + * it fails upstream */ + if (format != GST_FORMAT_TIME) { + gst_event_ref (event); + return gst_pad_push_event (parse->sinkpad, event); + } else { + gst_event_ref (event); + if (gst_pad_push_event (parse->sinkpad, event)) + return TRUE; + } + + /* get flush flag */ + flush = flags & GST_SEEK_FLAG_FLUSH; + + dstformat = GST_FORMAT_BYTES; + if (!gst_pad_query_convert (parse->srcpad, format, cur, &dstformat, &seekpos)) { + GST_DEBUG_OBJECT (parse, "conversion failed"); + return FALSE; + } + + GST_DEBUG_OBJECT (parse, "seek position %lld in bytes: %lld", cur, seekpos); + + if (parse->priv->pad_mode == GST_ACTIVATE_PULL) { + gint64 last_stop; + + GST_DEBUG_OBJECT (parse, "seek in PULL mode"); + + if (flush) { + if (parse->srcpad) { + GST_DEBUG_OBJECT (parse, "sending flush start"); + gst_pad_push_event (parse->srcpad, gst_event_new_flush_start ()); + } + } else { + gst_pad_pause_task (parse->sinkpad); + } + + /* we should now be able to grab the streaming thread because we stopped it + * with the above flush/pause code */ + GST_PAD_STREAM_LOCK (parse->sinkpad); + + /* save current position */ + last_stop = parse->segment.last_stop; + GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT, + last_stop); + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &parse->segment, sizeof (GstSegment)); + + GST_DEBUG_OBJECT (parse, "configuring seek"); + gst_segment_set_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + + /* figure out the last position we need to play. If it's configured (stop != + * -1), use that, else we play until the total duration of the file */ + if ((stop = seeksegment.stop) == -1) + stop = seeksegment.duration; + + parse->priv->offset = seekpos; + + /* prepare for streaming again */ + if (flush) { + GST_DEBUG_OBJECT (parse, "sending flush stop"); + gst_pad_push_event (parse->srcpad, gst_event_new_flush_stop ()); + } else { + if (parse->close_segment) + gst_event_unref (parse->close_segment); + + parse->close_segment = gst_event_new_new_segment (TRUE, + parse->segment.rate, parse->segment.format, + parse->segment.accum, parse->segment.last_stop, parse->segment.accum); + + /* keep track of our last_stop */ + seeksegment.accum = parse->segment.last_stop; + + GST_DEBUG_OBJECT (parse, "Created close seg format %d, " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, format, + GST_TIME_ARGS (parse->segment.accum), + GST_TIME_ARGS (parse->segment.last_stop), + GST_TIME_ARGS (parse->segment.accum)); + } + + memcpy (&parse->segment, &seeksegment, sizeof (GstSegment)); + + /* store the newsegment event so it can be sent from the streaming thread. */ + if (parse->pending_segment) + gst_event_unref (parse->pending_segment); + + /* This will be sent later in _loop() */ + parse->pending_segment = + gst_event_new_new_segment (FALSE, parse->segment.rate, + parse->segment.format, + parse->segment.last_stop, stop, parse->segment.last_stop); + + GST_DEBUG_OBJECT (parse, "Created newseg format %d, " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, format, + GST_TIME_ARGS (parse->segment.last_stop), + GST_TIME_ARGS (stop), GST_TIME_ARGS (parse->segment.last_stop)); + + /* mark discont if we are going to stream from another position. */ + if (last_stop != parse->segment.last_stop) { + GST_DEBUG_OBJECT (parse, + "mark DISCONT, we did a seek to another position"); + parse->priv->discont = TRUE; + } + + /* Start streaming thread if paused */ + gst_pad_start_task (parse->sinkpad, + (GstTaskFunction) gst_base_parse_loop, parse->sinkpad); + + GST_PAD_STREAM_UNLOCK (parse->sinkpad); + } else { + GstEvent *new_event; + /* The only thing we need to do in PUSH-mode is to send the + seek event (in bytes) to upstream. Segment / flush handling happens + in corresponding src event handlers */ + GST_DEBUG_OBJECT (parse, "seek in PUSH mode"); + new_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flush, + GST_SEEK_TYPE_SET, seekpos, stop_type, stop); + + res = gst_pad_push_event (parse->sinkpad, new_event); + } + +done: + return res; + + /* ERRORS */ +negative_rate: + { + GST_DEBUG_OBJECT (parse, "negative playback rates are not supported yet."); + res = FALSE; + goto done; + } +wrong_type: + { + GST_DEBUG_OBJECT (parse, "unsupported seek type."); + res = FALSE; + goto done; + } +} + + +/** + * gst_base_parse_sink_setcaps: + * @pad: #GstPad. + * @caps: #GstCaps. + * + * Returns: TRUE if caps were accepted. + */ +static gboolean +gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + gboolean res = TRUE; + + gchar *caps_str = gst_caps_to_string (caps); + g_free (caps_str); + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "setcaps: %s", caps_str); + + if (klass->set_sink_caps) + res = klass->set_sink_caps (parse, caps); + + parse->negotiated = res; + gst_object_unref (parse); + return gst_pad_set_caps (pad, caps); +} diff --git a/gst/aacparse/gstbaseparse.h b/gst/aacparse/gstbaseparse.h new file mode 100644 index 0000000000..a2856cc780 --- /dev/null +++ b/gst/aacparse/gstbaseparse.h @@ -0,0 +1,237 @@ +/* GStreamer + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +#ifndef __GST_BASE_PARSE_H__ +#define __GST_BASE_PARSE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_BASE_PARSE (gst_base_parse_get_type()) +#define GST_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_PARSE,GstBaseParse)) +#define GST_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_PARSE,GstBaseParseClass)) +#define GST_BASE_PARSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_BASE_PARSE,GstBaseParseClass)) +#define GST_IS_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_PARSE)) +#define GST_IS_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_PARSE)) +#define GST_BASE_PARSE_CAST(obj) ((GstBaseParse *)(obj)) + +/** + * GST_BASE_PARSE_SINK_NAME: + * + * the name of the templates for the sink pad + */ +#define GST_BASE_PARSE_SINK_NAME "sink" +/** + * GST_BASE_PARSE_SRC_NAME: + * + * the name of the templates for the source pad + */ +#define GST_BASE_PARSE_SRC_NAME "src" + +/** + * GST_BASE_PARSE_SRC_PAD: + * @obj: base parse instance + * + * Gives the pointer to the source #GstPad object of the element. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_SRC_PAD(obj) (GST_BASE_PARSE_CAST (obj)->srcpad) + +/** + * GST_BASE_PARSE_SINK_PAD: + * @obj: base parse instance + * + * Gives the pointer to the sink #GstPad object of the element. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_SINK_PAD(obj) (GST_BASE_PARSE_CAST (obj)->sinkpad) + +/** + * GST_BASE_PARSE_FLOW_DROPPED: + * + * A #GstFlowReturn that can be returned from parse_frame to + * indicate that no output buffer was generated. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS + +/** + * GST_BASE_PARSE_LOCK: + * @obj: base parse instance + * + * Obtain a lock to protect the parse function from concurrent access. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_LOCK(obj) g_mutex_lock (GST_BASE_PARSE_CAST (obj)->parse_lock) + +/** + * GST_BASE_PARSE_UNLOCK: + * @obj: base parse instance + * + * Release the lock that protects the parse function from concurrent access. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_UNLOCK(obj) g_mutex_unlock (GST_BASE_PARSE_CAST (obj)->parse_lock) + +typedef struct _GstBaseParse GstBaseParse; +typedef struct _GstBaseParseClass GstBaseParseClass; +typedef struct _GstBaseParsePrivate GstBaseParsePrivate; +typedef struct _GstBaseParseClassPrivate GstBaseParseClassPrivate; + +/** + * GstBaseParse: + * @element: the parent element. + * + * The opaque #GstBaseParse data structure. + */ +struct _GstBaseParse { + GstElement element; + GstAdapter *adapter; + + /*< protected >*/ + /* source and sink pads */ + GstPad *sinkpad; + GstPad *srcpad; + + /* MT-protected (with STREAM_LOCK) */ + GstSegment segment; + + /* Newsegment event to be sent after SEEK */ + GstEvent *pending_segment; + + /* Segment event that closes the running segment prior to SEEK */ + GstEvent *close_segment; + + /* Caps nego done already? */ + gboolean negotiated; + + GMutex *parse_lock; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; + GstBaseParsePrivate *priv; +}; + +/** + * GstBaseParseClass: + * @start: Optional. + * Called when the element starts processing. + * Allows opening external resources. + * @stop: Optional. + * Called when the element stops processing. + * Allows closing external resources. + * @set_sink_caps: allows the subclass to be notified of the actual caps set. + * @check_valid_frame: Check if the given piece of data contains a valid + * frame. + * @parse_frame: Parse the already checked frame. Subclass need to + * set the buffer timestamp, duration, caps and possibly + * other necessary metadata. This is called with srcpad's + * STREAM_LOCK held. + * @convert: Optional. + * Convert between formats. + * @find_frame: Optional. + * Finds a frame. Gets a position passed and should return + * TRUE and the offset in bytes where this position is. + * Will only be called in pull mode and the subclass can pull + * whatever it wants from upstream. If not implemented, + * the base class will implement it by calling + * @check_valid_frame and @parse_frame to find the wanted + * frame and build a seek table. + * @event: Optional. + * Event handler on the sink pad. This function should return + * TRUE if the event was handled and can be dropped. + * @src_event: Optional. + * Event handler on the source pad. Should return TRUE + * if the event was handled and can be dropped. + * @is_seekable: Optional. + * Subclass can override this if it wants to control the + * seekability of the stream. Otherwise the element assumes + * that stream is always seekable. + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At minimum @check_valid_frame and @parse_frame needs to be + * overridden. + */ +struct _GstBaseParseClass { + GstElementClass parent_class; + + /*< public >*/ + /* virtual methods for subclasses */ + + gboolean (*start) (GstBaseParse *parse); + + gboolean (*stop) (GstBaseParse *parse); + + gboolean (*set_sink_caps) (GstBaseParse *parse, + GstCaps *caps); + + gboolean (*check_valid_frame) (GstBaseParse *parse, + GstBuffer *buffer, + guint *framesize, + gint *skipsize); + + GstFlowReturn (*parse_frame) (GstBaseParse *parse, + GstBuffer *buffer); + + gboolean (*convert) (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, + GstFormat dest_format, + gint64 * dest_value); + + gboolean (*find_frame) (GstBaseParse *parse, + GstFormat src_format, + gint64 src_value, + gint64 * dest_value); + + gboolean (*event) (GstBaseParse *parse, + GstEvent *event); + + gboolean (*src_event) (GstBaseParse *parse, + GstEvent *event); + + gboolean (*is_seekable) (GstBaseParse *parse); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; + GstBaseParseClassPrivate *priv; +}; + +GType gst_base_parse_get_type (void); + + +void gst_base_parse_set_duration (GstBaseParse *parse, + GstFormat fmt, + gint64 duration); + +void gst_base_parse_set_min_frame_size (GstBaseParse *parse, + guint min_size); + +G_END_DECLS + +#endif /* __GST_BASE_PARSE_H__ */ diff --git a/gst/amrparse/Makefile.am b/gst/amrparse/Makefile.am new file mode 100644 index 0000000000..43798d5a09 --- /dev/null +++ b/gst/amrparse/Makefile.am @@ -0,0 +1,11 @@ +plugin_LTLIBRARIES = libgstamrparse.la + +libgstamrparse_la_SOURCES = \ + gstamrparse.c gstbaseparse.c + +libgstamrparse_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) +libgstamrparse_la_LIBADD = $(GST_BASE_LIBS) +libgstamrparse_la_LDFLAGS = $(PACKAGE_LIBS) $(GST_PLUGIN_LDFLAGS) +libgstamrparse_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstamrparse.h gstbaseparse.h diff --git a/gst/amrparse/gstamrparse.c b/gst/amrparse/gstamrparse.c new file mode 100644 index 0000000000..aeb928e19c --- /dev/null +++ b/gst/amrparse/gstamrparse.c @@ -0,0 +1,597 @@ +/* GStreamer Adaptive Multi-Rate parser plugin + * Copyright (C) 2006 Edgard Lima + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +/** + * SECTION:gstamrparse + * @short_description: AMR parser + * @see_also: #GstAmrnbDec, #GstAmrnbEnc + * + * + * + * This is an AMR parser capable of handling both narrow-band and wideband + * formats. + * + * Example launch line + * + * + * gst-launch filesrc location=abc.amr ! amrparse ! amrdec ! audioresample ! audioconvert ! alsasink + * + * + * + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstamrparse.h" + + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/AMR, " "rate = (int) 8000, " "channels = (int) 1;" + "audio/AMR-WB, " "rate = (int) 16000, " "channels = (int) 1;") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-amr-nb-sh; audio/x-amr-wb-sh")); + +GST_DEBUG_CATEGORY_STATIC (gst_amrparse_debug); +#define GST_CAT_DEFAULT gst_amrparse_debug + +static const gint block_size_nb[16] = + { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 }; + +static const gint block_size_wb[16] = + { 17, 23, 32, 36, 40, 46, 50, 58, 60, 5, 5, 0, 0, 0, 0, 0 }; + +/* AMR has a "hardcoded" framerate of 50fps */ +#define AMR_FRAMES_PER_SECOND 50 +#define AMR_FRAME_DURATION (GST_SECOND/AMR_FRAMES_PER_SECOND) +#define AMR_MIME_HEADER_SIZE 9 + +static void gst_amrparse_finalize (GObject * object); + +gboolean gst_amrparse_start (GstBaseParse * parse); +gboolean gst_amrparse_stop (GstBaseParse * parse); + +static gboolean gst_amrparse_sink_setcaps (GstBaseParse * parse, + GstCaps * caps); + +gboolean gst_amrparse_check_valid_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize); + +GstFlowReturn gst_amrparse_parse_frame (GstBaseParse * parse, + GstBuffer * buffer); + +gboolean gst_amrparse_convert (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, GstFormat dest_format, gint64 * dest_value); + +gboolean gst_amrparse_event (GstBaseParse * parse, GstEvent * event); + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (gst_amrparse_debug, "amrparse", 0, \ + "AMR-NB audio stream parser"); + +GST_BOILERPLATE_FULL (GstAmrParse, gst_amrparse, GstBaseParse, + GST_TYPE_BASE_PARSE, _do_init); + + +/** + * gst_amrparse_base_init: + * @klass: #GstElementClass. + * + */ +static void +gst_amrparse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstElementDetails details = GST_ELEMENT_DETAILS ("AMR audio stream parser", + "Codec/Parser/Audio", + "Adaptive Multi-Rate audio parser", + "Ronald Bultje "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details (element_class, &details); +} + + +/** + * gst_amrparse_class_init: + * @klass: GstAmrParseClass. + * + */ +static void +gst_amrparse_class_init (GstAmrParseClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + object_class->finalize = gst_amrparse_finalize; + + parse_class->start = GST_DEBUG_FUNCPTR (gst_amrparse_start); + parse_class->stop = GST_DEBUG_FUNCPTR (gst_amrparse_stop); + parse_class->event = GST_DEBUG_FUNCPTR (gst_amrparse_event); + parse_class->convert = GST_DEBUG_FUNCPTR (gst_amrparse_convert); + parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_amrparse_sink_setcaps); + parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_amrparse_parse_frame); + parse_class->check_valid_frame = + GST_DEBUG_FUNCPTR (gst_amrparse_check_valid_frame); +} + + +/** + * gst_amrparse_init: + * @amrparse: #GstAmrParse + * @klass: #GstAmrParseClass. + * + */ +static void +gst_amrparse_init (GstAmrParse * amrparse, GstAmrParseClass * klass) +{ + /* init rest */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (amrparse), 62); + amrparse->ts = 0; + GST_DEBUG ("initialized"); + +} + + +/** + * gst_amrparse_finalize: + * @object: + * + */ +static void +gst_amrparse_finalize (GObject * object) +{ + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (object); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/** + * gst_amrparse_set_src_caps: + * @amrparse: #GstAmrParse. + * + * Set source pad caps according to current knowledge about the + * audio stream. + * + * Returns: TRUE if caps were successfully set. + */ +static gboolean +gst_amrparse_set_src_caps (GstAmrParse * amrparse) +{ + GstCaps *src_caps = NULL; + gboolean res = FALSE; + + if (amrparse->wide) { + GST_DEBUG_OBJECT (amrparse, "setting srcpad caps to AMR-WB"); + src_caps = gst_caps_new_simple ("audio/AMR-WB", + "channels", G_TYPE_INT, 1, "rate", G_TYPE_INT, 16000, NULL); + } else { + GST_DEBUG_OBJECT (amrparse, "setting srcpad caps to AMR-NB"); + /* Max. size of NB frame is 31 bytes, so we can set the min. frame + size to 32 (+1 for next frame header) */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (amrparse), 32); + src_caps = gst_caps_new_simple ("audio/AMR", + "channels", G_TYPE_INT, 1, "rate", G_TYPE_INT, 8000, NULL); + } + gst_pad_use_fixed_caps (GST_BASE_PARSE (amrparse)->srcpad); + res = gst_pad_set_caps (GST_BASE_PARSE (amrparse)->srcpad, src_caps); + gst_pad_fixate_caps (GST_BASE_PARSE (amrparse)->srcpad, src_caps); + gst_caps_unref (src_caps); + return res; +} + + +/** + * gst_amrparse_sink_setcaps: + * @sinkpad: GstPad + * @caps: GstCaps + * + * Returns: TRUE on success. + */ +static gboolean +gst_amrparse_sink_setcaps (GstBaseParse * parse, GstCaps * caps) +{ + GstAmrParse *amrparse; + GstStructure *structure; + const gchar *name; + + amrparse = GST_AMRPARSE (parse); + structure = gst_caps_get_structure (caps, 0); + name = gst_structure_get_name (structure); + + GST_DEBUG_OBJECT (amrparse, "setcaps: %s", name); + + if (!strncmp (name, "audio/x-amr-wb-sh", 17)) { + amrparse->block_size = block_size_wb; + amrparse->wide = 1; + } else if (!strncmp (name, "audio/x-amr-nb-sh", 17)) { + amrparse->block_size = block_size_nb; + amrparse->wide = 0; + } else { + GST_WARNING ("Unknown caps"); + return FALSE; + } + + amrparse->need_header = FALSE; + gst_amrparse_set_src_caps (amrparse); + return TRUE; +} + + +/** + * gst_amrparse_update_duration: + * @amrparse: #GstAmrParse. + * + * Send duration information to base class. + */ +static void +gst_amrparse_update_duration (GstAmrParse * amrparse) +{ + GstPad *peer; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (amrparse); + + /* Cannot estimate duration. No data has been passed to us yet */ + if (!amrparse->framecount) { + return; + } + + peer = gst_pad_get_peer (parse->sinkpad); + if (peer) { + GstFormat pformat = GST_FORMAT_BYTES; + guint64 bpf = amrparse->bytecount / amrparse->framecount; + gboolean qres = FALSE; + gint64 ptot; + + qres = gst_pad_query_duration (peer, &pformat, &ptot); + gst_object_unref (GST_OBJECT (peer)); + if (qres && bpf) { + gst_base_parse_set_duration (parse, GST_FORMAT_TIME, + AMR_FRAME_DURATION * ptot / bpf); + } + } +} + + +/** + * gst_amrparse_parse_header: + * @amrparse: #GstAmrParse + * @data: Header data to be parsed. + * @skipsize: Output argument where the frame size will be stored. + * + * Check if the given data contains an AMR mime header. + * + * Returns: TRUE on success. + */ +static gboolean +gst_amrparse_parse_header (GstAmrParse * amrparse, + const guint8 * data, gint * skipsize) +{ + GST_DEBUG_OBJECT (amrparse, "Parsing header data"); + + if (!memcmp (data, "#!AMR-WB\n", 9)) { + GST_DEBUG_OBJECT (amrparse, "AMR-WB detected"); + amrparse->block_size = block_size_wb; + amrparse->wide = TRUE; + *skipsize = 9; + } else if (!memcmp (data, "#!AMR\n", 6)) { + GST_DEBUG_OBJECT (amrparse, "AMR-NB detected"); + amrparse->block_size = block_size_nb; + amrparse->wide = FALSE; + *skipsize = 6; + } else + return FALSE; + + gst_amrparse_set_src_caps (amrparse); + return TRUE; +} + + +/** + * gst_amrparse_check_valid_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * @framesize: Output variable where the found frame size is put. + * @skipsize: Output variable which tells how much data needs to be skipped + * until a frame header is found. + * + * Implementation of "check_valid_frame" vmethod in #GstBaseParse class. + * + * Returns: TRUE if the given data contains valid frame. + */ +gboolean +gst_amrparse_check_valid_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize) +{ + const guint8 *data; + gint fsize, mode, dsize; + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (parse); + data = GST_BUFFER_DATA (buffer); + dsize = GST_BUFFER_SIZE (buffer); + + GST_LOG ("buffer: %d bytes", dsize); + + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + /* Discontinuous stream -> drop the sync */ + amrparse->sync = FALSE; + } + + if (amrparse->need_header) { + if (dsize >= AMR_MIME_HEADER_SIZE && + gst_amrparse_parse_header (amrparse, data, skipsize)) { + amrparse->need_header = FALSE; + } else { + GST_WARNING ("media doesn't look like a AMR format"); + } + /* We return FALSE, so this frame won't get pushed forward. Instead, + the "skip" value is set, so next time we will receive a valid frame. */ + return FALSE; + } + + /* Does this look like a possible frame header candidate? */ + if ((data[0] & 0x83) == 0) { + /* Yep. Retrieve the frame size */ + mode = (data[0] >> 3) & 0x0F; + fsize = amrparse->block_size[mode] + 1; /* +1 for the header byte */ + + /* We recognize this data as a valid frame when: + * - We are in sync. There is no need for extra checks then + * - We are in EOS. There might not be enough data to check next frame + * - Sync is lost, but the following data after this frame seem + * to contain a valid header as well (and there is enough data to + * perform this check) + */ + if (amrparse->sync || amrparse->eos || + (dsize >= fsize && (data[fsize] & 0x83) == 0)) { + amrparse->sync = TRUE; + *framesize = fsize; + return TRUE; + } + } + + GST_LOG ("sync lost"); + amrparse->sync = FALSE; + return FALSE; +} + + +/** + * gst_amrparse_parse_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * + * Implementation of "parse" vmethod in #GstBaseParse class. + * + * Returns: #GstFlowReturn defining the parsing status. + */ +GstFlowReturn +gst_amrparse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) +{ + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (parse); + + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + gint64 btime; + gboolean r = gst_amrparse_convert (parse, GST_FORMAT_BYTES, + GST_BUFFER_OFFSET (buffer), + GST_FORMAT_TIME, &btime); + if (r) { + /* FIXME: What to do if the conversion fails? */ + amrparse->ts = btime; + } + } + + GST_BUFFER_DURATION (buffer) = AMR_FRAME_DURATION; + GST_BUFFER_TIMESTAMP (buffer) = amrparse->ts; + + if (GST_CLOCK_TIME_IS_VALID (amrparse->ts)) { + amrparse->ts += AMR_FRAME_DURATION; + } + + if (!(++amrparse->framecount % 50)) { + gst_amrparse_update_duration (amrparse); + } + amrparse->bytecount += GST_BUFFER_SIZE (buffer); + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + return GST_FLOW_OK; +} + + +/** + * gst_amrparse_start: + * @parse: #GstBaseParse. + * + * Implementation of "start" vmethod in #GstBaseParse class. + * + * Returns: TRUE on success. + */ +gboolean +gst_amrparse_start (GstBaseParse * parse) +{ + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (parse); + GST_DEBUG ("start"); + amrparse->need_header = TRUE; + amrparse->sync = TRUE; + amrparse->eos = FALSE; + amrparse->framecount = 0; + amrparse->bytecount = 0; + amrparse->ts = 0; + return TRUE; +} + + +/** + * gst_amrparse_stop: + * @parse: #GstBaseParse. + * + * Implementation of "stop" vmethod in #GstBaseParse class. + * + * Returns: TRUE on success. + */ +gboolean +gst_amrparse_stop (GstBaseParse * parse) +{ + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (parse); + GST_DEBUG ("stop"); + amrparse->need_header = TRUE; + amrparse->ts = -1; + return TRUE; +} + + +/** + * gst_amrparse_event: + * @parse: #GstBaseParse. + * @event: #GstEvent. + * + * Implementation of "event" vmethod in #GstBaseParse class. + * + * Returns: TRUE if the event was handled and can be dropped. + */ +gboolean +gst_amrparse_event (GstBaseParse * parse, GstEvent * event) +{ + GstAmrParse *amrparse; + + amrparse = GST_AMRPARSE (parse); + GST_DEBUG ("event"); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + amrparse->eos = TRUE; + GST_DEBUG ("EOS event"); + break; + default: + break; + } + + return parent_class->event (parse, event); +} + + +/** + * gst_amrparse_convert: + * @parse: #GstBaseParse. + * @src_format: #GstFormat describing the source format. + * @src_value: Source value to be converted. + * @dest_format: #GstFormat defining the converted format. + * @dest_value: Pointer where the conversion result will be put. + * + * Implementation of "convert" vmethod in #GstBaseParse class. + * + * Returns: TRUE if conversion was successful. + */ +gboolean +gst_amrparse_convert (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, GstFormat dest_format, gint64 * dest_value) +{ + gboolean ret = FALSE; + GstAmrParse *amrparse; + gfloat bpf; + + amrparse = GST_AMRPARSE (parse); + + /* We are not able to do any estimations until some data has been passed */ + if (!amrparse->framecount) + return FALSE; + + bpf = (gfloat) amrparse->bytecount / amrparse->framecount; + + if (src_format == GST_FORMAT_BYTES) { + if (dest_format == GST_FORMAT_TIME) { + /* BYTES -> TIME conversion */ + GST_DEBUG ("converting bytes -> time"); + + if (amrparse->framecount) { + *dest_value = AMR_FRAME_DURATION * src_value / bpf; + GST_DEBUG ("conversion result: %lld ms", *dest_value / GST_MSECOND); + ret = TRUE; + } + } + } else if (src_format == GST_FORMAT_TIME) { + GST_DEBUG ("converting time -> bytes"); + if (dest_format == GST_FORMAT_BYTES) { + if (amrparse->framecount) { + *dest_value = bpf * src_value / AMR_FRAME_DURATION; + GST_DEBUG ("time %lld ms in bytes = %lld", + src_value / GST_MSECOND, *dest_value); + ret = TRUE; + } + } + } else if (src_format == GST_FORMAT_DEFAULT) { + /* DEFAULT == frame-based */ + if (dest_format == GST_FORMAT_TIME) { + *dest_value = src_value * AMR_FRAME_DURATION; + ret = TRUE; + } else if (dest_format == GST_FORMAT_BYTES) { + } + } + + return ret; +} + + +/** + * plugin_init: + * @plugin: GstPlugin + * + * Returns: TRUE on success. + */ +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "amrparse", + GST_RANK_PRIMARY + 1, GST_TYPE_AMRPARSE); +} + + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "amrparse", + "Adaptive Multi-Rate Parser", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/amrparse/gstamrparse.h b/gst/amrparse/gstamrparse.h new file mode 100644 index 0000000000..e776f29529 --- /dev/null +++ b/gst/amrparse/gstamrparse.h @@ -0,0 +1,86 @@ +/* GStreamer Adaptive Multi-Rate parser + * Copyright (C) 2004 Ronald Bultje + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +#ifndef __GST_AMRPARSE_H__ +#define __GST_AMRPARSE_H__ + +#include +#include "gstbaseparse.h" + +G_BEGIN_DECLS + +#define GST_TYPE_AMRPARSE \ + (gst_amrparse_get_type()) +#define GST_AMRPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AMRPARSE, GstAmrParse)) +#define GST_AMRPARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AMRPARSE, GstAmrParseClass)) +#define GST_IS_AMRPARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AMRPARSE)) +#define GST_IS_AMRPARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AMRPARSE)) + + +typedef struct _GstAmrParse GstAmrParse; +typedef struct _GstAmrParseClass GstAmrParseClass; + +/** + * GstAmrParse: + * @element: the parent element. + * @block_size: Pointer to frame size lookup table. + * @need_header: Tells whether the MIME header should be read in the beginning. + * @wide: Wideband mode. + * @eos: Indicates the EOS situation. Set when EOS event is received. + * @sync: Tells whether the parser is in sync. + * @framecount: Total amount of frames handled. + * @bytecount: Total amount of bytes handled. + * @ts: Timestamp of the current media. + * + * The opaque GstAacParse data structure. + */ +struct _GstAmrParse { + GstBaseParse element; + const gint *block_size; + gboolean need_header; + gboolean wide; + gboolean eos; + gboolean sync; + guint64 framecount; + guint64 bytecount; + GstClockTime ts; +}; + +/** + * GstAmrParseClass: + * @parent_class: Element parent class. + * + * The opaque GstAmrParseClass data structure. + */ +struct _GstAmrParseClass { + GstBaseParseClass parent_class; +}; + +GType gst_amrparse_get_type (void); + +G_END_DECLS + +#endif /* __GST_AMRPARSE_H__ */ diff --git a/gst/amrparse/gstbaseparse.c b/gst/amrparse/gstbaseparse.c new file mode 100644 index 0000000000..18b396c3bf --- /dev/null +++ b/gst/amrparse/gstbaseparse.c @@ -0,0 +1,1741 @@ +/* GStreamer + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost + * Copyright (C) 2008 Sebastian Dröge . + * + * 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. + */ + +/** + * SECTION:gstbaseparse + * @short_description: Base class for stream parsers + * @see_also: #GstBaseTransform + * + * This base class is for parser elements that process data and splits it + * into separate audio/video/whatever frames. + * + * It provides for: + * + * One sinkpad and one srcpad + * Handles state changes + * Does flushing + * Push mode + * Pull mode + * Handles events (NEWSEGMENT/EOS/FLUSH) + * Handles seeking in both modes + * + * Handles POSITION/DURATION/SEEKING/FORMAT/CONVERT queries + * + * + * + * The purpose of this base class is to provide a basic functionality of + * a parser and share a lot of rather complex code. + * + * Description of the parsing mechanism: + * + * + * Set-up phase + * + * GstBaseParse class calls @set_sink_caps to inform the subclass about + * incoming sinkpad caps. Subclass should set the srcpad caps accordingly. + * + * + * GstBaseParse calls @start to inform subclass that data processing is + * about to start now. + * + * + * At least in this point subclass needs to tell the GstBaseParse class + * how big data chunks it wants to receive (min_frame_size). It can do + * this with @gst_base_parse_set_min_frame_size. + * + * + * GstBaseParse class sets up appropriate data passing mode (pull/push) + * and starts to process the data. + * + * + * + * + * + * Parsing phase + * + * GstBaseParse gathers at least min_frame_size bytes of data either + * by pulling it from upstream or collecting buffers into internal + * #GstAdapter. + * + * + * A buffer of min_frame_size bytes is passed to subclass with + * @check_valid_frame. Subclass checks the contents and returns TRUE + * if the buffer contains a valid frame. It also needs to set the + * @framesize according to the detected frame size. If buffer didn't + * contain a valid frame, this call must return FALSE and optionally + * set the @skipsize value to inform base class that how many bytes + * it needs to skip in order to find a valid frame. The passed buffer + * is read-only. + * + * + * After valid frame is found, it will be passed again to subclass with + * @parse_frame call. Now subclass is responsible for parsing the + * frame contents and setting the buffer timestamp, duration and caps. + * + * + * Finally the buffer can be pushed downstream and parsing loop starts + * over again. + * + * + * During the parsing process GstBaseClass will handle both srcpad and + * sinkpad events. They will be passed to subclass if @event or + * @src_event callbacks have been provided. + * + * + * + * + * Shutdown phase + * + * GstBaseParse class calls @stop to inform the subclass that data + * parsing will be stopped. + * + * + * + * + * + * Subclass is responsible for providing pad template caps for + * source and sink pads. The pads need to be named "sink" and "src". It also + * needs to set the fixed caps on srcpad, when the format is ensured (e.g. + * when base class calls subclass' @set_sink_caps function). + * + * This base class uses GST_FORMAT_DEFAULT as a meaning of frames. So, + * subclass conversion routine needs to know that conversion from + * GST_FORMAT_TIME to GST_FORMAT_DEFAULT must return the + * frame number that can be found from the given byte position. + * + * GstBaseParse uses subclasses conversion methods also for seeking. If + * subclass doesn't provide @convert function, seeking will get disabled. + * + * Subclass @start and @stop functions will be called to inform the beginning + * and end of data processing. + * + * Things that subclass need to take care of: + * + * Provide pad templates + * + * Fixate the source pad caps when appropriate + * + * + * Inform base class how big data chunks should be retrieved. This is + * done with @gst_base_parse_set_min_frame_size function. + * + * + * Examine data chunks passed to subclass with @check_valid_frame + * and tell if they contain a valid frame + * + * + * Set the caps and timestamp to frame that is passed to subclass with + * @parse_frame function. + * + * Provide conversion functions + * + * Update the duration information with @gst_base_parse_set_duration + * + * + * + */ + +/* TODO: + * - Better segment handling: + * - NEWSEGMENT for gaps + * - Not NEWSEGMENT starting at 0 but at first frame timestamp + * - GstIndex support + * - Seek table generation and subclass seek entry injection + * - Accurate seeking + * - In push mode provide a queue of adapter-"queued" buffers for upstream + * buffer metadata + * - Timestamp tracking and setting + * - Handle upstream timestamps + * - Queue buffers/events until caps are set + * - Bitrate tracking => inaccurate seeking, inaccurate duration calculation + * - Let subclass decide if frames outside the segment should be dropped + * - Send queries upstream + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "gstbaseparse.h" + +GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug); +#define GST_CAT_DEFAULT gst_base_parse_debug + +/* Supported formats */ +static GstFormat fmtlist[] = { + GST_FORMAT_DEFAULT, + GST_FORMAT_BYTES, + GST_FORMAT_TIME, + 0 +}; + +#define GST_BASE_PARSE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_PARSE, GstBaseParsePrivate)) + +struct _GstBaseParsePrivate +{ + GstActivateMode pad_mode; + + gint64 duration; + GstFormat duration_fmt; + + guint min_frame_size; + + gboolean discont; + gboolean flushing; + + gint64 offset; + gint64 pending_offset; + + GList *pending_events; + + GstBuffer *cache; +}; + +struct _GstBaseParseClassPrivate +{ + gpointer _padding; +}; + +static GstElementClass *parent_class = NULL; + +static void gst_base_parse_base_init (gpointer g_class); +static void gst_base_parse_base_finalize (gpointer g_class); +static void gst_base_parse_class_init (GstBaseParseClass * klass); +static void gst_base_parse_init (GstBaseParse * parse, + GstBaseParseClass * klass); + +GType +gst_base_parse_get_type (void) +{ + static GType base_parse_type = 0; + + if (!base_parse_type) { + static const GTypeInfo base_parse_info = { + sizeof (GstBaseParseClass), + (GBaseInitFunc) gst_base_parse_base_init, + (GBaseFinalizeFunc) gst_base_parse_base_finalize, + (GClassInitFunc) gst_base_parse_class_init, + NULL, + NULL, + sizeof (GstBaseParse), + 0, + (GInstanceInitFunc) gst_base_parse_init, + }; + + base_parse_type = g_type_register_static (GST_TYPE_ELEMENT, + "GstFlacBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT); + } + return base_parse_type; +} + +static void gst_base_parse_finalize (GObject * object); + +static gboolean gst_base_parse_sink_activate (GstPad * sinkpad); +static gboolean gst_base_parse_sink_activate_push (GstPad * pad, + gboolean active); +static gboolean gst_base_parse_sink_activate_pull (GstPad * pad, + gboolean active); +static gboolean gst_base_parse_handle_seek (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_base_parse_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_base_parse_query (GstPad * pad, GstQuery * query); +static gboolean gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps); +static const GstQueryType *gst_base_parse_get_querytypes (GstPad * pad); + +static GstFlowReturn gst_base_parse_chain (GstPad * pad, GstBuffer * buffer); +static void gst_base_parse_loop (GstPad * pad); + +static gboolean gst_base_parse_check_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize); + +static gboolean gst_base_parse_parse_frame (GstBaseParse * parse, + GstBuffer * buffer); + +static gboolean gst_base_parse_sink_eventfunc (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_src_eventfunc (GstBaseParse * parse, + GstEvent * event); + +static gboolean gst_base_parse_is_seekable (GstBaseParse * parse); + +static void gst_base_parse_drain (GstBaseParse * parse); + +static void +gst_base_parse_base_init (gpointer g_class) +{ + GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class); + GstBaseParseClassPrivate *priv; + + GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "flacbaseparse", 0, + "baseparse element"); + + /* TODO: Remove this once GObject supports class private data */ + priv = g_slice_new0 (GstBaseParseClassPrivate); + if (klass->priv) + memcpy (priv, klass->priv, sizeof (GstBaseParseClassPrivate)); + klass->priv = priv; +} + +static void +gst_base_parse_base_finalize (gpointer g_class) +{ + GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class); + + g_slice_free (GstBaseParseClassPrivate, klass->priv); + klass->priv = NULL; +} + +static void +gst_base_parse_finalize (GObject * object) +{ + GstBaseParse *parse = GST_BASE_PARSE (object); + + g_mutex_free (parse->parse_lock); + g_object_unref (parse->adapter); + + if (parse->pending_segment) { + gst_event_replace (&parse->pending_segment, NULL); + } + if (parse->close_segment) { + gst_event_replace (&parse->close_segment, NULL); + } + + if (parse->priv->cache) { + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_base_parse_class_init (GstBaseParseClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + g_type_class_add_private (klass, sizeof (GstBaseParsePrivate)); + parent_class = g_type_class_peek_parent (klass); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_base_parse_finalize); + + /* Default handlers */ + klass->check_valid_frame = gst_base_parse_check_frame; + klass->parse_frame = gst_base_parse_parse_frame; + klass->event = gst_base_parse_sink_eventfunc; + klass->src_event = gst_base_parse_src_eventfunc; + klass->is_seekable = gst_base_parse_is_seekable; +} + +static void +gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass) +{ + GstPadTemplate *pad_template; + + GST_DEBUG_OBJECT (parse, "gst_base_parse_init"); + + parse->priv = GST_BASE_PARSE_GET_PRIVATE (parse); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink"); + g_return_if_fail (pad_template != NULL); + parse->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_event_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_event)); + gst_pad_set_setcaps_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_setcaps)); + gst_pad_set_chain_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_chain)); + gst_pad_set_activate_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate)); + gst_pad_set_activatepush_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_push)); + gst_pad_set_activatepull_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_pull)); + gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); + + GST_DEBUG_OBJECT (parse, "sinkpad created"); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); + g_return_if_fail (pad_template != NULL); + parse->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_set_event_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_src_event)); + gst_pad_set_query_type_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_get_querytypes)); + gst_pad_set_query_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_base_parse_query)); + gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); + GST_DEBUG_OBJECT (parse, "src created"); + + parse->parse_lock = g_mutex_new (); + parse->adapter = gst_adapter_new (); + parse->pending_segment = NULL; + parse->close_segment = NULL; + + parse->priv->pad_mode = GST_ACTIVATE_NONE; + parse->priv->duration = -1; + parse->priv->min_frame_size = 1; + parse->priv->discont = FALSE; + parse->priv->flushing = FALSE; + parse->priv->offset = 0; + GST_DEBUG_OBJECT (parse, "init ok"); +} + + + +/** + * gst_base_parse_check_frame: + * @parse: #GstBaseParse. + * @buffer: GstBuffer. + * @framesize: This will be set to tell the found frame size in bytes. + * @skipsize: Output parameter that tells how much data needs to be skipped + * in order to find the following frame header. + * + * Default callback for check_valid_frame. + * + * Returns: Always TRUE. + */ +static gboolean +gst_base_parse_check_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize) +{ + *framesize = GST_BUFFER_SIZE (buffer); + *skipsize = 0; + return TRUE; +} + + +/** + * gst_base_parse_parse_frame: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * + * Default callback for parse_frame. + */ +static gboolean +gst_base_parse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) +{ + /* FIXME: Could we even _try_ to do something clever here? */ + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + return TRUE; +} + + +/** + * gst_base_parse_bytepos_to_time: + * @parse: #GstBaseParse. + * @bytepos: Position (in bytes) to be converted. + * @pos_in_time: #GstClockTime pointer where the result is set. + * + * Convert given byte position into #GstClockTime format. + * + * Returns: TRUE if conversion succeeded. + */ +static gboolean +gst_base_parse_bytepos_to_time (GstBaseParse * parse, gint64 bytepos, + GstClockTime * pos_in_time) +{ + GstBaseParseClass *klass; + gboolean res = FALSE; + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (klass->convert) { + res = klass->convert (parse, GST_FORMAT_BYTES, bytepos, + GST_FORMAT_TIME, (gint64 *) pos_in_time); + } + return res; +} + + +/** + * gst_base_parse_sink_event: + * @pad: #GstPad that received the event. + * @event: #GstEvent to be handled. + * + * Handler for sink pad events. + * + * Returns: TRUE if the event was handled. + */ +static gboolean +gst_base_parse_sink_event (GstPad * pad, GstEvent * event) +{ + GstBaseParse *parse; + GstBaseParseClass *bclass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "handling event %d", GST_EVENT_TYPE (event)); + + /* Cache all events except EOS, NEWSEGMENT and FLUSH_STOP if we have a + * pending segment */ + if (parse->pending_segment && GST_EVENT_TYPE (event) != GST_EVENT_EOS + && GST_EVENT_TYPE (event) != GST_EVENT_NEWSEGMENT + && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_START + && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP) { + parse->priv->pending_events = + g_list_append (parse->priv->pending_events, event); + ret = TRUE; + } else { + + if (bclass->event) + handled = bclass->event (parse, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + } + + gst_object_unref (parse); + GST_DEBUG_OBJECT (parse, "event handled"); + return ret; +} + + +/** + * gst_base_parse_sink_eventfunc: + * @parse: #GstBaseParse. + * @event: #GstEvent to be handled. + * + * Element-level event handler function. + * + * Returns: TRUE if the event was handled and not need forwarding. + */ +static gboolean +gst_base_parse_sink_eventfunc (GstBaseParse * parse, GstEvent * event) +{ + gboolean handled = FALSE; + GstEvent **eventp; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, pos; + gboolean update; + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &pos); + + + if (format == GST_FORMAT_BYTES) { + GstClockTime seg_start, seg_stop, seg_pos; + + /* stop time is allowed to be open-ended, but not start & pos */ + seg_stop = GST_CLOCK_TIME_NONE; + parse->priv->pending_offset = pos; + + if (gst_base_parse_bytepos_to_time (parse, start, &seg_start) && + gst_base_parse_bytepos_to_time (parse, pos, &seg_pos)) { + gst_event_unref (event); + event = gst_event_new_new_segment_full (update, rate, applied_rate, + GST_FORMAT_TIME, seg_start, seg_stop, seg_pos); + format = GST_FORMAT_TIME; + GST_DEBUG_OBJECT (parse, "Converted incoming segment to TIME. " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, GST_TIME_ARGS (seg_start), + GST_TIME_ARGS (seg_stop), GST_TIME_ARGS (seg_pos)); + } + } + + if (format != GST_FORMAT_TIME) { + /* Unknown incoming segment format. Output a default open-ended + * TIME segment */ + gst_event_unref (event); + event = gst_event_new_new_segment_full (update, rate, applied_rate, + GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0); + } + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &pos); + + gst_segment_set_newsegment_full (&parse->segment, update, rate, + applied_rate, format, start, stop, pos); + + GST_DEBUG_OBJECT (parse, "Created newseg rate %g, applied rate %g, " + "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (pos)); + + /* save the segment for later, right before we push a new buffer so that + * the caps are fixed and the next linked element can receive + * the segment. */ + eventp = &parse->pending_segment; + gst_event_replace (eventp, event); + gst_event_unref (event); + handled = TRUE; + break; + } + + case GST_EVENT_FLUSH_START: + parse->priv->flushing = TRUE; + /* Wait for _chain() to exit by taking the srcpad STREAM_LOCK */ + GST_PAD_STREAM_LOCK (parse->srcpad); + handled = gst_pad_push_event (parse->srcpad, event); + GST_PAD_STREAM_UNLOCK (parse->srcpad); + break; + + case GST_EVENT_FLUSH_STOP: + gst_adapter_clear (parse->adapter); + parse->priv->flushing = FALSE; + parse->priv->discont = TRUE; + break; + + case GST_EVENT_EOS: + gst_base_parse_drain (parse); + break; + + default: + break; + } + + return handled; +} + + +/** + * gst_base_parse_src_event: + * @pad: #GstPad that received the event. + * @event: #GstEvent that was received. + * + * Handler for source pad events. + * + * Returns: TRUE if the event was handled. + */ +static gboolean +gst_base_parse_src_event (GstPad * pad, GstEvent * event) +{ + GstBaseParse *parse; + GstBaseParseClass *bclass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + if (bclass->src_event) + handled = bclass->src_event (parse, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + + gst_object_unref (parse); + return ret; +} + + +/** + * gst_base_parse_src_eventfunc: + * @parse: #GstBaseParse. + * @event: #GstEvent that was received. + * + * Default srcpad event handler. + * + * Returns: TRUE if the event was handled and can be dropped. + */ +static gboolean +gst_base_parse_src_eventfunc (GstBaseParse * parse, GstEvent * event) +{ + gboolean handled = FALSE; + GstBaseParseClass *bclass; + + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + if (bclass->is_seekable (parse)) { + handled = gst_base_parse_handle_seek (parse, event); + gst_event_unref (event); + } + break; + } + default: + break; + } + return handled; +} + + +/** + * gst_base_parse_is_seekable: + * @parse: #GstBaseParse. + * + * Default handler for is_seekable. + * + * Returns: Always TRUE. + */ +static gboolean +gst_base_parse_is_seekable (GstBaseParse * parse) +{ + return TRUE; +} + + +/** + * gst_base_parse_handle_and_push_buffer: + * @parse: #GstBaseParse. + * @klass: #GstBaseParseClass. + * @buffer: #GstBuffer. + * + * Parses the frame from given buffer and pushes it forward. Also performs + * timestamp handling and checks the segment limits. + * + * This is called with srcpad STREAM_LOCK held. + * + * Returns: #GstFlowReturn + */ +static GstFlowReturn +gst_base_parse_handle_and_push_buffer (GstBaseParse * parse, + GstBaseParseClass * klass, GstBuffer * buffer) +{ + GstFlowReturn ret; + GstClockTime last_stop = GST_CLOCK_TIME_NONE; + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + parse->priv->discont = FALSE; + } + + ret = klass->parse_frame (parse, buffer); + + /* FIXME: Check the output buffer for any missing metadata, + * keep track of timestamp and calculate everything possible + * if not set already */ + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + last_stop = GST_BUFFER_TIMESTAMP (buffer); + if (last_stop != GST_CLOCK_TIME_NONE && GST_BUFFER_DURATION_IS_VALID (buffer)) + last_stop += GST_BUFFER_DURATION (buffer); + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + + /* TODO: Add to seek table */ + + if (ret == GST_FLOW_OK) { + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && + GST_CLOCK_TIME_IS_VALID (parse->segment.stop) && + GST_BUFFER_TIMESTAMP (buffer) > parse->segment.stop) { + GST_LOG_OBJECT (parse, "Dropped frame, after segment"); + gst_buffer_unref (buffer); + } else if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && + GST_BUFFER_DURATION_IS_VALID (buffer) && + GST_CLOCK_TIME_IS_VALID (parse->segment.start) && + GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION_IS_VALID (buffer) + < parse->segment.start) { + /* FIXME: subclass needs way to override the start as downstream might + * need frames before for proper decoding */ + GST_LOG_OBJECT (parse, "Dropped frame, before segment"); + gst_buffer_unref (buffer); + } else { + ret = gst_pad_push (parse->srcpad, buffer); + GST_LOG_OBJECT (parse, "frame (%d bytes) pushed: %d", + GST_BUFFER_SIZE (buffer), ret); + } + } else { + gst_buffer_unref (buffer); + } + + /* Update current running segment position */ + if (ret == GST_FLOW_OK && last_stop != GST_CLOCK_TIME_NONE) + gst_segment_set_last_stop (&parse->segment, GST_FORMAT_TIME, last_stop); + + /* convert internal flow to OK and mark discont for the next buffer. */ + if (ret == GST_BASE_PARSE_FLOW_DROPPED) { + parse->priv->discont = TRUE; + ret = GST_FLOW_OK; + } + return ret; +} + + +/** + * gst_base_parse_drain: + * @parse: #GstBaseParse. + * + * Drains the adapter until it is empty. It decreases the min_frame_size to + * match the current adapter size and calls chain method until the adapter + * is emptied or chain returns with error. + */ +static void +gst_base_parse_drain (GstBaseParse * parse) +{ + guint avail; + + for (;;) { + avail = gst_adapter_available (parse->adapter); + if (!avail) + break; + + gst_base_parse_set_min_frame_size (parse, avail); + if (gst_base_parse_chain (parse->sinkpad, NULL) != GST_FLOW_OK) { + break; + } + } +} + + +/** + * gst_base_parse_chain: + * @pad: #GstPad. + * @buffer: #GstBuffer. + * + * Returns: #GstFlowReturn. + */ +static GstFlowReturn +gst_base_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstBaseParseClass *bclass; + GstBaseParse *parse; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *outbuf = NULL; + GstBuffer *tmpbuf = NULL; + guint fsize = 0; + gint skip = -1; + const guint8 *data; + guint min_size; + + parse = GST_BASE_PARSE (GST_OBJECT_PARENT (pad)); + bclass = GST_BASE_PARSE_GET_CLASS (parse); + + if (parse->pending_segment) { + GST_DEBUG_OBJECT (parse, "chain pushing a pending segment"); + gst_pad_push_event (parse->srcpad, parse->pending_segment); + parse->pending_segment = NULL; + parse->priv->offset = parse->priv->pending_offset; + + /* Make sure that adapter doesn't have any old data after + newsegment has been pushed */ + + /* FIXME: when non-flushing seek occurs, chain is still processing the + data from old segment. If this processing loop is then interrupted + (e.g. paused), chain function exists and next time it gets called + all this old data gets lost and playback continues from new segment */ + gst_adapter_clear (parse->adapter); + } + + if (parse->priv->pending_events) { + GList *l; + + for (l = parse->priv->pending_events; l != NULL; l = l->next) { + gst_pad_push_event (parse->srcpad, GST_EVENT (l->data)); + } + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + } + + if (buffer) { + GST_LOG_OBJECT (parse, "buffer size: %d, offset = %lld", + GST_BUFFER_SIZE (buffer), GST_BUFFER_OFFSET (buffer)); + + + gst_adapter_push (parse->adapter, buffer); + } + + /* Parse and push as many frames as possible */ + /* Stop either when adapter is empty or we are flushing */ + while (!parse->priv->flushing) { + tmpbuf = gst_buffer_new (); + + GST_BASE_PARSE_LOCK (parse); + min_size = parse->priv->min_frame_size; + GST_BASE_PARSE_UNLOCK (parse); + + /* Synchronization loop */ + for (;;) { + /* Collect at least min_frame_size bytes */ + if (gst_adapter_available (parse->adapter) < min_size) { + GST_DEBUG_OBJECT (parse, "not enough data available (only %d bytes)", + gst_adapter_available (parse->adapter)); + gst_buffer_unref (tmpbuf); + goto done; + } + + data = gst_adapter_peek (parse->adapter, min_size); + GST_BUFFER_DATA (tmpbuf) = (guint8 *) data; + GST_BUFFER_SIZE (tmpbuf) = min_size; + GST_BUFFER_OFFSET (tmpbuf) = parse->priv->offset; + GST_BUFFER_FLAG_SET (tmpbuf, GST_MINI_OBJECT_FLAG_READONLY); + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + + skip = -1; + if (bclass->check_valid_frame (parse, tmpbuf, &fsize, &skip)) { + break; + } + if (skip > 0) { + GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip); + gst_adapter_flush (parse->adapter, skip); + parse->priv->offset += skip; + } else if (skip == -1) { + /* subclass didn't touch this value. By default we skip 1 byte */ + GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte"); + gst_adapter_flush (parse->adapter, 1); + parse->priv->offset++; + } + /* There is a possibility that subclass set the skip value to zero. + This means that it has probably found a frame but wants to ask + more data (by increasing the min_size) to be sure of this. */ + } + gst_buffer_unref (tmpbuf); + tmpbuf = NULL; + + if (skip > 0) { + /* Subclass found the sync, but still wants to skip some data */ + GST_LOG_OBJECT (parse, "skipping %d bytes", skip); + gst_adapter_flush (parse->adapter, skip); + parse->priv->offset += skip; + } + + /* Grab lock to prevent a race with FLUSH_START handler */ + GST_PAD_STREAM_LOCK (parse->srcpad); + + /* FLUSH_START event causes the "flushing" flag to be set. In this + * case we can leave the frame pushing loop */ + if (parse->priv->flushing) { + GST_PAD_STREAM_UNLOCK (parse->srcpad); + break; + } + + /* FIXME: Would it be more efficient to make a subbuffer instead? */ + outbuf = gst_adapter_take_buffer (parse->adapter, fsize); + + /* Subclass may want to know the data offset */ + GST_BUFFER_OFFSET (outbuf) = parse->priv->offset; + parse->priv->offset += fsize; + + ret = gst_base_parse_handle_and_push_buffer (parse, bclass, outbuf); + GST_PAD_STREAM_UNLOCK (parse->srcpad); + + if (ret != GST_FLOW_OK) { + GST_LOG_OBJECT (parse, "push returned %d", ret); + break; + } + } + +done: + GST_LOG_OBJECT (parse, "chain leaving"); + return ret; +} + +static GstFlowReturn +gst_base_parse_pull_range (GstBaseParse * parse, guint size, + GstBuffer ** buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + /* Caching here actually makes much less difference than one would expect. + * We do it mainly to avoid pulling buffers of 1 byte all the time */ + if (parse->priv->cache) { + guint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache); + guint cache_size = GST_BUFFER_SIZE (parse->priv->cache); + + if (cache_offset <= parse->priv->offset && + (parse->priv->offset + size) < (cache_offset + cache_size)) { + *buffer = gst_buffer_create_sub (parse->priv->cache, + parse->priv->offset - cache_offset, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + return GST_FLOW_OK; + } + /* not enough data in the cache, free cache and get a new one */ + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + /* refill the cache */ + ret = + gst_pad_pull_range (parse->sinkpad, parse->priv->offset, MAX (size, + 64 * 1024), &parse->priv->cache); + if (ret != GST_FLOW_OK) { + parse->priv->cache = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->priv->cache) >= size) { + *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + return GST_FLOW_OK; + } + + /* Not possible to get enough data, try a last time with + * requesting exactly the size we need */ + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + + ret = gst_pad_pull_range (parse->sinkpad, parse->priv->offset, size, + &parse->priv->cache); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "pull_range returned %d", ret); + *buffer = NULL; + return ret; + } + + if (GST_BUFFER_SIZE (parse->priv->cache) < size) { + GST_WARNING_OBJECT (parse, "Dropping short buffer at offset %" + G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", parse->priv->offset, + size, GST_BUFFER_SIZE (parse->priv->cache)); + + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + + *buffer = NULL; + return GST_FLOW_UNEXPECTED; + } + + *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size); + GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; + + return GST_FLOW_OK; +} + +/** + * gst_base_parse_loop: + * @pad: GstPad + * + * Loop that is used in pull mode to retrieve data from upstream. + */ +static void +gst_base_parse_loop (GstPad * pad) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + GstBuffer *buffer, *outbuf; + gboolean ret = FALSE; + guint fsize = 0, min_size; + gint skip = 0; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (parse->close_segment) { + GST_DEBUG_OBJECT (parse, "loop sending close segment"); + gst_pad_push_event (parse->srcpad, parse->close_segment); + parse->close_segment = NULL; + } + + if (parse->pending_segment) { + GST_DEBUG_OBJECT (parse, "loop push pending segment"); + gst_pad_push_event (parse->srcpad, parse->pending_segment); + parse->pending_segment = NULL; + } + + /* TODO: Check if we reach segment stop limit */ + + while (TRUE) { + + GST_BASE_PARSE_LOCK (parse); + min_size = parse->priv->min_frame_size; + GST_BASE_PARSE_UNLOCK (parse); + + ret = gst_base_parse_pull_range (parse, min_size, &buffer); + + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + else if (ret != GST_FLOW_OK) + goto need_pause; + + if (parse->priv->discont) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + + skip = -1; + if (klass->check_valid_frame (parse, buffer, &fsize, &skip)) { + break; + } + if (skip > 0) { + GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip); + parse->priv->offset += skip; + } else if (skip == -1) { + GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte"); + parse->priv->offset++; + } + GST_DEBUG_OBJECT (parse, "finding sync..."); + gst_buffer_unref (buffer); + } + + if (fsize <= GST_BUFFER_SIZE (buffer)) { + outbuf = gst_buffer_create_sub (buffer, 0, fsize); + GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buffer); + gst_buffer_unref (buffer); + } else { + gst_buffer_unref (buffer); + ret = gst_base_parse_pull_range (parse, fsize, &outbuf); + + if (ret == GST_FLOW_UNEXPECTED) + goto eos; + else if (ret != GST_FLOW_OK) + goto need_pause; + } + + parse->priv->offset += fsize; + + /* Does the subclass want to skip too? */ + if (skip > 0) + parse->priv->offset += skip; + + /* This always unrefs the outbuf, even if error occurs */ + ret = gst_base_parse_handle_and_push_buffer (parse, klass, outbuf); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (parse, "flow: %s", gst_flow_get_name (ret)); + if (GST_FLOW_IS_FATAL (ret)) { + GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL), + ("streaming task paused, reason: %s", gst_flow_get_name (ret))); + gst_pad_push_event (parse->srcpad, gst_event_new_eos ()); + } + goto need_pause; + } + + gst_object_unref (parse); + return; + +need_pause: + { + GST_LOG_OBJECT (parse, "pausing task"); + gst_pad_pause_task (pad); + gst_object_unref (parse); + return; + } +eos: + { + GST_LOG_OBJECT (parse, "pausing task %d", ret); + gst_pad_push_event (parse->srcpad, gst_event_new_eos ()); + gst_pad_pause_task (pad); + gst_object_unref (parse); + return; + } +} + + +/** + * gst_base_parse_sink_activate: + * @sinkpad: #GstPad to be activated. + * + * Returns: TRUE if activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate (GstPad * sinkpad) +{ + GstBaseParse *parse; + gboolean result = TRUE; + + parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad)); + + GST_DEBUG_OBJECT (parse, "sink activate"); + + if (gst_pad_check_pull_range (sinkpad)) { + GST_DEBUG_OBJECT (parse, "trying to activate in pull mode"); + result = gst_pad_activate_pull (sinkpad, TRUE); + } else { + GST_DEBUG_OBJECT (parse, "trying to activate in push mode"); + result = gst_pad_activate_push (sinkpad, TRUE); + } + + GST_DEBUG_OBJECT (parse, "sink activate return %d", result); + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_activate: + * @parse: #GstBaseParse. + * @active: TRUE if element will be activated, FALSE if disactivated. + * + * Returns: TRUE if the operation succeeded. + */ +static gboolean +gst_base_parse_activate (GstBaseParse * parse, gboolean active) +{ + GstBaseParseClass *klass; + gboolean result = FALSE; + + GST_DEBUG_OBJECT (parse, "activate"); + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + if (active) { + if (parse->priv->pad_mode == GST_ACTIVATE_NONE && klass->start) + result = klass->start (parse); + + GST_OBJECT_LOCK (parse); + gst_segment_init (&parse->segment, GST_FORMAT_TIME); + parse->priv->duration = -1; + parse->priv->discont = FALSE; + parse->priv->flushing = FALSE; + parse->priv->offset = 0; + + if (parse->pending_segment) + gst_event_unref (parse->pending_segment); + + parse->pending_segment = + gst_event_new_new_segment (FALSE, parse->segment.rate, + parse->segment.format, + parse->segment.start, parse->segment.stop, parse->segment.last_stop); + + GST_OBJECT_UNLOCK (parse); + } else { + /* We must make sure streaming has finished before resetting things + * and calling the ::stop vfunc */ + GST_PAD_STREAM_LOCK (parse->sinkpad); + GST_PAD_STREAM_UNLOCK (parse->sinkpad); + + if (parse->priv->pad_mode != GST_ACTIVATE_NONE && klass->stop) + result = klass->stop (parse); + + g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (parse->priv->pending_events); + parse->priv->pending_events = NULL; + + if (parse->priv->cache) { + gst_buffer_unref (parse->priv->cache); + parse->priv->cache = NULL; + } + + parse->priv->pad_mode = GST_ACTIVATE_NONE; + } + GST_DEBUG_OBJECT (parse, "activate: %d", result); + return result; +} + + +/** + * gst_base_parse_sink_activate_push: + * @pad: #GstPad to be (de)activated. + * @active: TRUE when activating, FALSE when deactivating. + * + * Returns: TRUE if (de)activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = TRUE; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (parse, "sink activate push"); + + result = gst_base_parse_activate (parse, active); + + if (result) + parse->priv->pad_mode = active ? GST_ACTIVATE_PUSH : GST_ACTIVATE_NONE; + + GST_DEBUG_OBJECT (parse, "sink activate push: %d", result); + + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_sink_activate_pull: + * @sinkpad: #GstPad to be (de)activated. + * @active: TRUE when activating, FALSE when deactivating. + * + * Returns: TRUE if (de)activation succeeded. + */ +static gboolean +gst_base_parse_sink_activate_pull (GstPad * sinkpad, gboolean active) +{ + gboolean result = FALSE; + GstBaseParse *parse; + + parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad)); + + GST_DEBUG_OBJECT (parse, "activate pull"); + + result = gst_base_parse_activate (parse, active); + + if (result) { + if (active) { + result &= gst_pad_start_task (sinkpad, + (GstTaskFunction) gst_base_parse_loop, sinkpad); + } else { + result &= gst_pad_stop_task (sinkpad); + } + } + + if (result) + parse->priv->pad_mode = active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE; + + GST_DEBUG_OBJECT (parse, "sink activate pull: %d", result); + + gst_object_unref (parse); + return result; +} + + +/** + * gst_base_parse_set_duration: + * @parse: #GstBaseParse. + * @fmt: #GstFormat. + * @duration: duration value. + * + * Sets the duration of the currently playing media. Subclass can use this + * when it notices a change in the media duration. + */ +void +gst_base_parse_set_duration (GstBaseParse * parse, + GstFormat fmt, gint64 duration) +{ + g_return_if_fail (parse != NULL); + + GST_BASE_PARSE_LOCK (parse); + if (duration != parse->priv->duration) { + GstMessage *m; + + m = gst_message_new_duration (GST_OBJECT (parse), fmt, duration); + gst_element_post_message (GST_ELEMENT (parse), m); + + /* TODO: what about duration tag? */ + } + parse->priv->duration = duration; + parse->priv->duration_fmt = fmt; + GST_DEBUG_OBJECT (parse, "set duration: %lld", duration); + GST_BASE_PARSE_UNLOCK (parse); +} + + +/** + * gst_base_parse_set_min_frame_size: + * @parse: #GstBaseParse. + * @min_size: Minimum size of the data that this base class should give to + * subclass. + * + * Subclass can use this function to tell the base class that it needs to + * give at least #min_size buffers. + */ +void +gst_base_parse_set_min_frame_size (GstBaseParse * parse, guint min_size) +{ + g_return_if_fail (parse != NULL); + + GST_BASE_PARSE_LOCK (parse); + parse->priv->min_frame_size = min_size; + GST_LOG_OBJECT (parse, "set frame_min_size: %d", min_size); + GST_BASE_PARSE_UNLOCK (parse); +} + + +/** + * gst_base_parse_get_querytypes: + * @pad: GstPad + * + * Returns: A table of #GstQueryType items describing supported query types. + */ +static const GstQueryType * +gst_base_parse_get_querytypes (GstPad * pad) +{ + static const GstQueryType list[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_FORMATS, + GST_QUERY_SEEKING, + GST_QUERY_CONVERT, + 0 + }; + + return list; +} + + +/** + * gst_base_parse_query: + * @pad: #GstPad. + * @query: #GstQuery. + * + * Returns: TRUE on success. + */ +static gboolean +gst_base_parse_query (GstPad * pad, GstQuery * query) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + gboolean res = FALSE; + + parse = GST_BASE_PARSE (GST_PAD_PARENT (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + /* If subclass doesn't provide conversion function we can't reply + to the query either */ + if (!klass->convert) { + return FALSE; + } + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + gint64 dest_value; + GstFormat format; + + GST_DEBUG_OBJECT (parse, "position query"); + + gst_query_parse_position (query, &format, NULL); + + g_mutex_lock (parse->parse_lock); + + if (format == GST_FORMAT_BYTES) { + dest_value = parse->priv->offset; + res = TRUE; + } else if (format == parse->segment.format && + GST_CLOCK_TIME_IS_VALID (parse->segment.last_stop)) { + dest_value = parse->segment.last_stop; + res = TRUE; + } else { + /* priv->offset is updated in both PUSH/PULL modes */ + res = klass->convert (parse, GST_FORMAT_BYTES, parse->priv->offset, + format, &dest_value); + } + g_mutex_unlock (parse->parse_lock); + + if (res) + gst_query_set_position (query, format, dest_value); + else + res = gst_pad_query_default (pad, query); + + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 dest_value; + + GST_DEBUG_OBJECT (parse, "duration query"); + + gst_query_parse_duration (query, &format, NULL); + + g_mutex_lock (parse->parse_lock); + + if (format == GST_FORMAT_BYTES) { + res = gst_pad_query_peer_duration (parse->sinkpad, &format, + &dest_value); + } else if (parse->priv->duration != -1 && + format == parse->priv->duration_fmt) { + dest_value = parse->priv->duration; + res = TRUE; + } else if (parse->priv->duration != -1) { + res = klass->convert (parse, parse->priv->duration_fmt, + parse->priv->duration, format, &dest_value); + } + + g_mutex_unlock (parse->parse_lock); + + if (res) + gst_query_set_duration (query, format, dest_value); + else + res = gst_pad_query_default (pad, query); + break; + } + case GST_QUERY_SEEKING: + { + GstFormat fmt; + gboolean seekable = FALSE; + + GST_DEBUG_OBJECT (parse, "seeking query"); + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + + if (fmt != GST_FORMAT_TIME) { + return gst_pad_query_default (pad, query); + } + + seekable = klass->is_seekable (parse); + + /* TODO: could this duration be calculated/converted if subclass + hasn't given it? */ + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, + (parse->priv->duration == -1) ? + GST_CLOCK_TIME_NONE : parse->priv->duration); + + GST_DEBUG_OBJECT (parse, "seekable: %d", seekable); + res = TRUE; + break; + } + case GST_QUERY_FORMATS: + gst_query_set_formatsv (query, 3, fmtlist); + res = TRUE; + break; + + case GST_QUERY_CONVERT: + { + GstFormat src_format, dest_format; + gint64 src_value, dest_value; + + gst_query_parse_convert (query, &src_format, &src_value, + &dest_format, &dest_value); + + /* FIXME: hm? doesn't make sense + * We require all those values to be given + if (src_format && src_value && dest_format && dest_value ) { */ + res = klass->convert (parse, src_format, src_value, + dest_format, &dest_value); + if (res) { + gst_query_set_convert (query, src_format, src_value, + dest_format, dest_value); + } + /*} */ + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + return res; +} + + +/** + * gst_base_parse_handle_seek: + * @parse: #GstBaseParse. + * @event: #GstEvent. + * + * Returns: TRUE if seek succeeded. + */ +static gboolean +gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) +{ + GstBaseParseClass *klass; + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type; + gboolean flush, update, res = TRUE; + gint64 cur, stop, seekpos; + GstSegment seeksegment = { 0, }; + GstFormat dstformat; + + klass = GST_BASE_PARSE_GET_CLASS (parse); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + /* no negative rates yet */ + if (rate < 0.0) + goto negative_rate; + + if (cur_type != GST_SEEK_TYPE_SET) + goto wrong_type; + + /* For any format other than TIME, see if upstream handles + * it directly or fail. For TIME, try upstream, but do it ourselves if + * it fails upstream */ + if (format != GST_FORMAT_TIME) { + gst_event_ref (event); + return gst_pad_push_event (parse->sinkpad, event); + } else { + gst_event_ref (event); + if (gst_pad_push_event (parse->sinkpad, event)) + return TRUE; + } + + /* get flush flag */ + flush = flags & GST_SEEK_FLAG_FLUSH; + + dstformat = GST_FORMAT_BYTES; + if (!gst_pad_query_convert (parse->srcpad, format, cur, &dstformat, &seekpos)) { + GST_DEBUG_OBJECT (parse, "conversion failed"); + return FALSE; + } + + GST_DEBUG_OBJECT (parse, "seek position %lld in bytes: %lld", cur, seekpos); + + if (parse->priv->pad_mode == GST_ACTIVATE_PULL) { + gint64 last_stop; + + GST_DEBUG_OBJECT (parse, "seek in PULL mode"); + + if (flush) { + if (parse->srcpad) { + GST_DEBUG_OBJECT (parse, "sending flush start"); + gst_pad_push_event (parse->srcpad, gst_event_new_flush_start ()); + } + } else { + gst_pad_pause_task (parse->sinkpad); + } + + /* we should now be able to grab the streaming thread because we stopped it + * with the above flush/pause code */ + GST_PAD_STREAM_LOCK (parse->sinkpad); + + /* save current position */ + last_stop = parse->segment.last_stop; + GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT, + last_stop); + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &parse->segment, sizeof (GstSegment)); + + GST_DEBUG_OBJECT (parse, "configuring seek"); + gst_segment_set_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + + /* figure out the last position we need to play. If it's configured (stop != + * -1), use that, else we play until the total duration of the file */ + if ((stop = seeksegment.stop) == -1) + stop = seeksegment.duration; + + parse->priv->offset = seekpos; + + /* prepare for streaming again */ + if (flush) { + GST_DEBUG_OBJECT (parse, "sending flush stop"); + gst_pad_push_event (parse->srcpad, gst_event_new_flush_stop ()); + } else { + if (parse->close_segment) + gst_event_unref (parse->close_segment); + + parse->close_segment = gst_event_new_new_segment (TRUE, + parse->segment.rate, parse->segment.format, + parse->segment.accum, parse->segment.last_stop, parse->segment.accum); + + /* keep track of our last_stop */ + seeksegment.accum = parse->segment.last_stop; + + GST_DEBUG_OBJECT (parse, "Created close seg format %d, " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, format, + GST_TIME_ARGS (parse->segment.accum), + GST_TIME_ARGS (parse->segment.last_stop), + GST_TIME_ARGS (parse->segment.accum)); + } + + memcpy (&parse->segment, &seeksegment, sizeof (GstSegment)); + + /* store the newsegment event so it can be sent from the streaming thread. */ + if (parse->pending_segment) + gst_event_unref (parse->pending_segment); + + /* This will be sent later in _loop() */ + parse->pending_segment = + gst_event_new_new_segment (FALSE, parse->segment.rate, + parse->segment.format, + parse->segment.last_stop, stop, parse->segment.last_stop); + + GST_DEBUG_OBJECT (parse, "Created newseg format %d, " + "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT + ", pos = %" GST_TIME_FORMAT, format, + GST_TIME_ARGS (parse->segment.last_stop), + GST_TIME_ARGS (stop), GST_TIME_ARGS (parse->segment.last_stop)); + + /* mark discont if we are going to stream from another position. */ + if (last_stop != parse->segment.last_stop) { + GST_DEBUG_OBJECT (parse, + "mark DISCONT, we did a seek to another position"); + parse->priv->discont = TRUE; + } + + /* Start streaming thread if paused */ + gst_pad_start_task (parse->sinkpad, + (GstTaskFunction) gst_base_parse_loop, parse->sinkpad); + + GST_PAD_STREAM_UNLOCK (parse->sinkpad); + } else { + GstEvent *new_event; + /* The only thing we need to do in PUSH-mode is to send the + seek event (in bytes) to upstream. Segment / flush handling happens + in corresponding src event handlers */ + GST_DEBUG_OBJECT (parse, "seek in PUSH mode"); + new_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flush, + GST_SEEK_TYPE_SET, seekpos, stop_type, stop); + + res = gst_pad_push_event (parse->sinkpad, new_event); + } + +done: + return res; + + /* ERRORS */ +negative_rate: + { + GST_DEBUG_OBJECT (parse, "negative playback rates are not supported yet."); + res = FALSE; + goto done; + } +wrong_type: + { + GST_DEBUG_OBJECT (parse, "unsupported seek type."); + res = FALSE; + goto done; + } +} + + +/** + * gst_base_parse_sink_setcaps: + * @pad: #GstPad. + * @caps: #GstCaps. + * + * Returns: TRUE if caps were accepted. + */ +static gboolean +gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseParse *parse; + GstBaseParseClass *klass; + gboolean res = TRUE; + + gchar *caps_str = gst_caps_to_string (caps); + g_free (caps_str); + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "setcaps: %s", caps_str); + + if (klass->set_sink_caps) + res = klass->set_sink_caps (parse, caps); + + parse->negotiated = res; + gst_object_unref (parse); + return gst_pad_set_caps (pad, caps); +} diff --git a/gst/amrparse/gstbaseparse.h b/gst/amrparse/gstbaseparse.h new file mode 100644 index 0000000000..a2856cc780 --- /dev/null +++ b/gst/amrparse/gstbaseparse.h @@ -0,0 +1,237 @@ +/* GStreamer + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost + * + * 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. + */ + +#ifndef __GST_BASE_PARSE_H__ +#define __GST_BASE_PARSE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_BASE_PARSE (gst_base_parse_get_type()) +#define GST_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_PARSE,GstBaseParse)) +#define GST_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_PARSE,GstBaseParseClass)) +#define GST_BASE_PARSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_BASE_PARSE,GstBaseParseClass)) +#define GST_IS_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_PARSE)) +#define GST_IS_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_PARSE)) +#define GST_BASE_PARSE_CAST(obj) ((GstBaseParse *)(obj)) + +/** + * GST_BASE_PARSE_SINK_NAME: + * + * the name of the templates for the sink pad + */ +#define GST_BASE_PARSE_SINK_NAME "sink" +/** + * GST_BASE_PARSE_SRC_NAME: + * + * the name of the templates for the source pad + */ +#define GST_BASE_PARSE_SRC_NAME "src" + +/** + * GST_BASE_PARSE_SRC_PAD: + * @obj: base parse instance + * + * Gives the pointer to the source #GstPad object of the element. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_SRC_PAD(obj) (GST_BASE_PARSE_CAST (obj)->srcpad) + +/** + * GST_BASE_PARSE_SINK_PAD: + * @obj: base parse instance + * + * Gives the pointer to the sink #GstPad object of the element. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_SINK_PAD(obj) (GST_BASE_PARSE_CAST (obj)->sinkpad) + +/** + * GST_BASE_PARSE_FLOW_DROPPED: + * + * A #GstFlowReturn that can be returned from parse_frame to + * indicate that no output buffer was generated. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS + +/** + * GST_BASE_PARSE_LOCK: + * @obj: base parse instance + * + * Obtain a lock to protect the parse function from concurrent access. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_LOCK(obj) g_mutex_lock (GST_BASE_PARSE_CAST (obj)->parse_lock) + +/** + * GST_BASE_PARSE_UNLOCK: + * @obj: base parse instance + * + * Release the lock that protects the parse function from concurrent access. + * + * Since: 0.10.x + */ +#define GST_BASE_PARSE_UNLOCK(obj) g_mutex_unlock (GST_BASE_PARSE_CAST (obj)->parse_lock) + +typedef struct _GstBaseParse GstBaseParse; +typedef struct _GstBaseParseClass GstBaseParseClass; +typedef struct _GstBaseParsePrivate GstBaseParsePrivate; +typedef struct _GstBaseParseClassPrivate GstBaseParseClassPrivate; + +/** + * GstBaseParse: + * @element: the parent element. + * + * The opaque #GstBaseParse data structure. + */ +struct _GstBaseParse { + GstElement element; + GstAdapter *adapter; + + /*< protected >*/ + /* source and sink pads */ + GstPad *sinkpad; + GstPad *srcpad; + + /* MT-protected (with STREAM_LOCK) */ + GstSegment segment; + + /* Newsegment event to be sent after SEEK */ + GstEvent *pending_segment; + + /* Segment event that closes the running segment prior to SEEK */ + GstEvent *close_segment; + + /* Caps nego done already? */ + gboolean negotiated; + + GMutex *parse_lock; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; + GstBaseParsePrivate *priv; +}; + +/** + * GstBaseParseClass: + * @start: Optional. + * Called when the element starts processing. + * Allows opening external resources. + * @stop: Optional. + * Called when the element stops processing. + * Allows closing external resources. + * @set_sink_caps: allows the subclass to be notified of the actual caps set. + * @check_valid_frame: Check if the given piece of data contains a valid + * frame. + * @parse_frame: Parse the already checked frame. Subclass need to + * set the buffer timestamp, duration, caps and possibly + * other necessary metadata. This is called with srcpad's + * STREAM_LOCK held. + * @convert: Optional. + * Convert between formats. + * @find_frame: Optional. + * Finds a frame. Gets a position passed and should return + * TRUE and the offset in bytes where this position is. + * Will only be called in pull mode and the subclass can pull + * whatever it wants from upstream. If not implemented, + * the base class will implement it by calling + * @check_valid_frame and @parse_frame to find the wanted + * frame and build a seek table. + * @event: Optional. + * Event handler on the sink pad. This function should return + * TRUE if the event was handled and can be dropped. + * @src_event: Optional. + * Event handler on the source pad. Should return TRUE + * if the event was handled and can be dropped. + * @is_seekable: Optional. + * Subclass can override this if it wants to control the + * seekability of the stream. Otherwise the element assumes + * that stream is always seekable. + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At minimum @check_valid_frame and @parse_frame needs to be + * overridden. + */ +struct _GstBaseParseClass { + GstElementClass parent_class; + + /*< public >*/ + /* virtual methods for subclasses */ + + gboolean (*start) (GstBaseParse *parse); + + gboolean (*stop) (GstBaseParse *parse); + + gboolean (*set_sink_caps) (GstBaseParse *parse, + GstCaps *caps); + + gboolean (*check_valid_frame) (GstBaseParse *parse, + GstBuffer *buffer, + guint *framesize, + gint *skipsize); + + GstFlowReturn (*parse_frame) (GstBaseParse *parse, + GstBuffer *buffer); + + gboolean (*convert) (GstBaseParse * parse, + GstFormat src_format, + gint64 src_value, + GstFormat dest_format, + gint64 * dest_value); + + gboolean (*find_frame) (GstBaseParse *parse, + GstFormat src_format, + gint64 src_value, + gint64 * dest_value); + + gboolean (*event) (GstBaseParse *parse, + GstEvent *event); + + gboolean (*src_event) (GstBaseParse *parse, + GstEvent *event); + + gboolean (*is_seekable) (GstBaseParse *parse); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; + GstBaseParseClassPrivate *priv; +}; + +GType gst_base_parse_get_type (void); + + +void gst_base_parse_set_duration (GstBaseParse *parse, + GstFormat fmt, + gint64 duration); + +void gst_base_parse_set_min_frame_size (GstBaseParse *parse, + guint min_size); + +G_END_DECLS + +#endif /* __GST_BASE_PARSE_H__ */