gstreamer/gst/audioparsers/gstwavpackparse.c
Sebastian Dröge ca4b5d795b audioparsers: Fix GstBaseParse::get_sink_caps() implementations
They should take the filter caps into account and always return
the template caps appended to the actual caps. Otherwise the
parsers stop to accept unparsed streams where upstream does not
know about channels, rate, etc.

Fixes bug #677401.
2012-06-05 09:21:08 +02:00

654 lines
20 KiB
C

/* GStreamer Wavpack parser
* Copyright (C) 2012 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
* Copyright (C) 2012 Nokia Corporation. All rights reserved.
* Contact: Stefan Kost <stefan.kost@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-wavpackparse
* @short_description: Wavpack parser
* @see_also: #GstAmrParse, #GstAACParse
*
* This is an Wavpack parser.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch filesrc location=abc.wavpack ! wavpackparse ! wavpackdec ! audioresample ! audioconvert ! autoaudiosink
* ]|
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "gstwavpackparse.h"
#include <gst/base/gstbytereader.h>
#include <gst/audio/audio.h>
GST_DEBUG_CATEGORY_STATIC (wavpack_parse_debug);
#define GST_CAT_DEFAULT wavpack_parse_debug
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-wavpack, "
"depth = (int) [ 1, 32 ], "
"channels = (int) [ 1, 8 ], "
"rate = (int) [ 6000, 192000 ], " "framed = (boolean) TRUE; "
"audio/x-wavpack-correction, " "framed = (boolean) TRUE")
);
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-wavpack"));
static void gst_wavpack_parse_finalize (GObject * object);
static gboolean gst_wavpack_parse_start (GstBaseParse * parse);
static gboolean gst_wavpack_parse_stop (GstBaseParse * parse);
static GstFlowReturn gst_wavpack_parse_handle_frame (GstBaseParse * parse,
GstBaseParseFrame * frame, gint * skipsize);
static GstCaps *gst_wavpack_parse_get_sink_caps (GstBaseParse * parse,
GstCaps * filter);
#define gst_wavpack_parse_parent_class parent_class
G_DEFINE_TYPE (GstWavpackParse, gst_wavpack_parse, GST_TYPE_BASE_PARSE);
static void
gst_wavpack_parse_class_init (GstWavpackParseClass * klass)
{
GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (wavpack_parse_debug, "wavpackparse", 0,
"Wavpack audio stream parser");
object_class->finalize = gst_wavpack_parse_finalize;
parse_class->start = GST_DEBUG_FUNCPTR (gst_wavpack_parse_start);
parse_class->stop = GST_DEBUG_FUNCPTR (gst_wavpack_parse_stop);
parse_class->handle_frame =
GST_DEBUG_FUNCPTR (gst_wavpack_parse_handle_frame);
parse_class->get_sink_caps =
GST_DEBUG_FUNCPTR (gst_wavpack_parse_get_sink_caps);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_template));
gst_element_class_set_static_metadata (element_class,
"Wavpack audio stream parser", "Codec/Parser/Audio",
"Wavpack parser", "Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>");
}
static void
gst_wavpack_parse_reset (GstWavpackParse * wvparse)
{
wvparse->channels = -1;
wvparse->channel_mask = 0;
wvparse->sample_rate = -1;
wvparse->width = -1;
wvparse->total_samples = 0;
}
static void
gst_wavpack_parse_init (GstWavpackParse * wvparse)
{
gst_wavpack_parse_reset (wvparse);
}
static void
gst_wavpack_parse_finalize (GObject * object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_wavpack_parse_start (GstBaseParse * parse)
{
GstWavpackParse *wvparse = GST_WAVPACK_PARSE (parse);
GST_DEBUG_OBJECT (parse, "starting");
gst_wavpack_parse_reset (wvparse);
/* need header at least */
gst_base_parse_set_min_frame_size (GST_BASE_PARSE (wvparse),
sizeof (WavpackHeader));
/* inform baseclass we can come up with ts, based on counters in packets */
gst_base_parse_set_has_timing_info (GST_BASE_PARSE_CAST (wvparse), TRUE);
gst_base_parse_set_syncable (GST_BASE_PARSE_CAST (wvparse), TRUE);
return TRUE;
}
static gboolean
gst_wavpack_parse_stop (GstBaseParse * parse)
{
GST_DEBUG_OBJECT (parse, "stopping");
return TRUE;
}
static gint
gst_wavpack_get_default_channel_mask (gint nchannels)
{
gint channel_mask = 0;
/* Set the default channel mask for the given number of channels.
* It's the same as for WAVE_FORMAT_EXTENDED:
* http://www.microsoft.com/whdc/device/audio/multichaud.mspx
*/
switch (nchannels) {
case 11:
channel_mask |= 0x00400;
channel_mask |= 0x00200;
case 9:
channel_mask |= 0x00100;
case 8:
channel_mask |= 0x00080;
channel_mask |= 0x00040;
case 6:
channel_mask |= 0x00020;
channel_mask |= 0x00010;
case 4:
channel_mask |= 0x00008;
case 3:
channel_mask |= 0x00004;
case 2:
channel_mask |= 0x00002;
channel_mask |= 0x00001;
break;
case 1:
/* For mono use front center */
channel_mask |= 0x00004;
break;
}
return channel_mask;
}
static const struct
{
const guint32 ms_mask;
const GstAudioChannelPosition gst_pos;
} layout_mapping[] = {
{
0x00001, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT}, {
0x00002, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
0x00004, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, {
0x00008, GST_AUDIO_CHANNEL_POSITION_LFE1}, {
0x00010, GST_AUDIO_CHANNEL_POSITION_REAR_LEFT}, {
0x00020, GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
0x00040, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER}, {
0x00080, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, {
0x00100, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, {
0x00200, GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT}, {
0x00400, GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}, {
0x00800, GST_AUDIO_CHANNEL_POSITION_TOP_CENTER}, {
0x01000, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT}, {
0x02000, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER}, {
0x04000, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT}, {
0x08000, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT}, {
0x10000, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER}, {
0x20000, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT}
};
#define MAX_CHANNEL_POSITIONS G_N_ELEMENTS (layout_mapping)
static gboolean
gst_wavpack_get_channel_positions (gint num_channels, gint layout,
GstAudioChannelPosition * pos)
{
gint i, p;
if (num_channels == 1 && layout == 0x00004) {
pos[0] = GST_AUDIO_CHANNEL_POSITION_MONO;
return TRUE;
}
p = 0;
for (i = 0; i < MAX_CHANNEL_POSITIONS; ++i) {
if ((layout & layout_mapping[i].ms_mask) != 0) {
if (p >= num_channels) {
GST_WARNING ("More bits set in the channel layout map than there "
"are channels! Broken file");
return FALSE;
}
if (layout_mapping[i].gst_pos == GST_AUDIO_CHANNEL_POSITION_INVALID) {
GST_WARNING ("Unsupported channel position (mask 0x%08x) in channel "
"layout map - ignoring those channels", layout_mapping[i].ms_mask);
/* what to do? just ignore it and let downstream deal with a channel
* layout that has INVALID positions in it for now ... */
}
pos[p] = layout_mapping[i].gst_pos;
++p;
}
}
if (p != num_channels) {
GST_WARNING ("Only %d bits set in the channel layout map, but there are "
"supposed to be %d channels! Broken file", p, num_channels);
return FALSE;
}
return TRUE;
}
static const guint32 sample_rates[] = {
6000, 8000, 9600, 11025, 12000, 16000, 22050,
24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000
};
#define CHECK(call) { \
if (!call) \
goto read_failed; \
}
/* caller ensures properly sync'ed with enough data */
static gboolean
gst_wavpack_parse_frame_metadata (GstWavpackParse * parse, GstBuffer * buf,
gint skip, WavpackHeader * wph, WavpackInfo * wpi)
{
GstByteReader br;
gint i;
GstMapInfo map;
g_return_val_if_fail (wph != NULL || wpi != NULL, FALSE);
g_return_val_if_fail (gst_buffer_get_size (buf) >=
skip + sizeof (WavpackHeader), FALSE);
gst_buffer_map (buf, &map, GST_MAP_READ);
gst_byte_reader_init (&br, map.data + skip, wph->ckSize + 8);
/* skip past header */
gst_byte_reader_skip_unchecked (&br, sizeof (WavpackHeader));
/* get some basics from header */
i = (wph->flags >> 23) & 0xF;
if (!wpi->rate)
wpi->rate = (i < G_N_ELEMENTS (sample_rates)) ? sample_rates[i] : 44100;
wpi->width = ((wph->flags & 0x3) + 1) * 8;
if (!wpi->channels)
wpi->channels = (wph->flags & 0x4) ? 1 : 2;
if (!wpi->channel_mask)
wpi->channel_mask = 5 - wpi->channels;
/* need to dig metadata blocks for some more */
while (gst_byte_reader_get_remaining (&br)) {
gint size = 0;
guint16 size2 = 0;
guint8 c, id;
const guint8 *data;
GstByteReader mbr;
CHECK (gst_byte_reader_get_uint8 (&br, &id));
CHECK (gst_byte_reader_get_uint8 (&br, &c));
if (id & ID_LARGE)
CHECK (gst_byte_reader_get_uint16_le (&br, &size2));
size = size2;
size <<= 8;
size += c;
size <<= 1;
if (id & ID_ODD_SIZE)
size--;
CHECK (gst_byte_reader_get_data (&br, size + (size & 1), &data));
gst_byte_reader_init (&mbr, data, size);
switch (id) {
case ID_WVC_BITSTREAM:
GST_LOG_OBJECT (parse, "correction bitstream");
wpi->correction = TRUE;
break;
case ID_WV_BITSTREAM:
case ID_WVX_BITSTREAM:
break;
case ID_SAMPLE_RATE:
if (size == 3) {
CHECK (gst_byte_reader_get_uint24_le (&mbr, &wpi->rate));
GST_LOG_OBJECT (parse, "updated with custom rate %d", wpi->rate);
} else {
GST_DEBUG_OBJECT (parse, "unexpected size for SAMPLE_RATE metadata");
}
break;
case ID_CHANNEL_INFO:
{
guint16 channels;
guint32 mask = 0;
if (size == 6) {
CHECK (gst_byte_reader_get_uint16_le (&mbr, &channels));
channels = channels & 0xFFF;
CHECK (gst_byte_reader_get_uint24_le (&mbr, &mask));
} else if (size) {
CHECK (gst_byte_reader_get_uint8 (&mbr, &c));
channels = c;
while (gst_byte_reader_get_uint8 (&mbr, &c))
mask |= (((guint32) c) << 8);
} else {
GST_DEBUG_OBJECT (parse, "unexpected size for CHANNEL_INFO metadata");
break;
}
wpi->channels = channels;
wpi->channel_mask = mask;
break;
}
default:
GST_LOG_OBJECT (parse, "unparsed ID 0x%x", id);
break;
}
}
gst_buffer_unmap (buf, &map);
return TRUE;
/* ERRORS */
read_failed:
{
gst_buffer_unmap (buf, &map);
GST_DEBUG_OBJECT (parse, "short read while parsing metadata");
/* let's look the other way anyway */
return TRUE;
}
}
/* caller ensures properly sync'ed with enough data */
static gboolean
gst_wavpack_parse_frame_header (GstWavpackParse * parse, GstBuffer * buf,
gint skip, WavpackHeader * _wph)
{
GstByteReader br;
WavpackHeader wph = { {0,}, 0, };
GstMapInfo map;
gboolean hdl = TRUE;
g_return_val_if_fail (gst_buffer_get_size (buf) >=
skip + sizeof (WavpackHeader), FALSE);
gst_buffer_map (buf, &map, GST_MAP_READ);
gst_byte_reader_init (&br, map.data, map.size);
/* marker */
gst_byte_reader_skip_unchecked (&br, skip + 4);
/* read */
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.ckSize);
hdl &= gst_byte_reader_get_uint16_le (&br, &wph.version);
hdl &= gst_byte_reader_get_uint8 (&br, &wph.track_no);
hdl &= gst_byte_reader_get_uint8 (&br, &wph.index_no);
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.total_samples);
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.block_index);
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.block_samples);
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.flags);
hdl &= gst_byte_reader_get_uint32_le (&br, &wph.crc);
if (!hdl)
GST_WARNING_OBJECT (parse, "Error reading header");
/* dump */
GST_LOG_OBJECT (parse, "size %d", wph.ckSize);
GST_LOG_OBJECT (parse, "version 0x%x", wph.version);
GST_LOG_OBJECT (parse, "total samples %d", wph.total_samples);
GST_LOG_OBJECT (parse, "block index %d", wph.block_index);
GST_LOG_OBJECT (parse, "block samples %d", wph.block_samples);
GST_LOG_OBJECT (parse, "flags 0x%x", wph.flags);
GST_LOG_OBJECT (parse, "crc 0x%x", wph.flags);
if (!parse->total_samples && wph.block_index == 0 && wph.total_samples != -1) {
GST_DEBUG_OBJECT (parse, "determined duration of %u samples",
wph.total_samples);
parse->total_samples = wph.total_samples;
}
if (_wph)
*_wph = wph;
gst_buffer_unmap (buf, &map);
return TRUE;
}
static GstFlowReturn
gst_wavpack_parse_handle_frame (GstBaseParse * parse,
GstBaseParseFrame * frame, gint * skipsize)
{
GstWavpackParse *wvparse = GST_WAVPACK_PARSE (parse);
GstBuffer *buf = frame->buffer;
GstByteReader reader;
gint off;
guint rate, chans, width, mask;
gboolean lost_sync, draining, final;
guint frmsize = 0;
WavpackHeader wph;
WavpackInfo wpi = { 0, };
GstMapInfo map;
if (G_UNLIKELY (gst_buffer_get_size (buf) < sizeof (WavpackHeader)))
return FALSE;
gst_buffer_map (buf, &map, GST_MAP_READ);
gst_byte_reader_init (&reader, map.data, map.size);
/* scan for 'wvpk' marker */
off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff, 0x7776706b,
0, map.size);
GST_LOG_OBJECT (parse, "possible sync at buffer offset %d", off);
/* didn't find anything that looks like a sync word, skip */
if (off < 0) {
*skipsize = map.size - 3;
goto skip;
}
/* possible frame header, but not at offset 0? skip bytes before sync */
if (off > 0) {
*skipsize = off;
goto skip;
}
/* make sure the values in the frame header look sane */
gst_wavpack_parse_frame_header (wvparse, buf, 0, &wph);
frmsize = wph.ckSize + 8;
/* need the entire frame for parsing */
if (gst_byte_reader_get_remaining (&reader) < frmsize)
goto more;
/* got a frame, now we can dig for some more metadata */
GST_LOG_OBJECT (parse, "got frame");
gst_wavpack_parse_frame_metadata (wvparse, buf, 0, &wph, &wpi);
lost_sync = GST_BASE_PARSE_LOST_SYNC (parse);
draining = GST_BASE_PARSE_DRAINING (parse);
while (!(final = (wph.flags & FLAG_FINAL_BLOCK)) || (lost_sync && !draining)) {
guint32 word = 0;
GST_LOG_OBJECT (wvparse, "checking next frame syncword; "
"lost_sync: %d, draining: %d, final: %d", lost_sync, draining, final);
if (!gst_byte_reader_skip (&reader, wph.ckSize + 8) ||
!gst_byte_reader_peek_uint32_be (&reader, &word)) {
GST_DEBUG_OBJECT (wvparse, "... but not sufficient data");
frmsize += 4;
goto more;
} else {
if (word != 0x7776706b) {
GST_DEBUG_OBJECT (wvparse, "0x%x not OK", word);
*skipsize = off + 2;
goto skip;
}
/* need to parse each frame/block for metadata if several ones */
if (!final) {
gint av;
GST_LOG_OBJECT (wvparse, "checking frame at offset %d (0x%x)",
frmsize, frmsize);
av = gst_byte_reader_get_remaining (&reader);
if (av < sizeof (WavpackHeader)) {
frmsize += sizeof (WavpackHeader);
goto more;
}
gst_wavpack_parse_frame_header (wvparse, buf, frmsize, &wph);
off = frmsize;
frmsize += wph.ckSize + 8;
if (av < wph.ckSize + 8)
goto more;
gst_wavpack_parse_frame_metadata (wvparse, buf, off, &wph, &wpi);
/* could also check for matching block_index and block_samples ?? */
}
}
/* resynced if we make it here */
lost_sync = FALSE;
}
rate = wpi.rate;
width = wpi.width;
chans = wpi.channels;
mask = wpi.channel_mask;
GST_LOG_OBJECT (parse, "rate: %u, width: %u, chans: %u", rate, width, chans);
GST_BUFFER_TIMESTAMP (buf) =
gst_util_uint64_scale_int (wph.block_index, GST_SECOND, rate);
GST_BUFFER_DURATION (buf) =
gst_util_uint64_scale_int (wph.block_index + wph.block_samples,
GST_SECOND, rate) - GST_BUFFER_TIMESTAMP (buf);
if (G_UNLIKELY (wvparse->sample_rate != rate || wvparse->channels != chans
|| wvparse->width != width || wvparse->channel_mask != mask)) {
GstCaps *caps;
if (wpi.correction) {
caps = gst_caps_new_simple ("audio/x-wavpack-correction",
"framed", G_TYPE_BOOLEAN, TRUE, NULL);
} else {
caps = gst_caps_new_simple ("audio/x-wavpack",
"channels", G_TYPE_INT, chans,
"rate", G_TYPE_INT, rate,
"depth", G_TYPE_INT, width, "framed", G_TYPE_BOOLEAN, TRUE, NULL);
if (!mask)
mask = gst_wavpack_get_default_channel_mask (wvparse->channels);
if (mask != 0) {
GstAudioChannelPosition pos[64] =
{ GST_AUDIO_CHANNEL_POSITION_INVALID, };
guint64 gmask;
if (!gst_wavpack_get_channel_positions (chans, mask, pos)) {
GST_WARNING_OBJECT (wvparse, "Failed to determine channel layout");
} else {
gst_audio_channel_positions_to_mask (pos, chans, &gmask);
if (gmask)
gst_caps_set_simple (caps,
"channel-mask", GST_TYPE_BITMASK, gmask, NULL);
}
}
}
gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps);
gst_caps_unref (caps);
wvparse->sample_rate = rate;
wvparse->channels = chans;
wvparse->width = width;
wvparse->channel_mask = mask;
if (wvparse->total_samples) {
GST_DEBUG_OBJECT (wvparse, "setting duration");
gst_base_parse_set_duration (GST_BASE_PARSE (wvparse),
GST_FORMAT_TIME, gst_util_uint64_scale_int (wvparse->total_samples,
GST_SECOND, wvparse->sample_rate), 0);
}
}
/* return to normal size */
gst_base_parse_set_min_frame_size (parse, sizeof (WavpackHeader));
gst_buffer_unmap (buf, &map);
return gst_base_parse_finish_frame (parse, frame, frmsize);
skip:
gst_buffer_unmap (buf, &map);
GST_LOG_OBJECT (wvparse, "skipping %d", *skipsize);
return GST_FLOW_OK;
more:
gst_buffer_unmap (buf, &map);
GST_LOG_OBJECT (wvparse, "need at least %u", frmsize);
gst_base_parse_set_min_frame_size (parse, frmsize);
*skipsize = 0;
return GST_FLOW_OK;
}
static GstCaps *
gst_wavpack_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));
peercaps = gst_pad_get_allowed_caps (GST_BASE_PARSE_SRC_PAD (parse));
if (peercaps) {
guint i, n;
/* Remove the framed field */
peercaps = gst_caps_make_writable (peercaps);
n = gst_caps_get_size (peercaps);
for (i = 0; i < n; i++) {
GstStructure *s = gst_caps_get_structure (peercaps, i);
gst_structure_remove_field (s, "framed");
}
res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (peercaps);
/* Append the template caps because we still want to accept
* caps without any fields in the case upstream does not
* know anything.
*/
gst_caps_append (res, 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;
}