/* 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, &params);

    /* 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);

  in_size = gst_buffer_get_size (frame->buffer);

  /* 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;
}
