/* GStreamer
 *
 * Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>.
 * Copyright (C) 2009 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
 * Copyright (C) 2009 Nokia Corporation. All rights reserved.
 *   Contact: Stefan Kost <stefan.kost@nokia.com>
 *
 * 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:element-flacparse
 * @see_also: flacdec, oggdemux, vorbisparse
 *
 * The flacparse element will parse the header packets of the FLAC
 * stream and put them as the streamheader in the caps. This is used in the
 * multifdsink case where you want to stream live FLAC streams to multiple
 * clients, each client has to receive the streamheaders first before they can
 * consume the FLAC packets.
 *
 * This element also makes sure that the buffers that it pushes out are properly
 * timestamped and that their offset and offset_end are set. The buffers that
 * flacparse outputs have all of the metadata that oggmux expects to receive,
 * which allows you to (for example) remux an ogg/flac or convert a native FLAC
 * format file to an ogg bitstream.
 *
 * <refsect2>
 * <title>Example pipelines</title>
 * |[
 * gst-launch -v filesrc location=sine.flac ! flacparse ! identity \
 *            ! oggmux ! filesink location=sine-remuxed.ogg
 * ]| This pipeline converts a native FLAC format file to an ogg bitstream.
 * It also illustrates that the streamheader is set in the caps, and that each
 * buffer has the timestamp, duration, offset, and offset_end set.
 * </refsect2>
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstflacparse.h"

#include <string.h>
#include <gst/tag/tag.h>
#include <gst/audio/audio.h>

#include <gst/base/gstbitreader.h>
#include <gst/base/gstbytereader.h>

GST_DEBUG_CATEGORY_STATIC (flacparse_debug);
#define GST_CAT_DEFAULT flacparse_debug

/* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */
static const guint8 crc8_table[256] = {
  0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
  0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
  0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
  0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
  0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
  0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
  0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
  0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
  0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
  0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
  0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
  0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
  0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
  0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
  0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
  0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
  0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
  0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
  0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
  0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
  0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
  0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
  0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
  0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
  0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
  0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
  0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
  0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
  0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
  0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
  0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
  0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
};

static guint8
gst_flac_calculate_crc8 (const guint8 * data, guint length)
{
  guint8 crc = 0;

  while (length--) {
    crc = crc8_table[crc ^ *data];
    ++data;
  }

  return crc;
}

/* CRC-16, poly = x^16 + x^15 + x^2 + x^0, init = 0 */
static const guint16 crc16_table[256] = {
  0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011,
  0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022,
  0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072,
  0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041,
  0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
  0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1,
  0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1,
  0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082,
  0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192,
  0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
  0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1,
  0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2,
  0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151,
  0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162,
  0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
  0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101,
  0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312,
  0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321,
  0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371,
  0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
  0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1,
  0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2,
  0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2,
  0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381,
  0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
  0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2,
  0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2,
  0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1,
  0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252,
  0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
  0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231,
  0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202
};

static guint16
gst_flac_calculate_crc16 (const guint8 * data, guint length)
{
  guint16 crc = 0;

  while (length--) {
    crc = ((crc << 8) ^ crc16_table[(crc >> 8) ^ *data]) & 0xffff;
    data++;
  }

  return crc;
}

enum
{
  PROP_0,
  PROP_CHECK_FRAME_CHECKSUMS
};

#define DEFAULT_CHECK_FRAME_CHECKSUMS FALSE

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-flac, framed = (boolean) true, "
        "channels = (int) [ 1, 8 ], " "rate = (int) [ 1, 655350 ]")
    );

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-flac, framed = (boolean) false")
    );

static void gst_flac_parse_finalize (GObject * object);
static void gst_flac_parse_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_flac_parse_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

static gboolean gst_flac_parse_start (GstBaseParse * parse);
static gboolean gst_flac_parse_stop (GstBaseParse * parse);
static gboolean gst_flac_parse_check_valid_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame, guint * framesize, gint * skipsize);
static GstFlowReturn gst_flac_parse_parse_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame);
static GstFlowReturn gst_flac_parse_pre_push_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame);
static gboolean gst_flac_parse_convert (GstBaseParse * parse,
    GstFormat src_format, gint64 src_value, GstFormat dest_format,
    gint64 * dest_value);

#define gst_flac_parse_parent_class parent_class
G_DEFINE_TYPE (GstFlacParse, gst_flac_parse, GST_TYPE_BASE_PARSE);

static void
gst_flac_parse_class_init (GstFlacParseClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GstBaseParseClass *baseparse_class = GST_BASE_PARSE_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (flacparse_debug, "flacparse", 0,
      "Flac parser element");

  gobject_class->finalize = gst_flac_parse_finalize;
  gobject_class->set_property = gst_flac_parse_set_property;
  gobject_class->get_property = gst_flac_parse_get_property;

  g_object_class_install_property (gobject_class, PROP_CHECK_FRAME_CHECKSUMS,
      g_param_spec_boolean ("check-frame-checksums", "Check Frame Checksums",
          "Check the overall checksums of every frame",
          DEFAULT_CHECK_FRAME_CHECKSUMS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  baseparse_class->start = GST_DEBUG_FUNCPTR (gst_flac_parse_start);
  baseparse_class->stop = GST_DEBUG_FUNCPTR (gst_flac_parse_stop);
  baseparse_class->check_valid_frame =
      GST_DEBUG_FUNCPTR (gst_flac_parse_check_valid_frame);
  baseparse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_flac_parse_parse_frame);
  baseparse_class->pre_push_frame =
      GST_DEBUG_FUNCPTR (gst_flac_parse_pre_push_frame);
  baseparse_class->convert = GST_DEBUG_FUNCPTR (gst_flac_parse_convert);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_factory));

  gst_element_class_set_details_simple (element_class, "FLAC audio parser",
      "Codec/Parser/Audio",
      "Parses audio with the FLAC lossless audio codec",
      "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
}

