| /* GStreamer Wavpack plugin |
| * (c) 2005 Arwed v. Merkatz <v.merkatz@gmx.net> |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <gst/gst.h> |
| |
| #include <math.h> |
| #include <string.h> |
| |
| #include <wavpack/wavpack.h> |
| #include "gstwavpackdec.h" |
| #include "gstwavpackcommon.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, " |
| "width = (int) { 8, 16, 24, 32 }, " |
| "channels = (int) { 1, 2 }, " |
| "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true") |
| ); |
| |
| static GstStaticPadTemplate wvc_sink_factory = |
| GST_STATIC_PAD_TEMPLATE ("wvcsink", |
| GST_PAD_SINK, |
| GST_PAD_REQUEST, |
| GST_STATIC_CAPS ("audio/x-wavpack-correction, " "framed = (boolean) true") |
| ); |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-raw-int, " |
| "width = (int) { 8, 16, 24, 32 }, " |
| "depth = (int) { 8, 16, 24, 32 }, " |
| "channels = (int) { 1, 2 }, " |
| "rate = (int) [ 6000, 192000 ], " |
| "endianness = (int) LITTLE_ENDIAN, " "signed = (boolean) true") |
| /* |
| "audio/x-raw-float, " |
| "width = (int) 32, " |
| "channels = (int) { 1, 2 }, " |
| "rate = (int) [ 6000, 192000 ], " "endianness = (int) LITTLE_ENDIAN" |
| */ |
| ); |
| |
| static GstFlowReturn gst_wavpack_dec_chain (GstPad * pad, GstBuffer * buffer); |
| static gboolean gst_wavpack_dec_sink_event (GstPad * pad, GstEvent * event); |
| |
| GST_BOILERPLATE (GstWavpackDec, gst_wavpack_dec, GstElement, GST_TYPE_ELEMENT) |
| |
| static gboolean gst_wavpack_dec_setcaps (GstPad * pad, GstCaps * caps) |
| { |
| GstWavpackDec *wavpackdec = GST_WAVPACK_DEC (gst_pad_get_parent (pad)); |
| GstStructure *structure; |
| GstCaps *srccaps; |
| gint bits, rate, channels; |
| |
| structure = gst_caps_get_structure (caps, 0); |
| if (!gst_structure_get_int (structure, "rate", &rate) || |
| !gst_structure_get_int (structure, "channels", &channels) || |
| !gst_structure_get_int (structure, "width", &bits)) { |
| return FALSE; |
| } |
| |
| wavpackdec->samplerate = rate; |
| wavpackdec->channels = channels; |
| wavpackdec->width = bits; |
| |
| /* 32-bit output seems to be in fact 32 bit int (e.g. Prod_Girls.wv) */ |
| /* if (bits != 32) { */ |
| srccaps = gst_caps_new_simple ("audio/x-raw-int", |
| "rate", G_TYPE_INT, wavpackdec->samplerate, |
| "channels", G_TYPE_INT, wavpackdec->channels, |
| "depth", G_TYPE_INT, bits, |
| "width", G_TYPE_INT, bits, |
| "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, |
| "signed", G_TYPE_BOOLEAN, TRUE, NULL); |
| /* |
| } else { |
| srccaps = gst_caps_new_simple ("audio/x-raw-float", |
| "rate", G_TYPE_INT, wavpackdec->samplerate, |
| "channels", G_TYPE_INT, wavpackdec->channels, |
| "width", G_TYPE_INT, 32, |
| "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, NULL); |
| } |
| */ |
| /* gst_pad_set_caps (wavpackdec->sinkpad, caps); */ |
| |
| gst_pad_set_caps (wavpackdec->srcpad, srccaps); |
| gst_pad_use_fixed_caps (wavpackdec->srcpad); |
| |
| return TRUE; |
| } |
| |
| #if 0 |
| static GstPadLinkReturn |
| gst_wavpack_dec_wvclink (GstPad * pad, GstPad * peer) |
| { |
| if (!gst_caps_is_fixed (GST_PAD_CAPS (peer))) |
| return GST_PAD_LINK_REFUSED; |
| |
| return GST_PAD_LINK_OK; |
| } |
| #endif |
| |
| static void |
| gst_wavpack_dec_base_init (gpointer klass) |
| { |
| static const GstElementDetails plugin_details = |
| GST_ELEMENT_DETAILS ("WavePack audio decoder", |
| "Codec/Decoder/Audio", |
| "Decode Wavpack audio data", |
| "Arwed v. Merkatz <v.merkatz@gmx.net>"); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (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_add_pad_template (element_class, |
| gst_static_pad_template_get (&wvc_sink_factory)); |
| gst_element_class_set_details (element_class, &plugin_details); |
| } |
| |
| static void |
| gst_wavpack_dec_dispose (GObject * object) |
| { |
| GstWavpackDec *wavpackdec = GST_WAVPACK_DEC (object); |
| |
| g_free (wavpackdec->decodebuf); |
| wavpackdec->decodebuf = NULL; |
| |
| /* FIXME: what about wavpackdec->stream and wavpackdec->context? (tpm) */ |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| static void |
| gst_wavpack_dec_class_init (GstWavpackDecClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| |
| gobject_class->dispose = gst_wavpack_dec_dispose; |
| } |
| |
| static gboolean |
| gst_wavpack_dec_src_query (GstPad * pad, GstQuery * query) |
| { |
| return gst_pad_query_default (pad, query); |
| } |
| |
| static gboolean |
| gst_wavpack_dec_sink_event (GstPad * pad, GstEvent * event) |
| { |
| GstWavpackDec *dec; |
| |
| dec = GST_WAVPACK_DEC (gst_pad_get_parent (pad)); |
| |
| GST_LOG_OBJECT (dec, "Received %s event", GST_EVENT_TYPE_NAME (event)); |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_NEWSEGMENT:{ |
| /* TODO: save current segment so we can do clipping, for now |
| * we'll just leave the clipping to the audio sink */ |
| break; |
| } |
| default: |
| break; |
| } |
| |
| gst_object_unref (dec); |
| |
| return gst_pad_event_default (pad, event); |
| } |
| |
| static void |
| gst_wavpack_dec_init (GstWavpackDec * wavpackdec, GstWavpackDecClass * gklass) |
| { |
| GstElementClass *klass = GST_ELEMENT_GET_CLASS (wavpackdec); |
| |
| wavpackdec->sinkpad = |
| gst_pad_new_from_template (gst_element_class_get_pad_template (klass, |
| "sink"), "sink"); |
| gst_element_add_pad (GST_ELEMENT (wavpackdec), wavpackdec->sinkpad); |
| |
| gst_pad_set_chain_function (wavpackdec->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_wavpack_dec_chain)); |
| gst_pad_set_setcaps_function (wavpackdec->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_wavpack_dec_setcaps)); |
| gst_pad_set_event_function (wavpackdec->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_wavpack_dec_sink_event)); |
| |
| #if 0 |
| wavpackdec->wvcsinkpad = |
| gst_pad_new_from_template (gst_element_class_get_pad_template (klass, |
| "wvcsink"), "wvcsink"); |
| gst_pad_set_link_function (wavpackdec->wvcsinkpad, gst_wavpack_dec_wvclink); |
| gst_element_add_pad (GST_ELEMENT (wavpackdec), wavpackdec->wvcsinkpad); |
| #endif |
| |
| |
| wavpackdec->srcpad = |
| gst_pad_new_from_template (gst_element_class_get_pad_template (klass, |
| "src"), "src"); |
| gst_pad_use_fixed_caps (wavpackdec->srcpad); |
| |
| gst_pad_set_query_function (wavpackdec->srcpad, |
| GST_DEBUG_FUNCPTR (gst_wavpack_dec_src_query)); |
| |
| gst_element_add_pad (GST_ELEMENT (wavpackdec), wavpackdec->srcpad); |
| |
| wavpackdec->decodebuf = NULL; |
| wavpackdec->decodebuf_size = 0; |
| wavpackdec->stream = (WavpackStream *) g_malloc0 (sizeof (WavpackStream)); |
| wavpackdec->context = (WavpackContext *) g_malloc0 (sizeof (WavpackContext)); |
| } |
| |
| static void |
| gst_wavpack_dec_setup_context (GstWavpackDec * wavpackdec, guchar * data, |
| guchar * cdata) |
| { |
| WavpackContext *context = wavpackdec->context; |
| WavpackStream *stream = wavpackdec->stream; |
| guint buffer_size; |
| |
| memset (context, 0, sizeof (context)); |
| |
| context->open_flags = 0; |
| context->current_stream = 0; |
| context->num_streams = 1; |
| |
| memset (stream, 0, sizeof (stream)); |
| context->streams[0] = stream; |
| |
| gst_wavpack_read_header (&stream->wphdr, data); |
| stream->blockbuff = data; |
| |
| if (cdata) { |
| context->wvc_flag = TRUE; |
| gst_wavpack_read_header (&stream->wphdr, cdata); |
| stream->block2buff = cdata; |
| } else { |
| context->wvc_flag = FALSE; |
| } |
| |
| buffer_size = |
| stream->wphdr.block_samples * wavpackdec->channels * sizeof (int32_t); |
| if (wavpackdec->decodebuf_size < buffer_size) { |
| wavpackdec->decodebuf = |
| (int32_t *) g_realloc (wavpackdec->decodebuf, buffer_size); |
| wavpackdec->decodebuf_size = buffer_size; |
| } |
| |
| unpack_init (context); |
| } |
| |
| static GstBuffer * |
| gst_wavpack_dec_format_samples (GstWavpackDec * wavpackdec, int32_t * samples, |
| guint num_samples) |
| { |
| GstBuffer *buf; |
| gint i; |
| guint8 *dst; |
| int32_t temp; |
| |
| buf = |
| gst_buffer_new_and_alloc (num_samples * wavpackdec->width / 8 * |
| wavpackdec->channels); |
| |
| dst = (guint8 *) GST_BUFFER_DATA (buf); |
| |
| switch (wavpackdec->width) { |
| case 8: |
| for (i = 0; i < num_samples * wavpackdec->channels; ++i) |
| *dst++ = *samples++ + 128; |
| break; |
| case 16: |
| for (i = 0; i < num_samples * wavpackdec->channels; ++i) { |
| *dst++ = (guint8) (temp = *samples++); |
| *dst++ = (guint8) (temp >> 8); |
| } |
| break; |
| case 24: |
| for (i = 0; i < num_samples * wavpackdec->channels; ++i) { |
| *dst++ = (guint8) (temp = *samples++); |
| *dst++ = (guint8) (temp >> 8); |
| *dst++ = (guint8) (temp >> 16); |
| } |
| break; |
| case 32: |
| for (i = 0; i < num_samples * wavpackdec->channels; ++i) { |
| *dst++ = (guint8) (temp = *samples++); |
| *dst++ = (guint8) (temp >> 8); |
| *dst++ = (guint8) (temp >> 16); |
| *dst++ = (guint8) (temp >> 24); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return buf; |
| } |
| |
| static GstFlowReturn |
| gst_wavpack_dec_chain (GstPad * pad, GstBuffer * buf) |
| { |
| |
| GstWavpackDec *wavpackdec = GST_WAVPACK_DEC (gst_pad_get_parent (pad)); |
| GstBuffer *outbuf, *cbuf = NULL; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| #if 0 |
| if (gst_pad_is_linked (wavpackdec->wvcsinkpad)) { |
| if (GST_FLOW_OK != gst_pad_pull_range (wavpackdec->wvcsinkpad, |
| wavpackdec->wvcflushed_bytes, -1, &cbuf)) { |
| cbuf = NULL; |
| } else { |
| wavpackdec->wvcflushed_bytes += GST_BUFFER_SIZE (cbuf); |
| } |
| |
| } |
| #endif |
| |
| gst_wavpack_dec_setup_context (wavpackdec, GST_BUFFER_DATA (buf), |
| cbuf ? GST_BUFFER_DATA (cbuf) : NULL); |
| unpack_samples (wavpackdec->context, wavpackdec->decodebuf, |
| wavpackdec->context->streams[0]->wphdr.block_samples); |
| outbuf = |
| gst_wavpack_dec_format_samples (wavpackdec, wavpackdec->decodebuf, |
| wavpackdec->context->streams[0]->wphdr.block_samples); |
| |
| gst_buffer_stamp (outbuf, buf); |
| |
| gst_buffer_unref (buf); |
| if (cbuf) { |
| gst_buffer_unref (cbuf); |
| } |
| |
| gst_buffer_set_caps (outbuf, GST_PAD_CAPS (wavpackdec->srcpad)); |
| |
| GST_LOG_OBJECT (wavpackdec, "pushing buffer with time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf))); |
| |
| ret = gst_pad_push (wavpackdec->srcpad, outbuf); |
| if (ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (wavpackdec, "pad_push: %s", gst_flow_get_name (ret)); |
| } |
| |
| return ret; |
| } |
| |
| 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; |
| } |