• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- mOde: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /* GStreamer .wav encoder
3  * Copyright (C) <2002> Iain Holmes <iain@prettypeople.org>
4  * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net>
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-wavenc
24  * @title: wavenc
25  *
26  * Format an audio stream into the wav format.
27  *
28  * ## Example launch line
29  * |[
30  * gst-launch-1.0 cdparanoiasrc mode=continuous ! queue ! audioconvert ! wavenc ! filesink location=cd.wav
31  * ]| Rip a whole audio CD into a single wav file, with the track table written into a CUE sheet inside the file
32  * |[
33  * gst-launch-1.0 cdparanoiasrc track=5 ! queue ! audioconvert ! wavenc ! filesink location=track5.wav
34  * ]| Rip track 5 of an audio CD into a single wav file containing unencoded raw audio samples.
35  *
36  */
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40 
41 #include <string.h>
42 #include "gstwavenc.h"
43 
44 #include <gst/audio/audio.h>
45 #include <gst/riff/riff-media.h>
46 #include <gst/base/gstbytewriter.h>
47 
48 GST_DEBUG_CATEGORY_STATIC (wavenc_debug);
49 #define GST_CAT_DEFAULT wavenc_debug
50 
51 typedef struct
52 {
53   /* Offset Size    Description   Value
54    * 0x00   4       ID            unique identification value
55    * 0x04   4       Position      play order position
56    * 0x08   4       Data Chunk ID RIFF ID of corresponding data chunk
57    * 0x0c   4       Chunk Start   Byte Offset of Data Chunk *
58    * 0x10   4       Block Start   Byte Offset to sample of First Channel
59    * 0x14   4       Sample Offset Byte Offset to sample byte of First Channel
60    */
61   guint32 id;
62   guint32 position;
63   guint8 data_chunk_id[4];
64   guint32 chunk_start;
65   guint32 block_start;
66   guint32 sample_offset;
67 } GstWavEncCue;
68 
69 typedef struct
70 {
71   /* Offset Size    Description     Value
72    * 0x00   4       Chunk ID        "labl" (0x6C61626C) or "note" (0x6E6F7465)
73    * 0x04   4       Chunk Data Size depends on contained text
74    * 0x08   4       Cue Point ID    0 - 0xFFFFFFFF
75    * 0x0c           Text
76    */
77   guint8 chunk_id[4];
78   guint32 chunk_data_size;
79   guint32 cue_point_id;
80   gchar *text;
81 } GstWavEncLabl, GstWavEncNote;
82 
83 /* FIXME: mono doesn't produce correct files it seems, at least mplayer xruns */
84 #define SINK_CAPS \
85     "audio/x-raw, "                      \
86     "rate = (int) [ 1, MAX ], "          \
87     "channels = (int) [ 1, 65535 ], "      \
88     "format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \
89     "layout = (string) interleaved"      \
90     "; "                                 \
91     "audio/x-alaw, "                     \
92     "rate = (int) [ 8000, 192000 ], "    \
93     "channels = (int) [ 1, 2 ]; "        \
94     "audio/x-mulaw, "                    \
95     "rate = (int) [ 8000, 192000 ], "    \
96     "channels = (int) [ 1, 2 ]"
97 
98 #define SRC_CAPS \
99     "audio/x-wav; " \
100     "audio/x-rf64"
101 
102 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
103     GST_PAD_SINK,
104     GST_PAD_ALWAYS,
105     GST_STATIC_CAPS (SINK_CAPS)
106     );
107 
108 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
109     GST_PAD_SRC,
110     GST_PAD_ALWAYS,
111     GST_STATIC_CAPS (SRC_CAPS)
112     );
113 
114 #define gst_wavenc_parent_class parent_class
115 G_DEFINE_TYPE_WITH_CODE (GstWavEnc, gst_wavenc, GST_TYPE_ELEMENT,
116     G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL)
117     G_IMPLEMENT_INTERFACE (GST_TYPE_TOC_SETTER, NULL)
118     );
119 GST_ELEMENT_REGISTER_DEFINE (wavenc, "wavenc", GST_RANK_PRIMARY,
120     GST_TYPE_WAVENC);
121 
122 static GstFlowReturn gst_wavenc_chain (GstPad * pad, GstObject * parent,
123     GstBuffer * buf);
124 static gboolean gst_wavenc_event (GstPad * pad, GstObject * parent,
125     GstEvent * event);
126 static GstStateChangeReturn gst_wavenc_change_state (GstElement * element,
127     GstStateChange transition);
128 static gboolean gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps);
129 
130 static void
gst_wavenc_class_init(GstWavEncClass * klass)131 gst_wavenc_class_init (GstWavEncClass * klass)
132 {
133   GstElementClass *element_class;
134 
135   element_class = (GstElementClass *) klass;
136 
137   element_class->change_state = GST_DEBUG_FUNCPTR (gst_wavenc_change_state);
138 
139   gst_element_class_set_static_metadata (element_class, "WAV audio muxer",
140       "Codec/Muxer/Audio",
141       "Encode raw audio into WAV", "Iain Holmes <iain@prettypeople.org>");
142 
143   gst_element_class_add_static_pad_template (element_class, &src_factory);
144   gst_element_class_add_static_pad_template (element_class, &sink_factory);
145 
146   GST_DEBUG_CATEGORY_INIT (wavenc_debug, "wavenc", 0, "WAV encoder element");
147 }
148 
149 static void
gst_wavenc_init(GstWavEnc * wavenc)150 gst_wavenc_init (GstWavEnc * wavenc)
151 {
152   wavenc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
153   gst_pad_set_chain_function (wavenc->sinkpad,
154       GST_DEBUG_FUNCPTR (gst_wavenc_chain));
155   gst_pad_set_event_function (wavenc->sinkpad,
156       GST_DEBUG_FUNCPTR (gst_wavenc_event));
157   gst_pad_use_fixed_caps (wavenc->sinkpad);
158   gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->sinkpad);
159 
160   wavenc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
161   gst_pad_use_fixed_caps (wavenc->srcpad);
162   gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad);
163 }
164 
165 #define RIFF_CHUNK_LEN    12
166 #define FMT_WAV_CHUNK_LEN 24
167 #define FMT_EXT_CHUNK_LEN 48
168 #define FACT_CHUNK_LEN    12
169 #define DATA_HEADER_LEN   8
170 #define DS64_CHUNK_LEN    36
171 
172 static gboolean
use_format_ext(GstWavEnc * wavenc)173 use_format_ext (GstWavEnc * wavenc)
174 {
175   return wavenc->channels > 2;
176 }
177 
178 static gboolean
use_fact_chunk(GstWavEnc * wavenc)179 use_fact_chunk (GstWavEnc * wavenc)
180 {
181   return use_format_ext (wavenc) && !wavenc->use_rf64;
182 }
183 
184 static int
get_header_len(GstWavEnc * wavenc)185 get_header_len (GstWavEnc * wavenc)
186 {
187   int len = RIFF_CHUNK_LEN;
188 
189   if (use_format_ext (wavenc))
190     len += FMT_EXT_CHUNK_LEN;
191   else
192     len += FMT_WAV_CHUNK_LEN;
193 
194   if (use_fact_chunk (wavenc))
195     len += FACT_CHUNK_LEN;
196 
197   if (wavenc->use_rf64)
198     len += DS64_CHUNK_LEN;
199 
200   return len + DATA_HEADER_LEN;
201 }
202 
203 static guint64
gstmask_to_wavmask(guint64 gstmask,GstAudioChannelPosition * pos)204 gstmask_to_wavmask (guint64 gstmask, GstAudioChannelPosition * pos)
205 {
206   const GstAudioChannelPosition valid_pos =
207       GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT |
208       GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT |
209       GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER |
210       GST_AUDIO_CHANNEL_POSITION_LFE1 |
211       GST_AUDIO_CHANNEL_POSITION_REAR_LEFT |
212       GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT |
213       GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER |
214       GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER |
215       GST_AUDIO_CHANNEL_POSITION_REAR_CENTER |
216       GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT |
217       GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT |
218       GST_AUDIO_CHANNEL_POSITION_TOP_CENTER |
219       GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT |
220       GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER |
221       GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT |
222       GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT |
223       GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER |
224       GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT;
225 
226   const GstAudioChannelPosition wav_pos[] = {
227     GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
228     GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
229     GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
230     GST_AUDIO_CHANNEL_POSITION_LFE1,
231     GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
232     GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
233     GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
234     GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
235     GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
236     GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
237     GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
238     GST_AUDIO_CHANNEL_POSITION_TOP_CENTER,
239     GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT,
240     GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER,
241     GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT,
242     GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT,
243     GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER,
244     GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT,
245   };
246   int k;
247   int chan = 0;
248   guint64 ret = 0;
249   guint64 mask = 1;
250 
251   if (gstmask == 0 || ((gstmask & ~valid_pos) != 0))
252     return 0;
253 
254   for (k = 0; k < G_N_ELEMENTS (wav_pos); ++k) {
255     if (gstmask & (G_GUINT64_CONSTANT (1) << wav_pos[k])) {
256       ret |= mask;
257       pos[chan++] = wav_pos[k];
258     }
259     mask <<= 1;
260   }
261 
262   return ret;
263 }
264 
265 static guint8 *
write_fmt_chunk(GstWavEnc * wavenc,guint8 * header)266 write_fmt_chunk (GstWavEnc * wavenc, guint8 * header)
267 {
268   guint16 wBlockAlign;
269 
270   wBlockAlign = (wavenc->width / 8) * wavenc->channels;
271 
272   memcpy (header, "fmt ", 4);
273   /* wChannels */
274   GST_WRITE_UINT16_LE (header + 10, wavenc->channels);
275   /* dwSamplesPerSec */
276   GST_WRITE_UINT32_LE (header + 12, wavenc->rate);
277   /* dwAvgBytesPerSec */
278   GST_WRITE_UINT32_LE (header + 16, wBlockAlign * wavenc->rate);
279   /* wBlockAlign */
280   GST_WRITE_UINT16_LE (header + 20, wBlockAlign);
281   /* wBitsPerSample */
282   GST_WRITE_UINT16_LE (header + 22, wavenc->width);
283 
284   if (use_format_ext (wavenc)) {
285     GST_DEBUG_OBJECT (wavenc, "Using WAVE_FORMAT_EXTENSIBLE");
286 
287     GST_WRITE_UINT32_LE (header + 4, FMT_EXT_CHUNK_LEN - 8);
288 
289     /* wFormatTag */
290     GST_WRITE_UINT16_LE (header + 8, 0xFFFE);
291     /* cbSize */
292     GST_WRITE_UINT16_LE (header + 24, 22);
293     /* wValidBitsPerSample */
294     GST_WRITE_UINT16_LE (header + 26, wavenc->width);
295     /* dwChannelMask */
296     GST_WRITE_UINT32_LE (header + 28, (guint32) wavenc->channel_mask);
297 
298     GST_WRITE_UINT16_LE (header + 32, wavenc->format);
299 
300     memcpy (header + 34,
301         "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71", 14);
302 
303     header += FMT_EXT_CHUNK_LEN;
304 
305   } else {
306     GST_WRITE_UINT32_LE (header + 4, FMT_WAV_CHUNK_LEN - 8);
307 
308     /* wFormatTag */
309     GST_WRITE_UINT16_LE (header + 8, wavenc->format);
310     header += FMT_WAV_CHUNK_LEN;
311   }
312 
313   return header;
314 }
315 
316 static guint64
get_num_frames(GstWavEnc * wavenc)317 get_num_frames (GstWavEnc * wavenc)
318 {
319   if (wavenc->channels == 0 || wavenc->width == 0)
320     return 0;
321   return wavenc->audio_length / (wavenc->width / 8) / wavenc->channels;
322 }
323 
324 static guint8 *
write_fact_chunk(GstWavEnc * wavenc,guint8 * header)325 write_fact_chunk (GstWavEnc * wavenc, guint8 * header)
326 {
327   memcpy (header, "fact", 4);
328   GST_WRITE_UINT32_LE (header + 4, FACT_CHUNK_LEN - 8);
329   /* compressed files are only supported up to 2 channels,
330    * that means we never write a fact chunk for them */
331   if (wavenc->use_rf64)
332     GST_WRITE_UINT32_LE (header + 8, 0xFFFFFFFF);
333   else
334     GST_WRITE_UINT32_LE (header + 8, (guint32) get_num_frames (wavenc));
335   return header + FACT_CHUNK_LEN;
336 }
337 
338 static guint8 *
write_ds64_chunk(GstWavEnc * wavenc,guint64 riffLen,guint8 * header)339 write_ds64_chunk (GstWavEnc * wavenc, guint64 riffLen, guint8 * header)
340 {
341   guint64 numFrames = get_num_frames (wavenc);
342 
343   GST_DEBUG_OBJECT (wavenc, "riffLen=%" G_GUINT64_FORMAT
344       ", audio length=%" G_GUINT64_FORMAT ", numFrames=%" G_GUINT64_FORMAT,
345       riffLen, wavenc->audio_length, numFrames);
346 
347   memcpy (header, "ds64", 4);
348   GST_WRITE_UINT32_LE (header + 4, DS64_CHUNK_LEN - 8);
349   /* riffSize */
350   GST_WRITE_UINT32_LE (header + 8, (guint32) (riffLen & 0xFFFFFFFF));
351   GST_WRITE_UINT32_LE (header + 12, (guint32) (riffLen >> 32));
352   /* dataSize */
353   GST_WRITE_UINT32_LE (header + 16,
354       (guint32) (wavenc->audio_length & 0xFFFFFFFF));
355   GST_WRITE_UINT32_LE (header + 20, (guint32) (wavenc->audio_length >> 32));
356   /* sampleCount */
357   GST_WRITE_UINT32_LE (header + 24, (guint32) (numFrames & 0xFFFFFFFF));
358   GST_WRITE_UINT32_LE (header + 28, (guint32) (numFrames >> 32));
359   /* tableLength always zero for now */
360   GST_WRITE_UINT32_LE (header + 32, 0);
361 
362   return header + DS64_CHUNK_LEN;
363 }
364 
365 static GstBuffer *
gst_wavenc_create_header_buf(GstWavEnc * wavenc)366 gst_wavenc_create_header_buf (GstWavEnc * wavenc)
367 {
368   GstBuffer *buf;
369   GstMapInfo map;
370   guint8 *header;
371   guint64 riffLen;
372 
373   GST_DEBUG_OBJECT (wavenc, "Header size: %d", get_header_len (wavenc));
374   buf = gst_buffer_new_and_alloc (get_header_len (wavenc));
375   gst_buffer_map (buf, &map, GST_MAP_WRITE);
376   header = map.data;
377   memset (header, 0, get_header_len (wavenc));
378 
379   riffLen = wavenc->meta_length + wavenc->audio_length
380       + get_header_len (wavenc) - 8;
381 
382   /* RIFF chunk */
383   if (wavenc->use_rf64) {
384     GST_DEBUG_OBJECT (wavenc, "Using RF64");
385     memcpy (header, "RF64", 4);
386     GST_WRITE_UINT32_LE (header + 4, 0xFFFFFFFF);
387   } else {
388     memcpy (header, "RIFF", 4);
389     GST_WRITE_UINT32_LE (header + 4, (guint32) riffLen);
390   }
391   memcpy (header + 8, "WAVE", 4);
392   header += RIFF_CHUNK_LEN;
393 
394   if (wavenc->use_rf64)
395     header = write_ds64_chunk (wavenc, riffLen, header);
396 
397   header = write_fmt_chunk (wavenc, header);
398   if (use_fact_chunk (wavenc))
399     header = write_fact_chunk (wavenc, header);
400 
401   /* data chunk */
402   memcpy (header, "data ", 4);
403   if (wavenc->use_rf64)
404     GST_WRITE_UINT32_LE (header + 4, 0xFFFFFFFF);
405   else
406     GST_WRITE_UINT32_LE (header + 4, (guint32) wavenc->audio_length);
407 
408   gst_buffer_unmap (buf, &map);
409 
410   return buf;
411 }
412 
413 static GstFlowReturn
gst_wavenc_push_header(GstWavEnc * wavenc)414 gst_wavenc_push_header (GstWavEnc * wavenc)
415 {
416   GstFlowReturn ret;
417   GstBuffer *outbuf;
418   GstSegment segment;
419 
420   /* seek to beginning of file */
421   gst_segment_init (&segment, GST_FORMAT_BYTES);
422   if (!gst_pad_push_event (wavenc->srcpad, gst_event_new_segment (&segment))) {
423     GST_WARNING_OBJECT (wavenc, "Seek to the beginning failed");
424     return GST_FLOW_ERROR;
425   }
426 
427   GST_DEBUG_OBJECT (wavenc, "writing header, meta_size=%u, audio_size=%"
428       G_GUINT64_FORMAT, wavenc->meta_length, wavenc->audio_length);
429 
430   outbuf = gst_wavenc_create_header_buf (wavenc);
431   GST_BUFFER_OFFSET (outbuf) = 0;
432 
433   ret = gst_pad_push (wavenc->srcpad, outbuf);
434 
435   if (ret != GST_FLOW_OK) {
436     GST_WARNING_OBJECT (wavenc, "push header failed: flow = %s",
437         gst_flow_get_name (ret));
438   }
439 
440   return ret;
441 }
442 
443 static gboolean
gst_wavenc_sink_setcaps(GstPad * pad,GstCaps * caps)444 gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps)
445 {
446   GstWavEnc *wavenc;
447   GstStructure *structure;
448   const gchar *name;
449   gint chans, rate;
450   GstCaps *ccaps;
451 
452   wavenc = GST_WAVENC (gst_pad_get_parent (pad));
453 
454   ccaps = gst_pad_get_current_caps (pad);
455   if (wavenc->sent_header && ccaps && !gst_caps_can_intersect (caps, ccaps)) {
456     gst_caps_unref (ccaps);
457     GST_WARNING_OBJECT (wavenc, "cannot change format in middle of stream");
458     goto fail;
459   }
460   if (ccaps)
461     gst_caps_unref (ccaps);
462 
463   GST_DEBUG_OBJECT (wavenc, "got caps: %" GST_PTR_FORMAT, caps);
464 
465   structure = gst_caps_get_structure (caps, 0);
466   name = gst_structure_get_name (structure);
467 
468   if (!gst_structure_get_int (structure, "channels", &chans) ||
469       !gst_structure_get_int (structure, "rate", &rate)) {
470     GST_WARNING_OBJECT (wavenc, "caps incomplete");
471     goto fail;
472   }
473 
474   wavenc->channels = chans;
475   wavenc->rate = rate;
476   wavenc->channel_mask = 0;
477 
478   if (strcmp (name, "audio/x-raw") == 0) {
479     GstAudioInfo info;
480     guint64 gstmask;
481 
482     if (!gst_audio_info_from_caps (&info, caps)) {
483       GST_WARNING_OBJECT (wavenc, "Could not retrieve audio info from caps");
484       goto fail;
485     }
486     if (gst_audio_channel_positions_to_mask (info.position, wavenc->channels,
487             FALSE, &gstmask)) {
488       wavenc->channel_mask = gstmask_to_wavmask (gstmask, wavenc->destPos);
489       memcpy (wavenc->srcPos, info.position, sizeof (info.position));
490       GST_DEBUG_OBJECT (wavenc, "Channel mask input: 0x%" G_GINT64_MODIFIER "x"
491           " output: 0x%" G_GINT64_MODIFIER "x", gstmask, wavenc->channel_mask);
492     }
493     wavenc->audio_format = GST_AUDIO_INFO_FORMAT (&info);
494 
495     if (GST_AUDIO_INFO_IS_INTEGER (&info))
496       wavenc->format = GST_RIFF_WAVE_FORMAT_PCM;
497     else if (GST_AUDIO_INFO_IS_FLOAT (&info))
498       wavenc->format = GST_RIFF_WAVE_FORMAT_IEEE_FLOAT;
499     else
500       goto fail;
501 
502     wavenc->width = GST_AUDIO_INFO_WIDTH (&info);
503   } else if (strcmp (name, "audio/x-alaw") == 0) {
504     wavenc->format = GST_RIFF_WAVE_FORMAT_ALAW;
505     wavenc->width = 8;
506   } else if (strcmp (name, "audio/x-mulaw") == 0) {
507     wavenc->format = GST_RIFF_WAVE_FORMAT_MULAW;
508     wavenc->width = 8;
509   } else {
510     GST_WARNING_OBJECT (wavenc, "Unsupported format %s", name);
511     goto fail;
512   }
513 
514   GST_LOG_OBJECT (wavenc,
515       "accepted caps: format=0x%04x chans=%u width=%u rate=%u",
516       wavenc->format, wavenc->channels, wavenc->width, wavenc->rate);
517 
518   gst_object_unref (wavenc);
519   return TRUE;
520 
521 fail:
522   gst_object_unref (wavenc);
523   return FALSE;
524 }
525 
526 static void
gst_wavparse_tags_foreach(const GstTagList * tags,const gchar * tag,gpointer data)527 gst_wavparse_tags_foreach (const GstTagList * tags, const gchar * tag,
528     gpointer data)
529 {
530   const struct
531   {
532     guint32 fcc;
533     const gchar *tag;
534   } rifftags[] = {
535     {
536     GST_RIFF_INFO_IARL, GST_TAG_LOCATION}, {
537     GST_RIFF_INFO_IART, GST_TAG_ARTIST}, {
538     GST_RIFF_INFO_ICMT, GST_TAG_COMMENT}, {
539     GST_RIFF_INFO_ICOP, GST_TAG_COPYRIGHT}, {
540     GST_RIFF_INFO_ICRD, GST_TAG_DATE}, {
541     GST_RIFF_INFO_IGNR, GST_TAG_GENRE}, {
542     GST_RIFF_INFO_IKEY, GST_TAG_KEYWORDS}, {
543     GST_RIFF_INFO_INAM, GST_TAG_TITLE}, {
544     GST_RIFF_INFO_IPRD, GST_TAG_ALBUM}, {
545     GST_RIFF_INFO_ISBJ, GST_TAG_ALBUM_ARTIST}, {
546     GST_RIFF_INFO_ISFT, GST_TAG_ENCODER}, {
547     GST_RIFF_INFO_ISRC, GST_TAG_ISRC}, {
548     0, NULL}
549   };
550   gint n;
551   size_t size;
552   gchar *str = NULL;
553   GstByteWriter *bw = data;
554   for (n = 0; rifftags[n].fcc != 0; n++) {
555     if (!strcmp (rifftags[n].tag, tag)) {
556       if (rifftags[n].fcc == GST_RIFF_INFO_ICRD) {
557         GDate *date;
558         /* special case for the date tag */
559         if (gst_tag_list_get_date (tags, tag, &date)) {
560           str =
561               g_strdup_printf ("%04d:%02d:%02d", g_date_get_year (date),
562               g_date_get_month (date), g_date_get_day (date));
563           g_date_free (date);
564         }
565       } else {
566         gst_tag_list_get_string (tags, tag, &str);
567       }
568       if (str) {
569         /* get string length including null termination */
570         size = strlen (str) + 1;
571         gst_byte_writer_put_uint32_le (bw, rifftags[n].fcc);
572         gst_byte_writer_put_uint32_le (bw, GST_ROUND_UP_2 (size));
573         gst_byte_writer_put_data (bw, (const guint8 *) str, size);
574         /* add padding if needed */
575         if (GST_ROUND_UP_2 (size) > size) {
576           gst_byte_writer_put_uint8 (bw, 0);
577         }
578         g_free (str);
579         str = NULL;
580         break;
581       }
582     }
583   }
584 
585 }
586 
587 static GstFlowReturn
gst_wavenc_write_tags(GstWavEnc * wavenc)588 gst_wavenc_write_tags (GstWavEnc * wavenc)
589 {
590   const GstTagList *user_tags;
591   GstTagList *tags;
592   guint size;
593   GstBuffer *buf;
594   GstByteWriter bw;
595 
596   g_return_val_if_fail (wavenc != NULL, GST_FLOW_OK);
597 
598   user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (wavenc));
599   if ((!wavenc->tags) && (!user_tags)) {
600     GST_DEBUG_OBJECT (wavenc, "have no tags");
601     return GST_FLOW_OK;
602   }
603   tags =
604       gst_tag_list_merge (user_tags, wavenc->tags,
605       gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (wavenc)));
606 
607   GST_DEBUG_OBJECT (wavenc, "writing tags");
608 
609   gst_byte_writer_init_with_size (&bw, 1024, FALSE);
610 
611   /* add LIST INFO chunk */
612   gst_byte_writer_put_data (&bw, (const guint8 *) "LIST", 4);
613   gst_byte_writer_put_uint32_le (&bw, 0);
614   gst_byte_writer_put_data (&bw, (const guint8 *) "INFO", 4);
615 
616   /* add tags */
617   gst_tag_list_foreach (tags, gst_wavparse_tags_foreach, &bw);
618 
619   /* sets real size of LIST INFO chunk */
620   size = gst_byte_writer_get_pos (&bw);
621   gst_byte_writer_set_pos (&bw, 4);
622   gst_byte_writer_put_uint32_le (&bw, size - 8);
623 
624   gst_tag_list_unref (tags);
625 
626   buf = gst_byte_writer_reset_and_get_buffer (&bw);
627   wavenc->meta_length += gst_buffer_get_size (buf);
628   return gst_pad_push (wavenc->srcpad, buf);
629 }
630 
631 static gboolean
gst_wavenc_is_cue_id_unique(guint32 id,GList * list)632 gst_wavenc_is_cue_id_unique (guint32 id, GList * list)
633 {
634   GstWavEncCue *cue;
635 
636   while (list) {
637     cue = list->data;
638     if (cue->id == id)
639       return FALSE;
640     list = g_list_next (list);
641   }
642 
643   return TRUE;
644 }
645 
646 static gboolean
gst_wavenc_parse_cue(GstWavEnc * wavenc,guint32 id,GstTocEntry * entry)647 gst_wavenc_parse_cue (GstWavEnc * wavenc, guint32 id, GstTocEntry * entry)
648 {
649   gint64 start;
650   GstWavEncCue *cue;
651 
652   g_return_val_if_fail (entry != NULL, FALSE);
653 
654   gst_toc_entry_get_start_stop_times (entry, &start, NULL);
655 
656   cue = g_new (GstWavEncCue, 1);
657   cue->id = id;
658   cue->position = gst_util_uint64_scale_round (start, wavenc->rate, GST_SECOND);
659   memcpy (cue->data_chunk_id, "data", 4);
660   cue->chunk_start = 0;
661   cue->block_start = 0;
662   cue->sample_offset = cue->position;
663   wavenc->cues = g_list_append (wavenc->cues, cue);
664 
665   return TRUE;
666 }
667 
668 static gboolean
gst_wavenc_parse_labl(GstWavEnc * wavenc,guint32 id,GstTocEntry * entry)669 gst_wavenc_parse_labl (GstWavEnc * wavenc, guint32 id, GstTocEntry * entry)
670 {
671   gchar *tag;
672   GstTagList *tags;
673   GstWavEncLabl *labl;
674 
675   g_return_val_if_fail (entry != NULL, FALSE);
676 
677   tags = gst_toc_entry_get_tags (entry);
678   if (!tags) {
679     GST_INFO_OBJECT (wavenc, "no tags for entry: %d", id);
680     return FALSE;
681   }
682   if (!gst_tag_list_get_string (tags, GST_TAG_TITLE, &tag)) {
683     GST_INFO_OBJECT (wavenc, "no title tag for entry: %d", id);
684     return FALSE;
685   }
686 
687   labl = g_new (GstWavEncLabl, 1);
688   memcpy (labl->chunk_id, "labl", 4);
689   labl->chunk_data_size = 4 + strlen (tag) + 1;
690   labl->cue_point_id = id;
691   labl->text = tag;
692 
693   GST_DEBUG_OBJECT (wavenc, "got labl: '%s'", tag);
694 
695   wavenc->labls = g_list_append (wavenc->labls, labl);
696 
697   return TRUE;
698 }
699 
700 static gboolean
gst_wavenc_parse_note(GstWavEnc * wavenc,guint32 id,GstTocEntry * entry)701 gst_wavenc_parse_note (GstWavEnc * wavenc, guint32 id, GstTocEntry * entry)
702 {
703   gchar *tag;
704   GstTagList *tags;
705   GstWavEncNote *note;
706 
707   g_return_val_if_fail (entry != NULL, FALSE);
708   tags = gst_toc_entry_get_tags (entry);
709   if (!tags) {
710     GST_INFO_OBJECT (wavenc, "no tags for entry: %d", id);
711     return FALSE;
712   }
713   if (!gst_tag_list_get_string (tags, GST_TAG_COMMENT, &tag)) {
714     GST_INFO_OBJECT (wavenc, "no comment tag for entry: %d", id);
715     return FALSE;
716   }
717 
718   note = g_new (GstWavEncNote, 1);
719   memcpy (note->chunk_id, "note", 4);
720   note->chunk_data_size = 4 + strlen (tag) + 1;
721   note->cue_point_id = id;
722   note->text = tag;
723 
724   GST_DEBUG_OBJECT (wavenc, "got note: '%s'", tag);
725 
726   wavenc->notes = g_list_append (wavenc->notes, note);
727 
728   return TRUE;
729 }
730 
731 static gboolean
gst_wavenc_write_cues(guint8 ** data,GList * list)732 gst_wavenc_write_cues (guint8 ** data, GList * list)
733 {
734   GstWavEncCue *cue;
735 
736   while (list) {
737     cue = list->data;
738     GST_WRITE_UINT32_LE (*data, cue->id);
739     GST_WRITE_UINT32_LE (*data + 4, cue->position);
740     memcpy (*data + 8, (gchar *) cue->data_chunk_id, 4);
741     GST_WRITE_UINT32_LE (*data + 12, cue->chunk_start);
742     GST_WRITE_UINT32_LE (*data + 16, cue->block_start);
743     GST_WRITE_UINT32_LE (*data + 20, cue->sample_offset);
744     *data += 24;
745     list = g_list_next (list);
746   }
747 
748   return TRUE;
749 }
750 
751 static gboolean
gst_wavenc_write_labls(guint8 ** data,GList * list)752 gst_wavenc_write_labls (guint8 ** data, GList * list)
753 {
754   GstWavEncLabl *labl;
755 
756   while (list) {
757     labl = list->data;
758     memcpy (*data, (gchar *) labl->chunk_id, 4);
759     GST_WRITE_UINT32_LE (*data + 4, labl->chunk_data_size);
760     GST_WRITE_UINT32_LE (*data + 8, labl->cue_point_id);
761     memcpy (*data + 12, (gchar *) labl->text, strlen (labl->text));
762     *data += 8 + GST_ROUND_UP_2 (labl->chunk_data_size);
763     list = g_list_next (list);
764   }
765 
766   return TRUE;
767 }
768 
769 static gboolean
gst_wavenc_write_notes(guint8 ** data,GList * list)770 gst_wavenc_write_notes (guint8 ** data, GList * list)
771 {
772   GstWavEncNote *note;
773 
774   while (list) {
775     note = list->data;
776     memcpy (*data, (gchar *) note->chunk_id, 4);
777     GST_WRITE_UINT32_LE (*data + 4, note->chunk_data_size);
778     GST_WRITE_UINT32_LE (*data + 8, note->cue_point_id);
779     memcpy (*data + 12, (gchar *) note->text, strlen (note->text));
780     *data += 8 + GST_ROUND_UP_2 (note->chunk_data_size);
781     list = g_list_next (list);
782   }
783 
784   return TRUE;
785 }
786 
787 static GstFlowReturn
gst_wavenc_write_toc(GstWavEnc * wavenc)788 gst_wavenc_write_toc (GstWavEnc * wavenc)
789 {
790   GList *list;
791   GstToc *toc;
792   GstTocEntry *entry, *subentry;
793   GstBuffer *buf;
794   GstMapInfo map;
795   guint8 *data;
796   guint32 ncues, size, cues_size, labls_size, notes_size;
797 
798   if (!wavenc->toc) {
799     GST_DEBUG_OBJECT (wavenc, "have no toc, checking toc_setter");
800     wavenc->toc = gst_toc_setter_get_toc (GST_TOC_SETTER (wavenc));
801   }
802   if (!wavenc->toc) {
803     GST_WARNING_OBJECT (wavenc, "have no toc");
804     return GST_FLOW_OK;
805   }
806 
807   toc = gst_toc_ref (wavenc->toc);
808   size = 0;
809   cues_size = 0;
810   labls_size = 0;
811   notes_size = 0;
812 
813   /* check if the TOC entries is valid */
814   list = gst_toc_get_entries (toc);
815   entry = list->data;
816   if (gst_toc_entry_is_alternative (entry)) {
817     list = gst_toc_entry_get_sub_entries (entry);
818     while (list) {
819       subentry = list->data;
820       if (!gst_toc_entry_is_sequence (subentry))
821         return FALSE;
822       list = g_list_next (list);
823     }
824     list = gst_toc_entry_get_sub_entries (entry);
825   }
826   if (gst_toc_entry_is_sequence (entry)) {
827     while (list) {
828       entry = list->data;
829       if (!gst_toc_entry_is_sequence (entry))
830         return FALSE;
831       list = g_list_next (list);
832     }
833     list = gst_toc_get_entries (toc);
834   }
835 
836   ncues = g_list_length (list);
837   GST_DEBUG_OBJECT (wavenc, "number of cue entries: %d", ncues);
838 
839   while (list) {
840     guint32 id = 0;
841     gint64 id64;
842     const gchar *uid;
843 
844     entry = list->data;
845     uid = gst_toc_entry_get_uid (entry);
846     id64 = g_ascii_strtoll (uid, NULL, 0);
847     /* check if id unique compatible with guint32 else generate random */
848     if (id64 >= 0 && gst_wavenc_is_cue_id_unique (id64, wavenc->cues)) {
849       id = (guint32) id64;
850     } else {
851       do {
852         id = g_random_int ();
853       } while (!gst_wavenc_is_cue_id_unique (id, wavenc->cues));
854     }
855     gst_wavenc_parse_cue (wavenc, id, entry);
856     gst_wavenc_parse_labl (wavenc, id, entry);
857     gst_wavenc_parse_note (wavenc, id, entry);
858     list = g_list_next (list);
859   }
860 
861   /* count cues size */
862   if (wavenc->cues) {
863     cues_size = 24 * g_list_length (wavenc->cues);
864     size += 12 + cues_size;
865   } else {
866     GST_WARNING_OBJECT (wavenc, "cue's not found");
867     return FALSE;
868   }
869   /* count labls size */
870   if (wavenc->labls) {
871     list = wavenc->labls;
872     while (list) {
873       GstWavEncLabl *labl;
874       labl = list->data;
875       labls_size += 8 + GST_ROUND_UP_2 (labl->chunk_data_size);
876       list = g_list_next (list);
877     }
878     size += labls_size;
879   }
880   /* count notes size */
881   if (wavenc->notes) {
882     list = wavenc->notes;
883     while (list) {
884       GstWavEncNote *note;
885       note = list->data;
886       notes_size += 8 + GST_ROUND_UP_2 (note->chunk_data_size);
887       list = g_list_next (list);
888     }
889     size += notes_size;
890   }
891   if (wavenc->labls || wavenc->notes) {
892     size += 12;
893   }
894 
895   buf = gst_buffer_new_and_alloc (size);
896   gst_buffer_map (buf, &map, GST_MAP_WRITE);
897   data = map.data;
898   memset (data, 0, size);
899 
900   /* write Cue Chunk */
901   if (wavenc->cues) {
902     memcpy (data, (gchar *) "cue ", 4);
903     GST_WRITE_UINT32_LE (data + 4, 4 + cues_size);
904     GST_WRITE_UINT32_LE (data + 8, ncues);
905     data += 12;
906     gst_wavenc_write_cues (&data, wavenc->cues);
907 
908     /* write Associated Data List Chunk */
909     if (wavenc->labls || wavenc->notes) {
910       memcpy (data, (gchar *) "LIST", 4);
911       GST_WRITE_UINT32_LE (data + 4, 4 + labls_size + notes_size);
912       memcpy (data + 8, (gchar *) "adtl", 4);
913       data += 12;
914       if (wavenc->labls)
915         gst_wavenc_write_labls (&data, wavenc->labls);
916       if (wavenc->notes)
917         gst_wavenc_write_notes (&data, wavenc->notes);
918     }
919   }
920 
921   /* free resources */
922   if (toc)
923     gst_toc_unref (toc);
924   if (wavenc->cues)
925     g_list_free_full (wavenc->cues, g_free);
926   if (wavenc->labls)
927     g_list_free_full (wavenc->labls, g_free);
928   if (wavenc->notes)
929     g_list_free_full (wavenc->notes, g_free);
930 
931   gst_buffer_unmap (buf, &map);
932   wavenc->meta_length += gst_buffer_get_size (buf);
933 
934   return gst_pad_push (wavenc->srcpad, buf);
935 }
936 
937 static gboolean
gst_wavenc_event(GstPad * pad,GstObject * parent,GstEvent * event)938 gst_wavenc_event (GstPad * pad, GstObject * parent, GstEvent * event)
939 {
940   gboolean res = TRUE;
941   GstWavEnc *wavenc;
942   GstTagList *tags;
943   GstToc *toc;
944 
945   wavenc = GST_WAVENC (parent);
946 
947   switch (GST_EVENT_TYPE (event)) {
948     case GST_EVENT_CAPS:
949     {
950       GstCaps *caps;
951 
952       gst_event_parse_caps (event, &caps);
953       gst_wavenc_sink_setcaps (pad, caps);
954 
955       /* have our own src caps */
956       gst_event_unref (event);
957       break;
958     }
959     case GST_EVENT_EOS:
960     {
961       GstFlowReturn flow;
962       GST_DEBUG_OBJECT (wavenc, "got EOS");
963 
964       flow = gst_wavenc_write_toc (wavenc);
965       if (flow != GST_FLOW_OK) {
966         GST_WARNING_OBJECT (wavenc, "error pushing toc: %s",
967             gst_flow_get_name (flow));
968       }
969       flow = gst_wavenc_write_tags (wavenc);
970       if (flow != GST_FLOW_OK) {
971         GST_WARNING_OBJECT (wavenc, "error pushing tags: %s",
972             gst_flow_get_name (flow));
973       }
974 
975       /* write header with correct length values */
976       gst_wavenc_push_header (wavenc);
977 
978       /* we're done with this file */
979       wavenc->finished_properly = TRUE;
980 
981       /* and forward the EOS event */
982       res = gst_pad_event_default (pad, parent, event);
983       break;
984     }
985     case GST_EVENT_SEGMENT:
986       /* Just drop it, it's probably in TIME format
987        * anyway. We'll send our own newsegment event */
988       gst_event_unref (event);
989       break;
990     case GST_EVENT_TOC:
991       gst_event_parse_toc (event, &toc, NULL);
992       if (toc) {
993         if (wavenc->toc != toc) {
994           if (wavenc->toc)
995             gst_toc_unref (wavenc->toc);
996           wavenc->toc = toc;
997         } else {
998           gst_toc_unref (toc);
999         }
1000       }
1001       res = gst_pad_event_default (pad, parent, event);
1002       break;
1003     case GST_EVENT_TAG:
1004       gst_event_parse_tag (event, &tags);
1005       if (tags) {
1006         if (wavenc->tags != tags) {
1007           if (wavenc->tags)
1008             gst_tag_list_unref (wavenc->tags);
1009           wavenc->tags = gst_tag_list_ref (tags);
1010         }
1011       }
1012       res = gst_pad_event_default (pad, parent, event);
1013       break;
1014     default:
1015       res = gst_pad_event_default (pad, parent, event);
1016       break;
1017   }
1018 
1019   return res;
1020 }
1021 
1022 static GstFlowReturn
gst_wavenc_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)1023 gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
1024 {
1025   GstWavEnc *wavenc = GST_WAVENC (parent);
1026   GstFlowReturn flow = GST_FLOW_OK;
1027 
1028   if (wavenc->channels <= 0) {
1029     GST_ERROR_OBJECT (wavenc, "Got data without caps");
1030     return GST_FLOW_NOT_NEGOTIATED;
1031   }
1032 
1033   if (G_UNLIKELY (!wavenc->sent_header)) {
1034     GstStructure *s;
1035     GstCaps *caps = gst_pad_get_allowed_caps (wavenc->srcpad);
1036 
1037     GST_DEBUG_OBJECT (wavenc, "allowed src caps: %" GST_PTR_FORMAT, caps);
1038     if (!gst_caps_is_fixed (caps)) {
1039       caps = gst_caps_truncate (caps);
1040     }
1041     s = gst_caps_get_structure (caps, 0);
1042     wavenc->use_rf64 = gst_structure_has_name (s, "audio/x-rf64");
1043 
1044     gst_pad_set_caps (wavenc->srcpad, caps);
1045     gst_caps_unref (caps);
1046 
1047     /* starting a file, means we have to finish it properly */
1048     wavenc->finished_properly = FALSE;
1049 
1050     /* push initial bogus header, it will be updated on EOS */
1051     flow = gst_wavenc_push_header (wavenc);
1052     if (flow != GST_FLOW_OK) {
1053       GST_WARNING_OBJECT (wavenc, "error pushing header: %s",
1054           gst_flow_get_name (flow));
1055       return flow;
1056     }
1057     GST_DEBUG_OBJECT (wavenc, "wrote dummy header");
1058     wavenc->audio_length = 0;
1059     wavenc->sent_header = TRUE;
1060   }
1061 
1062   GST_LOG_OBJECT (wavenc,
1063       "pushing %" G_GSIZE_FORMAT " bytes raw audio, ts=%" GST_TIME_FORMAT,
1064       gst_buffer_get_size (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
1065 
1066   buf = gst_buffer_make_writable (buf);
1067 
1068   GST_BUFFER_OFFSET (buf) = get_header_len (wavenc) + wavenc->audio_length;
1069   GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
1070 
1071   wavenc->audio_length += gst_buffer_get_size (buf);
1072 
1073   if (wavenc->channel_mask != 0 &&
1074       !gst_audio_buffer_reorder_channels (buf, wavenc->audio_format,
1075           wavenc->channels, wavenc->srcPos, wavenc->destPos)) {
1076     GST_WARNING_OBJECT (wavenc, "Could not reorder channels");
1077   }
1078 
1079   flow = gst_pad_push (wavenc->srcpad, buf);
1080 
1081   return flow;
1082 }
1083 
1084 static GstStateChangeReturn
gst_wavenc_change_state(GstElement * element,GstStateChange transition)1085 gst_wavenc_change_state (GstElement * element, GstStateChange transition)
1086 {
1087   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1088   GstWavEnc *wavenc = GST_WAVENC (element);
1089 
1090   switch (transition) {
1091     case GST_STATE_CHANGE_NULL_TO_READY:
1092       wavenc->format = 0;
1093       wavenc->channels = 0;
1094       wavenc->width = 0;
1095       wavenc->rate = 0;
1096       /* use bogus size initially, we'll write the real
1097        * header when we get EOS and know the exact length */
1098       wavenc->audio_length = 0x7FFF0000;
1099       wavenc->meta_length = 0;
1100       wavenc->sent_header = FALSE;
1101       /* its true because we haven't written anything */
1102       wavenc->finished_properly = TRUE;
1103       break;
1104     default:
1105       break;
1106   }
1107 
1108   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1109   if (ret != GST_STATE_CHANGE_SUCCESS)
1110     return ret;
1111 
1112   switch (transition) {
1113     case GST_STATE_CHANGE_PAUSED_TO_READY:
1114       if (!wavenc->finished_properly) {
1115         GST_ELEMENT_WARNING (wavenc, STREAM, MUX,
1116             ("Wav stream not finished properly"),
1117             ("Wav stream not finished properly, no EOS received "
1118                 "before shutdown"));
1119       }
1120       break;
1121     case GST_STATE_CHANGE_READY_TO_NULL:
1122       GST_DEBUG_OBJECT (wavenc, "tags: %p", wavenc->tags);
1123       if (wavenc->tags) {
1124         gst_tag_list_unref (wavenc->tags);
1125         wavenc->tags = NULL;
1126       }
1127       GST_DEBUG_OBJECT (wavenc, "toc: %p", wavenc->toc);
1128       if (wavenc->toc) {
1129         gst_toc_unref (wavenc->toc);
1130         wavenc->toc = NULL;
1131       }
1132       gst_tag_setter_reset_tags (GST_TAG_SETTER (wavenc));
1133       gst_toc_setter_reset (GST_TOC_SETTER (wavenc));
1134       break;
1135     default:
1136       break;
1137   }
1138 
1139   return ret;
1140 }
1141 
1142 static gboolean
plugin_init(GstPlugin * plugin)1143 plugin_init (GstPlugin * plugin)
1144 {
1145   return GST_ELEMENT_REGISTER (wavenc, plugin);
1146 }
1147 
1148 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1149     GST_VERSION_MINOR,
1150     wavenc,
1151     "Encode raw audio into WAV",
1152     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1153