static void
gst_flac_parse_init (GstFlacParse * flacparse)
{
  flacparse->check_frame_checksums = DEFAULT_CHECK_FRAME_CHECKSUMS;
}

static void
gst_flac_parse_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (object);

  switch (prop_id) {
    case PROP_CHECK_FRAME_CHECKSUMS:
      flacparse->check_frame_checksums = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_flac_parse_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (object);

  switch (prop_id) {
    case PROP_CHECK_FRAME_CHECKSUMS:
      g_value_set_boolean (value, flacparse->check_frame_checksums);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_flac_parse_finalize (GObject * object)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (object);

  if (flacparse->tags) {
    gst_tag_list_free (flacparse->tags);
    flacparse->tags = NULL;
  }

  g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (flacparse->headers);
  flacparse->headers = NULL;

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
gst_flac_parse_start (GstBaseParse * parse)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);

  flacparse->state = GST_FLAC_PARSE_STATE_INIT;
  flacparse->min_blocksize = 0;
  flacparse->max_blocksize = 0;
  flacparse->min_framesize = 0;
  flacparse->max_framesize = 0;

  flacparse->upstream_length = -1;

  flacparse->samplerate = 0;
  flacparse->channels = 0;
  flacparse->bps = 0;
  flacparse->total_samples = 0;

  flacparse->offset = GST_CLOCK_TIME_NONE;
  flacparse->blocking_strategy = 0;
  flacparse->block_size = 0;
  flacparse->sample_number = 0;

  /* "fLaC" marker */
  gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4);

  /* inform baseclass we can come up with ts, based on counters in packets */
  gst_base_parse_set_has_timing_info (GST_BASE_PARSE_CAST (flacparse), TRUE);
  gst_base_parse_set_syncable (GST_BASE_PARSE_CAST (flacparse), TRUE);

  return TRUE;
}

static gboolean
gst_flac_parse_stop (GstBaseParse * parse)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);

  if (flacparse->tags) {
    gst_tag_list_free (flacparse->tags);
    flacparse->tags = NULL;
  }

  g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (flacparse->headers);
  flacparse->headers = NULL;

  return TRUE;
}

static const guint8 sample_size_table[] = { 0, 8, 12, 0, 16, 20, 24, 0 };

static const guint16 blocksize_table[16] = {
  0, 192, 576 << 0, 576 << 1, 576 << 2, 576 << 3, 0, 0,
  256 << 0, 256 << 1, 256 << 2, 256 << 3, 256 << 4, 256 << 5, 256 << 6,
  256 << 7,
};

static const guint32 sample_rate_table[16] = {
  0,
  88200, 176400, 192000,
  8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000,
  0, 0, 0, 0,
};

typedef enum
{
  FRAME_HEADER_VALID,
  FRAME_HEADER_INVALID,
  FRAME_HEADER_MORE_DATA
} FrameHeaderCheckReturn;

