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