mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 05:31:15 +00:00
1915 lines
60 KiB
C
1915 lines
60 KiB
C
/* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-flacparse
|
|
* @title: 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.
|
|
*
|
|
* ## Example pipelines
|
|
* |[
|
|
* gst-launch-1.0 -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.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstaudioparserselements.h"
|
|
#include "gstflacparse.h"
|
|
|
|
#include <string.h>
|
|
#include <gst/tag/tag.h>
|
|
#include <gst/audio/audio.h>
|
|
#include <gst/base/base.h>
|
|
#include <gst/pbutils/pbutils.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")
|
|
);
|
|
|
|
static GstBuffer *gst_flac_parse_generate_vorbiscomment (GstFlacParse *
|
|
flacparse, gboolean is_last);
|
|
|
|
static inline void gst_flac_parse_reset_buffer_time_and_offset (GstBuffer *
|
|
buffer);
|
|
static void gst_flac_parse_reset (GstFlacParse * parser);
|
|
static gboolean gst_flac_parse_handle_block_type (GstFlacParse * flacparse,
|
|
guint type, GstBuffer * sbuffer);
|
|
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 GstFlowReturn gst_flac_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize);
|
|
static GstFlowReturn gst_flac_parse_parse_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint size);
|
|
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);
|
|
static gboolean gst_flac_parse_src_event (GstBaseParse * parse,
|
|
GstEvent * event);
|
|
static GstCaps *gst_flac_parse_get_sink_caps (GstBaseParse * parse,
|
|
GstCaps * filter);
|
|
static gboolean gst_flac_parse_set_sink_caps (GstBaseParse * parse,
|
|
GstCaps * caps);
|
|
|
|
#define gst_flac_parse_parent_class parent_class
|
|
G_DEFINE_TYPE (GstFlacParse, gst_flac_parse, GST_TYPE_BASE_PARSE);
|
|
GST_ELEMENT_REGISTER_DEFINE (flacparse, "flacparse",
|
|
GST_RANK_PRIMARY + 1, GST_TYPE_FLAC_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->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_flac_parse_handle_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);
|
|
baseparse_class->src_event = GST_DEBUG_FUNCPTR (gst_flac_parse_src_event);
|
|
baseparse_class->get_sink_caps =
|
|
GST_DEBUG_FUNCPTR (gst_flac_parse_get_sink_caps);
|
|
baseparse_class->set_sink_caps =
|
|
GST_DEBUG_FUNCPTR (gst_flac_parse_set_sink_caps);
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &src_factory);
|
|
gst_element_class_add_static_pad_template (element_class, &sink_factory);
|
|
|
|
gst_element_class_set_static_metadata (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;
|
|
GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (flacparse));
|
|
GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (flacparse));
|
|
}
|
|
|
|
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_reset (GstFlacParse * parser)
|
|
{
|
|
if (parser->tags) {
|
|
gst_tag_list_unref (parser->tags);
|
|
parser->tags = NULL;
|
|
}
|
|
if (parser->toc) {
|
|
gst_toc_unref (parser->toc);
|
|
parser->toc = NULL;
|
|
}
|
|
if (parser->seektable) {
|
|
gst_buffer_unref (parser->seektable);
|
|
parser->seektable = NULL;
|
|
}
|
|
|
|
g_list_foreach (parser->headers, (GFunc) gst_mini_object_unref, NULL);
|
|
g_list_free (parser->headers);
|
|
parser->headers = NULL;
|
|
}
|
|
|
|
static void
|
|
gst_flac_parse_finalize (GObject * object)
|
|
{
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (object);
|
|
|
|
gst_flac_parse_reset (flacparse);
|
|
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;
|
|
flacparse->strategy_checked = FALSE;
|
|
|
|
flacparse->sent_codec_tag = FALSE;
|
|
|
|
/* "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);
|
|
|
|
gst_flac_parse_reset (flacparse);
|
|
return TRUE;
|
|
}
|
|
|
|
static const guint8 sample_size_table[] = { 0, 8, 12, 0, 16, 20, 24, 32 };
|
|
|
|
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,
|
|
gboolean * suspect)
|
|
{
|
|
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);
|
|
if (flacparse->force_variable_block_size)
|
|
blocking_strategy = 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) {
|
|
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 == 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) {
|
|
GST_DEBUG_OBJECT (flacparse,
|
|
"Checksum mismatch. Header CRC was '%d' but frame has '%d'",
|
|
expected_crc, actual_crc);
|
|
goto error;
|
|
}
|
|
|
|
/* Sanity check sample number against blocking strategy, as it seems
|
|
some files claim fixed block size but supply sample numbers,
|
|
rather than block numbers. */
|
|
if (blocking_strategy == 0 && flacparse->block_size != 0) {
|
|
if (!flacparse->strategy_checked) {
|
|
if (block_size == sample_number) {
|
|
GST_WARNING_OBJECT (flacparse, "This file claims fixed block size, "
|
|
"but seems to be lying: assuming variable block size");
|
|
flacparse->force_variable_block_size = TRUE;
|
|
blocking_strategy = 1;
|
|
}
|
|
flacparse->strategy_checked = TRUE;
|
|
}
|
|
}
|
|
|
|
/* documentation says:
|
|
* The "blocking strategy" bit must be the same throughout the entire stream. */
|
|
if (flacparse->blocking_strategy != blocking_strategy) {
|
|
if (flacparse->block_size != 0) {
|
|
GST_WARNING_OBJECT (flacparse, "blocking strategy is not constant");
|
|
if (suspect)
|
|
*suspect = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
The FLAC format documentation says:
|
|
The "blocking strategy" bit determines how to calculate the sample number
|
|
of the first sample in the frame. If the bit is 0 (fixed-blocksize), the
|
|
frame header encodes the frame number as above, and the frame's starting
|
|
sample number will be the frame number times the blocksize. If it is 1
|
|
(variable-blocksize), the frame header encodes the frame's starting
|
|
sample number itself. (In the case of a fixed-blocksize stream, only the
|
|
last block may be shorter than the stream blocksize; its starting sample
|
|
number will be calculated as the frame number times the previous frame's
|
|
blocksize, or zero if it is the first frame).
|
|
|
|
Therefore, when in fixed block size mode, we only update the block size
|
|
the first time, then reuse that block size for subsequent calls.
|
|
This will also fix a timestamp problem with the last block's timestamp
|
|
being miscalculated by scaling the block number by a "wrong" block size.
|
|
*/
|
|
if (blocking_strategy == 0) {
|
|
if (flacparse->block_size != 0) {
|
|
/* after first block */
|
|
if (flacparse->block_size != block_size) {
|
|
/* TODO: can we know we're on the last frame, to avoid warning ? */
|
|
GST_WARNING_OBJECT (flacparse, "Block size is not constant");
|
|
block_size = flacparse->block_size;
|
|
if (suspect)
|
|
*suspect = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 metadata. Bits per sample "
|
|
"or sample rate not in frame header");
|
|
error:
|
|
return FRAME_HEADER_INVALID;
|
|
|
|
need_more_data:
|
|
return FRAME_HEADER_MORE_DATA;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_frame_is_valid (GstFlacParse * flacparse,
|
|
const guint8 * data, gsize size, guint * ret)
|
|
{
|
|
guint max;
|
|
guint i, search_start, search_end;
|
|
FrameHeaderCheckReturn header_ret;
|
|
guint16 block_size;
|
|
gboolean suspect_start = FALSE, suspect_end = FALSE;
|
|
|
|
/* minimum frame header size is 5 bytes and we read that much
|
|
* without checking the size below */
|
|
if (size < MAX (flacparse->min_framesize, 5))
|
|
goto need_more;
|
|
|
|
header_ret =
|
|
gst_flac_parse_frame_header_is_valid (flacparse, data, size, TRUE,
|
|
&block_size, &suspect_start);
|
|
if (header_ret == FRAME_HEADER_INVALID) {
|
|
*ret = 0;
|
|
return FALSE;
|
|
}
|
|
if (header_ret == FRAME_HEADER_MORE_DATA)
|
|
goto need_more;
|
|
|
|
/* mind unknown framesize */
|
|
search_start = MAX (2, flacparse->min_framesize);
|
|
|
|
/* at minimum 5 bytes are read below so stop 5 bytes before the end.
|
|
* we also checked above that at least 5 bytes are available. */
|
|
search_end = size - 5;
|
|
if (flacparse->max_framesize)
|
|
search_end = MIN (search_end, flacparse->max_framesize + 9 + 2);
|
|
|
|
for (i = search_start; i < search_end; i++) {
|
|
|
|
if ((GST_READ_UINT16_BE (data + i) & 0xfffe) != 0xfff8)
|
|
continue;
|
|
|
|
GST_LOG_OBJECT (flacparse, "possible frame end at offset %d", i);
|
|
suspect_end = FALSE;
|
|
header_ret =
|
|
gst_flac_parse_frame_header_is_valid (flacparse, data + i,
|
|
size - i, FALSE, NULL, &suspect_end);
|
|
if (header_ret == FRAME_HEADER_VALID) {
|
|
if (flacparse->check_frame_checksums || suspect_start || suspect_end) {
|
|
guint16 actual_crc = gst_flac_calculate_crc16 (data, i - 2);
|
|
guint16 expected_crc = GST_READ_UINT16_BE (data + i - 2);
|
|
|
|
GST_LOG_OBJECT (flacparse,
|
|
"Found possible frame (%d, %d). Checking for CRC match",
|
|
suspect_start, suspect_end);
|
|
if (actual_crc != expected_crc) {
|
|
GST_DEBUG_OBJECT (flacparse,
|
|
"Checksum mismatch. Header CRC was '%d' but frame has '%d'",
|
|
expected_crc, actual_crc);
|
|
continue;
|
|
}
|
|
}
|
|
*ret = i;
|
|
flacparse->block_size = block_size;
|
|
return TRUE;
|
|
} 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;
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
*ret = size;
|
|
flacparse->block_size = block_size;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* so we searched to expected end and found nothing,
|
|
* give up on this frame (start) */
|
|
if (flacparse->max_framesize && i > 2 * flacparse->max_framesize) {
|
|
GST_LOG_OBJECT (flacparse,
|
|
"could not determine valid frame end, discarding frame (start)");
|
|
*ret = 1;
|
|
return FALSE;
|
|
}
|
|
|
|
need_more:
|
|
max = flacparse->max_framesize + 16;
|
|
if (max == 16)
|
|
max = 1 << 24;
|
|
*ret = MIN (size + 4096, max);
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flac_parse_handle_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame, gint * skipsize)
|
|
{
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
|
|
GstBuffer *buffer = frame->buffer;
|
|
GstMapInfo map;
|
|
gboolean result = TRUE;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
guint framesize = 0;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
|
|
*skipsize = 1;
|
|
|
|
if (G_UNLIKELY (map.size < 4)) {
|
|
result = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) {
|
|
if (memcmp (map.data, "fLaC", 4) == 0) {
|
|
GST_DEBUG_OBJECT (flacparse, "fLaC marker found");
|
|
framesize = 4;
|
|
goto cleanup;
|
|
}
|
|
if (map.data[0] == 0xff && (map.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 + ((map.data[1] << 16) | (map.data[2] << 8) | (map.data[3]));
|
|
|
|
GST_DEBUG_OBJECT (flacparse, "Found metadata block of size %u", size);
|
|
framesize = size;
|
|
gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), framesize);
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((GST_READ_UINT16_BE (map.data) & 0xfffe) == 0xfff8) {
|
|
gboolean ret, is_first = !flacparse->strategy_checked;
|
|
guint next;
|
|
|
|
flacparse->offset = GST_BUFFER_OFFSET (buffer);
|
|
flacparse->blocking_strategy = 0;
|
|
flacparse->sample_number = 0;
|
|
|
|
GST_DEBUG_OBJECT (flacparse, "Found sync code");
|
|
ret = gst_flac_parse_frame_is_valid (flacparse, map.data, map.size, &next);
|
|
if (ret) {
|
|
if (is_first) {
|
|
GST_INFO_OBJECT (flacparse, "First sample number is %" G_GUINT64_FORMAT,
|
|
flacparse->sample_number);
|
|
flacparse->first_sample_number = flacparse->sample_number;
|
|
}
|
|
framesize = next;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* 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 > map.size) {
|
|
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 (%" G_GSIZE_FORMAT " bytes)", map.size);
|
|
result = FALSE;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
GstByteReader reader;
|
|
gint off;
|
|
|
|
gst_byte_reader_init (&reader, map.data, map.size);
|
|
off =
|
|
gst_byte_reader_masked_scan_uint32 (&reader, 0xfffc0000, 0xfff80000,
|
|
0, map.size);
|
|
|
|
if (off > 0) {
|
|
GST_DEBUG_OBJECT (parse, "Possible sync at buffer offset %d", off);
|
|
*skipsize = off;
|
|
result = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (flacparse, "Sync code not found");
|
|
*skipsize = map.size - 3;
|
|
result = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = FALSE;
|
|
|
|
cleanup:
|
|
gst_buffer_unmap (buffer, &map);
|
|
|
|
if (result)
|
|
*skipsize = 0;
|
|
|
|
if (result && framesize <= map.size) {
|
|
ret = gst_flac_parse_parse_frame (parse, frame, framesize);
|
|
if (ret == GST_BASE_PARSE_FLOW_DROPPED) {
|
|
frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP;
|
|
ret = GST_FLOW_OK;
|
|
}
|
|
if (ret == GST_FLOW_OK)
|
|
ret = gst_base_parse_finish_frame (parse, frame, framesize);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_handle_streaminfo (GstFlacParse * flacparse, GstBuffer * buffer)
|
|
{
|
|
GstBitReader reader;
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
gst_bit_reader_init (&reader, map.data, map.size);
|
|
|
|
if (map.size != 4 + 34) {
|
|
GST_ERROR_OBJECT (flacparse,
|
|
"Invalid metablock size for STREAMINFO: %" G_GSIZE_FORMAT "", map.size);
|
|
goto failure;
|
|
}
|
|
|
|
/* Skip metadata block header */
|
|
if (!gst_bit_reader_skip (&reader, 32))
|
|
goto error;
|
|
|
|
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, &map);
|
|
|
|
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, &map);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_handle_vorbiscomment (GstFlacParse * flacparse,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstTagList *tags;
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
|
|
tags =
|
|
gst_tag_list_from_vorbiscomment (map.data, map.size, map.data, 4, NULL);
|
|
gst_buffer_unmap (buffer, &map);
|
|
|
|
if (tags == NULL) {
|
|
GST_ERROR_OBJECT (flacparse, "Invalid vorbiscomment block");
|
|
} else if (gst_tag_list_is_empty (tags)) {
|
|
gst_tag_list_unref (tags);
|
|
} else if (flacparse->tags == NULL) {
|
|
flacparse->tags = tags;
|
|
} else {
|
|
gst_tag_list_insert (flacparse->tags, tags, GST_TAG_MERGE_APPEND);
|
|
gst_tag_list_unref (tags);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_handle_cuesheet (GstFlacParse * flacparse, GstBuffer * buffer)
|
|
{
|
|
GstByteReader reader;
|
|
GstMapInfo map;
|
|
guint i, j;
|
|
guint8 n_tracks, track_num, index;
|
|
guint64 offset;
|
|
gint64 start, stop;
|
|
gchar *id;
|
|
gchar isrc[13];
|
|
GstTagList *tags;
|
|
GstToc *toc;
|
|
GstTocEntry *cur_entry = NULL, *prev_entry = NULL;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
gst_byte_reader_init (&reader, map.data, map.size);
|
|
|
|
toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL);
|
|
|
|
/* skip 4 bytes METADATA_BLOCK_HEADER */
|
|
/* https://xiph.org/flac/format.html#metadata_block_header */
|
|
if (!gst_byte_reader_skip (&reader, 4))
|
|
goto error;
|
|
|
|
/* skip 395 bytes from METADATA_BLOCK_CUESHEET */
|
|
/* https://xiph.org/flac/format.html#metadata_block_cuesheet */
|
|
if (!gst_byte_reader_skip (&reader, 395))
|
|
goto error;
|
|
|
|
if (!gst_byte_reader_get_uint8 (&reader, &n_tracks))
|
|
goto error;
|
|
|
|
/* CUESHEET_TRACK */
|
|
/* https://xiph.org/flac/format.html#cuesheet_track */
|
|
for (i = 0; i < n_tracks; i++) {
|
|
if (!gst_byte_reader_get_uint64_be (&reader, &offset))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint8 (&reader, &track_num))
|
|
goto error;
|
|
|
|
if (gst_byte_reader_get_remaining (&reader) < 12)
|
|
goto error;
|
|
memcpy (isrc, map.data + gst_byte_reader_get_pos (&reader), 12);
|
|
/* \0-terminate the string */
|
|
isrc[12] = '\0';
|
|
if (!gst_byte_reader_skip (&reader, 12))
|
|
goto error;
|
|
|
|
/* skip 14 bytes from CUESHEET_TRACK */
|
|
if (!gst_byte_reader_skip (&reader, 14))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint8 (&reader, &index))
|
|
goto error;
|
|
/* add tracks in TOC */
|
|
/* lead-out tack has number 170 or 255 */
|
|
if (track_num != 170 && track_num != 255) {
|
|
prev_entry = cur_entry;
|
|
/* previous track stop time = current track start time */
|
|
if (prev_entry != NULL) {
|
|
gst_toc_entry_get_start_stop_times (prev_entry, &start, NULL);
|
|
stop =
|
|
gst_util_uint64_scale_round (offset, GST_SECOND,
|
|
flacparse->samplerate);
|
|
gst_toc_entry_set_start_stop_times (prev_entry, start, stop);
|
|
}
|
|
id = g_strdup_printf ("%08x", track_num);
|
|
cur_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_TRACK, id);
|
|
g_free (id);
|
|
start =
|
|
gst_util_uint64_scale_round (offset, GST_SECOND,
|
|
flacparse->samplerate);
|
|
gst_toc_entry_set_start_stop_times (cur_entry, start, -1);
|
|
/* add ISRC as tag in track */
|
|
if (strlen (isrc) != 0) {
|
|
tags = gst_tag_list_new_empty ();
|
|
gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_ISRC, isrc, NULL);
|
|
gst_toc_entry_set_tags (cur_entry, tags);
|
|
}
|
|
gst_toc_append_entry (toc, cur_entry);
|
|
/* CUESHEET_TRACK_INDEX */
|
|
/* https://xiph.org/flac/format.html#cuesheet_track_index */
|
|
for (j = 0; j < index; j++) {
|
|
if (!gst_byte_reader_skip (&reader, 12))
|
|
goto error;
|
|
}
|
|
} else {
|
|
/* set stop time in last track */
|
|
stop =
|
|
gst_util_uint64_scale_round (offset, GST_SECOND,
|
|
flacparse->samplerate);
|
|
gst_toc_entry_set_start_stop_times (cur_entry, start, stop);
|
|
}
|
|
}
|
|
|
|
/* send data as TOC */
|
|
if (!flacparse->toc)
|
|
flacparse->toc = toc;
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flacparse, "Error reading data");
|
|
gst_buffer_unmap (buffer, &map);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_handle_picture (GstFlacParse * flacparse, GstBuffer * buffer)
|
|
{
|
|
GstByteReader reader;
|
|
GstMapInfo map;
|
|
guint32 img_len = 0, img_type = 0;
|
|
guint32 img_mimetype_len = 0, img_description_len = 0;
|
|
const guint8 *img_data;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
gst_byte_reader_init (&reader, map.data, map.size);
|
|
|
|
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 (!gst_byte_reader_get_data (&reader, img_len, &img_data))
|
|
goto error;
|
|
|
|
GST_INFO_OBJECT (flacparse, "Got image of %d bytes", img_len);
|
|
|
|
if (img_len > 0) {
|
|
if (flacparse->tags == NULL)
|
|
flacparse->tags = gst_tag_list_new_empty ();
|
|
|
|
gst_tag_list_add_id3_image (flacparse->tags, img_data, img_len, img_type);
|
|
}
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flacparse, "Error reading data");
|
|
gst_buffer_unmap (buffer, &map);
|
|
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 */
|
|
if (flacparse->seektable)
|
|
gst_buffer_unref (flacparse->seektable);
|
|
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;
|
|
GstMapInfo map;
|
|
|
|
GST_DEBUG_OBJECT (flacparse,
|
|
"parsing seektable; base offset %" G_GINT64_FORMAT, boffset);
|
|
|
|
if (boffset <= 0)
|
|
goto exit;
|
|
|
|
gst_buffer_map (flacparse->seektable, &map, GST_MAP_READ);
|
|
gst_byte_reader_init (&br, map.data, map.size);
|
|
|
|
/* 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, &map);
|
|
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_HEADER);
|
|
gst_value_set_buffer (&value, buf);
|
|
gst_buffer_unref (buf);
|
|
gst_value_array_append_value (array_val, &value);
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flac_parse_handle_headers (GstFlacParse * flacparse)
|
|
{
|
|
GstBuffer *vorbiscomment = NULL;
|
|
GstBuffer *streaminfo = NULL;
|
|
GstBuffer *marker = NULL;
|
|
GValue array = { 0, };
|
|
GstCaps *caps;
|
|
GList *l;
|
|
GstFlowReturn res = GST_FLOW_OK;
|
|
gboolean is_streaminfo_last = FALSE;
|
|
|
|
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;
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (header, &map, GST_MAP_READ);
|
|
|
|
GST_BUFFER_FLAG_SET (header, GST_BUFFER_FLAG_HEADER);
|
|
|
|
if (map.size == 4 && memcmp (map.data, "fLaC", 4) == 0) {
|
|
marker = header;
|
|
} else if (map.size > 1 && (map.data[0] & 0x7f) == 0) {
|
|
streaminfo = header;
|
|
is_streaminfo_last = (map.data[0] & 0x80) != 0;
|
|
} else if (map.size > 1 && (map.data[0] & 0x7f) == 4) {
|
|
vorbiscomment = header;
|
|
}
|
|
|
|
gst_buffer_unmap (header, &map);
|
|
}
|
|
|
|
/* at least this one we can generate easily
|
|
* to provide full headers downstream */
|
|
if (vorbiscomment == NULL && streaminfo != NULL) {
|
|
GST_DEBUG_OBJECT (flacparse,
|
|
"missing vorbiscomment header; generating dummy");
|
|
/* this vorbiscomment header is inserted after streaminfo and inherits its last-metadata-block flag */
|
|
vorbiscomment =
|
|
gst_flac_parse_generate_vorbiscomment (flacparse, is_streaminfo_last);
|
|
flacparse->headers =
|
|
g_list_insert (flacparse->headers, vorbiscomment,
|
|
g_list_index (flacparse->headers, streaminfo) + 1);
|
|
}
|
|
|
|
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;
|
|
GstMapInfo sinfomap, writemap;
|
|
|
|
gst_buffer_map (streaminfo, &sinfomap, 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 + sinfomap.size);
|
|
gst_buffer_map (buf, &writemap, GST_MAP_WRITE);
|
|
|
|
writemap.data[0] = 0x7f;
|
|
memcpy (writemap.data + 1, "FLAC", 4);
|
|
writemap.data[5] = 0x01; /* mapping version major */
|
|
writemap.data[6] = 0x00; /* mapping version minor */
|
|
writemap.data[7] = (num & 0xFF00) >> 8;
|
|
writemap.data[8] = (num & 0x00FF) >> 0;
|
|
memcpy (writemap.data + 9, "fLaC", 4);
|
|
memcpy (writemap.data + 13, sinfomap.data, sinfomap.size);
|
|
/* clear the last-metadata-block flag because a VORBISCOMMENT always follows */
|
|
writemap.data[13] = 0x00; /* is_last = 0; type = 0; */
|
|
_value_array_append_buffer (&array, buf);
|
|
|
|
gst_buffer_unmap (streaminfo, &sinfomap);
|
|
gst_buffer_unmap (buf, &writemap);
|
|
gst_buffer_unref (buf);
|
|
}
|
|
|
|
/* add other headers, including VORBISCOMMENT */
|
|
for (l = flacparse->headers; l; l = l->next) {
|
|
if (GST_BUFFER_CAST (l->data) != marker &&
|
|
GST_BUFFER_CAST (l->data) != streaminfo) {
|
|
_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);
|
|
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;
|
|
res = gst_base_parse_push_frame (GST_BASE_PARSE (flacparse), &frame);
|
|
gst_base_parse_frame_free (&frame);
|
|
if (res != GST_FLOW_OK)
|
|
break;
|
|
}
|
|
g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL);
|
|
g_list_free (flacparse->headers);
|
|
flacparse->headers = NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* empty vorbiscomment */
|
|
static GstBuffer *
|
|
gst_flac_parse_generate_vorbiscomment (GstFlacParse * flacparse,
|
|
gboolean is_last)
|
|
{
|
|
GstTagList *taglist = gst_tag_list_new_empty ();
|
|
guchar header[4];
|
|
guint size;
|
|
GstBuffer *vorbiscomment;
|
|
GstMapInfo map;
|
|
|
|
header[0] = (is_last ? 0x80 : 0x00) | 0x04; /* is_last may vary; type = 4; */
|
|
|
|
vorbiscomment =
|
|
gst_tag_list_to_vorbiscomment_buffer (taglist, header,
|
|
sizeof (header), NULL);
|
|
gst_tag_list_unref (taglist);
|
|
|
|
gst_buffer_map (vorbiscomment, &map, GST_MAP_WRITE);
|
|
|
|
/* Get rid of framing bit */
|
|
if (map.data[map.size - 1] == 1) {
|
|
GstBuffer *sub;
|
|
|
|
sub =
|
|
gst_buffer_copy_region (vorbiscomment, GST_BUFFER_COPY_ALL, 0,
|
|
map.size - 1);
|
|
gst_buffer_unmap (vorbiscomment, &map);
|
|
gst_buffer_unref (vorbiscomment);
|
|
vorbiscomment = sub;
|
|
gst_buffer_map (vorbiscomment, &map, GST_MAP_WRITE);
|
|
}
|
|
|
|
size = map.size - 4;
|
|
map.data[1] = ((size & 0xFF0000) >> 16);
|
|
map.data[2] = ((size & 0x00FF00) >> 8);
|
|
map.data[3] = (size & 0x0000FF);
|
|
gst_buffer_unmap (vorbiscomment, &map);
|
|
gst_flac_parse_reset_buffer_time_and_offset (vorbiscomment);
|
|
|
|
return vorbiscomment;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_generate_headers (GstFlacParse * flacparse)
|
|
{
|
|
GstBuffer *marker, *streaminfo;
|
|
GstMapInfo map;
|
|
|
|
marker = gst_buffer_new_and_alloc (4);
|
|
gst_buffer_map (marker, &map, GST_MAP_WRITE);
|
|
memcpy (map.data, "fLaC", 4);
|
|
gst_buffer_unmap (marker, &map);
|
|
gst_flac_parse_reset_buffer_time_and_offset (marker);
|
|
flacparse->headers = g_list_append (flacparse->headers, marker);
|
|
|
|
streaminfo = gst_buffer_new_and_alloc (4 + 34);
|
|
gst_buffer_map (streaminfo, &map, GST_MAP_WRITE);
|
|
memset (map.data, 0, 4 + 34);
|
|
|
|
/* metadata block header */
|
|
map.data[0] = 0x00; /* is_last = 0; type = 0; */
|
|
map.data[1] = 0x00; /* length = 34; */
|
|
map.data[2] = 0x00;
|
|
map.data[3] = 0x22;
|
|
|
|
/* streaminfo */
|
|
|
|
map.data[4] = (flacparse->block_size >> 8) & 0xff; /* min blocksize = blocksize; */
|
|
map.data[5] = (flacparse->block_size) & 0xff;
|
|
map.data[6] = (flacparse->block_size >> 8) & 0xff; /* max blocksize = blocksize; */
|
|
map.data[7] = (flacparse->block_size) & 0xff;
|
|
|
|
map.data[8] = 0x00; /* min framesize = 0; */
|
|
map.data[9] = 0x00;
|
|
map.data[10] = 0x00;
|
|
map.data[11] = 0x00; /* max framesize = 0; */
|
|
map.data[12] = 0x00;
|
|
map.data[13] = 0x00;
|
|
|
|
map.data[14] = (flacparse->samplerate >> 12) & 0xff;
|
|
map.data[15] = (flacparse->samplerate >> 4) & 0xff;
|
|
map.data[16] = (flacparse->samplerate >> 0) & 0xf0;
|
|
|
|
map.data[16] |= (flacparse->channels - 1) << 1;
|
|
|
|
map.data[16] |= ((flacparse->bps - 1) >> 4) & 0x01;
|
|
map.data[17] = (((flacparse->bps - 1)) & 0x0f) << 4;
|
|
|
|
{
|
|
gint64 duration;
|
|
|
|
if (gst_pad_peer_query_duration (GST_BASE_PARSE_SINK_PAD (flacparse),
|
|
GST_FORMAT_TIME, &duration) && duration != -1) {
|
|
duration = GST_CLOCK_TIME_TO_FRAMES (duration, flacparse->samplerate);
|
|
|
|
map.data[17] |= (duration >> 32) & 0xff;
|
|
map.data[18] |= (duration >> 24) & 0xff;
|
|
map.data[19] |= (duration >> 16) & 0xff;
|
|
map.data[20] |= (duration >> 8) & 0xff;
|
|
map.data[21] |= (duration >> 0) & 0xff;
|
|
}
|
|
}
|
|
/* MD5 = 0; */
|
|
|
|
gst_buffer_unmap (streaminfo, &map);
|
|
gst_flac_parse_reset_buffer_time_and_offset (streaminfo);
|
|
flacparse->headers = g_list_append (flacparse->headers, streaminfo);
|
|
|
|
flacparse->headers = g_list_append (flacparse->headers,
|
|
gst_flac_parse_generate_vorbiscomment (flacparse, TRUE));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
gst_flac_parse_reset_buffer_time_and_offset (GstBuffer * buffer)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* Type 127 is invalid for a metadata block header & should
|
|
* be discarded _before_ calling this function */
|
|
static gboolean
|
|
gst_flac_parse_handle_block_type (GstFlacParse * flacparse, guint type,
|
|
GstBuffer * sbuffer)
|
|
{
|
|
gboolean ret = TRUE;
|
|
|
|
switch (type) {
|
|
case 0: /* STREAMINFO */
|
|
GST_INFO_OBJECT (flacparse, "STREAMINFO header");
|
|
ret = gst_flac_parse_handle_streaminfo (flacparse, sbuffer);
|
|
break;
|
|
case 3: /* SEEKTABLE */
|
|
GST_INFO_OBJECT (flacparse, "SEEKTABLE header");
|
|
ret = gst_flac_parse_handle_seektable (flacparse, sbuffer);
|
|
break;
|
|
case 4: /* VORBIS_COMMENT */
|
|
GST_INFO_OBJECT (flacparse, "VORBISCOMMENT header");
|
|
ret = gst_flac_parse_handle_vorbiscomment (flacparse, sbuffer);
|
|
break;
|
|
case 5: /* CUESHEET */
|
|
GST_INFO_OBJECT (flacparse, "CUESHEET header");
|
|
ret = gst_flac_parse_handle_cuesheet (flacparse, sbuffer);
|
|
break;
|
|
case 6: /* PICTURE */
|
|
GST_INFO_OBJECT (flacparse, "PICTURE header");
|
|
ret = gst_flac_parse_handle_picture (flacparse, sbuffer);
|
|
break;
|
|
case 1: /* PADDING */
|
|
GST_INFO_OBJECT (flacparse, "PADDING header");
|
|
break;
|
|
case 2: /* APPLICATION */
|
|
GST_INFO_OBJECT (flacparse, "APPLICATION header");
|
|
break;
|
|
default: /* RESERVED */
|
|
GST_INFO_OBJECT (flacparse, "Unhandled metadata header type '%u'", type);
|
|
GST_FIXME_OBJECT (flacparse, "FLAC version might not be fully supported");
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame,
|
|
gint size)
|
|
{
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
|
|
GstBuffer *buffer = frame->buffer, *sbuffer;
|
|
GstMapInfo map;
|
|
GstFlowReturn res = GST_FLOW_ERROR;
|
|
guint64 relative_sample_number;
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
|
|
if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) {
|
|
sbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0, size);
|
|
gst_flac_parse_reset_buffer_time_and_offset (sbuffer);
|
|
|
|
/* 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, sbuffer);
|
|
|
|
res = GST_BASE_PARSE_FLOW_DROPPED;
|
|
} else if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) {
|
|
gboolean is_last = map.data[0] >> 7;
|
|
guint type = (map.data[0] & 0x7F);
|
|
|
|
if (type == 127) {
|
|
GST_WARNING_OBJECT (flacparse, "Invalid metadata block type 127");
|
|
res = GST_BASE_PARSE_FLOW_DROPPED;
|
|
goto cleanup;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (flacparse, "Handling metadata block of type %u", type);
|
|
|
|
sbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0, size);
|
|
|
|
if (gst_flac_parse_handle_block_type (flacparse, type, sbuffer)) {
|
|
gst_flac_parse_reset_buffer_time_and_offset (sbuffer);
|
|
flacparse->headers = g_list_append (flacparse->headers, sbuffer);
|
|
} else {
|
|
GST_WARNING_OBJECT (parse, "failed to parse header of type %u", type);
|
|
GST_MEMDUMP_OBJECT (parse, "bad header data", map.data, size);
|
|
|
|
gst_buffer_unref (sbuffer);
|
|
|
|
/* error out unless we have a STREAMINFO header */
|
|
if (flacparse->samplerate == 0 || flacparse->bps == 0)
|
|
goto header_parsing_error;
|
|
|
|
/* .. in which case just stop header parsing and try to find audio */
|
|
is_last = TRUE;
|
|
}
|
|
|
|
if (is_last) {
|
|
res = gst_flac_parse_handle_headers (flacparse);
|
|
|
|
/* 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;
|
|
|
|
if (res != GST_FLOW_OK)
|
|
goto cleanup;
|
|
} else {
|
|
/* Header length */
|
|
gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4);
|
|
}
|
|
|
|
/* 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,
|
|
map.data, map.size, TRUE, NULL, 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");
|
|
|
|
res = gst_flac_parse_handle_headers (flacparse);
|
|
} else {
|
|
GST_DEBUG_OBJECT (flacparse, "Generating headers");
|
|
|
|
if (!gst_flac_parse_generate_headers (flacparse))
|
|
goto cleanup;
|
|
|
|
res = gst_flac_parse_handle_headers (flacparse);
|
|
}
|
|
flacparse->state = GST_FLAC_PARSE_STATE_DATA;
|
|
if (res != GST_FLOW_OK)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* also cater for oggmux metadata */
|
|
relative_sample_number =
|
|
flacparse->sample_number - flacparse->first_sample_number;
|
|
if (flacparse->blocking_strategy == 0) {
|
|
GST_BUFFER_PTS (buffer) =
|
|
gst_util_uint64_scale (relative_sample_number,
|
|
flacparse->block_size * GST_SECOND, flacparse->samplerate);
|
|
GST_BUFFER_OFFSET_END (buffer) =
|
|
relative_sample_number * flacparse->block_size +
|
|
flacparse->block_size;
|
|
} else {
|
|
GST_BUFFER_PTS (buffer) =
|
|
gst_util_uint64_scale (relative_sample_number, GST_SECOND,
|
|
flacparse->samplerate);
|
|
GST_BUFFER_OFFSET_END (buffer) =
|
|
relative_sample_number + flacparse->block_size;
|
|
}
|
|
|
|
GST_BUFFER_DTS (buffer) = GST_BUFFER_PTS (buffer);
|
|
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_PTS (buffer);
|
|
|
|
/* To simplify, we just assume that it's a fixed size header and ignore
|
|
* subframe headers. The first could lead us to be 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->sample_number = 0;
|
|
res = GST_FLOW_OK;
|
|
}
|
|
|
|
cleanup:
|
|
gst_buffer_unmap (buffer, &map);
|
|
return res;
|
|
|
|
header_parsing_error:
|
|
GST_ELEMENT_ERROR (flacparse, STREAM, DECODE, (NULL),
|
|
("Failed to parse headers"));
|
|
goto cleanup;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
|
|
{
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
|
|
|
|
if (!flacparse->sent_codec_tag) {
|
|
GstCaps *caps;
|
|
|
|
if (flacparse->tags == NULL)
|
|
flacparse->tags = gst_tag_list_new_empty ();
|
|
|
|
/* codec tag */
|
|
caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
|
|
if (G_UNLIKELY (caps == NULL)) {
|
|
if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) {
|
|
GST_INFO_OBJECT (parse, "Src pad is flushing");
|
|
return GST_FLOW_FLUSHING;
|
|
}
|
|
GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
gst_pb_utils_add_codec_description_to_tag_list (flacparse->tags,
|
|
GST_TAG_AUDIO_CODEC, caps);
|
|
gst_caps_unref (caps);
|
|
|
|
/* Announce our pending tags */
|
|
gst_base_parse_merge_tags (parse, flacparse->tags, GST_TAG_MERGE_REPLACE);
|
|
|
|
/* also signals the end of first-frame processing */
|
|
flacparse->sent_codec_tag = TRUE;
|
|
}
|
|
|
|
/* Push toc */
|
|
if (flacparse->toc) {
|
|
gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (flacparse),
|
|
gst_event_new_toc (flacparse->toc, FALSE));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_src_event (GstBaseParse * parse, GstEvent * event)
|
|
{
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
|
|
gboolean res = FALSE;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_TOC_SELECT:
|
|
{
|
|
GstTocEntry *entry = NULL;
|
|
GstEvent *seek_event;
|
|
GstToc *toc = NULL;
|
|
gint64 start_pos;
|
|
gchar *uid = NULL;
|
|
|
|
/* FIXME: some locking would be good */
|
|
if (flacparse->toc)
|
|
toc = gst_toc_ref (flacparse->toc);
|
|
|
|
if (toc != NULL) {
|
|
gst_event_parse_toc_select (event, &uid);
|
|
if (uid != NULL) {
|
|
entry = gst_toc_find_entry (toc, uid);
|
|
if (entry != NULL) {
|
|
gst_toc_entry_get_start_stop_times (entry, &start_pos, NULL);
|
|
|
|
/* FIXME: use segment rate here instead? */
|
|
seek_event = gst_event_new_seek (1.0,
|
|
GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_FLUSH,
|
|
GST_SEEK_TYPE_SET, start_pos, GST_SEEK_TYPE_NONE, -1);
|
|
|
|
res =
|
|
GST_BASE_PARSE_CLASS (parent_class)->src_event (parse,
|
|
seek_event);
|
|
|
|
} else {
|
|
GST_WARNING_OBJECT (parse, "no TOC entry with given UID: %s", uid);
|
|
}
|
|
g_free (uid);
|
|
}
|
|
gst_toc_unref (toc);
|
|
} else {
|
|
GST_DEBUG_OBJECT (flacparse, "no TOC to select");
|
|
}
|
|
gst_event_unref (event);
|
|
break;
|
|
}
|
|
default:
|
|
res = GST_BASE_PARSE_CLASS (parent_class)->src_event (parse, event);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
remove_fields (GstCaps * caps)
|
|
{
|
|
guint i, n;
|
|
|
|
n = gst_caps_get_size (caps);
|
|
for (i = 0; i < n; i++) {
|
|
GstStructure *s = gst_caps_get_structure (caps, i);
|
|
|
|
gst_structure_remove_field (s, "framed");
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_flac_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter)
|
|
{
|
|
GstCaps *peercaps, *templ;
|
|
GstCaps *res;
|
|
|
|
templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse));
|
|
if (filter) {
|
|
GstCaps *fcopy = gst_caps_copy (filter);
|
|
/* Remove the fields we convert */
|
|
remove_fields (fcopy);
|
|
peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy);
|
|
gst_caps_unref (fcopy);
|
|
} else
|
|
peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL);
|
|
|
|
if (peercaps) {
|
|
/* Remove the framed field */
|
|
peercaps = gst_caps_make_writable (peercaps);
|
|
remove_fields (peercaps);
|
|
|
|
res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (peercaps);
|
|
gst_caps_unref (templ);
|
|
} else {
|
|
res = templ;
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (res);
|
|
res = intersection;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps)
|
|
{
|
|
GstCaps *current_caps;
|
|
GstFlacParse *flacparse = GST_FLAC_PARSE (parse);
|
|
|
|
/* If caps are changing, drain any pending frames we have so that afterwards
|
|
* we can potentially accept a new stream that is starting with the FLAC
|
|
* headers again. If headers appear in the middle of the stream we can't
|
|
* detect them
|
|
*/
|
|
gst_base_parse_drain (parse);
|
|
|
|
/* If the caps did really change we need to reset the parser */
|
|
current_caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse));
|
|
if (current_caps) {
|
|
if (!gst_caps_is_strictly_equal (caps, current_caps)) {
|
|
GST_DEBUG_OBJECT (flacparse, "Reset parser on sink pad caps change");
|
|
gst_flac_parse_stop (parse);
|
|
gst_flac_parse_start (parse);
|
|
}
|
|
gst_caps_unref (current_caps);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|