static FrameHeaderCheckReturn
gst_flac_parse_frame_header_is_valid (GstFlacParse * flacparse,
    const guint8 * data, guint size, gboolean set, guint16 * block_size_ret)
{
  GstBitReader reader = GST_BIT_READER_INIT (data, size);
  guint8 blocking_strategy;
  guint16 block_size;
  guint32 samplerate = 0;
  guint64 sample_number;
  guint8 channels, bps;
  guint8 tmp = 0;
  guint8 actual_crc, expected_crc = 0;

  /* Skip 14 bit sync code */
  gst_bit_reader_skip_unchecked (&reader, 14);

  /* Must be 0 */
  if (gst_bit_reader_get_bits_uint8_unchecked (&reader, 1) != 0)
    goto error;

  /* 0 == fixed block size, 1 == variable block size */
  blocking_strategy = gst_bit_reader_get_bits_uint8_unchecked (&reader, 1);

  /* block size index, calculation of the real blocksize below */
  block_size = gst_bit_reader_get_bits_uint16_unchecked (&reader, 4);
  if (block_size == 0)
    goto error;

  /* sample rate index, calculation of the real samplerate below */
  samplerate = gst_bit_reader_get_bits_uint16_unchecked (&reader, 4);
  if (samplerate == 0x0f)
    goto error;

  /* channel assignment */
  channels = gst_bit_reader_get_bits_uint8_unchecked (&reader, 4);
  if (channels < 8) {
    channels++;
  } else if (channels <= 10) {
    channels = 2;
  } else if (channels > 10) {
    goto error;
  }
  if (flacparse->channels && flacparse->channels != channels)
    goto error;

  /* bits per sample */
  bps = gst_bit_reader_get_bits_uint8_unchecked (&reader, 3);
  if (bps == 0x03 || bps == 0x07) {
    goto error;
  } else if (bps == 0 && flacparse->bps == 0) {
    goto need_streaminfo;
  }
  bps = sample_size_table[bps];
  if (flacparse->bps && bps != flacparse->bps)
    goto error;

  /* reserved, must be 0 */
  if (gst_bit_reader_get_bits_uint8_unchecked (&reader, 1) != 0)
    goto error;

  /* read "utf8" encoded sample/frame number */
  {
    gint len = 0;

    len = gst_bit_reader_get_bits_uint8_unchecked (&reader, 8);

    /* This is slightly faster than a loop */
    if (!(len & 0x80)) {
      sample_number = len;
      len = 0;
    } else if ((len & 0xc0) && !(len & 0x20)) {
      sample_number = len & 0x1f;
      len = 1;
    } else if ((len & 0xe0) && !(len & 0x10)) {
      sample_number = len & 0x0f;
      len = 2;
    } else if ((len & 0xf0) && !(len & 0x08)) {
      sample_number = len & 0x07;
      len = 3;
    } else if ((len & 0xf8) && !(len & 0x04)) {
      sample_number = len & 0x03;
      len = 4;
    } else if ((len & 0xfc) && !(len & 0x02)) {
      sample_number = len & 0x01;
      len = 5;
    } else if ((len & 0xfe) && !(len & 0x01)) {
      sample_number = len & 0x0;
      len = 6;
    } else {
      goto error;
    }

    if ((blocking_strategy == 0 && len > 5) ||
        (blocking_strategy == 1 && len > 6))
      goto error;

    while (len > 0) {
      if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 8))
        goto need_more_data;

      if ((tmp & 0xc0) != 0x80)
        goto error;

      sample_number <<= 6;
      sample_number |= (tmp & 0x3f);
      len--;
    }
  }

  /* calculate real blocksize from the blocksize index */
  if (block_size == 0) {
    goto error;
  } else if (block_size == 6) {
    if (!gst_bit_reader_get_bits_uint16 (&reader, &block_size, 8))
      goto need_more_data;
    block_size++;
  } else if (block_size == 7) {
    if (!gst_bit_reader_get_bits_uint16 (&reader, &block_size, 16))
      goto need_more_data;
    block_size++;
  } else {
    block_size = blocksize_table[block_size];
  }

  /* calculate the real samplerate from the samplerate index */
  if (samplerate == 0 && flacparse->samplerate == 0) {
    goto need_streaminfo;
  } else if (samplerate < 12) {
    samplerate = sample_rate_table[samplerate];
  } else if (samplerate == 12) {
    if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 8))
      goto need_more_data;
    samplerate *= 1000;
  } else if (samplerate == 13) {
    if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 16))
      goto need_more_data;
  } else if (samplerate == 14) {
    if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 16))
      goto need_more_data;
    samplerate *= 10;
  }

  if (flacparse->samplerate && flacparse->samplerate != samplerate)
    goto error;

  /* check crc-8 for the header */
  if (!gst_bit_reader_get_bits_uint8 (&reader, &expected_crc, 8))
    goto need_more_data;

  actual_crc =
      gst_flac_calculate_crc8 (data,
      (gst_bit_reader_get_pos (&reader) / 8) - 1);
  if (actual_crc != expected_crc)
    goto error;

  if (set) {
    flacparse->block_size = block_size;
    if (!flacparse->samplerate)
      flacparse->samplerate = samplerate;
    if (!flacparse->bps)
      flacparse->bps = bps;
    if (!flacparse->blocking_strategy)
      flacparse->blocking_strategy = blocking_strategy;
    if (!flacparse->channels)
      flacparse->channels = channels;
    if (!flacparse->sample_number)
      flacparse->sample_number = sample_number;

    GST_DEBUG_OBJECT (flacparse,
        "Parsed frame at offset %" G_GUINT64_FORMAT ":\n" "Block size: %u\n"
        "Sample/Frame number: %" G_GUINT64_FORMAT, flacparse->offset,
        flacparse->block_size, flacparse->sample_number);
  }

  if (block_size_ret)
    *block_size_ret = block_size;

  return FRAME_HEADER_VALID;

need_streaminfo:
  GST_ERROR_OBJECT (flacparse, "Need STREAMINFO");
  return FRAME_HEADER_INVALID;
error:
  return FRAME_HEADER_INVALID;

need_more_data:
  return FRAME_HEADER_MORE_DATA;
}

static gboolean
gst_flac_parse_frame_is_valid (GstFlacParse * flacparse,
    GstBaseParseFrame * frame, guint * ret)
{
  GstBuffer *buffer;
  guint8 *data;
  gsize size;
  guint max, remaining;
  guint i, search_start, search_end;
  FrameHeaderCheckReturn header_ret;
  guint16 block_size;
  gboolean result = FALSE;

  buffer = frame->buffer;
  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);

  if (size <= flacparse->min_framesize)
    goto need_more;

  header_ret =
      gst_flac_parse_frame_header_is_valid (flacparse, data, size, TRUE,
      &block_size);
  if (header_ret == FRAME_HEADER_INVALID) {
    *ret = 0;
    goto cleanup;
  }
  if (header_ret == FRAME_HEADER_MORE_DATA)
    goto need_more;

  /* mind unknown framesize */
  search_start = MAX (2, flacparse->min_framesize);
  if (flacparse->max_framesize)
    search_end = MIN (size, flacparse->max_framesize + 9 + 2);
  else
    search_end = size;
  search_end -= 2;

  remaining = size;

  for (i = search_start; i < search_end; i++, remaining--) {
    if ((GST_READ_UINT16_BE (data + i) & 0xfffe) == 0xfff8) {
      header_ret =
          gst_flac_parse_frame_header_is_valid (flacparse, data + i, remaining,
          FALSE, NULL);
      if (header_ret == FRAME_HEADER_VALID) {
        if (flacparse->check_frame_checksums) {
          guint16 actual_crc = gst_flac_calculate_crc16 (data, i - 2);
          guint16 expected_crc = GST_READ_UINT16_BE (data + i - 2);

          if (actual_crc != expected_crc)
            continue;
        }
        *ret = i;
        flacparse->block_size = block_size;
        result = TRUE;
        goto cleanup;
      } else if (header_ret == FRAME_HEADER_MORE_DATA) {
        goto need_more;
      }
    }
  }

  /* For the last frame output everything to the end */
  if (G_UNLIKELY (GST_BASE_PARSE_DRAINING (flacparse))) {
    if (flacparse->check_frame_checksums) {
      guint16 actual_crc = gst_flac_calculate_crc16 (data, size - 2);
      guint16 expected_crc = GST_READ_UINT16_BE (data + size - 2);

      if (actual_crc == expected_crc) {
        *ret = size;
        flacparse->block_size = block_size;
        result = TRUE;
        goto cleanup;
      }
    } else {
      *ret = size;
      flacparse->block_size = block_size;
      result = TRUE;
      goto cleanup;
    }
  }

