Merge remote-tracking branch 'origin/master' into 0.11
[platform/upstream/gst-plugins-good.git] / ext / wavpack / gstwavpackenc.c
1 /* GStreamer Wavpack encoder plugin
2  * Copyright (c) 2006 Sebastian Dröge <slomo@circular-chaos.org>
3  *
4  * gstwavpackdec.c: Wavpack audio encoder
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:element-wavpackenc
24  *
25  * WavpackEnc encodes raw audio into a framed Wavpack stream.
26  * <ulink url="http://www.wavpack.com/">Wavpack</ulink> is an open-source
27  * audio codec that features both lossless and lossy encoding.
28  *
29  * <refsect2>
30  * <title>Example launch line</title>
31  * |[
32  * gst-launch audiotestsrc num-buffers=500 ! audioconvert ! wavpackenc ! filesink location=sinewave.wv
33  * ]| This pipeline encodes audio from audiotestsrc into a Wavpack file. The audioconvert element is needed
34  * as the Wavpack encoder only accepts input with 32 bit width (and every depth between 1 and 32 bits).
35  * |[
36  * gst-launch cdda://1 ! audioconvert ! wavpackenc ! filesink location=track1.wv
37  * ]| This pipeline encodes audio from an audio CD into a Wavpack file using
38  * lossless encoding (the file output will be fairly large).
39  * |[
40  * gst-launch cdda://1 ! audioconvert ! wavpackenc bitrate=128000 ! filesink location=track1.wv
41  * ]| This pipeline encodes audio from an audio CD into a Wavpack file using
42  * lossy encoding at a certain bitrate (the file will be fairly small).
43  * </refsect2>
44  */
45
46 /*
47  * TODO: - add 32 bit float mode. CONFIG_FLOAT_DATA
48  */
49
50 #include <string.h>
51 #include <gst/gst.h>
52 #include <glib/gprintf.h>
53
54 #include <wavpack/wavpack.h>
55 #include "gstwavpackenc.h"
56 #include "gstwavpackcommon.h"
57
58 static GstFlowReturn gst_wavpack_enc_chain (GstPad * pad, GstBuffer * buffer);
59 static gboolean gst_wavpack_enc_sink_set_caps (GstPad * pad, GstCaps * caps);
60 static int gst_wavpack_enc_push_block (void *id, void *data, int32_t count);
61 static gboolean gst_wavpack_enc_sink_event (GstPad * pad, GstEvent * event);
62 static GstStateChangeReturn gst_wavpack_enc_change_state (GstElement * element,
63     GstStateChange transition);
64 static void gst_wavpack_enc_set_property (GObject * object, guint prop_id,
65     const GValue * value, GParamSpec * pspec);
66 static void gst_wavpack_enc_get_property (GObject * object, guint prop_id,
67     GValue * value, GParamSpec * pspec);
68
69 enum
70 {
71   ARG_0,
72   ARG_MODE,
73   ARG_BITRATE,
74   ARG_BITSPERSAMPLE,
75   ARG_CORRECTION_MODE,
76   ARG_MD5,
77   ARG_EXTRA_PROCESSING,
78   ARG_JOINT_STEREO_MODE
79 };
80
81 GST_DEBUG_CATEGORY_STATIC (gst_wavpack_enc_debug);
82 #define GST_CAT_DEFAULT gst_wavpack_enc_debug
83
84 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
85     GST_PAD_SINK,
86     GST_PAD_ALWAYS,
87     GST_STATIC_CAPS ("audio/x-raw-int, "
88         "width = (int) 32, "
89         "depth = (int) [ 1, 32], "
90         "endianness = (int) BYTE_ORDER, "
91         "channels = (int) [ 1, 8 ], "
92         "rate = (int) [ 6000, 192000 ]," "signed = (boolean) TRUE")
93     );
94
95 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
96     GST_PAD_SRC,
97     GST_PAD_ALWAYS,
98     GST_STATIC_CAPS ("audio/x-wavpack, "
99         "width = (int) [ 1, 32 ], "
100         "channels = (int) [ 1, 2 ], "
101         "rate = (int) [ 6000, 192000 ], " "framed = (boolean) TRUE")
102     );
103
104 static GstStaticPadTemplate wvcsrc_factory = GST_STATIC_PAD_TEMPLATE ("wvcsrc",
105     GST_PAD_SRC,
106     GST_PAD_SOMETIMES,
107     GST_STATIC_CAPS ("audio/x-wavpack-correction, " "framed = (boolean) TRUE")
108     );
109
110 enum
111 {
112   GST_WAVPACK_ENC_MODE_VERY_FAST = 0,
113   GST_WAVPACK_ENC_MODE_FAST,
114   GST_WAVPACK_ENC_MODE_DEFAULT,
115   GST_WAVPACK_ENC_MODE_HIGH,
116   GST_WAVPACK_ENC_MODE_VERY_HIGH
117 };
118
119 #define GST_TYPE_WAVPACK_ENC_MODE (gst_wavpack_enc_mode_get_type ())
120 static GType
121 gst_wavpack_enc_mode_get_type (void)
122 {
123   static GType qtype = 0;
124
125   if (qtype == 0) {
126     static const GEnumValue values[] = {
127 #if 0
128       /* Very Fast Compression is not supported yet, but will be supported
129        * in future wavpack versions */
130       {GST_WAVPACK_ENC_MODE_VERY_FAST, "Very Fast Compression", "veryfast"},
131 #endif
132       {GST_WAVPACK_ENC_MODE_FAST, "Fast Compression", "fast"},
133       {GST_WAVPACK_ENC_MODE_DEFAULT, "Normal Compression", "normal"},
134       {GST_WAVPACK_ENC_MODE_HIGH, "High Compression", "high"},
135 #ifndef WAVPACK_OLD_API
136       {GST_WAVPACK_ENC_MODE_VERY_HIGH, "Very High Compression", "veryhigh"},
137 #endif
138       {0, NULL, NULL}
139     };
140
141     qtype = g_enum_register_static ("GstWavpackEncMode", values);
142   }
143   return qtype;
144 }
145
146 enum
147 {
148   GST_WAVPACK_CORRECTION_MODE_OFF = 0,
149   GST_WAVPACK_CORRECTION_MODE_ON,
150   GST_WAVPACK_CORRECTION_MODE_OPTIMIZED
151 };
152
153 #define GST_TYPE_WAVPACK_ENC_CORRECTION_MODE (gst_wavpack_enc_correction_mode_get_type ())
154 static GType
155 gst_wavpack_enc_correction_mode_get_type (void)
156 {
157   static GType qtype = 0;
158
159   if (qtype == 0) {
160     static const GEnumValue values[] = {
161       {GST_WAVPACK_CORRECTION_MODE_OFF, "Create no correction file", "off"},
162       {GST_WAVPACK_CORRECTION_MODE_ON, "Create correction file", "on"},
163       {GST_WAVPACK_CORRECTION_MODE_OPTIMIZED,
164           "Create optimized correction file", "optimized"},
165       {0, NULL, NULL}
166     };
167
168     qtype = g_enum_register_static ("GstWavpackEncCorrectionMode", values);
169   }
170   return qtype;
171 }
172
173 enum
174 {
175   GST_WAVPACK_JS_MODE_AUTO = 0,
176   GST_WAVPACK_JS_MODE_LEFT_RIGHT,
177   GST_WAVPACK_JS_MODE_MID_SIDE
178 };
179
180 #define GST_TYPE_WAVPACK_ENC_JOINT_STEREO_MODE (gst_wavpack_enc_joint_stereo_mode_get_type ())
181 static GType
182 gst_wavpack_enc_joint_stereo_mode_get_type (void)
183 {
184   static GType qtype = 0;
185
186   if (qtype == 0) {
187     static const GEnumValue values[] = {
188       {GST_WAVPACK_JS_MODE_AUTO, "auto", "auto"},
189       {GST_WAVPACK_JS_MODE_LEFT_RIGHT, "left/right", "leftright"},
190       {GST_WAVPACK_JS_MODE_MID_SIDE, "mid/side", "midside"},
191       {0, NULL, NULL}
192     };
193
194     qtype = g_enum_register_static ("GstWavpackEncJSMode", values);
195   }
196   return qtype;
197 }
198
199 static void
200 _do_init (GType object_type)
201 {
202   const GInterfaceInfo preset_interface_info = {
203     NULL,                       /* interface_init */
204     NULL,                       /* interface_finalize */
205     NULL                        /* interface_data */
206   };
207
208   g_type_add_interface_static (object_type, GST_TYPE_PRESET,
209       &preset_interface_info);
210 }
211
212 GST_BOILERPLATE_FULL (GstWavpackEnc, gst_wavpack_enc, GstElement,
213     GST_TYPE_ELEMENT, _do_init);
214
215 static void
216 gst_wavpack_enc_base_init (gpointer klass)
217 {
218   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
219
220   /* add pad templates */
221   gst_element_class_add_pad_template (element_class,
222       gst_static_pad_template_get (&sink_factory));
223   gst_element_class_add_pad_template (element_class,
224       gst_static_pad_template_get (&src_factory));
225   gst_element_class_add_pad_template (element_class,
226       gst_static_pad_template_get (&wvcsrc_factory));
227
228   /* set element details */
229   gst_element_class_set_details_simple (element_class, "Wavpack audio encoder",
230       "Codec/Encoder/Audio",
231       "Encodes audio with the Wavpack lossless/lossy audio codec",
232       "Sebastian Dröge <slomo@circular-chaos.org>");
233 }
234
235
236 static void
237 gst_wavpack_enc_class_init (GstWavpackEncClass * klass)
238 {
239   GObjectClass *gobject_class = (GObjectClass *) klass;
240   GstElementClass *gstelement_class = (GstElementClass *) klass;
241
242   parent_class = g_type_class_peek_parent (klass);
243
244   /* set state change handler */
245   gstelement_class->change_state =
246       GST_DEBUG_FUNCPTR (gst_wavpack_enc_change_state);
247
248   /* set property handlers */
249   gobject_class->set_property = gst_wavpack_enc_set_property;
250   gobject_class->get_property = gst_wavpack_enc_get_property;
251
252   /* install all properties */
253   g_object_class_install_property (gobject_class, ARG_MODE,
254       g_param_spec_enum ("mode", "Encoding mode",
255           "Speed versus compression tradeoff.",
256           GST_TYPE_WAVPACK_ENC_MODE, GST_WAVPACK_ENC_MODE_DEFAULT,
257           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
258   g_object_class_install_property (gobject_class, ARG_BITRATE,
259       g_param_spec_uint ("bitrate", "Bitrate",
260           "Try to encode with this average bitrate (bits/sec). "
261           "This enables lossy encoding, values smaller than 24000 disable it again.",
262           0, 9600000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
263   g_object_class_install_property (gobject_class, ARG_BITSPERSAMPLE,
264       g_param_spec_double ("bits-per-sample", "Bits per sample",
265           "Try to encode with this amount of bits per sample. "
266           "This enables lossy encoding, values smaller than 2.0 disable it again.",
267           0.0, 24.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
268   g_object_class_install_property (gobject_class, ARG_CORRECTION_MODE,
269       g_param_spec_enum ("correction-mode", "Correction stream mode",
270           "Use this mode for the correction stream. Only works in lossy mode!",
271           GST_TYPE_WAVPACK_ENC_CORRECTION_MODE, GST_WAVPACK_CORRECTION_MODE_OFF,
272           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
273   g_object_class_install_property (gobject_class, ARG_MD5,
274       g_param_spec_boolean ("md5", "MD5",
275           "Store MD5 hash of raw samples within the file.", FALSE,
276           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
277   g_object_class_install_property (gobject_class, ARG_EXTRA_PROCESSING,
278       g_param_spec_uint ("extra-processing", "Extra processing",
279           "Use better but slower filters for better compression/quality.",
280           0, 6, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
281   g_object_class_install_property (gobject_class, ARG_JOINT_STEREO_MODE,
282       g_param_spec_enum ("joint-stereo-mode", "Joint-Stereo mode",
283           "Use this joint-stereo mode.", GST_TYPE_WAVPACK_ENC_JOINT_STEREO_MODE,
284           GST_WAVPACK_JS_MODE_AUTO,
285           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
286 }
287
288 static void
289 gst_wavpack_enc_reset (GstWavpackEnc * enc)
290 {
291   /* close and free everything stream related if we already did something */
292   if (enc->wp_context) {
293     WavpackCloseFile (enc->wp_context);
294     enc->wp_context = NULL;
295   }
296   if (enc->wp_config) {
297     g_free (enc->wp_config);
298     enc->wp_config = NULL;
299   }
300   if (enc->first_block) {
301     g_free (enc->first_block);
302     enc->first_block = NULL;
303   }
304   enc->first_block_size = 0;
305   if (enc->md5_context) {
306     g_checksum_free (enc->md5_context);
307     enc->md5_context = NULL;
308   }
309
310   if (enc->pending_buffer) {
311     gst_buffer_unref (enc->pending_buffer);
312     enc->pending_buffer = NULL;
313     enc->pending_offset = 0;
314   }
315
316   /* reset the last returns to GST_FLOW_OK. This is only set to something else
317    * while WavpackPackSamples() or more specific gst_wavpack_enc_push_block()
318    * so not valid anymore */
319   enc->srcpad_last_return = enc->wvcsrcpad_last_return = GST_FLOW_OK;
320
321   /* reset stream information */
322   enc->samplerate = 0;
323   enc->depth = 0;
324   enc->channels = 0;
325   enc->channel_mask = 0;
326   enc->need_channel_remap = FALSE;
327
328   enc->timestamp_offset = GST_CLOCK_TIME_NONE;
329   enc->next_ts = GST_CLOCK_TIME_NONE;
330 }
331
332 static void
333 gst_wavpack_enc_init (GstWavpackEnc * enc, GstWavpackEncClass * gclass)
334 {
335   enc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
336   gst_pad_set_setcaps_function (enc->sinkpad,
337       GST_DEBUG_FUNCPTR (gst_wavpack_enc_sink_set_caps));
338   gst_pad_set_chain_function (enc->sinkpad,
339       GST_DEBUG_FUNCPTR (gst_wavpack_enc_chain));
340   gst_pad_set_event_function (enc->sinkpad,
341       GST_DEBUG_FUNCPTR (gst_wavpack_enc_sink_event));
342   gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
343
344   /* setup src pad */
345   enc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
346   gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
347
348   /* initialize object attributes */
349   enc->wp_config = NULL;
350   enc->wp_context = NULL;
351   enc->first_block = NULL;
352   enc->md5_context = NULL;
353   gst_wavpack_enc_reset (enc);
354
355   enc->wv_id.correction = FALSE;
356   enc->wv_id.wavpack_enc = enc;
357   enc->wv_id.passthrough = FALSE;
358   enc->wvc_id.correction = TRUE;
359   enc->wvc_id.wavpack_enc = enc;
360   enc->wvc_id.passthrough = FALSE;
361
362   /* set default values of params */
363   enc->mode = GST_WAVPACK_ENC_MODE_DEFAULT;
364   enc->bitrate = 0;
365   enc->bps = 0.0;
366   enc->correction_mode = GST_WAVPACK_CORRECTION_MODE_OFF;
367   enc->md5 = FALSE;
368   enc->extra_processing = 0;
369   enc->joint_stereo_mode = GST_WAVPACK_JS_MODE_AUTO;
370 }
371
372 static gboolean
373 gst_wavpack_enc_sink_set_caps (GstPad * pad, GstCaps * caps)
374 {
375   GstWavpackEnc *enc = GST_WAVPACK_ENC (gst_pad_get_parent (pad));
376   GstStructure *structure = gst_caps_get_structure (caps, 0);
377   GstAudioChannelPosition *pos;
378
379   if (!gst_structure_get_int (structure, "channels", &enc->channels) ||
380       !gst_structure_get_int (structure, "rate", &enc->samplerate) ||
381       !gst_structure_get_int (structure, "depth", &enc->depth)) {
382     GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL),
383         ("got invalid caps: %" GST_PTR_FORMAT, caps));
384     gst_object_unref (enc);
385     return FALSE;
386   }
387
388   pos = gst_audio_get_channel_positions (structure);
389   /* If one channel is NONE they'll be all undefined */
390   if (pos != NULL && pos[0] == GST_AUDIO_CHANNEL_POSITION_NONE) {
391     g_free (pos);
392     pos = NULL;
393   }
394
395   if (pos == NULL) {
396     GST_ELEMENT_ERROR (enc, STREAM, FORMAT, (NULL),
397         ("input has no valid channel layout"));
398
399     gst_object_unref (enc);
400     return FALSE;
401   }
402
403   enc->channel_mask =
404       gst_wavpack_get_channel_mask_from_positions (pos, enc->channels);
405   enc->need_channel_remap =
406       gst_wavpack_set_channel_mapping (pos, enc->channels,
407       enc->channel_mapping);
408   g_free (pos);
409
410   /* set fixed src pad caps now that we know what we will get */
411   caps = gst_caps_new_simple ("audio/x-wavpack",
412       "channels", G_TYPE_INT, enc->channels,
413       "rate", G_TYPE_INT, enc->samplerate,
414       "width", G_TYPE_INT, enc->depth, "framed", G_TYPE_BOOLEAN, TRUE, NULL);
415
416   if (!gst_wavpack_set_channel_layout (caps, enc->channel_mask))
417     GST_WARNING_OBJECT (enc, "setting channel layout failed");
418
419   if (!gst_pad_set_caps (enc->srcpad, caps)) {
420     GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL),
421         ("setting caps failed: %" GST_PTR_FORMAT, caps));
422     gst_caps_unref (caps);
423     gst_object_unref (enc);
424     return FALSE;
425   }
426   gst_pad_use_fixed_caps (enc->srcpad);
427
428   gst_caps_unref (caps);
429   gst_object_unref (enc);
430   return TRUE;
431 }
432
433 static void
434 gst_wavpack_enc_set_wp_config (GstWavpackEnc * enc)
435 {
436   enc->wp_config = g_new0 (WavpackConfig, 1);
437   /* set general stream informations in the WavpackConfig */
438   enc->wp_config->bytes_per_sample = GST_ROUND_UP_8 (enc->depth) / 8;
439   enc->wp_config->bits_per_sample = enc->depth;
440   enc->wp_config->num_channels = enc->channels;
441   enc->wp_config->channel_mask = enc->channel_mask;
442   enc->wp_config->sample_rate = enc->samplerate;
443
444   /*
445    * Set parameters in WavpackConfig
446    */
447
448   /* Encoding mode */
449   switch (enc->mode) {
450 #if 0
451     case GST_WAVPACK_ENC_MODE_VERY_FAST:
452       enc->wp_config->flags |= CONFIG_VERY_FAST_FLAG;
453       enc->wp_config->flags |= CONFIG_FAST_FLAG;
454       break;
455 #endif
456     case GST_WAVPACK_ENC_MODE_FAST:
457       enc->wp_config->flags |= CONFIG_FAST_FLAG;
458       break;
459     case GST_WAVPACK_ENC_MODE_DEFAULT:
460       break;
461     case GST_WAVPACK_ENC_MODE_HIGH:
462       enc->wp_config->flags |= CONFIG_HIGH_FLAG;
463       break;
464 #ifndef WAVPACK_OLD_API
465     case GST_WAVPACK_ENC_MODE_VERY_HIGH:
466       enc->wp_config->flags |= CONFIG_HIGH_FLAG;
467       enc->wp_config->flags |= CONFIG_VERY_HIGH_FLAG;
468       break;
469 #endif
470   }
471
472   /* Bitrate, enables lossy mode */
473   if (enc->bitrate) {
474     enc->wp_config->flags |= CONFIG_HYBRID_FLAG;
475     enc->wp_config->flags |= CONFIG_BITRATE_KBPS;
476     enc->wp_config->bitrate = enc->bitrate / 1000.0;
477   } else if (enc->bps) {
478     enc->wp_config->flags |= CONFIG_HYBRID_FLAG;
479     enc->wp_config->bitrate = enc->bps;
480   }
481
482   /* Correction Mode, only in lossy mode */
483   if (enc->wp_config->flags & CONFIG_HYBRID_FLAG) {
484     if (enc->correction_mode > GST_WAVPACK_CORRECTION_MODE_OFF) {
485       GstCaps *caps = gst_caps_new_simple ("audio/x-wavpack-correction",
486           "framed", G_TYPE_BOOLEAN, TRUE, NULL);
487
488       enc->wvcsrcpad =
489           gst_pad_new_from_static_template (&wvcsrc_factory, "wvcsrc");
490
491       /* try to add correction src pad, don't set correction mode on failure */
492       GST_DEBUG_OBJECT (enc, "Adding correction pad with caps %"
493           GST_PTR_FORMAT, caps);
494       if (!gst_pad_set_caps (enc->wvcsrcpad, caps)) {
495         enc->correction_mode = 0;
496         GST_WARNING_OBJECT (enc, "setting correction caps failed");
497       } else {
498         gst_pad_use_fixed_caps (enc->wvcsrcpad);
499         gst_pad_set_active (enc->wvcsrcpad, TRUE);
500         gst_element_add_pad (GST_ELEMENT (enc), enc->wvcsrcpad);
501         enc->wp_config->flags |= CONFIG_CREATE_WVC;
502         if (enc->correction_mode == GST_WAVPACK_CORRECTION_MODE_OPTIMIZED) {
503           enc->wp_config->flags |= CONFIG_OPTIMIZE_WVC;
504         }
505       }
506       gst_caps_unref (caps);
507     }
508   } else {
509     if (enc->correction_mode > GST_WAVPACK_CORRECTION_MODE_OFF) {
510       enc->correction_mode = 0;
511       GST_WARNING_OBJECT (enc, "setting correction mode only has "
512           "any effect if a bitrate is provided.");
513     }
514   }
515   gst_element_no_more_pads (GST_ELEMENT (enc));
516
517   /* MD5, setup MD5 context */
518   if ((enc->md5) && !(enc->md5_context)) {
519     enc->wp_config->flags |= CONFIG_MD5_CHECKSUM;
520     enc->md5_context = g_checksum_new (G_CHECKSUM_MD5);
521   }
522
523   /* Extra encode processing */
524   if (enc->extra_processing) {
525     enc->wp_config->flags |= CONFIG_EXTRA_MODE;
526     enc->wp_config->xmode = enc->extra_processing;
527   }
528
529   /* Joint stereo mode */
530   switch (enc->joint_stereo_mode) {
531     case GST_WAVPACK_JS_MODE_AUTO:
532       break;
533     case GST_WAVPACK_JS_MODE_LEFT_RIGHT:
534       enc->wp_config->flags |= CONFIG_JOINT_OVERRIDE;
535       enc->wp_config->flags &= ~CONFIG_JOINT_STEREO;
536       break;
537     case GST_WAVPACK_JS_MODE_MID_SIDE:
538       enc->wp_config->flags |= (CONFIG_JOINT_OVERRIDE | CONFIG_JOINT_STEREO);
539       break;
540   }
541 }
542
543 static int
544 gst_wavpack_enc_push_block (void *id, void *data, int32_t count)
545 {
546   GstWavpackEncWriteID *wid = (GstWavpackEncWriteID *) id;
547   GstWavpackEnc *enc = GST_WAVPACK_ENC (wid->wavpack_enc);
548   GstFlowReturn *flow;
549   GstBuffer *buffer;
550   GstPad *pad;
551   guchar *block = (guchar *) data;
552
553   pad = (wid->correction) ? enc->wvcsrcpad : enc->srcpad;
554   flow =
555       (wid->correction) ? &enc->wvcsrcpad_last_return : &enc->
556       srcpad_last_return;
557
558   *flow = gst_pad_alloc_buffer_and_set_caps (pad, GST_BUFFER_OFFSET_NONE,
559       count, GST_PAD_CAPS (pad), &buffer);
560
561   if (*flow != GST_FLOW_OK) {
562     GST_WARNING_OBJECT (enc, "flow on %s:%s = %s",
563         GST_DEBUG_PAD_NAME (pad), gst_flow_get_name (*flow));
564     return FALSE;
565   }
566
567   g_memmove (GST_BUFFER_DATA (buffer), block, count);
568
569   if (count > sizeof (WavpackHeader) && memcmp (block, "wvpk", 4) == 0) {
570     /* if it's a Wavpack block set buffer timestamp and duration, etc */
571     WavpackHeader wph;
572
573     GST_LOG_OBJECT (enc, "got %d bytes of encoded wavpack %sdata",
574         count, (wid->correction) ? "correction " : "");
575
576     gst_wavpack_read_header (&wph, block);
577
578     /* Only set when pushing the first buffer again, in that case
579      * we don't want to delay the buffer or push newsegment events
580      */
581     if (!wid->passthrough) {
582       /* Only push complete blocks */
583       if (enc->pending_buffer == NULL) {
584         enc->pending_buffer = buffer;
585         enc->pending_offset = wph.block_index;
586       } else if (enc->pending_offset == wph.block_index) {
587         enc->pending_buffer = gst_buffer_join (enc->pending_buffer, buffer);
588       } else {
589         GST_ERROR ("Got incomplete block, dropping");
590         gst_buffer_unref (enc->pending_buffer);
591         enc->pending_buffer = buffer;
592         enc->pending_offset = wph.block_index;
593       }
594
595       if (!(wph.flags & FINAL_BLOCK))
596         return TRUE;
597
598       buffer = enc->pending_buffer;
599       enc->pending_buffer = NULL;
600       enc->pending_offset = 0;
601
602       /* if it's the first wavpack block, send a NEW_SEGMENT event */
603       if (wph.block_index == 0) {
604         gst_pad_push_event (pad,
605             gst_event_new_new_segment (FALSE,
606                 1.0, GST_FORMAT_TIME, 0, GST_BUFFER_OFFSET_NONE, 0));
607
608         /* save header for later reference, so we can re-send it later on
609          * EOS with fixed up values for total sample count etc. */
610         if (enc->first_block == NULL && !wid->correction) {
611           enc->first_block =
612               g_memdup (GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
613           enc->first_block_size = GST_BUFFER_SIZE (buffer);
614         }
615       }
616     }
617
618     /* set buffer timestamp, duration, offset, offset_end from
619      * the wavpack header */
620     GST_BUFFER_TIMESTAMP (buffer) = enc->timestamp_offset +
621         gst_util_uint64_scale_int (GST_SECOND, wph.block_index,
622         enc->samplerate);
623     GST_BUFFER_DURATION (buffer) =
624         gst_util_uint64_scale_int (GST_SECOND, wph.block_samples,
625         enc->samplerate);
626     GST_BUFFER_OFFSET (buffer) = wph.block_index;
627     GST_BUFFER_OFFSET_END (buffer) = wph.block_index + wph.block_samples;
628   } else {
629     /* if it's something else set no timestamp and duration on the buffer */
630     GST_DEBUG_OBJECT (enc, "got %d bytes of unknown data", count);
631
632     GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE;
633     GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
634   }
635
636   /* push the buffer and forward errors */
637   GST_DEBUG_OBJECT (enc, "pushing buffer with %d bytes",
638       GST_BUFFER_SIZE (buffer));
639   *flow = gst_pad_push (pad, buffer);
640
641   if (*flow != GST_FLOW_OK) {
642     GST_WARNING_OBJECT (enc, "flow on %s:%s = %s",
643         GST_DEBUG_PAD_NAME (pad), gst_flow_get_name (*flow));
644     return FALSE;
645   }
646
647   return TRUE;
648 }
649
650 static void
651 gst_wavpack_enc_fix_channel_order (GstWavpackEnc * enc, gint32 * data,
652     gint nsamples)
653 {
654   gint i, j;
655   gint32 tmp[8];
656
657   for (i = 0; i < nsamples / enc->channels; i++) {
658     for (j = 0; j < enc->channels; j++) {
659       tmp[enc->channel_mapping[j]] = data[j];
660     }
661     for (j = 0; j < enc->channels; j++) {
662       data[j] = tmp[j];
663     }
664     data += enc->channels;
665   }
666 }
667
668 static GstFlowReturn
669 gst_wavpack_enc_chain (GstPad * pad, GstBuffer * buf)
670 {
671   GstWavpackEnc *enc = GST_WAVPACK_ENC (gst_pad_get_parent (pad));
672   uint32_t sample_count = GST_BUFFER_SIZE (buf) / 4;
673   GstFlowReturn ret;
674
675   /* reset the last returns to GST_FLOW_OK. This is only set to something else
676    * while WavpackPackSamples() or more specific gst_wavpack_enc_push_block()
677    * so not valid anymore */
678   enc->srcpad_last_return = enc->wvcsrcpad_last_return = GST_FLOW_OK;
679
680   GST_DEBUG ("got %u raw samples", sample_count);
681
682   /* check if we already have a valid WavpackContext, otherwise make one */
683   if (!enc->wp_context) {
684     /* create raw context */
685     enc->wp_context =
686         WavpackOpenFileOutput (gst_wavpack_enc_push_block, &enc->wv_id,
687         (enc->correction_mode > 0) ? &enc->wvc_id : NULL);
688     if (!enc->wp_context) {
689       GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL),
690           ("error creating Wavpack context"));
691       gst_object_unref (enc);
692       gst_buffer_unref (buf);
693       return GST_FLOW_ERROR;
694     }
695
696     /* set the WavpackConfig according to our parameters */
697     gst_wavpack_enc_set_wp_config (enc);
698
699     /* set the configuration to the context now that we know everything
700      * and initialize the encoder */
701     if (!WavpackSetConfiguration (enc->wp_context,
702             enc->wp_config, (uint32_t) (-1))
703         || !WavpackPackInit (enc->wp_context)) {
704       GST_ELEMENT_ERROR (enc, LIBRARY, SETTINGS, (NULL),
705           ("error setting up wavpack encoding context"));
706       WavpackCloseFile (enc->wp_context);
707       gst_object_unref (enc);
708       gst_buffer_unref (buf);
709       return GST_FLOW_ERROR;
710     }
711     GST_DEBUG ("setup of encoding context successfull");
712   }
713
714   /* Save the timestamp of the first buffer. This will be later
715    * used as offset for all following buffers */
716   if (enc->timestamp_offset == GST_CLOCK_TIME_NONE) {
717     if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
718       enc->timestamp_offset = GST_BUFFER_TIMESTAMP (buf);
719       enc->next_ts = GST_BUFFER_TIMESTAMP (buf);
720     } else {
721       enc->timestamp_offset = 0;
722       enc->next_ts = 0;
723     }
724   }
725
726   /* Check if we have a continous stream, if not drop some samples or the buffer or
727    * insert some silence samples */
728   if (enc->next_ts != GST_CLOCK_TIME_NONE &&
729       GST_BUFFER_TIMESTAMP (buf) < enc->next_ts) {
730     guint64 diff = enc->next_ts - GST_BUFFER_TIMESTAMP (buf);
731     guint64 diff_bytes;
732
733     GST_WARNING_OBJECT (enc, "Buffer is older than previous "
734         "timestamp + duration (%" GST_TIME_FORMAT "< %" GST_TIME_FORMAT
735         "), cannot handle. Clipping buffer.",
736         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
737         GST_TIME_ARGS (enc->next_ts));
738
739     diff_bytes =
740         GST_CLOCK_TIME_TO_FRAMES (diff, enc->samplerate) * enc->channels * 2;
741     if (diff_bytes >= GST_BUFFER_SIZE (buf)) {
742       gst_buffer_unref (buf);
743       return GST_FLOW_OK;
744     }
745     buf = gst_buffer_make_metadata_writable (buf);
746     GST_BUFFER_DATA (buf) += diff_bytes;
747     GST_BUFFER_SIZE (buf) -= diff_bytes;
748
749     GST_BUFFER_TIMESTAMP (buf) += diff;
750     if (GST_BUFFER_DURATION_IS_VALID (buf))
751       GST_BUFFER_DURATION (buf) -= diff;
752   }
753
754   /* Allow a diff of at most 5 ms */
755   if (enc->next_ts != GST_CLOCK_TIME_NONE
756       && GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
757     if (GST_BUFFER_TIMESTAMP (buf) != enc->next_ts &&
758         GST_BUFFER_TIMESTAMP (buf) - enc->next_ts > 5 * GST_MSECOND) {
759       GST_WARNING_OBJECT (enc,
760           "Discontinuity detected: %" G_GUINT64_FORMAT " > %" G_GUINT64_FORMAT,
761           GST_BUFFER_TIMESTAMP (buf) - enc->next_ts, 5 * GST_MSECOND);
762
763       WavpackFlushSamples (enc->wp_context);
764       enc->timestamp_offset += (GST_BUFFER_TIMESTAMP (buf) - enc->next_ts);
765     }
766   }
767
768   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)
769       && GST_BUFFER_DURATION_IS_VALID (buf))
770     enc->next_ts = GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf);
771   else
772     enc->next_ts = GST_CLOCK_TIME_NONE;
773
774   if (enc->need_channel_remap) {
775     buf = gst_buffer_make_writable (buf);
776     gst_wavpack_enc_fix_channel_order (enc, (gint32 *) GST_BUFFER_DATA (buf),
777         sample_count);
778   }
779
780   /* if we want to append the MD5 sum to the stream update it here
781    * with the current raw samples */
782   if (enc->md5) {
783     g_checksum_update (enc->md5_context, GST_BUFFER_DATA (buf),
784         GST_BUFFER_SIZE (buf));
785   }
786
787   /* encode and handle return values from encoding */
788   if (WavpackPackSamples (enc->wp_context, (int32_t *) GST_BUFFER_DATA (buf),
789           sample_count / enc->channels)) {
790     GST_DEBUG ("encoding samples successful");
791     ret = GST_FLOW_OK;
792   } else {
793     if ((enc->srcpad_last_return == GST_FLOW_RESEND) ||
794         (enc->wvcsrcpad_last_return == GST_FLOW_RESEND)) {
795       ret = GST_FLOW_RESEND;
796     } else if ((enc->srcpad_last_return == GST_FLOW_OK) ||
797         (enc->wvcsrcpad_last_return == GST_FLOW_OK)) {
798       ret = GST_FLOW_OK;
799     } else if ((enc->srcpad_last_return == GST_FLOW_NOT_LINKED) &&
800         (enc->wvcsrcpad_last_return == GST_FLOW_NOT_LINKED)) {
801       ret = GST_FLOW_NOT_LINKED;
802     } else if ((enc->srcpad_last_return == GST_FLOW_FLUSHING) &&
803         (enc->wvcsrcpad_last_return == GST_FLOW_FLUSHING)) {
804       ret = GST_FLOW_FLUSHING;
805     } else {
806       GST_ELEMENT_ERROR (enc, LIBRARY, ENCODE, (NULL),
807           ("encoding samples failed"));
808       ret = GST_FLOW_ERROR;
809     }
810   }
811
812   gst_buffer_unref (buf);
813   gst_object_unref (enc);
814   return ret;
815 }
816
817 static void
818 gst_wavpack_enc_rewrite_first_block (GstWavpackEnc * enc)
819 {
820   GstEvent *event = gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_BYTES,
821       0, GST_BUFFER_OFFSET_NONE, 0);
822   gboolean ret;
823
824   g_return_if_fail (enc);
825   g_return_if_fail (enc->first_block);
826
827   /* update the sample count in the first block */
828   WavpackUpdateNumSamples (enc->wp_context, enc->first_block);
829
830   /* try to seek to the beginning of the output */
831   ret = gst_pad_push_event (enc->srcpad, event);
832   if (ret) {
833     /* try to rewrite the first block */
834     GST_DEBUG_OBJECT (enc, "rewriting first block ...");
835     enc->wv_id.passthrough = TRUE;
836     ret = gst_wavpack_enc_push_block (&enc->wv_id,
837         enc->first_block, enc->first_block_size);
838     enc->wv_id.passthrough = FALSE;
839   } else {
840     GST_WARNING_OBJECT (enc, "rewriting of first block failed. "
841         "Seeking to first block failed!");
842   }
843 }
844
845 static gboolean
846 gst_wavpack_enc_sink_event (GstPad * pad, GstEvent * event)
847 {
848   GstWavpackEnc *enc = GST_WAVPACK_ENC (gst_pad_get_parent (pad));
849   gboolean ret = TRUE;
850
851   GST_DEBUG ("Received %s event on sinkpad", GST_EVENT_TYPE_NAME (event));
852
853   switch (GST_EVENT_TYPE (event)) {
854     case GST_EVENT_EOS:
855       /* Encode all remaining samples and flush them to the src pads */
856       WavpackFlushSamples (enc->wp_context);
857
858       /* Drop all remaining data, this is no complete block otherwise
859        * it would've been pushed already */
860       if (enc->pending_buffer) {
861         gst_buffer_unref (enc->pending_buffer);
862         enc->pending_buffer = NULL;
863         enc->pending_offset = 0;
864       }
865
866       /* write the MD5 sum if we have to write one */
867       if ((enc->md5) && (enc->md5_context)) {
868         guint8 md5_digest[16];
869         gsize digest_len = sizeof (md5_digest);
870
871         g_checksum_get_digest (enc->md5_context, md5_digest, &digest_len);
872         if (digest_len == sizeof (md5_digest))
873           WavpackStoreMD5Sum (enc->wp_context, md5_digest);
874         else
875           GST_WARNING_OBJECT (enc, "Calculating MD5 digest failed");
876       }
877
878       /* Try to rewrite the first frame with the correct sample number */
879       if (enc->first_block)
880         gst_wavpack_enc_rewrite_first_block (enc);
881
882       /* close the context if not already happened */
883       if (enc->wp_context) {
884         WavpackCloseFile (enc->wp_context);
885         enc->wp_context = NULL;
886       }
887
888       ret = gst_pad_event_default (pad, event);
889       break;
890     case GST_EVENT_NEWSEGMENT:
891       if (enc->wp_context) {
892         GST_WARNING_OBJECT (enc, "got NEWSEGMENT after encoding "
893             "already started");
894       }
895       /* drop NEWSEGMENT events, we create our own when pushing
896        * the first buffer to the pads */
897       gst_event_unref (event);
898       ret = TRUE;
899       break;
900     default:
901       ret = gst_pad_event_default (pad, event);
902       break;
903   }
904
905   gst_object_unref (enc);
906   return ret;
907 }
908
909 static GstStateChangeReturn
910 gst_wavpack_enc_change_state (GstElement * element, GstStateChange transition)
911 {
912   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
913   GstWavpackEnc *enc = GST_WAVPACK_ENC (element);
914
915   switch (transition) {
916     case GST_STATE_CHANGE_NULL_TO_READY:
917       /* set the last returned GstFlowReturns of the two pads to GST_FLOW_OK
918        * as they're only set to something else in WavpackPackSamples() or more
919        * specific gst_wavpack_enc_push_block() and nothing happened there yet */
920       enc->srcpad_last_return = enc->wvcsrcpad_last_return = GST_FLOW_OK;
921       break;
922     case GST_STATE_CHANGE_READY_TO_PAUSED:
923       break;
924     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
925     default:
926       break;
927   }
928
929   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
930
931   switch (transition) {
932     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
933       break;
934     case GST_STATE_CHANGE_PAUSED_TO_READY:
935       gst_wavpack_enc_reset (enc);
936       break;
937     case GST_STATE_CHANGE_READY_TO_NULL:
938       break;
939     default:
940       break;
941   }
942
943   return ret;
944 }
945
946 static void
947 gst_wavpack_enc_set_property (GObject * object, guint prop_id,
948     const GValue * value, GParamSpec * pspec)
949 {
950   GstWavpackEnc *enc = GST_WAVPACK_ENC (object);
951
952   switch (prop_id) {
953     case ARG_MODE:
954       enc->mode = g_value_get_enum (value);
955       break;
956     case ARG_BITRATE:{
957       guint val = g_value_get_uint (value);
958
959       if ((val >= 24000) && (val <= 9600000)) {
960         enc->bitrate = val;
961         enc->bps = 0.0;
962       } else {
963         enc->bitrate = 0;
964         enc->bps = 0.0;
965       }
966       break;
967     }
968     case ARG_BITSPERSAMPLE:{
969       gdouble val = g_value_get_double (value);
970
971       if ((val >= 2.0) && (val <= 24.0)) {
972         enc->bps = val;
973         enc->bitrate = 0;
974       } else {
975         enc->bps = 0.0;
976         enc->bitrate = 0;
977       }
978       break;
979     }
980     case ARG_CORRECTION_MODE:
981       enc->correction_mode = g_value_get_enum (value);
982       break;
983     case ARG_MD5:
984       enc->md5 = g_value_get_boolean (value);
985       break;
986     case ARG_EXTRA_PROCESSING:
987       enc->extra_processing = g_value_get_uint (value);
988       break;
989     case ARG_JOINT_STEREO_MODE:
990       enc->joint_stereo_mode = g_value_get_enum (value);
991       break;
992     default:
993       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
994       break;
995   }
996 }
997
998 static void
999 gst_wavpack_enc_get_property (GObject * object, guint prop_id, GValue * value,
1000     GParamSpec * pspec)
1001 {
1002   GstWavpackEnc *enc = GST_WAVPACK_ENC (object);
1003
1004   switch (prop_id) {
1005     case ARG_MODE:
1006       g_value_set_enum (value, enc->mode);
1007       break;
1008     case ARG_BITRATE:
1009       if (enc->bps == 0.0) {
1010         g_value_set_uint (value, enc->bitrate);
1011       } else {
1012         g_value_set_uint (value, 0);
1013       }
1014       break;
1015     case ARG_BITSPERSAMPLE:
1016       if (enc->bitrate == 0) {
1017         g_value_set_double (value, enc->bps);
1018       } else {
1019         g_value_set_double (value, 0.0);
1020       }
1021       break;
1022     case ARG_CORRECTION_MODE:
1023       g_value_set_enum (value, enc->correction_mode);
1024       break;
1025     case ARG_MD5:
1026       g_value_set_boolean (value, enc->md5);
1027       break;
1028     case ARG_EXTRA_PROCESSING:
1029       g_value_set_uint (value, enc->extra_processing);
1030       break;
1031     case ARG_JOINT_STEREO_MODE:
1032       g_value_set_enum (value, enc->joint_stereo_mode);
1033       break;
1034     default:
1035       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1036       break;
1037   }
1038 }
1039
1040 gboolean
1041 gst_wavpack_enc_plugin_init (GstPlugin * plugin)
1042 {
1043   if (!gst_element_register (plugin, "wavpackenc",
1044           GST_RANK_NONE, GST_TYPE_WAVPACK_ENC))
1045     return FALSE;
1046
1047   GST_DEBUG_CATEGORY_INIT (gst_wavpack_enc_debug, "wavpack_enc", 0,
1048       "Wavpack encoder");
1049
1050   return TRUE;
1051 }