| /* 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_static_pad_template (element_class, &src_factory); |
| gst_element_class_add_static_pad_template (element_class, &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_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST |
| (dec), TRUE); |
| GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (dec)); |
| |
| 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); |
| gst_tag_list_unref (list); |
| } |
| } |
| |
| 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) || |
| (dec->channel_mask != WavpackGetChannelMask (dec->context)); |
| |
| 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; |
| |
| channel_mask = WavpackGetChannelMask (dec->context); |
| 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) { |
| reason = WavpackGetErrorMessage (dec->context); |
| } 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; |
| } |