need_more:
  max = flacparse->max_framesize + 16;
  if (max == 16)
    max = 1 << 24;
  *ret = MIN (size + 4096, max);
  result = TRUE;

cleanup:
  gst_buffer_unmap (buffer, data, size);
  return result;
}

static gboolean
gst_flac_parse_check_valid_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame, guint * framesize, gint * skipsize)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
  GstBuffer *buffer = frame->buffer;
  guint8 *data;
  gsize bufsize;
  gboolean result = TRUE;

  data = gst_buffer_map (buffer, &bufsize, NULL, GST_MAP_READ);

  if (G_UNLIKELY (bufsize < 4)) {
    result = FALSE;
    goto cleanup;
  }

  if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) {
    if (memcmp (data, "fLaC", 4) == 0) {
      GST_DEBUG_OBJECT (flacparse, "fLaC marker found");
      *framesize = 4;
      goto cleanup;
    }
    if (data[0] == 0xff && (data[1] >> 2) == 0x3e) {
      GST_DEBUG_OBJECT (flacparse, "Found headerless FLAC");
      /* Minimal size of a frame header */
      gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 9);
      flacparse->state = GST_FLAC_PARSE_STATE_GENERATE_HEADERS;
      *skipsize = 0;
      result = FALSE;
      goto cleanup;
    }
    GST_DEBUG_OBJECT (flacparse, "fLaC marker not found");
    result = FALSE;
    goto cleanup;
  }

  if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) {
    guint size = 4 + ((data[1] << 16) | (data[2] << 8) | (data[3]));

    GST_DEBUG_OBJECT (flacparse, "Found metadata block of size %u", size);
    *framesize = size;
    goto cleanup;
  }

  if ((GST_READ_UINT16_BE (data) & 0xfffe) == 0xfff8) {
    gboolean ret;
    guint next;

    flacparse->offset = GST_BUFFER_OFFSET (buffer);
    flacparse->blocking_strategy = 0;
    flacparse->block_size = 0;
    flacparse->sample_number = 0;

    GST_DEBUG_OBJECT (flacparse, "Found sync code");
    ret = gst_flac_parse_frame_is_valid (flacparse, frame, &next);
    if (ret) {
      *framesize = next;
      goto cleanup;
    } else {
      /* If we're at EOS and the frame was not valid, drop it! */
      if (G_UNLIKELY (GST_BASE_PARSE_DRAINING (flacparse))) {
        GST_WARNING_OBJECT (flacparse, "EOS");
        result = FALSE;
        goto cleanup;
      }

      if (next == 0) {
      } else if (next > bufsize) {
        GST_DEBUG_OBJECT (flacparse, "Requesting %u bytes", next);
        *skipsize = 0;
        gst_base_parse_set_min_frame_size (parse, next);
        result = FALSE;
        goto cleanup;
      } else {
        GST_ERROR_OBJECT (flacparse,
            "Giving up on invalid frame (%d bytes)", bufsize);
        result = FALSE;
        goto cleanup;
      }
    }
  } else {
    GstByteReader reader;
    gint off;

    gst_byte_reader_init (&reader, data, bufsize);
    off =
        gst_byte_reader_masked_scan_uint32 (&reader, 0xfffc0000, 0xfff80000,
        0, bufsize);

    if (off > 0) {
      GST_DEBUG_OBJECT (parse, "Possible sync at buffer offset %d", off);
      *skipsize = off;
      result = FALSE;
      goto cleanup;
    } else {
      GST_DEBUG_OBJECT (flacparse, "Sync code not found");
      *skipsize = bufsize - 3;
      result = FALSE;
      goto cleanup;
    }
  }

  result = FALSE;

cleanup:
  gst_buffer_unmap (buffer, data, bufsize);
  return result;
}

static gboolean
gst_flac_parse_handle_streaminfo (GstFlacParse * flacparse, GstBuffer * buffer)
{
  GstBitReader reader;
  guint8 *data;
  gsize size;

  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
  gst_bit_reader_init (&reader, data, size);

  if (size != 4 + 34) {
    GST_ERROR_OBJECT (flacparse, "Invalid metablock size for STREAMINFO: %u",
        size);
    goto failure;
  }

  /* Skip metadata block header */
  gst_bit_reader_skip (&reader, 32);

  if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->min_blocksize, 16))
    goto error;
  if (flacparse->min_blocksize < 16) {
    GST_WARNING_OBJECT (flacparse, "Invalid minimum block size: %u",
        flacparse->min_blocksize);
  }

  if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->max_blocksize, 16))
    goto error;
  if (flacparse->max_blocksize < 16) {
    GST_WARNING_OBJECT (flacparse, "Invalid maximum block size: %u",
        flacparse->max_blocksize);
  }

  if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->min_framesize, 24))
    goto error;
  if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->max_framesize, 24))
    goto error;

  if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->samplerate, 20))
    goto error;
  if (flacparse->samplerate == 0) {
    GST_ERROR_OBJECT (flacparse, "Invalid sample rate 0");
    goto failure;
  }

  if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->channels, 3))
    goto error;
  flacparse->channels++;
  if (flacparse->channels > 8) {
    GST_ERROR_OBJECT (flacparse, "Invalid number of channels %u",
        flacparse->channels);
    goto failure;
  }

  if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->bps, 5))
    goto error;
  flacparse->bps++;

  if (!gst_bit_reader_get_bits_uint64 (&reader, &flacparse->total_samples, 36))
    goto error;
  if (flacparse->total_samples) {
    gst_base_parse_set_duration (GST_BASE_PARSE (flacparse),
        GST_FORMAT_DEFAULT, flacparse->total_samples, 0);
  }

  gst_buffer_unmap (buffer, data, size);

  GST_DEBUG_OBJECT (flacparse, "STREAMINFO:\n"
      "\tmin/max blocksize: %u/%u,\n"
      "\tmin/max framesize: %u/%u,\n"
      "\tsamplerate: %u,\n"
      "\tchannels: %u,\n"
      "\tbits per sample: %u,\n"
      "\ttotal samples: %" G_GUINT64_FORMAT,
      flacparse->min_blocksize, flacparse->max_blocksize,
      flacparse->min_framesize, flacparse->max_framesize,
      flacparse->samplerate,
      flacparse->channels, flacparse->bps, flacparse->total_samples);

  return TRUE;

