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