mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 09:10:36 +00:00
3f6a868f7e
for files which have corrupted header, libflac is not able to process the metadata properly. We just try to ignore the error and continue with the processing, since metadata parsing is not making much of a difference to libflac https://bugzilla.gnome.org/show_bug.cgi?id=751334
805 lines
26 KiB
C
805 lines
26 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
* Copyright (C) <2006,2011> Tim-Philipp Müller <tim centricular net>
|
|
* Copyright (C) <2006> Jan Schmidt <thaytan at mad scientist 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-flacdec
|
|
* @see_also: #GstFlacEnc
|
|
*
|
|
* flacdec decodes FLAC streams.
|
|
* <ulink url="http://flac.sourceforge.net/">FLAC</ulink>
|
|
* is a Free Lossless Audio Codec.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 filesrc location=media/small/dark.441-16-s.flac ! flacparse ! flacdec ! audioconvert ! audioresample ! autoaudiosink
|
|
* ]|
|
|
* |[
|
|
* gst-launch-1.0 souphttpsrc location=http://gstreamer.freedesktop.org/media/small/dark.441-16-s.flac ! flacparse ! flacdec ! audioconvert ! audioresample ! queue min-threshold-buffers=10 ! autoaudiosink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "gstflacdec.h"
|
|
#include <gst/gst-i18n-plugin.h>
|
|
#include <gst/tag/tag.h>
|
|
|
|
/* Taken from http://flac.sourceforge.net/format.html#frame_header */
|
|
static const GstAudioChannelPosition channel_positions[8][8] = {
|
|
{GST_AUDIO_CHANNEL_POSITION_MONO},
|
|
{GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
|
|
/* FIXME: 7/8 channel layouts are not defined in the FLAC specs */
|
|
{
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
|
|
};
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (flacdec_debug);
|
|
#define GST_CAT_DEFAULT flacdec_debug
|
|
|
|
static FLAC__StreamDecoderReadStatus
|
|
gst_flac_dec_read_stream (const FLAC__StreamDecoder * decoder,
|
|
FLAC__byte buffer[], size_t * bytes, void *client_data);
|
|
static FLAC__StreamDecoderWriteStatus
|
|
gst_flac_dec_write_stream (const FLAC__StreamDecoder * decoder,
|
|
const FLAC__Frame * frame,
|
|
const FLAC__int32 * const buffer[], void *client_data);
|
|
static gboolean
|
|
gst_flac_dec_handle_decoder_error (GstFlacDec * dec, gboolean msg);
|
|
static void gst_flac_dec_metadata_cb (const FLAC__StreamDecoder *
|
|
decoder, const FLAC__StreamMetadata * metadata, void *client_data);
|
|
static void gst_flac_dec_error_cb (const FLAC__StreamDecoder *
|
|
decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
|
|
|
static void gst_flac_dec_flush (GstAudioDecoder * audio_dec, gboolean hard);
|
|
static gboolean gst_flac_dec_set_format (GstAudioDecoder * dec, GstCaps * caps);
|
|
static gboolean gst_flac_dec_start (GstAudioDecoder * dec);
|
|
static gboolean gst_flac_dec_stop (GstAudioDecoder * dec);
|
|
static GstFlowReturn gst_flac_dec_handle_frame (GstAudioDecoder * audio_dec,
|
|
GstBuffer * buf);
|
|
|
|
G_DEFINE_TYPE (GstFlacDec, gst_flac_dec, GST_TYPE_AUDIO_DECODER);
|
|
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
#define FORMATS "{ S8, S16LE, S24_32LE, S32LE } "
|
|
#else
|
|
#define FORMATS "{ S8, S16BE, S24_32BE, S32BE } "
|
|
#endif
|
|
|
|
#define GST_FLAC_DEC_SRC_CAPS \
|
|
"audio/x-raw, " \
|
|
"format = (string) " FORMATS ", " \
|
|
"layout = (string) interleaved, " \
|
|
"rate = (int) [ 1, 655350 ], " \
|
|
"channels = (int) [ 1, 8 ]"
|
|
|
|
#define GST_FLAC_DEC_SINK_CAPS \
|
|
"audio/x-flac, " \
|
|
"framed = (boolean) true, " \
|
|
"rate = (int) [ 1, 655350 ], " \
|
|
"channels = (int) [ 1, 8 ]"
|
|
|
|
static GstStaticPadTemplate flac_dec_src_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_FLAC_DEC_SRC_CAPS));
|
|
static GstStaticPadTemplate flac_dec_sink_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_FLAC_DEC_SINK_CAPS));
|
|
|
|
static void
|
|
gst_flac_dec_class_init (GstFlacDecClass * klass)
|
|
{
|
|
GstAudioDecoderClass *audiodecoder_class;
|
|
GstElementClass *gstelement_class;
|
|
|
|
audiodecoder_class = (GstAudioDecoderClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (flacdec_debug, "flacdec", 0, "flac decoder");
|
|
|
|
audiodecoder_class->stop = GST_DEBUG_FUNCPTR (gst_flac_dec_stop);
|
|
audiodecoder_class->start = GST_DEBUG_FUNCPTR (gst_flac_dec_start);
|
|
audiodecoder_class->flush = GST_DEBUG_FUNCPTR (gst_flac_dec_flush);
|
|
audiodecoder_class->set_format = GST_DEBUG_FUNCPTR (gst_flac_dec_set_format);
|
|
audiodecoder_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_flac_dec_handle_frame);
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&flac_dec_src_factory));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&flac_dec_sink_factory));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "FLAC audio decoder",
|
|
"Codec/Decoder/Audio", "Decodes FLAC lossless audio streams",
|
|
"Tim-Philipp Müller <tim@centricular.net>, "
|
|
"Wim Taymans <wim.taymans@gmail.com>");
|
|
}
|
|
|
|
static void
|
|
gst_flac_dec_init (GstFlacDec * flacdec)
|
|
{
|
|
gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (flacdec), TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_dec_start (GstAudioDecoder * audio_dec)
|
|
{
|
|
FLAC__StreamDecoderInitStatus s;
|
|
GstFlacDec *dec;
|
|
|
|
dec = GST_FLAC_DEC (audio_dec);
|
|
|
|
dec->adapter = gst_adapter_new ();
|
|
|
|
dec->decoder = FLAC__stream_decoder_new ();
|
|
|
|
gst_audio_info_init (&dec->info);
|
|
dec->depth = 0;
|
|
|
|
/* no point calculating MD5 since it's never checked here */
|
|
FLAC__stream_decoder_set_md5_checking (dec->decoder, false);
|
|
|
|
GST_DEBUG_OBJECT (dec, "initializing decoder");
|
|
s = FLAC__stream_decoder_init_stream (dec->decoder,
|
|
gst_flac_dec_read_stream, NULL, NULL, NULL, NULL,
|
|
gst_flac_dec_write_stream, gst_flac_dec_metadata_cb,
|
|
gst_flac_dec_error_cb, dec);
|
|
|
|
if (s != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (dec), LIBRARY, INIT, (NULL), (NULL));
|
|
return FALSE;
|
|
}
|
|
|
|
dec->got_headers = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_dec_stop (GstAudioDecoder * dec)
|
|
{
|
|
GstFlacDec *flacdec = GST_FLAC_DEC (dec);
|
|
|
|
if (flacdec->decoder) {
|
|
FLAC__stream_decoder_delete (flacdec->decoder);
|
|
flacdec->decoder = NULL;
|
|
}
|
|
|
|
if (flacdec->adapter) {
|
|
gst_adapter_clear (flacdec->adapter);
|
|
g_object_unref (flacdec->adapter);
|
|
flacdec->adapter = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_dec_set_format (GstAudioDecoder * dec, GstCaps * caps)
|
|
{
|
|
const GValue *headers;
|
|
GstFlacDec *flacdec;
|
|
GstStructure *s;
|
|
guint i, num;
|
|
|
|
flacdec = GST_FLAC_DEC (dec);
|
|
|
|
GST_LOG_OBJECT (dec, "sink caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
headers = gst_structure_get_value (s, "streamheader");
|
|
if (headers == NULL || !GST_VALUE_HOLDS_ARRAY (headers)) {
|
|
GST_WARNING_OBJECT (dec, "no 'streamheader' field in input caps, try "
|
|
"adding a flacparse element upstream");
|
|
return FALSE;
|
|
}
|
|
|
|
if (gst_adapter_available (flacdec->adapter) > 0) {
|
|
GST_WARNING_OBJECT (dec, "unexpected data left in adapter");
|
|
gst_adapter_clear (flacdec->adapter);
|
|
}
|
|
|
|
num = gst_value_array_get_size (headers);
|
|
for (i = 0; i < num; ++i) {
|
|
const GValue *header_val;
|
|
GstBuffer *header_buf;
|
|
|
|
header_val = gst_value_array_get_value (headers, i);
|
|
if (header_val == NULL || !GST_VALUE_HOLDS_BUFFER (header_val))
|
|
return FALSE;
|
|
|
|
header_buf = g_value_dup_boxed (header_val);
|
|
GST_INFO_OBJECT (dec, "pushing header buffer of %" G_GSIZE_FORMAT " bytes "
|
|
"into adapter", gst_buffer_get_size (header_buf));
|
|
gst_adapter_push (flacdec->adapter, header_buf);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (dec, "Processing headers and metadata");
|
|
if (!FLAC__stream_decoder_process_until_end_of_metadata (flacdec->decoder)) {
|
|
GST_WARNING_OBJECT (dec, "process_until_end_of_metadata failed");
|
|
if (FLAC__stream_decoder_get_state (flacdec->decoder) ==
|
|
FLAC__STREAM_DECODER_ABORTED) {
|
|
GST_WARNING_OBJECT (flacdec, "Read callback caused internal abort");
|
|
/* allow recovery */
|
|
gst_adapter_clear (flacdec->adapter);
|
|
FLAC__stream_decoder_flush (flacdec->decoder);
|
|
gst_flac_dec_handle_decoder_error (flacdec, TRUE);
|
|
}
|
|
}
|
|
GST_INFO_OBJECT (dec, "headers and metadata are now processed");
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* FIXME: for our purposes it's probably enough to just check for the sync
|
|
* marker - we just want to know if it's a header frame or not */
|
|
static gboolean
|
|
gst_flac_dec_scan_got_frame (GstFlacDec * flacdec, const guint8 * data,
|
|
guint size)
|
|
{
|
|
guint headerlen;
|
|
guint sr_from_end = 0; /* can be 0, 8 or 16 */
|
|
guint bs_from_end = 0; /* can be 0, 8 or 16 */
|
|
guint32 val = 0;
|
|
guint8 bs, sr, ca, ss, pb;
|
|
gboolean vbs;
|
|
|
|
if (size < 10)
|
|
return FALSE;
|
|
|
|
/* sync */
|
|
if (data[0] != 0xFF || (data[1] & 0xFC) != 0xF8)
|
|
return FALSE;
|
|
|
|
vbs = ! !(data[1] & 1); /* variable blocksize */
|
|
bs = (data[2] & 0xF0) >> 4; /* blocksize marker */
|
|
sr = (data[2] & 0x0F); /* samplerate marker */
|
|
ca = (data[3] & 0xF0) >> 4; /* channel assignment */
|
|
ss = (data[3] & 0x0F) >> 1; /* sample size marker */
|
|
pb = (data[3] & 0x01); /* padding bit */
|
|
|
|
GST_LOG_OBJECT (flacdec,
|
|
"got sync, vbs=%d,bs=%x,sr=%x,ca=%x,ss=%x,pb=%x", vbs, bs, sr, ca, ss,
|
|
pb);
|
|
|
|
if (bs == 0 || sr == 0x0F || ca >= 0x0B || ss == 0x03 || ss == 0x07) {
|
|
return FALSE;
|
|
}
|
|
|
|
/* read block size from end of header? */
|
|
if (bs == 6)
|
|
bs_from_end = 8;
|
|
else if (bs == 7)
|
|
bs_from_end = 16;
|
|
|
|
/* read sample rate from end of header? */
|
|
if (sr == 0x0C)
|
|
sr_from_end = 8;
|
|
else if (sr == 0x0D || sr == 0x0E)
|
|
sr_from_end = 16;
|
|
|
|
val = data[4];
|
|
/* This is slightly faster than a loop */
|
|
if (!(val & 0x80)) {
|
|
val = 0;
|
|
} else if ((val & 0xc0) && !(val & 0x20)) {
|
|
val = 1;
|
|
} else if ((val & 0xe0) && !(val & 0x10)) {
|
|
val = 2;
|
|
} else if ((val & 0xf0) && !(val & 0x08)) {
|
|
val = 3;
|
|
} else if ((val & 0xf8) && !(val & 0x04)) {
|
|
val = 4;
|
|
} else if ((val & 0xfc) && !(val & 0x02)) {
|
|
val = 5;
|
|
} else if ((val & 0xfe) && !(val & 0x01)) {
|
|
val = 6;
|
|
} else {
|
|
GST_LOG_OBJECT (flacdec, "failed to read sample/frame");
|
|
return FALSE;
|
|
}
|
|
|
|
val++;
|
|
headerlen = 4 + val + (bs_from_end / 8) + (sr_from_end / 8);
|
|
|
|
if (gst_flac_calculate_crc8 (data, headerlen) != data[headerlen]) {
|
|
GST_LOG_OBJECT (flacdec, "invalid checksum");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flac_dec_handle_decoder_error (GstFlacDec * dec, gboolean msg)
|
|
{
|
|
gboolean ret;
|
|
|
|
dec->error_count++;
|
|
if (dec->error_count > 10) {
|
|
if (msg)
|
|
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), (NULL));
|
|
dec->last_flow = GST_FLOW_ERROR;
|
|
ret = TRUE;
|
|
} else {
|
|
GST_DEBUG_OBJECT (dec, "ignoring error for now at count %d",
|
|
dec->error_count);
|
|
ret = FALSE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_flac_dec_metadata_cb (const FLAC__StreamDecoder * decoder,
|
|
const FLAC__StreamMetadata * metadata, void *client_data)
|
|
{
|
|
GstFlacDec *flacdec = GST_FLAC_DEC (client_data);
|
|
GstAudioChannelPosition position[8];
|
|
|
|
GST_LOG_OBJECT (flacdec, "metadata type: %d", metadata->type);
|
|
|
|
switch (metadata->type) {
|
|
case FLAC__METADATA_TYPE_STREAMINFO:{
|
|
gint64 samples;
|
|
guint depth, width, gdepth, channels;
|
|
|
|
samples = metadata->data.stream_info.total_samples;
|
|
|
|
flacdec->min_blocksize = metadata->data.stream_info.min_blocksize;
|
|
flacdec->max_blocksize = metadata->data.stream_info.max_blocksize;
|
|
flacdec->depth = depth = metadata->data.stream_info.bits_per_sample;
|
|
|
|
if (depth < 9) {
|
|
gdepth = width = 8;
|
|
} else if (depth < 17) {
|
|
gdepth = width = 16;
|
|
} else if (depth < 25) {
|
|
gdepth = 24;
|
|
width = 32;
|
|
} else {
|
|
gdepth = width = 32;
|
|
}
|
|
|
|
channels = metadata->data.stream_info.channels;
|
|
memcpy (position, channel_positions[channels - 1], sizeof (position));
|
|
gst_audio_channel_positions_to_valid_order (position, channels);
|
|
/* Note: we create the inverse reordering map here */
|
|
gst_audio_get_channel_reorder_map (channels,
|
|
position, channel_positions[channels - 1],
|
|
flacdec->channel_reorder_map);
|
|
|
|
gst_audio_info_set_format (&flacdec->info,
|
|
gst_audio_format_build_integer (TRUE, G_BYTE_ORDER, width, gdepth),
|
|
metadata->data.stream_info.sample_rate,
|
|
metadata->data.stream_info.channels, position);
|
|
|
|
GST_DEBUG_OBJECT (flacdec, "blocksize: min=%u, max=%u",
|
|
flacdec->min_blocksize, flacdec->max_blocksize);
|
|
GST_DEBUG_OBJECT (flacdec, "sample rate: %u, channels: %u",
|
|
flacdec->info.rate, flacdec->info.channels);
|
|
GST_DEBUG_OBJECT (flacdec, "depth: %u, width: %u", flacdec->depth,
|
|
flacdec->info.finfo->width);
|
|
|
|
GST_DEBUG_OBJECT (flacdec, "total samples = %" G_GINT64_FORMAT, samples);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_flac_dec_error_cb (const FLAC__StreamDecoder * d,
|
|
FLAC__StreamDecoderErrorStatus status, void *client_data)
|
|
{
|
|
const gchar *error;
|
|
GstFlacDec *dec;
|
|
|
|
dec = GST_FLAC_DEC (client_data);
|
|
|
|
switch (status) {
|
|
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
|
|
/* Ignore this error and keep processing */
|
|
return;
|
|
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
|
|
error = "bad header";
|
|
break;
|
|
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
|
|
error = "CRC mismatch";
|
|
break;
|
|
default:
|
|
error = "unknown error";
|
|
break;
|
|
}
|
|
|
|
if (gst_flac_dec_handle_decoder_error (dec, FALSE))
|
|
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("%s (%d)", error, status));
|
|
}
|
|
|
|
static FLAC__StreamDecoderReadStatus
|
|
gst_flac_dec_read_stream (const FLAC__StreamDecoder * decoder,
|
|
FLAC__byte buffer[], size_t * bytes, void *client_data)
|
|
{
|
|
GstFlacDec *dec = GST_FLAC_DEC (client_data);
|
|
guint len;
|
|
|
|
len = MIN (gst_adapter_available (dec->adapter), *bytes);
|
|
|
|
if (len == 0) {
|
|
GST_LOG_OBJECT (dec, "0 bytes available at the moment");
|
|
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
|
}
|
|
|
|
GST_LOG_OBJECT (dec, "feeding %u bytes to decoder "
|
|
"(available=%" G_GSIZE_FORMAT ", bytes=%u)",
|
|
len, gst_adapter_available (dec->adapter), (guint) * bytes);
|
|
gst_adapter_copy (dec->adapter, buffer, 0, len);
|
|
*bytes = len;
|
|
|
|
gst_adapter_flush (dec->adapter, len);
|
|
|
|
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
|
}
|
|
|
|
static FLAC__StreamDecoderWriteStatus
|
|
gst_flac_dec_write (GstFlacDec * flacdec, const FLAC__Frame * frame,
|
|
const FLAC__int32 * const buffer[])
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBuffer *outbuf;
|
|
guint depth = frame->header.bits_per_sample;
|
|
guint width, gdepth;
|
|
guint sample_rate = frame->header.sample_rate;
|
|
guint channels = frame->header.channels;
|
|
guint samples = frame->header.blocksize;
|
|
guint j, i;
|
|
GstMapInfo map;
|
|
gboolean caps_changed;
|
|
GstAudioChannelPosition chanpos[8];
|
|
|
|
GST_LOG_OBJECT (flacdec, "samples in frame header: %d", samples);
|
|
|
|
if (depth == 0) {
|
|
if (flacdec->depth < 4 || flacdec->depth > 32) {
|
|
GST_ERROR_OBJECT (flacdec, "unsupported depth %d from STREAMINFO",
|
|
flacdec->depth);
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
depth = flacdec->depth;
|
|
}
|
|
|
|
switch (depth) {
|
|
case 8:
|
|
gdepth = width = 8;
|
|
break;
|
|
case 12:
|
|
case 16:
|
|
gdepth = width = 16;
|
|
break;
|
|
case 20:
|
|
case 24:
|
|
gdepth = 24;
|
|
width = 32;
|
|
break;
|
|
case 32:
|
|
gdepth = width = 32;
|
|
break;
|
|
default:
|
|
GST_ERROR_OBJECT (flacdec, "unsupported depth %d", depth);
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
if (sample_rate == 0) {
|
|
if (flacdec->info.rate != 0) {
|
|
sample_rate = flacdec->info.rate;
|
|
} else {
|
|
GST_ERROR_OBJECT (flacdec, "unknown sample rate");
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
caps_changed = (sample_rate != GST_AUDIO_INFO_RATE (&flacdec->info))
|
|
|| (width != GST_AUDIO_INFO_WIDTH (&flacdec->info))
|
|
|| (gdepth != GST_AUDIO_INFO_DEPTH (&flacdec->info))
|
|
|| (channels != GST_AUDIO_INFO_CHANNELS (&flacdec->info));
|
|
|
|
if (caps_changed
|
|
|| !gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (flacdec))) {
|
|
GST_DEBUG_OBJECT (flacdec, "Negotiating %d Hz @ %d channels", sample_rate,
|
|
channels);
|
|
|
|
memcpy (chanpos, channel_positions[flacdec->info.channels - 1],
|
|
sizeof (chanpos));
|
|
gst_audio_channel_positions_to_valid_order (chanpos,
|
|
flacdec->info.channels);
|
|
gst_audio_info_set_format (&flacdec->info,
|
|
gst_audio_format_build_integer (TRUE, G_BYTE_ORDER, width, gdepth),
|
|
sample_rate, channels, chanpos);
|
|
|
|
/* Note: we create the inverse reordering map here */
|
|
gst_audio_get_channel_reorder_map (flacdec->info.channels,
|
|
flacdec->info.position, channel_positions[flacdec->info.channels - 1],
|
|
flacdec->channel_reorder_map);
|
|
|
|
flacdec->depth = depth;
|
|
|
|
gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (flacdec),
|
|
&flacdec->info);
|
|
}
|
|
|
|
outbuf =
|
|
gst_buffer_new_allocate (NULL, samples * channels * (width / 8), NULL);
|
|
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
if (width == 8) {
|
|
gint8 *outbuffer = (gint8 *) map.data;
|
|
gint *reorder_map = flacdec->channel_reorder_map;
|
|
|
|
g_assert (gdepth == 8 && depth == 8);
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint8) buffer[reorder_map[j]][i];
|
|
}
|
|
}
|
|
} else if (width == 16) {
|
|
gint16 *outbuffer = (gint16 *) map.data;
|
|
gint *reorder_map = flacdec->channel_reorder_map;
|
|
|
|
if (gdepth != depth) {
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ =
|
|
(gint16) (buffer[reorder_map[j]][i] << (gdepth - depth));
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint16) buffer[reorder_map[j]][i];
|
|
}
|
|
}
|
|
}
|
|
} else if (width == 32) {
|
|
gint32 *outbuffer = (gint32 *) map.data;
|
|
gint *reorder_map = flacdec->channel_reorder_map;
|
|
|
|
if (gdepth != depth) {
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ =
|
|
(gint32) (buffer[reorder_map[j]][i] << (gdepth - depth));
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint32) buffer[reorder_map[j]][i];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
gst_buffer_unmap (outbuf, &map);
|
|
|
|
GST_DEBUG_OBJECT (flacdec, "pushing %d samples", samples);
|
|
if (flacdec->error_count)
|
|
flacdec->error_count--;
|
|
|
|
ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (flacdec), outbuf, 1);
|
|
|
|
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
|
|
GST_DEBUG_OBJECT (flacdec, "finish_frame flow %s", gst_flow_get_name (ret));
|
|
}
|
|
|
|
done:
|
|
|
|
/* we act on the flow return value later in the handle_frame function, as we
|
|
* don't want to mess up the internal decoder state by returning ABORT when
|
|
* the error is in fact non-fatal (like a pad in flushing mode) and we want
|
|
* to continue later. So just pretend everything's dandy and act later. */
|
|
flacdec->last_flow = ret;
|
|
|
|
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
|
}
|
|
|
|
static FLAC__StreamDecoderWriteStatus
|
|
gst_flac_dec_write_stream (const FLAC__StreamDecoder * decoder,
|
|
const FLAC__Frame * frame,
|
|
const FLAC__int32 * const buffer[], void *client_data)
|
|
{
|
|
return gst_flac_dec_write (GST_FLAC_DEC (client_data), frame, buffer);
|
|
}
|
|
|
|
static void
|
|
gst_flac_dec_flush (GstAudioDecoder * audio_dec, gboolean hard)
|
|
{
|
|
GstFlacDec *dec = GST_FLAC_DEC (audio_dec);
|
|
|
|
if (!hard) {
|
|
guint available = gst_adapter_available (dec->adapter);
|
|
|
|
if (available > 0) {
|
|
GST_INFO_OBJECT (dec, "draining, %u bytes left in adapter", available);
|
|
FLAC__stream_decoder_process_until_end_of_stream (dec->decoder);
|
|
}
|
|
}
|
|
|
|
FLAC__stream_decoder_flush (dec->decoder);
|
|
gst_adapter_clear (dec->adapter);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flac_dec_handle_frame (GstAudioDecoder * audio_dec, GstBuffer * buf)
|
|
{
|
|
GstFlacDec *dec;
|
|
|
|
dec = GST_FLAC_DEC (audio_dec);
|
|
|
|
/* drain remaining data? */
|
|
if (G_UNLIKELY (buf == NULL)) {
|
|
gst_flac_dec_flush (audio_dec, FALSE);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
GST_LOG_OBJECT (dec, "frame: ts %" GST_TIME_FORMAT ", flags 0x%04x, "
|
|
"%" G_GSIZE_FORMAT " bytes", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
|
|
GST_BUFFER_FLAGS (buf), gst_buffer_get_size (buf));
|
|
|
|
/* drop any in-stream headers, we've processed those in set_format already */
|
|
if (G_UNLIKELY (!dec->got_headers)) {
|
|
gboolean got_audio_frame;
|
|
GstMapInfo map;
|
|
|
|
/* check if this is a flac audio frame (rather than a header or junk) */
|
|
gst_buffer_map (buf, &map, GST_MAP_READ);
|
|
got_audio_frame = gst_flac_dec_scan_got_frame (dec, map.data, map.size);
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
if (!got_audio_frame) {
|
|
GST_INFO_OBJECT (dec, "dropping in-stream header, %" G_GSIZE_FORMAT " "
|
|
"bytes", map.size);
|
|
gst_audio_decoder_finish_frame (audio_dec, NULL, 1);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
GST_INFO_OBJECT (dec, "first audio frame, got all in-stream headers now");
|
|
dec->got_headers = TRUE;
|
|
}
|
|
|
|
gst_adapter_push (dec->adapter, gst_buffer_ref (buf));
|
|
buf = NULL;
|
|
|
|
dec->last_flow = GST_FLOW_OK;
|
|
|
|
/* framed - there should always be enough data to decode something */
|
|
GST_LOG_OBJECT (dec, "%" G_GSIZE_FORMAT " bytes available",
|
|
gst_adapter_available (dec->adapter));
|
|
|
|
if (!FLAC__stream_decoder_process_single (dec->decoder)) {
|
|
GST_INFO_OBJECT (dec, "process_single failed");
|
|
if (FLAC__stream_decoder_get_state (dec->decoder) ==
|
|
FLAC__STREAM_DECODER_ABORTED) {
|
|
GST_WARNING_OBJECT (dec, "Read callback caused internal abort");
|
|
/* allow recovery */
|
|
gst_adapter_clear (dec->adapter);
|
|
FLAC__stream_decoder_flush (dec->decoder);
|
|
gst_flac_dec_handle_decoder_error (dec, TRUE);
|
|
}
|
|
}
|
|
|
|
return dec->last_flow;
|
|
}
|