error:
  GST_ERROR_OBJECT (flacparse, "Failed to read data");
failure:
  gst_buffer_unmap (buffer, data, size);
  return FALSE;
}

static gboolean
gst_flac_parse_handle_vorbiscomment (GstFlacParse * flacparse,
    GstBuffer * buffer)
{
  guint8 *data;
  gsize size;

  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);

  flacparse->tags = gst_tag_list_from_vorbiscomment (data, size, data, 4, NULL);
  gst_buffer_unmap (buffer, data, size);

  if (flacparse->tags == NULL) {
    GST_ERROR_OBJECT (flacparse, "Invalid vorbiscomment block");
  } else if (gst_tag_list_is_empty (flacparse->tags)) {
    gst_tag_list_free (flacparse->tags);
    flacparse->tags = NULL;
  }

  return TRUE;
}

static gboolean
gst_flac_parse_handle_picture (GstFlacParse * flacparse, GstBuffer * buffer)
{
  GstByteReader reader;
  guint8 *data;
  gsize bufsize;
  guint32 img_len = 0, img_type = 0;
  guint32 img_mimetype_len = 0, img_description_len = 0;

  data = gst_buffer_map (buffer, &bufsize, NULL, GST_MAP_READ);
  gst_byte_reader_init (&reader, data, bufsize);

  if (!gst_byte_reader_skip (&reader, 4))
    goto error;

  if (!gst_byte_reader_get_uint32_be (&reader, &img_type))
    goto error;

  if (!gst_byte_reader_get_uint32_be (&reader, &img_mimetype_len))
    goto error;
  if (!gst_byte_reader_skip (&reader, img_mimetype_len))
    goto error;

  if (!gst_byte_reader_get_uint32_be (&reader, &img_description_len))
    goto error;
  if (!gst_byte_reader_skip (&reader, img_description_len))
    goto error;

  if (!gst_byte_reader_skip (&reader, 4 * 4))
    goto error;

  if (!gst_byte_reader_get_uint32_be (&reader, &img_len))
    goto error;

  if (!flacparse->tags)
    flacparse->tags = gst_tag_list_new ();

  gst_tag_list_add_id3_image (flacparse->tags,
      data + gst_byte_reader_get_pos (&reader), img_len, img_type);

  if (gst_tag_list_is_empty (flacparse->tags)) {
    gst_tag_list_free (flacparse->tags);
    flacparse->tags = NULL;
  }

  gst_buffer_unmap (buffer, data, bufsize);
  return TRUE;

error:
  GST_ERROR_OBJECT (flacparse, "Error reading data");
  gst_buffer_unmap (buffer, data, bufsize);
  return FALSE;
}

static gboolean
gst_flac_parse_handle_seektable (GstFlacParse * flacparse, GstBuffer * buffer)
{

  GST_DEBUG_OBJECT (flacparse, "storing seektable");
  /* only store for now;
   * offset of the first frame is needed to get real info */
  flacparse->seektable = gst_buffer_ref (buffer);

  return TRUE;
}

static void
gst_flac_parse_process_seektable (GstFlacParse * flacparse, gint64 boffset)
{
  GstByteReader br;
  gint64 offset = 0, samples = 0;
  gpointer data;
  gsize bufsize;

  GST_DEBUG_OBJECT (flacparse,
      "parsing seektable; base offset %" G_GINT64_FORMAT, boffset);

  if (boffset <= 0)
    goto exit;

  data = gst_buffer_map (flacparse->seektable, &bufsize, NULL, GST_MAP_READ);
  gst_byte_reader_init (&br, data, bufsize);

  /* skip header */
  if (!gst_byte_reader_skip (&br, 4))
    goto done;

  /* seekpoints */
  while (gst_byte_reader_get_remaining (&br)) {
    if (!gst_byte_reader_get_int64_be (&br, &samples))
      break;
    if (!gst_byte_reader_get_int64_be (&br, &offset))
      break;
    if (!gst_byte_reader_skip (&br, 2))
      break;

    GST_LOG_OBJECT (flacparse, "samples %" G_GINT64_FORMAT " -> offset %"
        G_GINT64_FORMAT, samples, offset);

    /* sanity check */
    if (G_LIKELY (offset > 0 && samples > 0)) {
      gst_base_parse_add_index_entry (GST_BASE_PARSE (flacparse),
          boffset + offset, gst_util_uint64_scale (samples, GST_SECOND,
              flacparse->samplerate), TRUE, FALSE);
    }
  }

done:
  gst_buffer_unmap (flacparse->seektable, data, bufsize);
exit:
  gst_buffer_unref (flacparse->seektable);
  flacparse->seektable = NULL;
}

