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