Merge branch 'master' into 0.11
[platform/upstream/gstreamer.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 gboolean gst_wavpack_enc_start (GstAudioEncoder * enc);
59 static gboolean gst_wavpack_enc_stop (GstAudioEncoder * enc);
60 static gboolean gst_wavpack_enc_set_format (GstAudioEncoder * enc,
61     GstAudioInfo * info);
62 static GstFlowReturn gst_wavpack_enc_handle_frame (GstAudioEncoder * enc,
63     GstBuffer * in_buf);
64 static gboolean gst_wavpack_enc_sink_event (GstAudioEncoder * enc,
65     GstEvent * event);
66
67 static int gst_wavpack_enc_push_block (void *id, void *data, int32_t count);
68 static GstFlowReturn gst_wavpack_enc_drain (GstWavpackEnc * enc);
69
70 static void gst_wavpack_enc_set_property (GObject * object, guint prop_id,
71     const GValue * value, GParamSpec * pspec);
72 static void gst_wavpack_enc_get_property (GObject * object, guint prop_id,
73     GValue * value, GParamSpec * pspec);
74
75 enum
76 {
77   ARG_0,
78   ARG_MODE,
79   ARG_BITRATE,
80   ARG_BITSPERSAMPLE,
81   ARG_CORRECTION_MODE,
82   ARG_MD5,
83   ARG_EXTRA_PROCESSING,
84   ARG_JOINT_STEREO_MODE
85 };
86
87 GST_DEBUG_CATEGORY_STATIC (gst_wavpack_enc_debug);
88 #define GST_CAT_DEFAULT gst_wavpack_enc_debug
89
90 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
91     GST_PAD_SINK,
92     GST_PAD_ALWAYS,
93     GST_STATIC_CAPS ("audio/x-raw-int, "
94         "width = (int) 32, "
95         "depth = (int) { 24, 32 }, "
96         "endianness = (int) BYTE_ORDER, "
97         "channels = (int) [ 1, 8 ], "
98         "rate = (int) [ 6000, 192000 ]," "signed = (boolean) TRUE")
99     );
100
101 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
102     GST_PAD_SRC,
103     GST_PAD_ALWAYS,
104     GST_STATIC_CAPS ("audio/x-wavpack, "
105         "width = (int) [ 1, 32 ], "
106         "channels = (int) [ 1, 8 ], "
107         "rate = (int) [ 6000, 192000 ], " "framed = (boolean) TRUE")
108     );
109
110 static GstStaticPadTemplate wvcsrc_factory = GST_STATIC_PAD_TEMPLATE ("wvcsrc",
111     GST_PAD_SRC,
112     GST_PAD_SOMETIMES,
113     GST_STATIC_CAPS ("audio/x-wavpack-correction, " "framed = (boolean) TRUE")
114     );
115
116 enum
117 {
118   GST_WAVPACK_ENC_MODE_VERY_FAST = 0,
119   GST_WAVPACK_ENC_MODE_FAST,
120   GST_WAVPACK_ENC_MODE_DEFAULT,
121   GST_WAVPACK_ENC_MODE_HIGH,
122   GST_WAVPACK_ENC_MODE_VERY_HIGH
123 };
124
125 #define GST_TYPE_WAVPACK_ENC_MODE (gst_wavpack_enc_mode_get_type ())
126 static GType
127 gst_wavpack_enc_mode_get_type (void)
128 {
129   static GType qtype = 0;
130
131   if (qtype == 0) {
132     static const GEnumValue values[] = {
133 #if 0
134       /* Very Fast Compression is not supported yet, but will be supported
135        * in future wavpack versions */
136       {GST_WAVPACK_ENC_MODE_VERY_FAST, "Very Fast Compression", "veryfast"},
137 #endif
138       {GST_WAVPACK_ENC_MODE_FAST, "Fast Compression", "fast"},
139       {GST_WAVPACK_ENC_MODE_DEFAULT, "Normal Compression", "normal"},
140       {GST_WAVPACK_ENC_MODE_HIGH, "High Compression", "high"},
141 #ifndef WAVPACK_OLD_API
142       {GST_WAVPACK_ENC_MODE_VERY_HIGH, "Very High Compression", "veryhigh"},
143 #endif
144       {0, NULL, NULL}
145     };
146
147     qtype = g_enum_register_static ("GstWavpackEncMode", values);
148   }
149   return qtype;
150 }
151
152 enum
153 {
154   GST_WAVPACK_CORRECTION_MODE_OFF = 0,
155   GST_WAVPACK_CORRECTION_MODE_ON,
156   GST_WAVPACK_CORRECTION_MODE_OPTIMIZED
157 };
158
159 #define GST_TYPE_WAVPACK_ENC_CORRECTION_MODE (gst_wavpack_enc_correction_mode_get_type ())
160 static GType
161 gst_wavpack_enc_correction_mode_get_type (void)
162 {
163   static GType qtype = 0;
164
165   if (qtype == 0) {
166     static const GEnumValue values[] = {
167       {GST_WAVPACK_CORRECTION_MODE_OFF, "Create no correction file", "off"},
168       {GST_WAVPACK_CORRECTION_MODE_ON, "Create correction file", "on"},
169       {GST_WAVPACK_CORRECTION_MODE_OPTIMIZED,
170           "Create optimized correction file", "optimized"},
171       {0, NULL, NULL}
172     };
173
174     qtype = g_enum_register_static ("GstWavpackEncCorrectionMode", values);
175   }
176   return qtype;
177 }
178
179 enum
180 {
181   GST_WAVPACK_JS_MODE_AUTO = 0,
182   GST_WAVPACK_JS_MODE_LEFT_RIGHT,
183   GST_WAVPACK_JS_MODE_MID_SIDE
184 };
185
186 #define GST_TYPE_WAVPACK_ENC_JOINT_STEREO_MODE (gst_wavpack_enc_joint_stereo_mode_get_type ())
187 static GType
188 gst_wavpack_enc_joint_stereo_mode_get_type (void)
189 {
190   static GType qtype = 0;
191
192   if (qtype == 0) {
193     static const GEnumValue values[] = {
194       {GST_WAVPACK_JS_MODE_AUTO, "auto", "auto"},
195       {GST_WAVPACK_JS_MODE_LEFT_RIGHT, "left/right", "leftright"},
196       {GST_WAVPACK_JS_MODE_MID_SIDE, "mid/side", "midside"},
197       {0, NULL, NULL}
198     };
199
200     qtype = g_enum_register_static ("GstWavpackEncJSMode", values);
201   }
202   return qtype;
203 }
204
205 GST_BOILERPLATE (GstWavpackEnc, gst_wavpack_enc, GstAudioEncoder,
206     GST_TYPE_AUDIO_ENCODER);
207
208 static void
209 gst_wavpack_enc_base_init (gpointer klass)
210 {
211   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
212
213   /* add pad templates */
214   gst_element_class_add_pad_template (element_class,
215       gst_static_pad_template_get (&sink_factory));
216   gst_element_class_add_pad_template (element_class,
217       gst_static_pad_template_get (&src_factory));
218   gst_element_class_add_pad_template (element_class,
219       gst_static_pad_template_get (&wvcsrc_factory));
220
221   /* set element details */
222   gst_element_class_set_details_simple (element_class, "Wavpack audio encoder",
223       "Codec/Encoder/Audio",
224       "Encodes audio with the Wavpack lossless/lossy audio codec",
225       "Sebastian Dröge <slomo@circular-chaos.org>");
226 }
227
228 static void
229 gst_wavpack_enc_class_init (GstWavpackEncClass * klass)
230 {
231   GObjectClass *gobject_class = (GObjectClass *) klass;
232   GstAudioEncoderClass *base_class = (GstAudioEncoderClass *) (klass);
233
234   parent_class = g_type_class_peek_parent (klass);
235
236   /* set property handlers */
237   gobject_class->set_property = gst_wavpack_enc_set_property;
238   gobject_class->get_property = gst_wavpack_enc_get_property;
239
240   base_class->start = GST_DEBUG_FUNCPTR (gst_wavpack_enc_start);
241   base_class->stop = GST_DEBUG_FUNCPTR (gst_wavpack_enc_stop);
242   base_class->set_format = GST_DEBUG_FUNCPTR (gst_wavpack_enc_set_format);
243   base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_wavpack_enc_handle_frame);
244   base_class->event = GST_DEBUG_FUNCPTR (gst_wavpack_enc_sink_event);
245
246   /* install all properties */
247   g_object_class_install_property (gobject_class, ARG_MODE,
248       g_param_spec_enum ("mode", "Encoding mode",
249           "Speed versus compression tradeoff.",
250           GST_TYPE_WAVPACK_ENC_MODE, GST_WAVPACK_ENC_MODE_DEFAULT,
251           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
252   g_object_class_install_property (gobject_class, ARG_BITRATE,
253       g_param_spec_uint ("bitrate", "Bitrate",
254           "Try to encode with this average bitrate (bits/sec). "
255           "This enables lossy encoding, values smaller than 24000 disable it again.",
256           0, 9600000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
257   g_object_class_install_property (gobject_class, ARG_BITSPERSAMPLE,
258       g_param_spec_double ("bits-per-sample", "Bits per sample",
259           "Try to encode with this amount of bits per sample. "
260           "This enables lossy encoding, values smaller than 2.0 disable it again.",
261           0.0, 24.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
262   g_object_class_install_property (gobject_class, ARG_CORRECTION_MODE,
263       g_param_spec_enum ("correction-mode", "Correction stream mode",
264           "Use this mode for the correction stream. Only works in lossy mode!",
265           GST_TYPE_WAVPACK_ENC_CORRECTION_MODE, GST_WAVPACK_CORRECTION_MODE_OFF,
266           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
267   g_object_class_install_property (gobject_class, ARG_MD5,
268       g_param_spec_boolean ("md5", "MD5",
269           "Store MD5 hash of raw samples within the file.", FALSE,
270           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
271   g_object_class_install_property (gobject_class, ARG_EXTRA_PROCESSING,
272       g_param_spec_uint ("extra-processing", "Extra processing",
273           "Use better but slower filters for better compression/quality.",
274           0, 6, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
275   g_object_class_install_property (gobject_class, ARG_JOINT_STEREO_MODE,
276       g_param_spec_enum ("joint-stereo-mode", "Joint-Stereo mode",
277           "Use this joint-stereo mode.", GST_TYPE_WAVPACK_ENC_JOINT_STEREO_MODE,
278           GST_WAVPACK_JS_MODE_AUTO,
279           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
280 }
281
282 static void
283 gst_wavpack_enc_reset (GstWavpackEnc * enc)
284 {
285   /* close and free everything stream related if we already did something */
286   if (enc->wp_context) {
287     WavpackCloseFile (enc->wp_context);
288     enc->wp_context = NULL;
289   }
290   if (enc->wp_config) {
291     g_free (enc->wp_config);
292     enc->wp_config = NULL;
293   }
294   if (enc->first_block) {
295     g_free (enc->first_block);
296     enc->first_block = NULL;
297   }
298   enc->first_block_size = 0;
299   if (enc->md5_context) {
300     g_checksum_free (enc->md5_context);
301     enc->md5_context = NULL;
302   }
303   if (enc->pending_segment)
304     gst_event_unref (enc->pending_segment);
305   enc->pending_segment = NULL;
306
307   if (enc->pending_buffer) {
308     gst_buffer_unref (enc->pending_buffer);
309     enc->pending_buffer = NULL;
310     enc->pending_offset = 0;
311   }
312
313   /* reset the last returns to GST_FLOW_OK. This is only set to something else
314    * while WavpackPackSamples() or more specific gst_wavpack_enc_push_block()
315    * so not valid anymore */
316   enc->srcpad_last_return = enc->wvcsrcpad_last_return = GST_FLOW_OK;
317
318   /* reset stream information */
319   enc->samplerate = 0;
320   enc->depth = 0;
321   enc->channels = 0;
322   enc->channel_mask = 0;
323   enc->need_channel_remap = FALSE;
324
325   enc->timestamp_offset = GST_CLOCK_TIME_NONE;
326   enc->next_ts = GST_CLOCK_TIME_NONE;
327 }
328
329 static void
330 gst_wavpack_enc_init (GstWavpackEnc * enc, GstWavpackEncClass * gclass)
331 {
332   GstAudioEncoder *benc = GST_AUDIO_ENCODER (enc);
333
334   /* initialize object attributes */
335   enc->wp_config = NULL;
336   enc->wp_context = NULL;
337   enc->first_block = NULL;
338   enc->md5_context = NULL;
339   gst_wavpack_enc_reset (enc);
340
341   enc->wv_id.correction = FALSE;
342   enc->wv_id.wavpack_enc = enc;
343   enc->wv_id.passthrough = FALSE;
344   enc->wvc_id.correction = TRUE;
345   enc->wvc_id.wavpack_enc = enc;
346   enc->wvc_id.passthrough = FALSE;
347
348   /* set default values of params */
349   enc->mode = GST_WAVPACK_ENC_MODE_DEFAULT;
350   enc->bitrate = 0;
351   enc->bps = 0.0;
352   enc->correction_mode = GST_WAVPACK_CORRECTION_MODE_OFF;
353   enc->md5 = FALSE;
354   enc->extra_processing = 0;
355   enc->joint_stereo_mode = GST_WAVPACK_JS_MODE_AUTO;
356
357   /* require perfect ts */
358   gst_audio_encoder_set_perfect_timestamp (benc, TRUE);
359 }
360
361
362 static gboolean
363 gst_wavpack_enc_start (GstAudioEncoder * enc)
364 {
365   GST_DEBUG_OBJECT (enc, "start");
366
367   return TRUE;
368 }
369
370 static gboolean
371 gst_wavpack_enc_stop (GstAudioEncoder * enc)
372 {
373   GstWavpackEnc *wpenc = GST_WAVPACK_ENC (enc);
374
375   GST_DEBUG_OBJECT (enc, "stop");
376   gst_wavpack_enc_reset (wpenc);
377
378   return TRUE;
379 }
380
381 static gboolean
382 gst_wavpack_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
383 {
384   GstWavpackEnc *enc = GST_WAVPACK_ENC (benc);
385   GstAudioChannelPosition *pos;
386   GstCaps *caps;
387
388   /* we may be configured again, but that change should have cleanup context */
389   g_assert (enc->wp_context == NULL);
390
391   enc->channels = GST_AUDIO_INFO_CHANNELS (info);
392   enc->depth = GST_AUDIO_INFO_DEPTH (info);
393   enc->samplerate = GST_AUDIO_INFO_RATE (info);
394
395   pos = info->position;
396   g_assert (pos);
397
398   /* If one channel is NONE they'll be all undefined */
399   if (pos != NULL && pos[0] == GST_AUDIO_CHANNEL_POSITION_NONE) {
400     goto invalid_channels;
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
409   /* set fixed src pad caps now that we know what we will get */
410   caps = gst_caps_new_simple ("audio/x-wavpack",
411       "channels", G_TYPE_INT, enc->channels,
412       "rate", G_TYPE_INT, enc->samplerate,
413       "width", G_TYPE_INT, enc->depth, "framed", G_TYPE_BOOLEAN, TRUE, NULL);
414
415   if (!gst_wavpack_set_channel_layout (caps, enc->channel_mask))
416     GST_WARNING_OBJECT (enc, "setting channel layout failed");
417
418   if (!gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps))
419     goto setting_src_caps_failed;
420
421   gst_caps_unref (caps);
422
423   /* no special feedback to base class; should provide all available samples */
424
425   return TRUE;
426
427   /* ERRORS */
428 setting_src_caps_failed:
429   {
430     GST_DEBUG_OBJECT (enc,
431         "Couldn't set caps on source pad: %" GST_PTR_FORMAT, caps);
432     gst_caps_unref (caps);
433     return FALSE;
434   }
435 invalid_channels:
436   {
437     GST_DEBUG_OBJECT (enc, "input has invalid channel layout");
438     return FALSE;
439   }
440 }
441
442 static void
443 gst_wavpack_enc_set_wp_config (GstWavpackEnc * enc)
444 {
445   enc->wp_config = g_new0 (WavpackConfig, 1);
446   /* set general stream informations in the WavpackConfig */
447   enc->wp_config->bytes_per_sample = GST_ROUND_UP_8 (enc->depth) / 8;
448   enc->wp_config->bits_per_sample = enc->depth;
449   enc->wp_config->num_channels = enc->channels;
450   enc->wp_config->channel_mask = enc->channel_mask;
451   enc->wp_config->sample_rate = enc->samplerate;
452
453   /*
454    * Set parameters in WavpackConfig
455    */
456
457   /* Encoding mode */
458   switch (enc->mode) {
459 #if 0
460     case GST_WAVPACK_ENC_MODE_VERY_FAST:
461       enc->wp_config->flags |= CONFIG_VERY_FAST_FLAG;
462       enc->wp_config->flags |= CONFIG_FAST_FLAG;
463       break;
464 #endif
465     case GST_WAVPACK_ENC_MODE_FAST:
466       enc->wp_config->flags |= CONFIG_FAST_FLAG;
467       break;
468     case GST_WAVPACK_ENC_MODE_DEFAULT:
469       break;
470     case GST_WAVPACK_ENC_MODE_HIGH:
471       enc->wp_config->flags |= CONFIG_HIGH_FLAG;
472       break;
473 #ifndef WAVPACK_OLD_API
474     case GST_WAVPACK_ENC_MODE_VERY_HIGH:
475       enc->wp_config->flags |= CONFIG_HIGH_FLAG;
476       enc->wp_config->flags |= CONFIG_VERY_HIGH_FLAG;
477       break;
478 #endif
479   }
480
481   /* Bitrate, enables lossy mode */
482   if (enc->bitrate) {
483     enc->wp_config->flags |= CONFIG_HYBRID_FLAG;
484     enc->wp_config->flags |= CONFIG_BITRATE_KBPS;
485     enc->wp_config->bitrate = enc->bitrate / 1000.0;
486   } else if (enc->bps) {
487     enc->wp_config->flags |= CONFIG_HYBRID_FLAG;
488     enc->wp_config->bitrate = enc->bps;
489   }
490
491   /* Correction Mode, only in lossy mode */
492   if (enc->wp_config->flags & CONFIG_HYBRID_FLAG) {
493     if (enc->correction_mode > GST_WAVPACK_CORRECTION_MODE_OFF) {
494       GstCaps *caps = gst_caps_new_simple ("audio/x-wavpack-correction",
495           "framed", G_TYPE_BOOLEAN, TRUE, NULL);
496
497       enc->wvcsrcpad =
498           gst_pad_new_from_static_template (&wvcsrc_factory, "wvcsrc");
499
500       /* try to add correction src pad, don't set correction mode on failure */
501       GST_DEBUG_OBJECT (enc, "Adding correction pad with caps %"
502           GST_PTR_FORMAT, caps);
503       if (!gst_pad_set_caps (enc->wvcsrcpad, caps)) {
504         enc->correction_mode = 0;
505         GST_WARNING_OBJECT (enc, "setting correction caps failed");
506       } else {
507         gst_pad_use_fixed_caps (enc->wvcsrcpad);
508         gst_pad_set_active (enc->wvcsrcpad, TRUE);
509         gst_element_add_pad (GST_ELEMENT (enc), enc->wvcsrcpad);
510         enc->wp_config->flags |= CONFIG_CREATE_WVC;
511         if (enc->correction_mode == GST_WAVPACK_CORRECTION_MODE_OPTIMIZED) {
512           enc->wp_config->flags |= CONFIG_OPTIMIZE_WVC;
513         }
514       }
515       gst_caps_unref (caps);
516     }
517   } else {
518     if (enc->correction_mode > GST_WAVPACK_CORRECTION_MODE_OFF) {
519       enc->correction_mode = 0;
520       GST_WARNING_OBJECT (enc, "setting correction mode only has "
521           "any effect if a bitrate is provided.");
522     }
523   }
524   gst_element_no_more_pads (GST_ELEMENT (enc));
525
526   /* MD5, setup MD5 context */
527   if ((enc->md5) && !(enc->md5_context)) {
528     enc->wp_config->flags |= CONFIG_MD5_CHECKSUM;
529     enc->md5_context = g_checksum_new (G_CHECKSUM_MD5);
530   }
531
532   /* Extra encode processing */
533   if (enc->extra_processing) {
534     enc->wp_config->flags |= CONFIG_EXTRA_MODE;
535     enc->wp_config->xmode = enc->extra_processing;
536   }
537
538   /* Joint stereo mode */
539   switch (enc->joint_stereo_mode) {
540     case GST_WAVPACK_JS_MODE_AUTO:
541       break;
542     case GST_WAVPACK_JS_MODE_LEFT_RIGHT:
543       enc->wp_config->flags |= CONFIG_JOINT_OVERRIDE;
544       enc->wp_config->flags &= ~CONFIG_JOINT_STEREO;
545       break;
546     case GST_WAVPACK_JS_MODE_MID_SIDE:
547       enc->wp_config->flags |= (CONFIG_JOINT_OVERRIDE | CONFIG_JOINT_STEREO);
548       break;
549   }
550 }
551
552 static int
553 gst_wavpack_enc_push_block (void *id, void *data, int32_t count)
554 {
555   GstWavpackEncWriteID *wid = (GstWavpackEncWriteID *) id;
556   GstWavpackEnc *enc = GST_WAVPACK_ENC (wid->wavpack_enc);
557   GstFlowReturn *flow;
558   GstBuffer *buffer;
559   GstPad *pad;
560   guchar *block = (guchar *) data;
561   gint samples = 0;
562
563   pad = (wid->correction) ? enc->wvcsrcpad : GST_AUDIO_ENCODER_SRC_PAD (enc);
564   flow =
565       (wid->correction) ? &enc->wvcsrcpad_last_return : &enc->
566       srcpad_last_return;
567
568   buffer = gst_buffer_new_and_alloc (count);
569   g_memmove (GST_BUFFER_DATA (buffer), block, count);
570
571   if (count > sizeof (WavpackHeader) && memcmp (block, "wvpk", 4) == 0) {
572     /* if it's a Wavpack block set buffer timestamp and duration, etc */
573     WavpackHeader wph;
574
575     GST_LOG_OBJECT (enc, "got %d bytes of encoded wavpack %sdata",
576         count, (wid->correction) ? "correction " : "");
577
578     gst_wavpack_read_header (&wph, block);
579
580     /* Only set when pushing the first buffer again, in that case
581      * we don't want to delay the buffer or push newsegment events
582      */
583     if (!wid->passthrough) {
584       /* Only push complete blocks */
585       if (enc->pending_buffer == NULL) {
586         enc->pending_buffer = buffer;
587         enc->pending_offset = wph.block_index;
588       } else if (enc->pending_offset == wph.block_index) {
589         enc->pending_buffer = gst_buffer_join (enc->pending_buffer, buffer);
590       } else {
591         GST_ERROR ("Got incomplete block, dropping");
592         gst_buffer_unref (enc->pending_buffer);
593         enc->pending_buffer = buffer;
594         enc->pending_offset = wph.block_index;
595       }
596
597       if (!(wph.flags & FINAL_BLOCK))
598         return TRUE;
599
600       buffer = enc->pending_buffer;
601       enc->pending_buffer = NULL;
602       enc->pending_offset = 0;
603
604       /* only send segment on correction pad,
605        * regular pad is handled normally by baseclass */
606       if (wid->correction && enc->pending_segment) {
607         gst_pad_push_event (pad, enc->pending_segment);
608         enc->pending_segment = NULL;
609       }
610
611       if (wph.block_index == 0) {
612         /* save header for later reference, so we can re-send it later on
613          * EOS with fixed up values for total sample count etc. */
614         if (enc->first_block == NULL && !wid->correction) {
615           enc->first_block =
616               g_memdup (GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
617           enc->first_block_size = GST_BUFFER_SIZE (buffer);
618         }
619       }
620     }
621     samples = wph.block_samples;
622
623     /* decorate buffer */
624     /* NOTE: this will get overwritten by baseclass, but stay for those
625      * that are pushed directly
626      * FIXME: add setting to baseclass to avoid overwriting it ?? */
627     GST_BUFFER_OFFSET (buffer) = wph.block_index;
628     GST_BUFFER_OFFSET_END (buffer) = wph.block_index + wph.block_samples;
629   } else {
630     /* if it's something else set no timestamp and duration on the buffer */
631     GST_DEBUG_OBJECT (enc, "got %d bytes of unknown data", count);
632   }
633
634   if (wid->correction || wid->passthrough) {
635     /* push the buffer and forward errors */
636     GST_DEBUG_OBJECT (enc, "pushing buffer with %d bytes",
637         GST_BUFFER_SIZE (buffer));
638     *flow = gst_pad_push (pad, buffer);
639   } else {
640     GST_DEBUG_OBJECT (enc, "handing frame of %d bytes",
641         GST_BUFFER_SIZE (buffer));
642     *flow = gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), buffer,
643         samples);
644   }
645
646   if (*flow != GST_FLOW_OK) {
647     GST_WARNING_OBJECT (enc, "flow on %s:%s = %s",
648         GST_DEBUG_PAD_NAME (pad), gst_flow_get_name (*flow));
649     return FALSE;
650   }
651
652   return TRUE;
653 }
654
655 static void
656 gst_wavpack_enc_fix_channel_order (GstWavpackEnc * enc, gint32 * data,
657     gint nsamples)
658 {
659   gint i, j;
660   gint32 tmp[8];
661
662   for (i = 0; i < nsamples / enc->channels; i++) {
663     for (j = 0; j < enc->channels; j++) {
664       tmp[enc->channel_mapping[j]] = data[j];
665     }
666     for (j = 0; j < enc->channels; j++) {
667       data[j] = tmp[j];
668     }
669     data += enc->channels;
670   }
671 }
672
673 static GstFlowReturn
674 gst_wavpack_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
675 {
676   GstWavpackEnc *enc = GST_WAVPACK_ENC (benc);
677   uint32_t sample_count;
678   GstFlowReturn ret;
679
680   /* base class ensures configuration */
681   g_return_val_if_fail (enc->depth != 0, GST_FLOW_NOT_NEGOTIATED);
682
683   /* reset the last returns to GST_FLOW_OK. This is only set to something else
684    * while WavpackPackSamples() or more specific gst_wavpack_enc_push_block()
685    * so not valid anymore */
686   enc->srcpad_last_return = enc->wvcsrcpad_last_return = GST_FLOW_OK;
687
688   if (G_UNLIKELY (!buf))
689     return gst_wavpack_enc_drain (enc);
690
691   sample_count = GST_BUFFER_SIZE (buf) / 4;
692   GST_DEBUG_OBJECT (enc, "got %u raw samples", sample_count);
693
694   /* check if we already have a valid WavpackContext, otherwise make one */
695   if (!enc->wp_context) {
696     /* create raw context */
697     enc->wp_context =
698         WavpackOpenFileOutput (gst_wavpack_enc_push_block, &enc->wv_id,
699         (enc->correction_mode > 0) ? &enc->wvc_id : NULL);
700     if (!enc->wp_context)
701       goto context_failed;
702
703     /* set the WavpackConfig according to our parameters */
704     gst_wavpack_enc_set_wp_config (enc);
705
706     /* set the configuration to the context now that we know everything
707      * and initialize the encoder */
708     if (!WavpackSetConfiguration (enc->wp_context,
709             enc->wp_config, (uint32_t) (-1))
710         || !WavpackPackInit (enc->wp_context)) {
711       WavpackCloseFile (enc->wp_context);
712       goto config_failed;
713     }
714     GST_DEBUG_OBJECT (enc, "setup of encoding context successfull");
715   }
716
717   if (enc->need_channel_remap) {
718     buf = gst_buffer_make_writable (buf);
719     gst_wavpack_enc_fix_channel_order (enc, (gint32 *) GST_BUFFER_DATA (buf),
720         sample_count);
721   }
722
723   /* if we want to append the MD5 sum to the stream update it here
724    * with the current raw samples */
725   if (enc->md5) {
726     g_checksum_update (enc->md5_context, GST_BUFFER_DATA (buf),
727         GST_BUFFER_SIZE (buf));
728   }
729
730   /* encode and handle return values from encoding */
731   if (WavpackPackSamples (enc->wp_context, (int32_t *) GST_BUFFER_DATA (buf),
732           sample_count / enc->channels)) {
733     GST_DEBUG_OBJECT (enc, "encoding samples successful");
734     ret = GST_FLOW_OK;
735   } else {
736     if ((enc->srcpad_last_return == GST_FLOW_RESEND) ||
737         (enc->wvcsrcpad_last_return == GST_FLOW_RESEND)) {
738       ret = GST_FLOW_RESEND;
739     } else if ((enc->srcpad_last_return == GST_FLOW_OK) ||
740         (enc->wvcsrcpad_last_return == GST_FLOW_OK)) {
741       ret = GST_FLOW_OK;
742     } else if ((enc->srcpad_last_return == GST_FLOW_NOT_LINKED) &&
743         (enc->wvcsrcpad_last_return == GST_FLOW_NOT_LINKED)) {
744       ret = GST_FLOW_NOT_LINKED;
745     } else if ((enc->srcpad_last_return == GST_FLOW_FLUSHING) &&
746         (enc->wvcsrcpad_last_return == GST_FLOW_FLUSHING)) {
747       ret = GST_FLOW_FLUSHING;
748     } else {
749       goto encoding_failed;
750     }
751   }
752
753 exit:
754   return ret;
755
756   /* ERRORS */
757 encoding_failed:
758   {
759     GST_ELEMENT_ERROR (enc, LIBRARY, ENCODE, (NULL),
760         ("encoding samples failed"));
761     ret = GST_FLOW_ERROR;
762     goto exit;
763   }
764 config_failed:
765   {
766     GST_ELEMENT_ERROR (enc, LIBRARY, SETTINGS, (NULL),
767         ("error setting up wavpack encoding context"));
768     ret = GST_FLOW_ERROR;
769     goto exit;
770   }
771 context_failed:
772   {
773     GST_ELEMENT_ERROR (enc, LIBRARY, INIT, (NULL),
774         ("error creating Wavpack context"));
775     ret = GST_FLOW_ERROR;
776     goto exit;
777   }
778 }
779
780 static void
781 gst_wavpack_enc_rewrite_first_block (GstWavpackEnc * enc)
782 {
783   GstEvent *event = gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_BYTES,
784       0, GST_BUFFER_OFFSET_NONE, 0);
785   gboolean ret;
786
787   g_return_if_fail (enc);
788   g_return_if_fail (enc->first_block);
789
790   /* update the sample count in the first block */
791   WavpackUpdateNumSamples (enc->wp_context, enc->first_block);
792
793   /* try to seek to the beginning of the output */
794   ret = gst_pad_push_event (GST_AUDIO_ENCODER_SRC_PAD (enc), event);
795   if (ret) {
796     /* try to rewrite the first block */
797     GST_DEBUG_OBJECT (enc, "rewriting first block ...");
798     enc->wv_id.passthrough = TRUE;
799     ret = gst_wavpack_enc_push_block (&enc->wv_id,
800         enc->first_block, enc->first_block_size);
801     enc->wv_id.passthrough = FALSE;
802     g_free (enc->first_block);
803     enc->first_block = NULL;
804   } else {
805     GST_WARNING_OBJECT (enc, "rewriting of first block failed. "
806         "Seeking to first block failed!");
807   }
808 }
809
810 static GstFlowReturn
811 gst_wavpack_enc_drain (GstWavpackEnc * enc)
812 {
813   if (!enc->wp_context)
814     return GST_FLOW_OK;
815
816   GST_DEBUG_OBJECT (enc, "draining");
817
818   /* Encode all remaining samples and flush them to the src pads */
819   WavpackFlushSamples (enc->wp_context);
820
821   /* Drop all remaining data, this is no complete block otherwise
822    * it would've been pushed already */
823   if (enc->pending_buffer) {
824     gst_buffer_unref (enc->pending_buffer);
825     enc->pending_buffer = NULL;
826     enc->pending_offset = 0;
827   }
828
829   /* write the MD5 sum if we have to write one */
830   if ((enc->md5) && (enc->md5_context)) {
831     guint8 md5_digest[16];
832     gsize digest_len = sizeof (md5_digest);
833
834     g_checksum_get_digest (enc->md5_context, md5_digest, &digest_len);
835     if (digest_len == sizeof (md5_digest)) {
836       WavpackStoreMD5Sum (enc->wp_context, md5_digest);
837       WavpackFlushSamples (enc->wp_context);
838     } else
839       GST_WARNING_OBJECT (enc, "Calculating MD5 digest failed");
840   }
841
842   /* Try to rewrite the first frame with the correct sample number */
843   if (enc->first_block)
844     gst_wavpack_enc_rewrite_first_block (enc);
845
846   /* close the context if not already happened */
847   if (enc->wp_context) {
848     WavpackCloseFile (enc->wp_context);
849     enc->wp_context = NULL;
850   }
851
852   return GST_FLOW_OK;
853 }
854
855 static gboolean
856 gst_wavpack_enc_sink_event (GstAudioEncoder * benc, GstEvent * event)
857 {
858   GstWavpackEnc *enc = GST_WAVPACK_ENC (benc);
859
860   GST_DEBUG_OBJECT (enc, "Received %s event on sinkpad",
861       GST_EVENT_TYPE_NAME (event));
862
863   switch (GST_EVENT_TYPE (event)) {
864     case GST_EVENT_NEWSEGMENT:
865       if (enc->wp_context) {
866         GST_WARNING_OBJECT (enc, "got NEWSEGMENT after encoding "
867             "already started");
868       }
869       /* peek and hold NEWSEGMENT events for sending on correction pad */
870       if (enc->pending_segment)
871         gst_event_unref (enc->pending_segment);
872       enc->pending_segment = gst_event_ref (event);
873       break;
874     default:
875       break;
876   }
877
878   /* baseclass handles rest */
879   return FALSE;
880 }
881
882 static void
883 gst_wavpack_enc_set_property (GObject * object, guint prop_id,
884     const GValue * value, GParamSpec * pspec)
885 {
886   GstWavpackEnc *enc = GST_WAVPACK_ENC (object);
887
888   switch (prop_id) {
889     case ARG_MODE:
890       enc->mode = g_value_get_enum (value);
891       break;
892     case ARG_BITRATE:{
893       guint val = g_value_get_uint (value);
894
895       if ((val >= 24000) && (val <= 9600000)) {
896         enc->bitrate = val;
897         enc->bps = 0.0;
898       } else {
899         enc->bitrate = 0;
900         enc->bps = 0.0;
901       }
902       break;
903     }
904     case ARG_BITSPERSAMPLE:{
905       gdouble val = g_value_get_double (value);
906
907       if ((val >= 2.0) && (val <= 24.0)) {
908         enc->bps = val;
909         enc->bitrate = 0;
910       } else {
911         enc->bps = 0.0;
912         enc->bitrate = 0;
913       }
914       break;
915     }
916     case ARG_CORRECTION_MODE:
917       enc->correction_mode = g_value_get_enum (value);
918       break;
919     case ARG_MD5:
920       enc->md5 = g_value_get_boolean (value);
921       break;
922     case ARG_EXTRA_PROCESSING:
923       enc->extra_processing = g_value_get_uint (value);
924       break;
925     case ARG_JOINT_STEREO_MODE:
926       enc->joint_stereo_mode = g_value_get_enum (value);
927       break;
928     default:
929       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
930       break;
931   }
932 }
933
934 static void
935 gst_wavpack_enc_get_property (GObject * object, guint prop_id, GValue * value,
936     GParamSpec * pspec)
937 {
938   GstWavpackEnc *enc = GST_WAVPACK_ENC (object);
939
940   switch (prop_id) {
941     case ARG_MODE:
942       g_value_set_enum (value, enc->mode);
943       break;
944     case ARG_BITRATE:
945       if (enc->bps == 0.0) {
946         g_value_set_uint (value, enc->bitrate);
947       } else {
948         g_value_set_uint (value, 0);
949       }
950       break;
951     case ARG_BITSPERSAMPLE:
952       if (enc->bitrate == 0) {
953         g_value_set_double (value, enc->bps);
954       } else {
955         g_value_set_double (value, 0.0);
956       }
957       break;
958     case ARG_CORRECTION_MODE:
959       g_value_set_enum (value, enc->correction_mode);
960       break;
961     case ARG_MD5:
962       g_value_set_boolean (value, enc->md5);
963       break;
964     case ARG_EXTRA_PROCESSING:
965       g_value_set_uint (value, enc->extra_processing);
966       break;
967     case ARG_JOINT_STEREO_MODE:
968       g_value_set_enum (value, enc->joint_stereo_mode);
969       break;
970     default:
971       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
972       break;
973   }
974 }
975
976 gboolean
977 gst_wavpack_enc_plugin_init (GstPlugin * plugin)
978 {
979   if (!gst_element_register (plugin, "wavpackenc",
980           GST_RANK_NONE, GST_TYPE_WAVPACK_ENC))
981     return FALSE;
982
983   GST_DEBUG_CATEGORY_INIT (gst_wavpack_enc_debug, "wavpack_enc", 0,
984       "Wavpack encoder");
985
986   return TRUE;
987 }