static void
_value_array_append_buffer (GValue * array_val, GstBuffer * buf)
{
  GValue value = { 0, };

  g_value_init (&value, GST_TYPE_BUFFER);
  /* copy buffer to avoid problems with circular refcounts */
  buf = gst_buffer_copy (buf);
  /* again, for good measure */
  GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
  gst_value_set_buffer (&value, buf);
  gst_buffer_unref (buf);
  gst_value_array_append_value (array_val, &value);
  g_value_unset (&value);
}

static gboolean
gst_flac_parse_handle_headers (GstFlacParse * flacparse)
{
  GstBuffer *vorbiscomment = NULL;
  GstBuffer *streaminfo = NULL;
  GstBuffer *marker = NULL;
  GValue array = { 0, };
  GstCaps *caps;
  GList *l;
  gboolean res = TRUE;

  caps = gst_caps_new_simple ("audio/x-flac",
      "channels", G_TYPE_INT, flacparse->channels,
      "framed", G_TYPE_BOOLEAN, TRUE,
      "rate", G_TYPE_INT, flacparse->samplerate, NULL);

  if (!flacparse->headers)
    goto push_headers;

  for (l = flacparse->headers; l; l = l->next) {
    GstBuffer *header = l->data;
    guint8 *data;
    gsize size;

    data = gst_buffer_map (header, &size, NULL, GST_MAP_READ);

    GST_BUFFER_FLAG_SET (header, GST_BUFFER_FLAG_IN_CAPS);

    if (size == 4 && memcmp (data, "fLaC", 4) == 0) {
      marker = header;
    } else if (size > 1 && (data[0] & 0x7f) == 0) {
      streaminfo = header;
    } else if (size > 1 && (data[0] & 0x7f) == 4) {
      vorbiscomment = header;
    }

    gst_buffer_unmap (header, data, size);
  }

  if (marker == NULL || streaminfo == NULL || vorbiscomment == NULL) {
    GST_WARNING_OBJECT (flacparse,
        "missing header %p %p %p, muxing into container "
        "formats may be broken", marker, streaminfo, vorbiscomment);
    goto push_headers;
  }

  g_value_init (&array, GST_TYPE_ARRAY);

  /* add marker including STREAMINFO header */
  {
    GstBuffer *buf;
    guint16 num;
    guint8 *sinfodata, *writedata;
    gsize sinfosize, writesize;

    sinfodata = gst_buffer_map (streaminfo, &sinfosize, NULL, GST_MAP_READ);

    /* minus one for the marker that is merged with streaminfo here */
    num = g_list_length (flacparse->headers) - 1;

    buf = gst_buffer_new_and_alloc (13 + sinfosize);
    writedata = gst_buffer_map (buf, &writesize, NULL, GST_MAP_WRITE);

    writedata[0] = 0x7f;
    memcpy (writedata + 1, "FLAC", 4);
    writedata[5] = 0x01;        /* mapping version major */
    writedata[6] = 0x00;        /* mapping version minor */
    writedata[7] = (num & 0xFF00) >> 8;
    writedata[8] = (num & 0x00FF) >> 0;
    memcpy (writedata + 9, "fLaC", 4);
    memcpy (writedata + 13, sinfodata, sinfosize);
    _value_array_append_buffer (&array, buf);

    gst_buffer_unmap (streaminfo, sinfodata, sinfosize);
    gst_buffer_unmap (buf, writedata, writesize);
    gst_buffer_unref (buf);
  }

  /* add VORBISCOMMENT header */
  _value_array_append_buffer (&array, vorbiscomment);

  /* add other headers, if there are any */
  for (l = flacparse->headers; l; l = l->next) {
    if (GST_BUFFER_CAST (l->data) != marker &&
        GST_BUFFER_CAST (l->data) != streaminfo &&
        GST_BUFFER_CAST (l->data) != vorbiscomment) {
      _value_array_append_buffer (&array, GST_BUFFER_CAST (l->data));
    }
  }

  gst_structure_set_value (gst_caps_get_structure (caps, 0),
      "streamheader", &array);
  g_value_unset (&array);

push_headers:

  gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)), caps);
  gst_caps_unref (caps);

  /* push header buffers; update caps, so when we push the first buffer the
   * negotiated caps will change to caps that include the streamheader field */
  while (flacparse->headers) {
    GstBuffer *buf = GST_BUFFER (flacparse->headers->data);
    GstFlowReturn ret;
    GstBaseParseFrame frame;

    flacparse->headers =
        g_list_delete_link (flacparse->headers, flacparse->headers);
    buf = gst_buffer_make_writable (buf);

    /* init, set and give away frame */
    gst_base_parse_frame_init (&frame);
    frame.buffer = buf;
    frame.overhead = -1;
    ret = gst_base_parse_push_frame (GST_BASE_PARSE (flacparse), &frame);
    if (ret != GST_FLOW_OK) {
      res = FALSE;
      break;
    }
  }
  g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (flacparse->headers);
  flacparse->headers = NULL;

  return res;
}

