mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-29 19:50:40 +00:00
0d88e3553a
Accept wavpack correction streams (.wvc) on sink pad, so that wavpackparse can also be used to packetise correction streams. Fix parsing of subblock ID tags - the higher bits are flags and are not part of the ID. This resulted in correction blocks not being recognised properly and the output not having the right (correction) caps.
708 lines
21 KiB
C
708 lines
21 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, 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-1.0 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/base.h>
|
|
#include <gst/pbutils/pbutils.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; audio/x-wavpack-correction"));
|
|
|
|
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);
|
|
static GstFlowReturn gst_wavpack_parse_pre_push_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame);
|
|
|
|
#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);
|
|
parse_class->pre_push_frame =
|
|
GST_DEBUG_FUNCPTR (gst_wavpack_parse_pre_push_frame);
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &sink_template);
|
|
gst_element_class_add_static_pad_template (element_class, &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;
|
|
wvparse->sent_codec_tag = FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_wavpack_parse_init (GstWavpackParse * wvparse)
|
|
{
|
|
gst_wavpack_parse_reset (wvparse);
|
|
GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (wvparse));
|
|
GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (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);
|
|
|
|
/* 0x1f is the metadata id mask and 0x20 flag is for later extensions
|
|
* that do not need to be handled by the decoder */
|
|
switch (id & 0x3f) {
|
|
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_PTS (buf) =
|
|
gst_util_uint64_scale_int (wph.block_index, GST_SECOND, rate);
|
|
GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf);
|
|
GST_BUFFER_DURATION (buf) =
|
|
gst_util_uint64_scale_int (wph.block_index + wph.block_samples,
|
|
GST_SECOND, rate) - GST_BUFFER_PTS (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, FALSE, &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 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_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));
|
|
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 GstFlowReturn
|
|
gst_wavpack_parse_pre_push_frame (GstBaseParse * parse,
|
|
GstBaseParseFrame * frame)
|
|
{
|
|
GstWavpackParse *wavpackparse = GST_WAVPACK_PARSE (parse);
|
|
|
|
if (!wavpackparse->sent_codec_tag) {
|
|
GstTagList *taglist;
|
|
GstCaps *caps;
|
|
|
|
/* 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;
|
|
} else {
|
|
GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
}
|
|
|
|
taglist = gst_tag_list_new_empty ();
|
|
gst_pb_utils_add_codec_description_to_tag_list (taglist,
|
|
GST_TAG_AUDIO_CODEC, caps);
|
|
gst_caps_unref (caps);
|
|
|
|
gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE);
|
|
gst_tag_list_unref (taglist);
|
|
|
|
/* also signals the end of first-frame processing */
|
|
wavpackparse->sent_codec_tag = TRUE;
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|