mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 04:00:37 +00:00
790 lines
29 KiB
C
790 lines
29 KiB
C
/* GStreamer
|
|
* Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gstrawbaseparse
|
|
* @short_description: Base class for raw media data parsers
|
|
*
|
|
* This base class is for parsers which read raw media data and output
|
|
* timestamped buffers with an integer number of frames inside.
|
|
*
|
|
* The format of the raw media data is specified in one of two ways: either,
|
|
* the information from the sink pad's caps is taken, or the information from
|
|
* the properties is used (this is chosen by the use-sink-caps property).
|
|
* These two ways are internally referred to as "configurations". The configuration
|
|
* that receives its information from the sink pad's caps is called the
|
|
* "sink caps configuration", while the one that depends on the information from
|
|
* the properties is the "properties configuration". Configurations have a
|
|
* "readiness". A configuration is "ready" when it contains valid information.
|
|
* For example, with an audio parser, a configuration is not ready unless it
|
|
* contains a valid sample rate, sample format, and channel count.
|
|
*
|
|
* The properties configuration must always be ready, even right from the start.
|
|
* Subclasses must ensure this. The underlying reason is that properties have valid
|
|
* values right from the start, and with the properties configuration, there is
|
|
* nothing that readies it before actual data is sent (unlike with the sink caps
|
|
* configuration, where a sink caps event will ready it before data is pushed
|
|
* downstream).
|
|
*
|
|
* It is possible to switch between the configurations during a stream by
|
|
* setting the use-sink-caps property. Subclasses typically allow for updating the
|
|
* properties configuration during a stream by setting the various properties
|
|
* (like sample-rate for a raw audio parser).
|
|
* In these cases, the parser will produce a new CAPS event and push it downstream
|
|
* to announce the caps for the new configuration. This also happens if the sink
|
|
* caps change.
|
|
*
|
|
* A common mistake when trying to parse raw data with no input caps (for example,
|
|
* a file with raw PCM samples when using rawaudioparse) is to forget to set the
|
|
* use-sink-caps property to FALSE. In this case, the parser will report an error
|
|
* when it tries to access the current configuration (because then the sink caps
|
|
* configuration will be the current one and it will not contain valid values
|
|
* since no sink caps were seen at this point).
|
|
*
|
|
* Subclasses must ensure that the properties configuration is the default one.
|
|
*
|
|
* The sink caps configuration is mostly useful with push-based sources, because these
|
|
* will produce caps events and send them downstream. With pull-based sources, it is
|
|
* possible that this doesn't happen. Since the sink caps configuration requires a caps
|
|
* event to arrive at the sinkpad, this will cause the parser to fail then.
|
|
*
|
|
* The base class identifies the configurations by means of the GstRawAudioParseConfig
|
|
* enum. It instructs the subclass to switch between configurations this way, and
|
|
* also requests information about the current configuration, a configuration's
|
|
* frame size, its readiness, etc. Subclasses are not required to use any particular
|
|
* structure for the configuration implementations.
|
|
*
|
|
* Use the GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK and GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK
|
|
* macros to protect configuration modifications.
|
|
*
|
|
* <listitem>
|
|
* <itemizedlist>
|
|
* <title>Summary of the subclass requirements</title>
|
|
* <listitem><para>
|
|
* Sink caps and properties configurations must both be
|
|
* implemented and supported. It must also be ensured that there is a
|
|
* "current" configuration.
|
|
* </para></listitem>
|
|
* Modifications to the configurations must be protected with the
|
|
* GstRawBaseParse lock. This is typically necessary when the
|
|
* properties configuration is modified by setting new property values.
|
|
* (Note that the lock is held during *all* vfunc calls.)
|
|
* <listitem><para>
|
|
* If the properties configuration is updated (typically by
|
|
* setting new property values), gst_raw_base_parse_invalidate_src_caps()
|
|
* must be called if the properties config is the current one. This is
|
|
* necessary to ensure that GstBaseParse pushes a new caps event downstream
|
|
* which contains caps from the updated configuration.
|
|
* </para></listitem>
|
|
* <listitem><para>
|
|
* In case there are bytes in each frame that aren't part of the actual
|
|
* payload, the get_overhead_size() vfunc must be defined, and the
|
|
* @get_config_frame_size() vfunc must return a frame size that includes
|
|
* the number of non-payload bytes (= the overhead). Otherwise, the
|
|
* timestamps will incorrectly include the overhead bytes.
|
|
* </para></listitem>
|
|
* </listitem>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include "gstrawbaseparse.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (raw_base_parse_debug);
|
|
#define GST_CAT_DEFAULT raw_base_parse_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_USE_SINK_CAPS
|
|
};
|
|
|
|
#define DEFAULT_USE_SINK_CAPS FALSE
|
|
#define INITIAL_PARSER_CONFIG \
|
|
((DEFAULT_USE_SINK_CAPS) ? GST_RAW_BASE_PARSE_CONFIG_SINKCAPS : \
|
|
GST_RAW_BASE_PARSE_CONFIG_PROPERTIES)
|
|
|
|
#define gst_raw_base_parse_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE (GstRawBaseParse, gst_raw_base_parse,
|
|
GST_TYPE_BASE_PARSE);
|
|
|
|
static void gst_raw_base_parse_finalize (GObject * object);
|
|
static void gst_raw_base_parse_set_property (GObject * object, guint prop_id,
|
|
GValue const *value, GParamSpec * pspec);
|
|
static void gst_raw_base_parse_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static gboolean gst_raw_base_parse_start (GstBaseParse * parse);
|
|
static gboolean gst_raw_base_parse_stop (GstBaseParse * parse);
|
|
static gboolean gst_raw_base_parse_set_sink_caps (GstBaseParse * parse,
|
|
GstCaps * caps);
|
|
static GstFlowReturn gst_raw_base_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize);
|
|
static gboolean gst_raw_base_parse_convert (GstBaseParse * parse,
|
|
GstFormat src_format, gint64 src_value, GstFormat dest_format,
|
|
gint64 * dest_value);
|
|
|
|
static gboolean gst_raw_base_parse_is_using_sink_caps (GstRawBaseParse *
|
|
raw_base_parse);
|
|
static gboolean gst_raw_base_parse_is_gstformat_supported (GstRawBaseParse *
|
|
raw_base_parse, GstFormat format);
|
|
|
|
static void
|
|
gst_raw_base_parse_class_init (GstRawBaseParseClass * klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
GstBaseParseClass *baseparse_class;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (raw_base_parse_debug, "rawbaseparse", 0,
|
|
"raw base parse class");
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
baseparse_class = GST_BASE_PARSE_CLASS (klass);
|
|
|
|
object_class->finalize = GST_DEBUG_FUNCPTR (gst_raw_base_parse_finalize);
|
|
object_class->set_property =
|
|
GST_DEBUG_FUNCPTR (gst_raw_base_parse_set_property);
|
|
object_class->get_property =
|
|
GST_DEBUG_FUNCPTR (gst_raw_base_parse_get_property);
|
|
|
|
baseparse_class->start = GST_DEBUG_FUNCPTR (gst_raw_base_parse_start);
|
|
baseparse_class->stop = GST_DEBUG_FUNCPTR (gst_raw_base_parse_stop);
|
|
baseparse_class->set_sink_caps =
|
|
GST_DEBUG_FUNCPTR (gst_raw_base_parse_set_sink_caps);
|
|
baseparse_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_raw_base_parse_handle_frame);
|
|
baseparse_class->convert = GST_DEBUG_FUNCPTR (gst_raw_base_parse_convert);
|
|
|
|
/**
|
|
* GstRawBaseParse::use-sink-caps:
|
|
*
|
|
* Use sink caps configuration. If set to false, the parser
|
|
* will use the properties configuration instead. It is possible
|
|
* to switch between these during playback.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_USE_SINK_CAPS,
|
|
g_param_spec_boolean ("use-sink-caps",
|
|
"Use sink caps",
|
|
"Use the sink caps for defining the output format",
|
|
DEFAULT_USE_SINK_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
|
|
);
|
|
}
|
|
|
|
static void
|
|
gst_raw_base_parse_init (GstRawBaseParse * raw_base_parse)
|
|
{
|
|
raw_base_parse->src_caps_set = FALSE;
|
|
g_mutex_init (&(raw_base_parse->config_mutex));
|
|
}
|
|
|
|
static void
|
|
gst_raw_base_parse_finalize (GObject * object)
|
|
{
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (object);
|
|
|
|
g_mutex_clear (&(raw_base_parse->config_mutex));
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_raw_base_parse_set_property (GObject * object, guint prop_id,
|
|
GValue const *value, GParamSpec * pspec)
|
|
{
|
|
GstBaseParse *base_parse = GST_BASE_PARSE (object);
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (object);
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (object);
|
|
|
|
g_assert (klass->is_config_ready);
|
|
g_assert (klass->set_current_config);
|
|
|
|
switch (prop_id) {
|
|
case PROP_USE_SINK_CAPS:
|
|
{
|
|
gboolean new_state, cur_state;
|
|
GstRawBaseParseConfig new_config;
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object);
|
|
|
|
/* Check to ensure nothing is done if the value stays the same */
|
|
new_state = g_value_get_boolean (value);
|
|
cur_state = gst_raw_base_parse_is_using_sink_caps (raw_base_parse);
|
|
if (new_state == cur_state) {
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object);
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (raw_base_parse, "switching to %s config",
|
|
new_state ? "sink caps" : "properties");
|
|
new_config =
|
|
new_state ? GST_RAW_BASE_PARSE_CONFIG_SINKCAPS :
|
|
GST_RAW_BASE_PARSE_CONFIG_PROPERTIES;
|
|
|
|
if (!klass->set_current_config (raw_base_parse, new_config)) {
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object);
|
|
GST_ELEMENT_ERROR (raw_base_parse, STREAM, FAILED,
|
|
("could not set new current config"), ("use-sink-caps property: %d",
|
|
new_state));
|
|
break;
|
|
}
|
|
|
|
/* Update the minimum frame size if the config is ready. This ensures that
|
|
* the next buffer that is passed to handle_frame contains complete frames.
|
|
* If the current config is the properties config, then it will always be
|
|
* ready, and its frame size will be valid. Ensure that the baseparse minimum
|
|
* frame size is set properly then.
|
|
* If the current config is the sink caps config, then it will initially not
|
|
* be ready until the sink caps are set, so the minimum frame size cannot be
|
|
* set right here. However, since the caps always come in *before* the actual
|
|
* data, the config will be readied in the set_sink_caps function, and be ready
|
|
* by the time handle_frame is called. There, the minimum frame size is set as
|
|
* well. */
|
|
if (klass->is_config_ready (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT)) {
|
|
gsize frame_size = klass->get_config_frame_size (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT);
|
|
gst_base_parse_set_min_frame_size (base_parse, frame_size);
|
|
}
|
|
|
|
/* Since the current config was switched, the source caps change. Ensure the
|
|
* new caps are pushed downstream by setting src_caps_set to FALSE: This way,
|
|
* the next handle_frame call will take care of that. */
|
|
raw_base_parse->src_caps_set = FALSE;
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_raw_base_parse_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_USE_SINK_CAPS:
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (object);
|
|
g_value_set_boolean (value,
|
|
gst_raw_base_parse_is_using_sink_caps (raw_base_parse));
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (object);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_start (GstBaseParse * parse)
|
|
{
|
|
GstBaseParse *base_parse = GST_BASE_PARSE (parse);
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (parse);
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (parse);
|
|
|
|
g_assert (klass->set_current_config);
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (raw_base_parse);
|
|
|
|
/* If the config is ready from the start, set the min frame size
|
|
* (this will happen with the properties config) */
|
|
if (klass->is_config_ready (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT)) {
|
|
gsize frame_size = klass->get_config_frame_size (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT);
|
|
gst_base_parse_set_min_frame_size (base_parse, frame_size);
|
|
}
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_stop (GstBaseParse * parse)
|
|
{
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (parse);
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (raw_base_parse);
|
|
raw_base_parse->src_caps_set = FALSE;
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (parse);
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (parse);
|
|
|
|
g_assert (klass->set_config_from_caps);
|
|
g_assert (klass->get_caps_from_config);
|
|
g_assert (klass->get_config_frame_size);
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (raw_base_parse);
|
|
|
|
GST_DEBUG_OBJECT (parse, "getting config from new sink caps");
|
|
|
|
/* Convert the new sink caps to sink caps config. This also
|
|
* readies the config. */
|
|
ret =
|
|
klass->set_config_from_caps (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_SINKCAPS, caps);
|
|
if (!ret) {
|
|
GST_ERROR_OBJECT (raw_base_parse, "could not get config from sink caps");
|
|
goto done;
|
|
}
|
|
|
|
/* If the sink caps config is currently active, push caps downstream,
|
|
* set the minimum frame size (to guarantee that input buffers hold
|
|
* complete frames), and update the src_caps_set flag. If the sink
|
|
* caps config isn't the currently active config, just exit, since in
|
|
* that case, the caps will always be pushed downstream in handle_frame. */
|
|
if (gst_raw_base_parse_is_using_sink_caps (raw_base_parse)) {
|
|
GstCaps *new_src_caps;
|
|
gsize frame_size;
|
|
|
|
GST_DEBUG_OBJECT (parse,
|
|
"sink caps config is the current one; trying to push new caps downstream");
|
|
|
|
/* Convert back to caps. The caps may have changed, for example
|
|
* audio/x-unaligned-raw may have been replaced with audio/x-raw.
|
|
* (Also, this keeps the behavior in sync with that of the block
|
|
* in handle_frame that pushes caps downstream if not done already.) */
|
|
if (!klass->get_caps_from_config (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT, &new_src_caps)) {
|
|
GST_ERROR_OBJECT (raw_base_parse,
|
|
"could not get src caps from current config");
|
|
goto done;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (raw_base_parse,
|
|
"got new sink caps; updating src caps to %" GST_PTR_FORMAT,
|
|
(gpointer) new_src_caps);
|
|
|
|
frame_size =
|
|
klass->get_config_frame_size (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT);
|
|
gst_base_parse_set_min_frame_size (parse, frame_size);
|
|
|
|
raw_base_parse->src_caps_set = TRUE;
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
|
|
/* Push caps outside of the lock */
|
|
gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (raw_base_parse),
|
|
gst_event_new_caps (new_src_caps)
|
|
);
|
|
|
|
gst_caps_unref (new_src_caps);
|
|
} else {
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_raw_base_parse_align_buffer (GstRawBaseParse * raw_base_parse,
|
|
gsize alignment, GstBuffer * buffer, gsize out_size)
|
|
{
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
|
|
if (map.size < sizeof (guintptr)) {
|
|
gst_buffer_unmap (buffer, &map);
|
|
return NULL;
|
|
}
|
|
|
|
if (((guintptr) map.data) & (alignment - 1)) {
|
|
GstBuffer *new_buffer;
|
|
GstAllocationParams params = { 0, alignment - 1, 0, 0, };
|
|
|
|
new_buffer = gst_buffer_new_allocate (NULL, out_size, ¶ms);
|
|
|
|
/* Copy data "by hand", so ensure alignment is kept: */
|
|
gst_buffer_fill (new_buffer, 0, map.data, out_size);
|
|
|
|
gst_buffer_copy_into (new_buffer, buffer, GST_BUFFER_COPY_METADATA, 0,
|
|
out_size);
|
|
GST_DEBUG_OBJECT (raw_base_parse,
|
|
"We want output aligned on %" G_GSIZE_FORMAT ", reallocated",
|
|
alignment);
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
|
|
return new_buffer;
|
|
}
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_raw_base_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize)
|
|
{
|
|
gsize in_size, out_size;
|
|
guint frame_size;
|
|
guint num_out_frames;
|
|
gsize units_n, units_d;
|
|
guint64 buffer_duration;
|
|
GstFlowReturn flow_ret = GST_FLOW_OK;
|
|
GstEvent *new_caps_event = NULL;
|
|
gint alignment;
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (parse);
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (parse);
|
|
|
|
g_assert (klass->is_config_ready);
|
|
g_assert (klass->get_caps_from_config);
|
|
g_assert (klass->get_config_frame_size);
|
|
g_assert (klass->get_units_per_second);
|
|
|
|
/* We never skip any bytes this way. Instead, subclass takes care
|
|
* of skipping any overhead (necessary, since the way it needs to
|
|
* be skipped is completely subclass specific). */
|
|
*skipsize = 0;
|
|
|
|
/* The operations below access the current config. Protect
|
|
* against race conditions by using the object lock. */
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (raw_base_parse);
|
|
|
|
/* If the source pad caps haven't been set yet, or need to be
|
|
* set again, do so now, BEFORE any buffers are pushed out */
|
|
if (G_UNLIKELY (!raw_base_parse->src_caps_set)) {
|
|
GstCaps *new_src_caps;
|
|
|
|
if (G_UNLIKELY (!klass->is_config_ready (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT))) {
|
|
/* The current configuration is not ready. No caps can be
|
|
* generated out of it.
|
|
* The most likely reason for this is that the sink caps config
|
|
* is the current one and no valid sink caps have been pushed
|
|
* by upstream. Report the problem and exit. */
|
|
|
|
if (gst_raw_base_parse_is_using_sink_caps (raw_base_parse)) {
|
|
goto config_not_ready;
|
|
} else {
|
|
/* This should not be reached if the property config is active */
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (parse,
|
|
"setting src caps since this has not been done yet");
|
|
|
|
/* Convert the current config to a caps structure to
|
|
* inform downstream about the new format */
|
|
if (!klass->get_caps_from_config (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT, &new_src_caps)) {
|
|
GST_ERROR_OBJECT (raw_base_parse,
|
|
"could not get src caps from current config");
|
|
flow_ret = GST_FLOW_NOT_NEGOTIATED;
|
|
goto error_locked;
|
|
}
|
|
|
|
new_caps_event = gst_event_new_caps (new_src_caps);
|
|
gst_caps_unref (new_src_caps);
|
|
|
|
raw_base_parse->src_caps_set = TRUE;
|
|
}
|
|
|
|
frame_size =
|
|
klass->get_config_frame_size (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT);
|
|
if (frame_size <= 0) {
|
|
GST_ELEMENT_ERROR (parse, STREAM, FORMAT,
|
|
("Non strictly positive frame size"), (NULL));
|
|
flow_ret = GST_FLOW_ERROR;
|
|
goto error_locked;
|
|
}
|
|
|
|
in_size = gst_buffer_get_size (frame->buffer);
|
|
|
|
/* drop incomplete frame at the end of the stream
|
|
* https://bugzilla.gnome.org/show_bug.cgi?id=773666
|
|
*/
|
|
if (GST_BASE_PARSE_DRAINING (parse) && in_size < frame_size) {
|
|
GST_DEBUG_OBJECT (raw_base_parse,
|
|
"Dropping %" G_GSIZE_FORMAT " bytes at EOS", in_size);
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP;
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
|
|
return gst_base_parse_finish_frame (parse, frame, in_size);
|
|
}
|
|
|
|
/* gst_base_parse_set_min_frame_size() is called when the current
|
|
* configuration changes and the change affects the frame size. This
|
|
* means that a buffer must contain at least as many bytes as indicated
|
|
* by the frame size. If there are fewer inside an error occurred;
|
|
* either something in the parser went wrong, or the min frame size
|
|
* wasn't updated properly. */
|
|
g_assert (in_size >= frame_size);
|
|
|
|
/* Determine how many complete frames would fit in the input buffer.
|
|
* Then check if this amount exceeds the maximum number of frames
|
|
* as indicated by the subclass. */
|
|
num_out_frames = (in_size / frame_size);
|
|
if (klass->get_max_frames_per_buffer) {
|
|
guint max_num_out_frames = klass->get_max_frames_per_buffer (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT);
|
|
num_out_frames = MIN (num_out_frames, max_num_out_frames);
|
|
}
|
|
|
|
/* Ensure that the size of the buffers that get pushed downstream
|
|
* is always an integer multiple of the frame size to prevent cases
|
|
* where downstream gets buffers with incomplete frames. */
|
|
out_size = num_out_frames * frame_size;
|
|
|
|
/* Set the overhead size to ensure that timestamping excludes these
|
|
* extra overhead bytes. */
|
|
frame->overhead =
|
|
klass->get_overhead_size ? klass->get_overhead_size (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT) : 0;
|
|
|
|
g_assert (out_size >= (guint) (frame->overhead));
|
|
out_size -= frame->overhead;
|
|
|
|
GST_LOG_OBJECT (raw_base_parse,
|
|
"%" G_GSIZE_FORMAT " bytes input %" G_GSIZE_FORMAT
|
|
" bytes output (%u frame(s)) %d bytes overhead", in_size, out_size,
|
|
num_out_frames, frame->overhead);
|
|
|
|
/* Calculate buffer duration */
|
|
klass->get_units_per_second (raw_base_parse, GST_FORMAT_BYTES,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT, &units_n, &units_d);
|
|
if (units_n == 0 || units_d == 0)
|
|
buffer_duration = GST_CLOCK_TIME_NONE;
|
|
else
|
|
buffer_duration =
|
|
gst_util_uint64_scale (out_size, GST_SECOND * units_d, units_n);
|
|
|
|
if (klass->process) {
|
|
GstBuffer *processed_data = NULL;
|
|
|
|
if (!klass->process (raw_base_parse, GST_RAW_BASE_PARSE_CONFIG_CURRENT,
|
|
frame->buffer, in_size, out_size, &processed_data))
|
|
goto process_error;
|
|
|
|
frame->out_buffer = processed_data;
|
|
} else {
|
|
frame->out_buffer = NULL;
|
|
}
|
|
|
|
if (klass->get_alignment
|
|
&& (alignment =
|
|
klass->get_alignment (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT)) != 1) {
|
|
GstBuffer *aligned_buffer;
|
|
|
|
aligned_buffer =
|
|
gst_raw_base_parse_align_buffer (raw_base_parse, alignment,
|
|
frame->out_buffer ? frame->out_buffer : frame->buffer, out_size);
|
|
|
|
if (aligned_buffer) {
|
|
if (frame->out_buffer)
|
|
gst_buffer_unref (frame->out_buffer);
|
|
frame->out_buffer = aligned_buffer;
|
|
}
|
|
}
|
|
|
|
/* Set the duration of the output buffer, or if none exists, of
|
|
* the input buffer. Do this after the process() call, since in
|
|
* case out_buffer is set, the subclass has created a new buffer.
|
|
* Instead of requiring subclasses to set the duration (which
|
|
* anyway must always be buffer_duration), let's do it here. */
|
|
if (frame->out_buffer != NULL)
|
|
GST_BUFFER_DURATION (frame->out_buffer) = buffer_duration;
|
|
else
|
|
GST_BUFFER_DURATION (frame->buffer) = buffer_duration;
|
|
|
|
/* Access to the current config is not needed in subsequent
|
|
* operations, so the lock can be released */
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
|
|
/* If any new caps have to be pushed downstrean, do so
|
|
* *before* the frame is finished */
|
|
if (G_UNLIKELY (new_caps_event != NULL)) {
|
|
gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (raw_base_parse),
|
|
new_caps_event);
|
|
new_caps_event = NULL;
|
|
}
|
|
|
|
flow_ret =
|
|
gst_base_parse_finish_frame (parse, frame, out_size + frame->overhead);
|
|
|
|
return flow_ret;
|
|
|
|
config_not_ready:
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
GST_ELEMENT_ERROR (parse, STREAM, FORMAT,
|
|
("sink caps config is the current config, and it is not ready -"
|
|
"upstream may not have pushed a caps event yet"), (NULL));
|
|
flow_ret = GST_FLOW_ERROR;
|
|
goto error_end;
|
|
|
|
process_error:
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
GST_ELEMENT_ERROR (parse, STREAM, DECODE, ("could not process data"), (NULL));
|
|
flow_ret = GST_FLOW_ERROR;
|
|
goto error_end;
|
|
|
|
error_locked:
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
goto error_end;
|
|
|
|
error_end:
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP;
|
|
if (new_caps_event != NULL)
|
|
gst_event_unref (new_caps_event);
|
|
return flow_ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_convert (GstBaseParse * parse, GstFormat src_format,
|
|
gint64 src_value, GstFormat dest_format, gint64 * dest_value)
|
|
{
|
|
GstRawBaseParse *raw_base_parse = GST_RAW_BASE_PARSE (parse);
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (parse);
|
|
gboolean ret = TRUE;
|
|
gsize units_n, units_d;
|
|
|
|
g_assert (klass->is_config_ready);
|
|
g_assert (klass->get_units_per_second);
|
|
|
|
/* The operations below access the current config. Protect
|
|
* against race conditions by using the object lock. */
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK (raw_base_parse);
|
|
|
|
if (!klass->is_config_ready (raw_base_parse,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT)) {
|
|
if (gst_raw_base_parse_is_using_sink_caps (raw_base_parse)) {
|
|
goto config_not_ready;
|
|
} else {
|
|
/* This should not be reached if the property config is active */
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
if (G_UNLIKELY (src_format == dest_format)) {
|
|
*dest_value = src_value;
|
|
} else if ((src_format == GST_FORMAT_TIME || dest_format == GST_FORMAT_TIME)
|
|
&& gst_raw_base_parse_is_gstformat_supported (raw_base_parse, src_format)
|
|
&& gst_raw_base_parse_is_gstformat_supported (raw_base_parse, src_format)) {
|
|
/* Perform conversions here if either the src or dest format
|
|
* are GST_FORMAT_TIME and the other format is supported by
|
|
* the subclass. This is because we perform TIME<->non-TIME
|
|
* conversions here. Typically, subclasses only support
|
|
* BYTES and DEFAULT formats. */
|
|
|
|
if (src_format == GST_FORMAT_TIME) {
|
|
/* The source format is time, so perform a TIME -> non-TIME conversion */
|
|
klass->get_units_per_second (raw_base_parse, dest_format,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT, &units_n, &units_d);
|
|
*dest_value = (units_n == 0
|
|
|| units_d == 0) ? src_value : gst_util_uint64_scale (src_value,
|
|
units_n, GST_SECOND * units_d);
|
|
} else {
|
|
/* The dest format is time, so perform a non-TIME -> TIME conversion */
|
|
klass->get_units_per_second (raw_base_parse, src_format,
|
|
GST_RAW_BASE_PARSE_CONFIG_CURRENT, &units_n, &units_d);
|
|
*dest_value = (units_n == 0
|
|
|| units_d == 0) ? src_value : gst_util_uint64_scale (src_value,
|
|
GST_SECOND * units_d, units_n);
|
|
}
|
|
} else {
|
|
/* Fallback for other conversions */
|
|
ret =
|
|
gst_base_parse_convert_default (parse, src_format, src_value,
|
|
dest_format, dest_value);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (parse,
|
|
"converted %s -> %s %" G_GINT64_FORMAT " -> %" GST_TIME_FORMAT,
|
|
gst_format_get_name (src_format), gst_format_get_name (dest_format),
|
|
src_value, GST_TIME_ARGS (*dest_value));
|
|
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
return ret;
|
|
|
|
config_not_ready:
|
|
GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK (raw_base_parse);
|
|
GST_ELEMENT_ERROR (parse, STREAM, FORMAT,
|
|
("sink caps config is the current config, and it is not ready - "
|
|
"upstream may not have pushed a caps event yet"), (NULL));
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_is_using_sink_caps (GstRawBaseParse * raw_base_parse)
|
|
{
|
|
/* must be called with lock */
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (raw_base_parse);
|
|
g_assert (klass->get_current_config);
|
|
return klass->get_current_config (raw_base_parse) ==
|
|
GST_RAW_BASE_PARSE_CONFIG_SINKCAPS;
|
|
}
|
|
|
|
static gboolean
|
|
gst_raw_base_parse_is_gstformat_supported (GstRawBaseParse * raw_base_parse,
|
|
GstFormat format)
|
|
{
|
|
/* must be called with lock */
|
|
GstRawBaseParseClass *klass = GST_RAW_BASE_PARSE_GET_CLASS (raw_base_parse);
|
|
g_assert (klass->is_unit_format_supported);
|
|
return klass->is_unit_format_supported (raw_base_parse, format);
|
|
}
|
|
|
|
/**
|
|
* gst_raw_base_parse_invalidate_src_caps:
|
|
* @raw_base_parse: a #GstRawBaseParse instance
|
|
*
|
|
* Flags the current source caps as invalid. Before the next downstream
|
|
* buffer push, @get_caps_from_config is called, and the created caps are
|
|
* pushed downstream in a new caps event, This is used if for example the
|
|
* properties configuration is modified in the subclass.
|
|
*
|
|
* Note that this must be called with the parser lock held. Use the
|
|
* GST_RAW_BASE_PARSE_CONFIG_MUTEX_LOCK() and GST_RAW_BASE_PARSE_CONFIG_MUTEX_UNLOCK()
|
|
* macros for this purpose.
|
|
*/
|
|
void
|
|
gst_raw_base_parse_invalidate_src_caps (GstRawBaseParse * raw_base_parse)
|
|
{
|
|
/* must be called with lock */
|
|
g_assert (raw_base_parse != NULL);
|
|
raw_base_parse->src_caps_set = FALSE;
|
|
}
|