static gboolean
gst_flac_parse_generate_headers (GstFlacParse * flacparse)
{
  GstBuffer *marker, *streaminfo, *vorbiscomment;
  guint8 *data;
  gsize bufsize;

  marker = gst_buffer_new_and_alloc (4);
  data = gst_buffer_map (marker, &bufsize, NULL, GST_MAP_WRITE);
  memcpy (data, "fLaC", 4);
  gst_buffer_unmap (marker, data, bufsize);
  GST_BUFFER_TIMESTAMP (marker) = GST_CLOCK_TIME_NONE;
  GST_BUFFER_DURATION (marker) = GST_CLOCK_TIME_NONE;
  GST_BUFFER_OFFSET (marker) = 0;
  GST_BUFFER_OFFSET_END (marker) = 0;
  flacparse->headers = g_list_append (flacparse->headers, marker);

  streaminfo = gst_buffer_new_and_alloc (4 + 34);
  data = gst_buffer_map (streaminfo, &bufsize, NULL, GST_MAP_WRITE);
  memset (data, 0, 4 + 34);

  /* metadata block header */
  data[0] = 0x00;               /* is_last = 0; type = 0; */
  data[1] = 0x00;               /* length = 34; */
  data[2] = 0x00;
  data[3] = 0x22;

  /* streaminfo */

  data[4] = (flacparse->block_size >> 8) & 0xff;        /* min blocksize = blocksize; */
  data[5] = (flacparse->block_size) & 0xff;
  data[6] = (flacparse->block_size >> 8) & 0xff;        /* max blocksize = blocksize; */
  data[7] = (flacparse->block_size) & 0xff;

  data[8] = 0x00;               /* min framesize = 0; */
  data[9] = 0x00;
  data[10] = 0x00;
  data[11] = 0x00;              /* max framesize = 0; */
  data[12] = 0x00;
  data[13] = 0x00;

  data[14] = (flacparse->samplerate >> 12) & 0xff;
  data[15] = (flacparse->samplerate >> 4) & 0xff;
  data[16] = (flacparse->samplerate >> 0) & 0xf0;

  data[16] |= (flacparse->channels - 1) << 1;

  data[16] |= ((flacparse->bps - 1) >> 4) & 0x01;
  data[17] = (((flacparse->bps - 1)) & 0x0f) << 4;

  {
    gint64 duration;
    GstFormat fmt = GST_FORMAT_TIME;

    if (gst_pad_query_peer_duration (GST_BASE_PARSE_SINK_PAD (GST_BASE_PARSE
                (flacparse)), &fmt, &duration) && fmt == GST_FORMAT_TIME) {
      duration = GST_CLOCK_TIME_TO_FRAMES (duration, flacparse->samplerate);

      data[17] |= (duration >> 32) & 0xff;
      data[18] |= (duration >> 24) & 0xff;
      data[19] |= (duration >> 16) & 0xff;
      data[20] |= (duration >> 8) & 0xff;
      data[21] |= (duration >> 0) & 0xff;
    }
  }
  /* MD5 = 0; */

  gst_buffer_unmap (streaminfo, data, bufsize);
  GST_BUFFER_TIMESTAMP (streaminfo) = GST_CLOCK_TIME_NONE;
  GST_BUFFER_DURATION (streaminfo) = GST_CLOCK_TIME_NONE;
  GST_BUFFER_OFFSET (streaminfo) = 0;
  GST_BUFFER_OFFSET_END (streaminfo) = 0;
  flacparse->headers = g_list_append (flacparse->headers, streaminfo);

  /* empty vorbiscomment */
  {
    GstTagList *taglist = gst_tag_list_new ();
    guchar header[4];
    guint size;

    header[0] = 0x84;           /* is_last = 1; type = 4; */

    vorbiscomment =
        gst_tag_list_to_vorbiscomment_buffer (taglist, header,
        sizeof (header), NULL);
    gst_tag_list_free (taglist);

    data = gst_buffer_map (vorbiscomment, &bufsize, NULL, GST_MAP_WRITE);

    /* Get rid of framing bit */
    if (data[bufsize - 1] == 1) {
      GstBuffer *sub;

      sub =
          gst_buffer_copy_region (vorbiscomment, GST_BUFFER_COPY_ALL, 0,
          bufsize - 1);
      gst_buffer_unmap (vorbiscomment, data, bufsize);
      gst_buffer_unref (vorbiscomment);
      vorbiscomment = sub;
      data = gst_buffer_map (vorbiscomment, &bufsize, NULL, GST_MAP_WRITE);
    }

    size = bufsize - 4;
    data[1] = ((size & 0xFF0000) >> 16);
    data[2] = ((size & 0x00FF00) >> 8);
    data[3] = (size & 0x0000FF);

    gst_buffer_unmap (vorbiscomment, data, bufsize);
    GST_BUFFER_TIMESTAMP (vorbiscomment) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_DURATION (vorbiscomment) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_OFFSET (vorbiscomment) = 0;
    GST_BUFFER_OFFSET_END (vorbiscomment) = 0;
    flacparse->headers = g_list_append (flacparse->headers, vorbiscomment);
  }

  return TRUE;
}

