mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 01:30:38 +00:00
a49818f876
Not that this seems to be actually needed, libflac happily decodes stuff even if we just drop all headers and never feed it to the library.
735 lines
23 KiB
C
735 lines
23 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, 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-0.11 filesrc location=media/small/dark.441-16-s.flac ! flacparse ! flacdec ! audioconvert ! audioresample ! autoaudiosink
|
|
* ]|
|
|
* |[
|
|
* gst-launch-0.11 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/audio/multichannel.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_FRONT_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_LFE,
|
|
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_LFE,
|
|
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_LFE,
|
|
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 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 "{ S8LE, S16LE, S32LE } "
|
|
#else
|
|
#define FORMATS "{ S8BE, S16BE, S32BE } "
|
|
#endif
|
|
|
|
/* FIXME 0.11: Use width=32 for all depths and let audioconvert
|
|
* handle the conversions instead of doing it ourself.
|
|
*/
|
|
#define GST_FLAC_DEC_SRC_CAPS \
|
|
"audio/x-raw, " \
|
|
"format = (string) " FORMATS ", " \
|
|
"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_details_simple (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)
|
|
{
|
|
/* nothing to do here */
|
|
}
|
|
|
|
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 ();
|
|
|
|
/* 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 %d 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");
|
|
}
|
|
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 (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, guint8 * data, guint size,
|
|
gint64 * last_sample_num)
|
|
{
|
|
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;
|
|
|
|
if (size < 10)
|
|
return FALSE;
|
|
|
|
/* sync */
|
|
if (data[0] != 0xFF || (data[1] & 0xFC) != 0xF8)
|
|
return FALSE;
|
|
if (data[1] & 1) {
|
|
GST_WARNING_OBJECT (flacdec, "Variable block size FLAC unsupported");
|
|
return FALSE;
|
|
}
|
|
|
|
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, bs=%x,sr=%x,ca=%x,ss=%x,pb=%x", 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;
|
|
|
|
/* FIXME: This is can be 36 bit if variable block size is used,
|
|
* fortunately not encoder supports this yet and we check for that
|
|
* above.
|
|
*/
|
|
val = (guint32) g_utf8_get_char_validated ((gchar *) data + 4, -1);
|
|
|
|
if (val == (guint32) - 1 || val == (guint32) - 2) {
|
|
GST_LOG_OBJECT (flacdec, "failed to read sample/frame");
|
|
return FALSE;
|
|
}
|
|
|
|
headerlen = 4 + g_unichar_to_utf8 ((gunichar) val, NULL) +
|
|
(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;
|
|
}
|
|
|
|
if (flacdec->min_blocksize == flacdec->max_blocksize) {
|
|
*last_sample_num = (val + 1) * flacdec->min_blocksize;
|
|
} else {
|
|
*last_sample_num = 0; /* FIXME: + length of last block in samples */
|
|
}
|
|
|
|
/* FIXME: only valid for fixed block size streams */
|
|
GST_DEBUG_OBJECT (flacdec, "frame number: %" G_GINT64_FORMAT,
|
|
*last_sample_num);
|
|
|
|
if (flacdec->sample_rate > 0 && *last_sample_num != 0) {
|
|
GST_DEBUG_OBJECT (flacdec, "last sample %" G_GINT64_FORMAT " = %"
|
|
GST_TIME_FORMAT, *last_sample_num,
|
|
GST_TIME_ARGS (*last_sample_num * GST_SECOND / flacdec->sample_rate));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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);
|
|
|
|
GST_LOG_OBJECT (flacdec, "metadata type: %d", metadata->type);
|
|
|
|
switch (metadata->type) {
|
|
case FLAC__METADATA_TYPE_STREAMINFO:{
|
|
gint64 samples;
|
|
guint depth;
|
|
|
|
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->sample_rate = metadata->data.stream_info.sample_rate;
|
|
flacdec->depth = depth = metadata->data.stream_info.bits_per_sample;
|
|
flacdec->channels = metadata->data.stream_info.channels;
|
|
|
|
if (depth < 9)
|
|
flacdec->width = 8;
|
|
else if (depth < 17)
|
|
flacdec->width = 16;
|
|
else
|
|
flacdec->width = 32;
|
|
|
|
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->sample_rate, flacdec->channels);
|
|
GST_DEBUG_OBJECT (flacdec, "depth: %u, width: %u", flacdec->depth,
|
|
flacdec->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;
|
|
}
|
|
|
|
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("%s (%d)", error, status));
|
|
dec->last_flow = GST_FLOW_ERROR;
|
|
}
|
|
|
|
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=%u, 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;
|
|
guint sample_rate = frame->header.sample_rate;
|
|
guint channels = frame->header.channels;
|
|
guint samples = frame->header.blocksize;
|
|
guint j, i;
|
|
gpointer data;
|
|
gsize size;
|
|
const gchar *format;
|
|
|
|
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;
|
|
if (depth < 9)
|
|
depth = 8;
|
|
else if (depth < 17)
|
|
depth = 16;
|
|
else
|
|
depth = 32;
|
|
}
|
|
|
|
switch (depth) {
|
|
case 8:
|
|
width = 8;
|
|
format = GST_AUDIO_NE (S8);
|
|
break;
|
|
case 12:
|
|
case 16:
|
|
width = 16;
|
|
format = GST_AUDIO_NE (S16);
|
|
break;
|
|
case 20:
|
|
case 24:
|
|
case 32:
|
|
width = 32;
|
|
format = GST_AUDIO_NE (S32);
|
|
break;
|
|
default:
|
|
GST_ERROR_OBJECT (flacdec, "unsupported depth %d", depth);
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
if (sample_rate == 0) {
|
|
if (flacdec->sample_rate != 0) {
|
|
sample_rate = flacdec->sample_rate;
|
|
} else {
|
|
GST_ERROR_OBJECT (flacdec, "unknown sample rate");
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (!gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (flacdec))) {
|
|
GstCaps *caps;
|
|
|
|
GST_DEBUG_OBJECT (flacdec, "Negotiating %d Hz @ %d channels",
|
|
frame->header.sample_rate, channels);
|
|
|
|
caps = gst_caps_new_simple ("audio/x-raw",
|
|
"format", G_TYPE_STRING, format,
|
|
"rate", G_TYPE_INT, frame->header.sample_rate,
|
|
"channels", G_TYPE_INT, channels, NULL);
|
|
|
|
if (channels > 2) {
|
|
GstStructure *s = gst_caps_get_structure (caps, 0);
|
|
|
|
gst_audio_set_channel_positions (s, channel_positions[channels - 1]);
|
|
}
|
|
|
|
flacdec->depth = depth;
|
|
flacdec->width = width;
|
|
flacdec->channels = channels;
|
|
flacdec->sample_rate = sample_rate;
|
|
|
|
gst_audio_decoder_set_outcaps (GST_AUDIO_DECODER (flacdec), caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
GST_LOG_OBJECT (flacdec, "alloc_buffer_and_set_caps");
|
|
outbuf = gst_buffer_new_allocate (NULL, samples * channels * (width / 8), 0);
|
|
|
|
data = gst_buffer_map (outbuf, &size, NULL, GST_MAP_WRITE);
|
|
if (width == 8) {
|
|
gint8 *outbuffer = (gint8 *) data;
|
|
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint8) buffer[j][i];
|
|
}
|
|
}
|
|
} else if (width == 16) {
|
|
gint16 *outbuffer = (gint16 *) data;
|
|
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint16) buffer[j][i];
|
|
}
|
|
}
|
|
} else if (width == 32) {
|
|
gint32 *outbuffer = (gint32 *) data;
|
|
|
|
for (i = 0; i < samples; i++) {
|
|
for (j = 0; j < channels; j++) {
|
|
*outbuffer++ = (gint32) buffer[j][i];
|
|
}
|
|
}
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
gst_buffer_unmap (outbuf, data, size);
|
|
|
|
GST_DEBUG_OBJECT (flacdec, "pushing %d samples", samples);
|
|
|
|
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, %u 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;
|
|
gint64 unused;
|
|
guint8 *data;
|
|
gsize size;
|
|
|
|
/* check if this is a flac audio frame (rather than a header or junk) */
|
|
data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
|
|
got_audio_frame = gst_flac_dec_scan_got_frame (dec, data, size, &unused);
|
|
gst_buffer_unmap (buf, data, size);
|
|
|
|
if (!got_audio_frame) {
|
|
GST_INFO_OBJECT (dec, "dropping in-stream header, %d bytes", size);
|
|
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, "%u bytes available",
|
|
gst_adapter_available (dec->adapter));
|
|
|
|
if (!FLAC__stream_decoder_process_single (dec->decoder)) {
|
|
GST_INFO_OBJECT (dec, "process_single failed");
|
|
}
|
|
|
|
return dec->last_flow;
|
|
}
|