diff options
Diffstat (limited to 'ext/wavpack/gstwavpackdec.c')
-rwxr-xr-x | ext/wavpack/gstwavpackdec.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/ext/wavpack/gstwavpackdec.c b/ext/wavpack/gstwavpackdec.c new file mode 100755 index 0000000..10659ec --- /dev/null +++ b/ext/wavpack/gstwavpackdec.c @@ -0,0 +1,483 @@ +/* GStreamer Wavpack plugin + * Copyright (c) 2005 Arwed v. Merkatz <v.merkatz@gmx.net> + * Copyright (c) 2006 Edward Hervey <bilboed@gmail.com> + * Copyright (c) 2006 Sebastian Dröge <slomo@circular-chaos.org> + * + * gstwavpackdec.c: raw Wavpack bitstream decoder + * + * 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-wavpackdec + * + * WavpackDec decodes framed (for example by the WavpackParse element) + * Wavpack streams and decodes them to raw audio. + * <ulink url="http://www.wavpack.com/">Wavpack</ulink> is an open-source + * audio codec that features both lossless and lossy encoding. + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 filesrc location=test.wv ! wavpackparse ! wavpackdec ! audioconvert ! audioresample ! autoaudiosink + * ]| This pipeline decodes the Wavpack file test.wv into raw audio buffers and + * tries to play it back using an automatically found audio sink. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/audio/audio.h> + +#include <math.h> +#include <string.h> + +#include <wavpack/wavpack.h> +#include "gstwavpackdec.h" +#include "gstwavpackcommon.h" +#include "gstwavpackstreamreader.h" + + +GST_DEBUG_CATEGORY_STATIC (gst_wavpack_dec_debug); +#define GST_CAT_DEFAULT gst_wavpack_dec_debug + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack, " + "depth = (int) [ 1, 32 ], " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true") + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) S8, " + "layout = (string) interleaved, " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ]; " + "audio/x-raw, " + "format = (string) " GST_AUDIO_NE (S16) ", " + "layout = (string) interleaved, " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ]; " + "audio/x-raw, " + "format = (string) " GST_AUDIO_NE (S32) ", " + "layout = (string) interleaved, " + "channels = (int) [ 1, 8 ], " "rate = (int) [ 6000, 192000 ]") + ); + +static gboolean gst_wavpack_dec_start (GstAudioDecoder * dec); +static gboolean gst_wavpack_dec_stop (GstAudioDecoder * dec); +static gboolean gst_wavpack_dec_set_format (GstAudioDecoder * dec, + GstCaps * caps); +static GstFlowReturn gst_wavpack_dec_handle_frame (GstAudioDecoder * dec, + GstBuffer * buffer); + +static void gst_wavpack_dec_finalize (GObject * object); +static void gst_wavpack_dec_post_tags (GstWavpackDec * dec); + +#define gst_wavpack_dec_parent_class parent_class +G_DEFINE_TYPE (GstWavpackDec, gst_wavpack_dec, GST_TYPE_AUDIO_DECODER); + +static void +gst_wavpack_dec_class_init (GstWavpackDecClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) (klass); + GstAudioDecoderClass *base_class = (GstAudioDecoderClass *) (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_static_metadata (element_class, "Wavpack audio decoder", + "Codec/Decoder/Audio", + "Decodes Wavpack audio data", + "Arwed v. Merkatz <v.merkatz@gmx.net>, " + "Sebastian Dröge <slomo@circular-chaos.org>"); + + gobject_class->finalize = gst_wavpack_dec_finalize; + + base_class->start = GST_DEBUG_FUNCPTR (gst_wavpack_dec_start); + base_class->stop = GST_DEBUG_FUNCPTR (gst_wavpack_dec_stop); + base_class->set_format = GST_DEBUG_FUNCPTR (gst_wavpack_dec_set_format); + base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_wavpack_dec_handle_frame); +} + +static void +gst_wavpack_dec_reset (GstWavpackDec * dec) +{ + dec->wv_id.buffer = NULL; + dec->wv_id.position = dec->wv_id.length = 0; + + dec->channels = 0; + dec->channel_mask = 0; + dec->sample_rate = 0; + dec->depth = 0; +} + +static void +gst_wavpack_dec_init (GstWavpackDec * dec) +{ + dec->context = NULL; + dec->stream_reader = gst_wavpack_stream_reader_new (); + + gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (dec), TRUE); + + gst_wavpack_dec_reset (dec); +} + +static void +gst_wavpack_dec_finalize (GObject * object) +{ + GstWavpackDec *dec = GST_WAVPACK_DEC (object); + + g_free (dec->stream_reader); + dec->stream_reader = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_wavpack_dec_start (GstAudioDecoder * dec) +{ + GST_DEBUG_OBJECT (dec, "start"); + + /* never mind a few errors */ + gst_audio_decoder_set_max_errors (dec, 16); + /* don't bother us with flushing */ + gst_audio_decoder_set_drainable (dec, FALSE); + /* aim for some perfect timestamping */ + gst_audio_decoder_set_tolerance (dec, 10 * GST_MSECOND); + + return TRUE; +} + +static gboolean +gst_wavpack_dec_stop (GstAudioDecoder * dec) +{ + GstWavpackDec *wpdec = GST_WAVPACK_DEC (dec); + + GST_DEBUG_OBJECT (dec, "stop"); + + if (wpdec->context) { + WavpackCloseFile (wpdec->context); + wpdec->context = NULL; + } + + gst_wavpack_dec_reset (wpdec); + + return TRUE; +} + +static void +gst_wavpack_dec_negotiate (GstWavpackDec * dec) +{ + GstAudioInfo info; + GstAudioFormat fmt; + GstAudioChannelPosition pos[64] = { GST_AUDIO_CHANNEL_POSITION_INVALID, }; + + /* arrange for 1, 2 or 4-byte width == depth output */ + dec->width = dec->depth; + switch (dec->depth) { + case 8: + fmt = GST_AUDIO_FORMAT_S8; + break; + case 16: + fmt = _GST_AUDIO_FORMAT_NE (S16); + break; + case 24: + case 32: + fmt = _GST_AUDIO_FORMAT_NE (S32); + dec->width = 32; + break; + default: + fmt = GST_AUDIO_FORMAT_UNKNOWN; + g_assert_not_reached (); + break; + } + + g_assert (dec->channel_mask != 0); + + if (!gst_wavpack_get_channel_positions (dec->channels, + dec->channel_mask, pos)) + GST_WARNING_OBJECT (dec, "Failed to set channel layout"); + + gst_audio_info_init (&info); + gst_audio_info_set_format (&info, fmt, dec->sample_rate, dec->channels, pos); + + gst_audio_channel_positions_to_valid_order (info.position, info.channels); + gst_audio_get_channel_reorder_map (info.channels, + info.position, pos, dec->channel_reorder_map); + + /* should always succeed */ + gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (dec), &info); +} + +static gboolean +gst_wavpack_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps) +{ + /* pretty much nothing to do here, + * we'll parse it all from the stream and setup then */ + + return TRUE; +} + +static void +gst_wavpack_dec_post_tags (GstWavpackDec * dec) +{ + GstTagList *list; + GstFormat format_time = GST_FORMAT_TIME, format_bytes = GST_FORMAT_BYTES; + gint64 duration, size; + + /* try to estimate the average bitrate */ + if (gst_pad_peer_query_duration (GST_AUDIO_DECODER_SINK_PAD (dec), + format_bytes, &size) && + gst_pad_peer_query_duration (GST_AUDIO_DECODER_SINK_PAD (dec), + format_time, &duration) && size > 0 && duration > 0) { + guint64 bitrate; + + list = gst_tag_list_new_empty (); + + bitrate = gst_util_uint64_scale (size, 8 * GST_SECOND, duration); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE, + (guint) bitrate, NULL); + gst_audio_decoder_merge_tags (GST_AUDIO_DECODER (dec), list, + GST_TAG_MERGE_REPLACE); + } +} + +static GstFlowReturn +gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) +{ + GstWavpackDec *dec; + GstBuffer *outbuf = NULL; + GstFlowReturn ret = GST_FLOW_OK; + WavpackHeader wph; + int32_t decoded, unpacked_size; + gboolean format_changed; + gint width, depth, i, j, max; + gint32 *dec_data = NULL; + guint8 *out_data; + GstMapInfo map, omap; + + dec = GST_WAVPACK_DEC (bdec); + + g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR); + + gst_buffer_map (buf, &map, GST_MAP_READ); + + /* check input, we only accept framed input with complete chunks */ + if (map.size < sizeof (WavpackHeader)) + goto input_not_framed; + + if (!gst_wavpack_read_header (&wph, map.data)) + goto invalid_header; + + if (map.size < wph.ckSize + 4 * 1 + 4) + goto input_not_framed; + + if (!(wph.flags & INITIAL_BLOCK)) + goto input_not_framed; + + dec->wv_id.buffer = map.data; + dec->wv_id.length = map.size; + dec->wv_id.position = 0; + + /* create a new wavpack context if there is none yet but if there + * was already one (i.e. caps were set on the srcpad) check whether + * the new one has the same caps */ + if (!dec->context) { + gchar error_msg[80]; + + dec->context = WavpackOpenFileInputEx (dec->stream_reader, + &dec->wv_id, NULL, error_msg, OPEN_STREAMING, 0); + + /* expect this to work */ + if (!dec->context) { + GST_WARNING_OBJECT (dec, "Couldn't decode buffer: %s", error_msg); + goto context_failed; + } + } + + g_assert (dec->context != NULL); + + format_changed = + (dec->sample_rate != WavpackGetSampleRate (dec->context)) || + (dec->channels != WavpackGetNumChannels (dec->context)) || + (dec->depth != WavpackGetBytesPerSample (dec->context) * 8) || +#ifdef WAVPACK_OLD_API + (dec->channel_mask != dec->context->config.channel_mask); +#else + (dec->channel_mask != WavpackGetChannelMask (dec->context)); +#endif + + if (!gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (dec)) || + format_changed) { + gint channel_mask; + + dec->sample_rate = WavpackGetSampleRate (dec->context); + dec->channels = WavpackGetNumChannels (dec->context); + dec->depth = WavpackGetBytesPerSample (dec->context) * 8; + +#ifdef WAVPACK_OLD_API + channel_mask = dec->context->config.channel_mask; +#else + channel_mask = WavpackGetChannelMask (dec->context); +#endif + if (channel_mask == 0) + channel_mask = gst_wavpack_get_default_channel_mask (dec->channels); + + dec->channel_mask = channel_mask; + + gst_wavpack_dec_negotiate (dec); + + /* send GST_TAG_AUDIO_CODEC and GST_TAG_BITRATE tags before something + * is decoded or after the format has changed */ + gst_wavpack_dec_post_tags (dec); + } + + /* alloc output buffer */ + dec_data = g_malloc (4 * wph.block_samples * dec->channels); + + /* decode */ + decoded = WavpackUnpackSamples (dec->context, dec_data, wph.block_samples); + if (decoded != wph.block_samples) + goto decode_error; + + unpacked_size = (dec->width / 8) * wph.block_samples * dec->channels; + outbuf = gst_buffer_new_and_alloc (unpacked_size); + + /* legacy; pass along offset, whatever that might entail */ + GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buf); + + gst_buffer_map (outbuf, &omap, GST_MAP_WRITE); + out_data = omap.data; + + width = dec->width; + depth = dec->depth; + max = dec->channels * wph.block_samples; + if (width == 8) { + gint8 *outbuffer = (gint8 *) out_data; + gint *reorder_map = dec->channel_reorder_map; + + for (i = 0; i < max; i += dec->channels) { + for (j = 0; j < dec->channels; j++) + *outbuffer++ = (gint8) (dec_data[i + reorder_map[j]]); + } + } else if (width == 16) { + gint16 *outbuffer = (gint16 *) out_data; + gint *reorder_map = dec->channel_reorder_map; + + for (i = 0; i < max; i += dec->channels) { + for (j = 0; j < dec->channels; j++) + *outbuffer++ = (gint16) (dec_data[i + reorder_map[j]]); + } + } else if (dec->width == 32) { + gint32 *outbuffer = (gint32 *) out_data; + gint *reorder_map = dec->channel_reorder_map; + + if (width != depth) { + for (i = 0; i < max; i += dec->channels) { + for (j = 0; j < dec->channels; j++) + *outbuffer++ = + (gint32) (dec_data[i + reorder_map[j]] << (width - depth)); + } + } else { + for (i = 0; i < max; i += dec->channels) { + for (j = 0; j < dec->channels; j++) + *outbuffer++ = (gint32) (dec_data[i + reorder_map[j]]); + } + } + } else { + g_assert_not_reached (); + } + + gst_buffer_unmap (outbuf, &omap); + gst_buffer_unmap (buf, &map); + buf = NULL; + + g_free (dec_data); + + ret = gst_audio_decoder_finish_frame (bdec, outbuf, 1); + +out: + if (buf) + gst_buffer_unmap (buf, &map); + + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + GST_DEBUG_OBJECT (dec, "flow: %s", gst_flow_get_name (ret)); + } + + return ret; + +/* ERRORS */ +input_not_framed: + { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("Expected framed input")); + ret = GST_FLOW_ERROR; + goto out; + } +invalid_header: + { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("Invalid wavpack header")); + ret = GST_FLOW_ERROR; + goto out; + } +context_failed: + { + GST_AUDIO_DECODER_ERROR (bdec, 1, LIBRARY, INIT, (NULL), + ("error creating Wavpack context"), ret); + goto out; + } +decode_error: + { + const gchar *reason = "unknown"; + + if (dec->context) { +#ifdef WAVPACK_OLD_API + reason = dec->context->error_message; +#else + reason = WavpackGetErrorMessage (dec->context); +#endif + } else { + reason = "couldn't create decoder context"; + } + GST_AUDIO_DECODER_ERROR (bdec, 1, STREAM, DECODE, (NULL), + ("decoding error: %s", reason), ret); + g_free (dec_data); + if (ret == GST_FLOW_OK) + gst_audio_decoder_finish_frame (bdec, NULL, 1); + goto out; + } +} + +gboolean +gst_wavpack_dec_plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "wavpackdec", + GST_RANK_PRIMARY, GST_TYPE_WAVPACK_DEC)) + return FALSE; + GST_DEBUG_CATEGORY_INIT (gst_wavpack_dec_debug, "wavpackdec", 0, + "Wavpack decoder"); + return TRUE; +} |