static GstFlowReturn
gst_flac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
  GstBuffer *buffer = frame->buffer;
  guint8 *data = NULL;
  gsize bufsize;
  GstFlowReturn res = GST_FLOW_ERROR;

  data = gst_buffer_map (buffer, &bufsize, NULL, GST_MAP_READ);

  if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) {
    GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_OFFSET (buffer) = 0;
    GST_BUFFER_OFFSET_END (buffer) = 0;

    /* 32 bits metadata block */
    gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4);
    flacparse->state = GST_FLAC_PARSE_STATE_HEADERS;

    flacparse->headers =
        g_list_append (flacparse->headers, gst_buffer_ref (buffer));

    res = GST_BASE_PARSE_FLOW_DROPPED;
  } else if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) {
    gboolean is_last = ((data[0] & 0x80) == 0x80);
    guint type = (data[0] & 0x7F);

    if (type == 127) {
      GST_WARNING_OBJECT (flacparse, "Invalid metadata block type");
      res = GST_BASE_PARSE_FLOW_DROPPED;
      goto cleanup;
    }

    GST_DEBUG_OBJECT (flacparse, "Handling metadata block of type %u", type);

    switch (type) {
      case 0:                  /* STREAMINFO */
        if (!gst_flac_parse_handle_streaminfo (flacparse, buffer))
          goto cleanup;
        break;
      case 3:                  /* SEEKTABLE */
        if (!gst_flac_parse_handle_seektable (flacparse, buffer))
          goto cleanup;
        break;
      case 4:                  /* VORBIS_COMMENT */
        if (!gst_flac_parse_handle_vorbiscomment (flacparse, buffer))
          goto cleanup;
        break;
      case 6:                  /* PICTURE */
        if (!gst_flac_parse_handle_picture (flacparse, buffer))
          goto cleanup;
        break;
      case 1:                  /* PADDING */
      case 2:                  /* APPLICATION */
      case 5:                  /* CUESHEET */
      default:                 /* RESERVED */
        break;
    }

    GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
    GST_BUFFER_OFFSET (buffer) = 0;
    GST_BUFFER_OFFSET_END (buffer) = 0;

    flacparse->headers =
        g_list_append (flacparse->headers, gst_buffer_ref (buffer));

    if (is_last) {
      if (!gst_flac_parse_handle_headers (flacparse))
        goto cleanup;

      /* Minimal size of a frame header */
      gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (9,
              flacparse->min_framesize));
      flacparse->state = GST_FLAC_PARSE_STATE_DATA;
    }

    /* DROPPED because we pushed already or will push all headers manually */
    res = GST_BASE_PARSE_FLOW_DROPPED;
  } else {
    if (flacparse->offset != GST_BUFFER_OFFSET (buffer)) {
      FrameHeaderCheckReturn ret;

      flacparse->offset = GST_BUFFER_OFFSET (buffer);
      ret =
          gst_flac_parse_frame_header_is_valid (flacparse,
          data, bufsize, TRUE, NULL);
      if (ret != FRAME_HEADER_VALID) {
        GST_ERROR_OBJECT (flacparse,
            "Baseclass didn't provide a complete frame");
        goto cleanup;
      }
    }

    if (flacparse->block_size == 0) {
      GST_ERROR_OBJECT (flacparse, "Unparsed frame");
      goto cleanup;
    }

    if (flacparse->seektable)
      gst_flac_parse_process_seektable (flacparse, GST_BUFFER_OFFSET (buffer));

    if (flacparse->state == GST_FLAC_PARSE_STATE_GENERATE_HEADERS) {
      if (flacparse->blocking_strategy == 1) {
        GST_WARNING_OBJECT (flacparse,
            "Generating headers for variable blocksize streams not supported");

        if (!gst_flac_parse_handle_headers (flacparse))
          goto cleanup;
      } else {
        GST_DEBUG_OBJECT (flacparse, "Generating headers");

        if (!gst_flac_parse_generate_headers (flacparse))
          goto cleanup;

        if (!gst_flac_parse_handle_headers (flacparse))
          goto cleanup;
      }
      flacparse->state = GST_FLAC_PARSE_STATE_DATA;
    }

    /* also cater for oggmux metadata */
    if (flacparse->blocking_strategy == 0) {
      GST_BUFFER_TIMESTAMP (buffer) =
          gst_util_uint64_scale (flacparse->sample_number,
          flacparse->block_size * GST_SECOND, flacparse->samplerate);
      GST_BUFFER_OFFSET_END (buffer) =
          flacparse->sample_number * flacparse->block_size +
          flacparse->block_size;
    } else {
      GST_BUFFER_TIMESTAMP (buffer) =
          gst_util_uint64_scale (flacparse->sample_number, GST_SECOND,
          flacparse->samplerate);
      GST_BUFFER_OFFSET_END (buffer) =
          flacparse->sample_number + flacparse->block_size;
    }
    GST_BUFFER_OFFSET (buffer) =
        gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
        flacparse->samplerate);
    GST_BUFFER_DURATION (buffer) =
        GST_BUFFER_OFFSET (buffer) - GST_BUFFER_TIMESTAMP (buffer);

    /* To simplify, we just assume that it's a fixed size header and ignore
     * subframe headers. The first could lead us to being off by 88 bits and
     * the second even less, so the total inaccuracy is negligible. */
    frame->overhead = 7;

    /* Minimal size of a frame header */
    gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (9,
            flacparse->min_framesize));

    flacparse->offset = -1;
    flacparse->blocking_strategy = 0;
    flacparse->block_size = 0;
    flacparse->sample_number = 0;
    res = GST_FLOW_OK;
  }

cleanup:
  if (data)
    gst_buffer_unmap (buffer, data, bufsize);

  return res;
}

static GstFlowReturn
gst_flac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);

  /* Push tags */
  if (flacparse->tags) {
    gst_element_found_tags (GST_ELEMENT (flacparse), flacparse->tags);
    flacparse->tags = NULL;
  }

  frame->flags |= GST_BASE_PARSE_FRAME_FLAG_CLIP;

  return GST_FLOW_OK;
}

static gboolean
gst_flac_parse_convert (GstBaseParse * parse,
    GstFormat src_format, gint64 src_value, GstFormat dest_format,
    gint64 * dest_value)
{
  GstFlacParse *flacparse = GST_FLAC_PARSE (parse);

  if (flacparse->samplerate > 0) {
    if (src_format == GST_FORMAT_DEFAULT && dest_format == GST_FORMAT_TIME) {
      if (src_value != -1)
        *dest_value =
            gst_util_uint64_scale (src_value, GST_SECOND,
            flacparse->samplerate);
      else
        *dest_value = -1;
      return TRUE;
    } else if (src_format == GST_FORMAT_TIME &&
        dest_format == GST_FORMAT_DEFAULT) {
      if (src_value != -1)
        *dest_value =
            gst_util_uint64_scale (src_value, flacparse->samplerate,
            GST_SECOND);
      else
        *dest_value = -1;
      return TRUE;
    }
  }

  return GST_BASE_PARSE_CLASS (parent_class)->convert (parse, src_format,
      src_value, dest_format, dest_value);
}