gst/wavenc/gstwavenc.c: Correctly handle width!=depth input.
[platform/upstream/gst-plugins-good.git] / gst / wavenc / gstwavenc.c
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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  * 
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <string.h>
28 #include "gstwavenc.h"
29 #include "riff.h"
30
31 GST_DEBUG_CATEGORY_STATIC (wavenc_debug);
32 #define GST_CAT_DEFAULT wavenc_debug
33
34 #define WAVE_FORMAT_PCM 0x0001
35
36 struct riff_struct
37 {
38   guint8 id[4];                 /* RIFF */
39   guint32 len;
40   guint8 wav_id[4];             /* WAVE */
41 };
42
43 struct chunk_struct
44 {
45   guint8 id[4];
46   guint32 len;
47 };
48
49 struct common_struct
50 {
51   guint16 wFormatTag;
52   guint16 wChannels;
53   guint32 dwSamplesPerSec;
54   guint32 dwAvgBytesPerSec;
55   guint16 wBlockAlign;
56   guint16 wBitsPerSample;       /* Only for PCM */
57 };
58
59 struct wave_header
60 {
61   struct riff_struct riff;
62   struct chunk_struct format;
63   struct common_struct common;
64   struct chunk_struct data;
65 };
66
67 static const GstElementDetails gst_wavenc_details =
68 GST_ELEMENT_DETAILS ("WAV audio muxer",
69     "Codec/Muxer/Audio",
70     "Encode raw audio into WAV",
71     "Iain Holmes <iain@prettypeople.org>");
72
73 /* FIXME: mono doesn't produce correct files it seems, at least mplayer xruns */
74 /* Max. of two channels, more channels need WAVFORMATEX with
75  * channel layout, which we do not support yet */
76 #define SINK_CAPS \
77     "audio/x-raw-int, "                  \
78     "rate = (int) [ 1, MAX ], "          \
79     "channels = (int) [ 1, 2 ], "        \
80     "endianness = (int) LITTLE_ENDIAN, " \
81     "width = (int) { 16, 24, 32 }, "     \
82     "depth = (int) [ 1, 32 ], "          \
83     "signed = (boolean) true"            \
84     "; "                                 \
85     "audio/x-raw-int, "                  \
86     "rate = (int) [ 1, MAX ], "          \
87     "channels = (int) [ 1, 2 ], "        \
88     "width = (int) 8, "                  \
89     "depth = (int) [ 1, 8 ], "           \
90     "signed = (boolean) false"
91
92 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
93     GST_PAD_SINK,
94     GST_PAD_ALWAYS,
95     GST_STATIC_CAPS (SINK_CAPS)
96     );
97
98 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
99     GST_PAD_SRC,
100     GST_PAD_ALWAYS,
101     GST_STATIC_CAPS ("audio/x-wav")
102     );
103
104 GST_BOILERPLATE (GstWavEnc, gst_wavenc, GstElement, GST_TYPE_ELEMENT);
105
106 static GstFlowReturn gst_wavenc_chain (GstPad * pad, GstBuffer * buf);
107 static gboolean gst_wavenc_event (GstPad * pad, GstEvent * event);
108 static GstStateChangeReturn gst_wavenc_change_state (GstElement * element,
109     GstStateChange transition);
110 static gboolean gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps);
111
112 static void
113 gst_wavenc_base_init (gpointer g_class)
114 {
115   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
116
117   gst_element_class_set_details (element_class, &gst_wavenc_details);
118
119   gst_element_class_add_pad_template (element_class,
120       gst_static_pad_template_get (&src_factory));
121   gst_element_class_add_pad_template (element_class,
122       gst_static_pad_template_get (&sink_factory));
123
124   GST_DEBUG_CATEGORY_INIT (wavenc_debug, "wavenc", 0, "WAV encoder element");
125 }
126
127 static void
128 gst_wavenc_class_init (GstWavEncClass * klass)
129 {
130   GstElementClass *element_class;
131
132   element_class = (GstElementClass *) klass;
133
134   element_class->change_state = GST_DEBUG_FUNCPTR (gst_wavenc_change_state);
135 }
136
137 static void
138 gst_wavenc_init (GstWavEnc * wavenc, GstWavEncClass * klass)
139 {
140   wavenc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
141   gst_pad_set_chain_function (wavenc->sinkpad,
142       GST_DEBUG_FUNCPTR (gst_wavenc_chain));
143   gst_pad_set_event_function (wavenc->sinkpad,
144       GST_DEBUG_FUNCPTR (gst_wavenc_event));
145   gst_pad_set_setcaps_function (wavenc->sinkpad,
146       GST_DEBUG_FUNCPTR (gst_wavenc_sink_setcaps));
147   gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->sinkpad);
148
149   wavenc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
150   gst_pad_use_fixed_caps (wavenc->srcpad);
151   gst_pad_set_caps (wavenc->srcpad,
152       gst_static_pad_template_get_caps (&src_factory));
153   gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad);
154 }
155
156 #define WAV_HEADER_LEN 44
157
158 static GstBuffer *
159 gst_wavenc_create_header_buf (GstWavEnc * wavenc, guint audio_data_size)
160 {
161   struct wave_header wave;
162   GstBuffer *buf;
163   guint8 *header;
164
165   buf = gst_buffer_new_and_alloc (WAV_HEADER_LEN);
166   header = GST_BUFFER_DATA (buf);
167   memset (header, 0, WAV_HEADER_LEN);
168
169   wave.common.wChannels = wavenc->channels;
170   wave.common.wBitsPerSample = wavenc->depth;
171   wave.common.dwSamplesPerSec = wavenc->rate;
172
173   /* Fill out our wav-header with some information */
174   memcpy (wave.riff.id, "RIFF", 4);
175   wave.riff.len = audio_data_size + WAV_HEADER_LEN - 8;
176   memcpy (wave.riff.wav_id, "WAVE", 4);
177
178   memcpy (wave.format.id, "fmt ", 4);
179   wave.format.len = 16;
180
181   wave.common.wFormatTag = WAVE_FORMAT_PCM;
182   wave.common.wBlockAlign = (wavenc->width / 8) * wave.common.wChannels;
183   wave.common.dwAvgBytesPerSec = wave.common.wBlockAlign *
184       wave.common.dwSamplesPerSec;
185
186   memcpy (wave.data.id, "data", 4);
187   wave.data.len = audio_data_size;
188
189   memcpy (header, (char *) wave.riff.id, 4);
190   GST_WRITE_UINT32_LE (header + 4, wave.riff.len);
191   memcpy (header + 8, (char *) wave.riff.wav_id, 4);
192   memcpy (header + 12, (char *) wave.format.id, 4);
193   GST_WRITE_UINT32_LE (header + 16, wave.format.len);
194   GST_WRITE_UINT16_LE (header + 20, wave.common.wFormatTag);
195   GST_WRITE_UINT16_LE (header + 22, wave.common.wChannels);
196   GST_WRITE_UINT32_LE (header + 24, wave.common.dwSamplesPerSec);
197   GST_WRITE_UINT32_LE (header + 28, wave.common.dwAvgBytesPerSec);
198   GST_WRITE_UINT16_LE (header + 32, wave.common.wBlockAlign);
199   GST_WRITE_UINT16_LE (header + 34, wave.common.wBitsPerSample);
200   memcpy (header + 36, (char *) wave.data.id, 4);
201   GST_WRITE_UINT32_LE (header + 40, wave.data.len);
202
203   gst_buffer_set_caps (buf, GST_PAD_CAPS (wavenc->srcpad));
204
205   return buf;
206 }
207
208 static GstFlowReturn
209 gst_wavenc_push_header (GstWavEnc * wavenc, guint audio_data_size)
210 {
211   GstFlowReturn ret;
212   GstBuffer *outbuf;
213
214   /* seek to beginning of file */
215   gst_pad_push_event (wavenc->srcpad,
216       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
217           0, GST_CLOCK_TIME_NONE, 0));
218
219   GST_DEBUG_OBJECT (wavenc, "writing header with datasize=%u", audio_data_size);
220
221   outbuf = gst_wavenc_create_header_buf (wavenc, audio_data_size);
222   GST_BUFFER_OFFSET (outbuf) = 0;
223
224   ret = gst_pad_push (wavenc->srcpad, outbuf);
225
226   if (ret != GST_FLOW_OK) {
227     GST_WARNING_OBJECT (wavenc, "push header failed: flow = %s",
228         gst_flow_get_name (ret));
229   }
230
231   return ret;
232 }
233
234 static gboolean
235 gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps)
236 {
237   GstWavEnc *wavenc;
238   GstStructure *structure;
239   gint chans, rate, width, depth;
240
241   wavenc = GST_WAVENC (gst_pad_get_parent (pad));
242
243   if (wavenc->sent_header) {
244     GST_WARNING_OBJECT (wavenc, "cannot change format in middle of stream");
245     goto fail;
246   }
247
248   GST_DEBUG_OBJECT (wavenc, "got caps: %" GST_PTR_FORMAT, caps);
249
250   structure = gst_caps_get_structure (caps, 0);
251   if (!gst_structure_get_int (structure, "channels", &chans) ||
252       !gst_structure_get_int (structure, "rate", &rate) ||
253       !gst_structure_get_int (structure, "width", &width) ||
254       !gst_structure_get_int (structure, "depth", &depth)) {
255     GST_WARNING_OBJECT (wavenc, "caps incomplete");
256     goto fail;
257   }
258
259   wavenc->channels = chans;
260   wavenc->width = width;
261   wavenc->depth = depth;
262   wavenc->rate = rate;
263
264   GST_LOG_OBJECT (wavenc, "accepted caps: chans=%u width=%u depth=%u rate=%u",
265       wavenc->channels, wavenc->width, wavenc->depth, wavenc->rate);
266
267   gst_object_unref (wavenc);
268   return TRUE;
269
270 fail:
271   gst_object_unref (wavenc);
272   return FALSE;
273 }
274
275 #if 0
276 static struct _maps
277 {
278   const guint32 id;
279   const gchar *name;
280 } maps[] = {
281   {
282   GST_RIFF_INFO_IARL, "Location"}, {
283   GST_RIFF_INFO_IART, "Artist"}, {
284   GST_RIFF_INFO_ICMS, "Commissioner"}, {
285   GST_RIFF_INFO_ICMT, "Comment"}, {
286   GST_RIFF_INFO_ICOP, "Copyright"}, {
287   GST_RIFF_INFO_ICRD, "Creation Date"}, {
288   GST_RIFF_INFO_IENG, "Engineer"}, {
289   GST_RIFF_INFO_IGNR, "Genre"}, {
290   GST_RIFF_INFO_IKEY, "Keywords"}, {
291   GST_RIFF_INFO_INAM, "Title"}, {
292   GST_RIFF_INFO_IPRD, "Product"}, {
293   GST_RIFF_INFO_ISBJ, "Subject"}, {
294   GST_RIFF_INFO_ISFT, "Software"}, {
295   GST_RIFF_INFO_ITCH, "Technician"}
296 };
297
298 static guint32
299 get_id_from_name (const char *name)
300 {
301   int i;
302
303   for (i = 0; i < G_N_ELEMENTS (maps); i++) {
304     if (strcasecmp (maps[i].name, name) == 0) {
305       return maps[i].id;
306     }
307   }
308
309   return 0;
310 }
311
312 static void
313 write_metadata (GstWavEnc * wavenc)
314 {
315   GString *info_str;
316   GList *props;
317   int total = 4;
318   gboolean need_to_write = FALSE;
319
320   info_str = g_string_new ("LIST    INFO");
321
322   for (props = wavenc->metadata->properties->properties; props;
323       props = props->next) {
324     GstPropsEntry *entry = props->data;
325     const char *name;
326     guint32 id;
327
328     name = gst_props_entry_get_name (entry);
329     id = get_id_from_name (name);
330     if (id != 0) {
331       const char *text;
332       char *tmp;
333       int len, req, i;
334
335       need_to_write = TRUE;     /* We've got at least one entry */
336
337       gst_props_entry_get_string (entry, &text);
338       len = strlen (text) + 1;  /* The length in the file includes the \0 */
339
340       tmp = g_strdup_printf ("%" GST_FOURCC_FORMAT "%d%s", GST_FOURCC_ARGS (id),
341           GUINT32_TO_LE (len), text);
342       g_string_append (info_str, tmp);
343       g_free (tmp);
344
345       /* Check that we end on an even boundary */
346       req = ((len + 8) + 1) & ~1;
347       for (i = 0; i < req - len; i++) {
348         g_string_append_printf (info_str, "%c", 0);
349       }
350
351       total += req;
352     }
353   }
354
355   if (need_to_write) {
356     GstBuffer *buf;
357
358     /* Now we've got all the strings together, we can write our length in */
359     info_str->str[4] = GUINT32_TO_LE (total);
360
361     buf = gst_buffer_new ();
362     gst_buffer_set_data (buf, info_str->str, info_str->len);
363
364     gst_pad_push (wavenc->srcpad, GST_DATA (buf));
365     g_string_free (info_str, FALSE);
366   }
367 }
368
369 static void
370 write_cues (GstWavEnc * wavenc)
371 {
372   GString *cue_string, *point_string;
373   GstBuffer *buf;
374   GList *cue_list, *c;
375   int num_cues, total = 4;
376
377   if (gst_props_get (wavenc->metadata->properties,
378           "cues", &cue_list, NULL) == FALSE) {
379     /* No cues, move along please, nothing to see here */
380     return;
381   }
382
383   /* Space for 'cue ', chunk size and number of cuepoints */
384   cue_string = g_string_new ("cue         ");
385 #define CUEPOINT_SIZE 24
386   point_string = g_string_sized_new (CUEPOINT_SIZE);
387
388   for (c = cue_list, num_cues = 0; c; c = c->next, num_cues++) {
389     GstCaps *cue_caps = c->data;
390     guint32 pos;
391
392     gst_props_get (cue_caps->properties, "position", &pos, NULL);
393
394     point_string->str[0] = GUINT32_TO_LE (num_cues + 1);
395     point_string->str[4] = GUINT32_TO_LE (0);
396     /* Fixme: There is probably a macro for this */
397     point_string->str[8] = 'd';
398     point_string->str[9] = 'a';
399     point_string->str[10] = 't';
400     point_string->str[11] = 'a';
401     point_string->str[12] = GUINT32_TO_LE (0);
402     point_string->str[16] = GUINT32_TO_LE (0);
403     point_string->str[20] = GUINT32_TO_LE (pos);
404
405     total += CUEPOINT_SIZE;
406   }
407
408   /* Set the length and chunk size */
409   cue_string->str[4] = GUINT32_TO_LE (total);
410   cue_string->str[8] = GUINT32_TO_LE (num_cues);
411   /* Stick the cue points on the end */
412   g_string_append (cue_string, point_string->str);
413   g_string_free (point_string, TRUE);
414
415   buf = gst_buffer_new ();
416   gst_buffer_set_data (buf, cue_string->str, cue_string->len);
417
418   gst_pad_push (wavenc->srcpad, GST_DATA (buf));
419   g_string_free (cue_string, FALSE);
420 }
421
422 static void
423 write_labels (GstWavEnc * wavenc)
424 {
425   GstBuffer *buf;
426   GString *info_str;
427   int total = 4;
428   GList *caps;
429
430   info_str = g_string_new ("LIST    adtl");
431   if (gst_props_get (wavenc->metadata->properties, "ltxts", &caps, NULL)) {
432     GList *p;
433     int i;
434
435     for (p = caps, i = 1; p; p = p->next, i++) {
436       GstCaps *ltxt_caps = p->data;
437       GString *ltxt;
438       char *label = NULL;
439       int len, req, j;
440
441       gst_props_get (ltxt_caps->properties, "name", &label, NULL);
442       len = strlen (label);
443
444 #define LTXT_SIZE 28
445       ltxt = g_string_new ("ltxt                        ");
446       ltxt->str[8] = GUINT32_TO_LE (i); /* Identifier */
447       ltxt->str[12] = GUINT32_TO_LE (0);        /* Sample Length */
448       ltxt->str[16] = GUINT32_TO_LE (0);        /* FIXME: Don't save the purpose yet */
449       ltxt->str[20] = GUINT16_TO_LE (0);        /* Country */
450       ltxt->str[22] = GUINT16_TO_LE (0);        /* Language */
451       ltxt->str[24] = GUINT16_TO_LE (0);        /* Dialect */
452       ltxt->str[26] = GUINT16_TO_LE (0);        /* Code Page */
453       g_string_append (ltxt, label);
454       g_free (label);
455
456       len += LTXT_SIZE;
457
458       ltxt->str[4] = GUINT32_TO_LE (len);
459
460       /* Check that we end on an even boundary */
461       req = ((len + 8) + 1) & ~1;
462       for (j = 0; j < req - len; j++) {
463         g_string_append_printf (ltxt, "%c", 0);
464       }
465
466       total += req;
467
468       g_string_append (info_str, ltxt->str);
469       g_string_free (ltxt, TRUE);
470     }
471   }
472
473   if (gst_props_get (wavenc->metadata->properties, "labels", &caps, NULL)) {
474     GList *p;
475     int i;
476
477     for (p = caps, i = 1; p; p = p->next, i++) {
478       GstCaps *labl_caps = p->data;
479       GString *labl;
480       char *label = NULL;
481       int len, req, j;
482
483       gst_props_get (labl_caps->properties, "name", &label, NULL);
484       len = strlen (label);
485
486 #define LABL_SIZE 4
487       labl = g_string_new ("labl        ");
488       labl->str[8] = GUINT32_TO_LE (i);
489       g_string_append (labl, label);
490       g_free (label);
491
492       len += LABL_SIZE;
493
494       labl->str[4] = GUINT32_TO_LE (len);
495
496       /* Check our size */
497       req = ((len + 8) + 1) & ~1;
498       for (j = 0; j < req - len; j++) {
499         g_string_append_printf (labl, "%c", 0);
500       }
501
502       total += req;
503
504       g_string_append (info_str, labl->str);
505       g_string_free (labl, TRUE);
506     }
507   }
508
509   if (gst_props_get (wavenc->metadata->properties, "notes", &caps, NULL)) {
510     GList *p;
511     int i;
512
513     for (p = caps, i = 1; p; p = p->next, i++) {
514       GstCaps *note_caps = p->data;
515       GString *note;
516       char *label = NULL;
517       int len, req, j;
518
519       gst_props_get (note_caps->properties, "name", &label, NULL);
520       len = strlen (label);
521
522 #define NOTE_SIZE 4
523       note = g_string_new ("note        ");
524       note->str[8] = GUINT32_TO_LE (i);
525       g_string_append (note, label);
526       g_free (label);
527
528       len += NOTE_SIZE;
529
530       note->str[4] = GUINT32_TO_LE (len);
531
532       /* Size check */
533       req = ((len + 8) + 1) & ~1;
534       for (j = 0; j < req - len; j++) {
535         g_string_append_printf (note, "%c", 0);
536       }
537
538       total += req;
539
540       g_string_append (info_str, note->str);
541       g_string_free (note, TRUE);
542     }
543   }
544
545   info_str->str[4] = GUINT32_TO_LE (total);
546
547   buf = gst_buffer_new ();
548   gst_buffer_set_data (buf, info_str->str, info_str->len);
549
550   gst_pad_push (wavenc->srcpad, GST_DATA (buf));
551   g_string_free (info_str, FALSE);
552 }
553 #endif
554
555 static gboolean
556 gst_wavenc_event (GstPad * pad, GstEvent * event)
557 {
558   gboolean res = TRUE;
559   GstWavEnc *wavenc;
560
561   wavenc = GST_WAVENC (gst_pad_get_parent (pad));
562
563   switch (GST_EVENT_TYPE (event)) {
564     case GST_EVENT_EOS:{
565       GST_DEBUG_OBJECT (wavenc, "got EOS");
566 #if 0
567       /* Write our metadata if we have any */
568       if (wavenc->metadata) {
569         write_metadata (wavenc);
570         write_cues (wavenc);
571         write_labels (wavenc);
572       }
573 #endif
574       /* write header with correct length values */
575       gst_wavenc_push_header (wavenc, wavenc->length);
576
577       /* and forward the EOS event */
578       res = gst_pad_event_default (pad, event);
579       break;
580     }
581     case GST_EVENT_NEWSEGMENT:
582       /* Just drop it, it's probably in TIME format
583        * anyway. We'll send our own newsegment event */
584       gst_event_unref (event);
585       break;
586     default:
587       res = gst_pad_event_default (pad, event);
588       break;
589   }
590
591   gst_object_unref (wavenc);
592   return res;
593 }
594
595 static GstFlowReturn
596 gst_wavenc_chain (GstPad * pad, GstBuffer * buf)
597 {
598   GstWavEnc *wavenc = GST_WAVENC (GST_PAD_PARENT (pad));
599   GstFlowReturn flow = GST_FLOW_OK;
600
601   g_return_val_if_fail (wavenc->channels > 0, GST_FLOW_WRONG_STATE);
602
603   if (!wavenc->sent_header) {
604     /* use bogus size initially, we'll write the real
605      * header when we get EOS and know the exact length */
606     flow = gst_wavenc_push_header (wavenc, 0x7FFF0000);
607
608     if (flow != GST_FLOW_OK)
609       return flow;
610
611     GST_DEBUG_OBJECT (wavenc, "wrote dummy header");
612     wavenc->sent_header = TRUE;
613   }
614
615   wavenc->length += GST_BUFFER_SIZE (buf);
616
617   GST_LOG_OBJECT (wavenc, "pushing %u bytes raw audio, ts=%" GST_TIME_FORMAT,
618       GST_BUFFER_SIZE (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
619
620   buf = gst_buffer_make_metadata_writable (buf);
621   gst_buffer_set_caps (buf, GST_PAD_CAPS (wavenc->srcpad));
622   GST_BUFFER_OFFSET (buf) = WAV_HEADER_LEN + wavenc->length;
623   GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
624
625   flow = gst_pad_push (wavenc->srcpad, buf);
626
627   return flow;
628 }
629
630 static GstStateChangeReturn
631 gst_wavenc_change_state (GstElement * element, GstStateChange transition)
632 {
633   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
634   GstWavEnc *wavenc = GST_WAVENC (element);
635
636   switch (transition) {
637     case GST_STATE_CHANGE_NULL_TO_READY:
638       wavenc->channels = 0;
639       wavenc->width = 0;
640       wavenc->depth = 0;
641       wavenc->rate = 0;
642       wavenc->length = 0;
643       wavenc->sent_header = FALSE;
644       break;
645     default:
646       break;
647   }
648
649   ret = parent_class->change_state (element, transition);
650   if (ret != GST_STATE_CHANGE_SUCCESS)
651     return ret;
652
653   return ret;
654 }
655
656 static gboolean
657 plugin_init (GstPlugin * plugin)
658 {
659   return gst_element_register (plugin, "wavenc", GST_RANK_NONE,
660       GST_TYPE_WAVENC);
661 }
662
663 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
664     GST_VERSION_MINOR,
665     "wavenc",
666     "Encode raw audio into WAV",
667     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)