1 /* GStreamer AIFF muxer
2  * Copyright (C) 2009 Robert Swain <robert.swain@gmail.com>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  *
22  * Alternatively, the contents of this file may be used under the
23  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
24  * which case the following provisions apply instead of the ones
25  * mentioned above:
26  *
27  * This library is free software; you can redistribute it and/or
28  * modify it under the terms of the GNU Library General Public
29  * License as published by the Free Software Foundation; either
30  * version 2 of the License, or (at your option) any later version.
31  *
32  * This library is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
35  * Library General Public License for more details.
36  *
37  * You should have received a copy of the GNU Library General Public
38  * License along with this library; if not, write to the
39  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
40  * Boston, MA 02110-1301, USA.
41  */
42 
43 /**
44  * SECTION:element-aiffmux
45  * @title: aiffmux
46  *
47  * Format an audio stream into the Audio Interchange File Format
48  *
49  */
50 
51 #ifdef HAVE_CONFIG_H
52 #  include <config.h>
53 #endif
54 
55 #include <string.h>
56 #include <math.h>
57 #include <gst/gst.h>
58 #include <gst/base/gstbytewriter.h>
59 
60 #include "aiffmux.h"
61 
62 GST_DEBUG_CATEGORY (aiffmux_debug);
63 #define GST_CAT_DEFAULT aiffmux_debug
64 
65 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
66     GST_PAD_SINK,
67     GST_PAD_ALWAYS,
68     GST_STATIC_CAPS ("audio/x-raw, "
69         "format = { S8, S16BE, S24BE, S32BE },"
70         "channels = (int) [ 1, MAX ], " "rate = (int) [ 1, MAX ]")
71     );
72 
73 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
74     GST_PAD_SRC,
75     GST_PAD_ALWAYS,
76     GST_STATIC_CAPS ("audio/x-aiff")
77     );
78 
79 #define gst_aiff_mux_parent_class parent_class
80 G_DEFINE_TYPE (GstAiffMux, gst_aiff_mux, GST_TYPE_ELEMENT);
81 
82 static GstStateChangeReturn
gst_aiff_mux_change_state(GstElement * element,GstStateChange transition)83 gst_aiff_mux_change_state (GstElement * element, GstStateChange transition)
84 {
85   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
86   GstAiffMux *aiffmux = GST_AIFF_MUX (element);
87 
88   switch (transition) {
89     case GST_STATE_CHANGE_READY_TO_PAUSED:
90       gst_audio_info_init (&aiffmux->info);
91       aiffmux->length = 0;
92       aiffmux->sent_header = FALSE;
93       aiffmux->overflow = FALSE;
94       break;
95     default:
96       break;
97   }
98 
99   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
100   if (ret != GST_STATE_CHANGE_SUCCESS)
101     return ret;
102 
103   return ret;
104 }
105 
106 static void
gst_aiff_mux_class_init(GstAiffMuxClass * klass)107 gst_aiff_mux_class_init (GstAiffMuxClass * klass)
108 {
109   GstElementClass *gstelement_class;
110 
111   gstelement_class = (GstElementClass *) klass;
112 
113   gst_element_class_set_static_metadata (gstelement_class,
114       "AIFF audio muxer", "Muxer/Audio", "Multiplex raw audio into AIFF",
115       "Robert Swain <robert.swain@gmail.com>");
116 
117   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
118   gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
119 
120   gstelement_class->change_state =
121       GST_DEBUG_FUNCPTR (gst_aiff_mux_change_state);
122 }
123 
124 #define AIFF_FORM_HEADER_LEN 8 + 4
125 #define AIFF_COMM_HEADER_LEN 8 + 18
126 #define AIFF_SSND_HEADER_LEN 8 + 8
127 #define AIFF_HEADER_LEN \
128   (AIFF_FORM_HEADER_LEN + AIFF_COMM_HEADER_LEN + AIFF_SSND_HEADER_LEN)
129 
130 static void
gst_aiff_mux_write_form_header(GstAiffMux * aiffmux,guint32 audio_data_size,GstByteWriter * writer)131 gst_aiff_mux_write_form_header (GstAiffMux * aiffmux, guint32 audio_data_size,
132     GstByteWriter * writer)
133 {
134   guint64 cur_size;
135 
136   /* ckID == 'FORM' */
137   gst_byte_writer_put_uint32_le_unchecked (writer,
138       GST_MAKE_FOURCC ('F', 'O', 'R', 'M'));
139 
140   /* AIFF chunks must be even aligned */
141   cur_size = AIFF_HEADER_LEN - 8 + audio_data_size;
142   if ((cur_size & 1) && cur_size + 1 < G_MAXUINT32) {
143     cur_size += 1;
144   }
145 
146   gst_byte_writer_put_uint32_be_unchecked (writer, cur_size);
147   /* formType == 'AIFF' */
148   gst_byte_writer_put_uint32_le_unchecked (writer,
149       GST_MAKE_FOURCC ('A', 'I', 'F', 'F'));
150 }
151 
152 /*
153  * BEGIN: Code borrowed from FFmpeg's libavutil/intfloat_readwrite.{c,h}
154  * Copyright (c) 2005 Michael Niedermayer <michaelni@gmx.at>
155  */
156 
157 /* IEEE 80 bits extended float */
158 typedef struct AVExtFloat
159 {
160   guint8 exponent[2];
161   guint8 mantissa[8];
162 } AVExtFloat;
163 
164 /* Courtesy http://www.devx.com/tips/Tip/42853 */
165 static inline gint
gst_aiff_mux_isinf(gdouble x)166 gst_aiff_mux_isinf (gdouble x)
167 {
168   volatile gdouble temp = x;
169   if ((temp == x) && ((temp - x) != 0.0))
170     return (x < 0.0 ? -1 : 1);
171   else
172     return 0;
173 }
174 
175 static void
gst_aiff_mux_write_ext(GstByteWriter * writer,double d)176 gst_aiff_mux_write_ext (GstByteWriter * writer, double d)
177 {
178   struct AVExtFloat ext = { {0} };
179   gint e, i;
180   gdouble f;
181   guint64 m;
182 
183   f = fabs (frexp (d, &e));
184   if (f >= 0.5 && f < 1) {
185     e += 16382;
186     ext.exponent[0] = e >> 8;
187     ext.exponent[1] = e;
188     m = (guint64) ldexp (f, 64);
189     for (i = 0; i < 8; i++)
190       ext.mantissa[i] = m >> (56 - (i << 3));
191   } else if (f != 0.0) {
192     ext.exponent[0] = 0x7f;
193     ext.exponent[1] = 0xff;
194     if (!gst_aiff_mux_isinf (f))
195       ext.mantissa[0] = ~0;
196   }
197   if (d < 0)
198     ext.exponent[0] |= 0x80;
199 
200   gst_byte_writer_put_data_unchecked (writer, ext.exponent, 2);
201   gst_byte_writer_put_data_unchecked (writer, ext.mantissa, 8);
202 }
203 
204 /*
205  * END: Code borrowed from FFmpeg's libavutil/intfloat_readwrite.{c,h}
206  */
207 
208 static void
gst_aiff_mux_write_comm_header(GstAiffMux * aiffmux,guint32 audio_data_size,GstByteWriter * writer)209 gst_aiff_mux_write_comm_header (GstAiffMux * aiffmux, guint32 audio_data_size,
210     GstByteWriter * writer)
211 {
212   guint16 channels;
213   guint16 width, depth;
214   gdouble rate;
215 
216   channels = GST_AUDIO_INFO_CHANNELS (&aiffmux->info);
217   width = GST_AUDIO_INFO_WIDTH (&aiffmux->info);
218   depth = GST_AUDIO_INFO_DEPTH (&aiffmux->info);
219   rate = GST_AUDIO_INFO_RATE (&aiffmux->info);
220 
221   gst_byte_writer_put_uint32_le_unchecked (writer,
222       GST_MAKE_FOURCC ('C', 'O', 'M', 'M'));
223   gst_byte_writer_put_uint32_be_unchecked (writer, 18);
224   gst_byte_writer_put_uint16_be_unchecked (writer, channels);
225   /* numSampleFrames value will be overwritten when known */
226   gst_byte_writer_put_uint32_be_unchecked (writer,
227       audio_data_size / (width / 8 * channels));
228   gst_byte_writer_put_uint16_be_unchecked (writer, depth);
229   gst_aiff_mux_write_ext (writer, rate);
230 }
231 
232 static void
gst_aiff_mux_write_ssnd_header(GstAiffMux * aiffmux,guint32 audio_data_size,GstByteWriter * writer)233 gst_aiff_mux_write_ssnd_header (GstAiffMux * aiffmux, guint32 audio_data_size,
234     GstByteWriter * writer)
235 {
236   gst_byte_writer_put_uint32_le_unchecked (writer,
237       GST_MAKE_FOURCC ('S', 'S', 'N', 'D'));
238   /* ckSize will be overwritten when known */
239   gst_byte_writer_put_uint32_be_unchecked (writer,
240       audio_data_size + AIFF_SSND_HEADER_LEN - 8);
241   /* offset and blockSize are set to 0 as we don't support block-aligned sample data yet */
242   gst_byte_writer_put_uint32_be_unchecked (writer, 0);
243   gst_byte_writer_put_uint32_be_unchecked (writer, 0);
244 }
245 
246 static GstFlowReturn
gst_aiff_mux_push_header(GstAiffMux * aiffmux,guint32 audio_data_size)247 gst_aiff_mux_push_header (GstAiffMux * aiffmux, guint32 audio_data_size)
248 {
249   GstFlowReturn ret;
250   GstBuffer *outbuf;
251   GstByteWriter writer;
252   GstSegment seg;
253 
254   /* seek to beginning of file */
255   gst_segment_init (&seg, GST_FORMAT_BYTES);
256 
257   if (gst_pad_push_event (aiffmux->srcpad,
258           gst_event_new_segment (&seg)) == FALSE) {
259     GST_ELEMENT_WARNING (aiffmux, STREAM, MUX,
260         ("An output stream seeking error occurred when multiplexing."),
261         ("Failed to seek to beginning of stream to write header."));
262   }
263 
264   GST_DEBUG_OBJECT (aiffmux, "writing header with datasize=%u",
265       audio_data_size);
266 
267   gst_byte_writer_init_with_size (&writer, AIFF_HEADER_LEN, TRUE);
268 
269   gst_aiff_mux_write_form_header (aiffmux, audio_data_size, &writer);
270   gst_aiff_mux_write_comm_header (aiffmux, audio_data_size, &writer);
271   gst_aiff_mux_write_ssnd_header (aiffmux, audio_data_size, &writer);
272 
273   outbuf = gst_byte_writer_reset_and_get_buffer (&writer);
274 
275   ret = gst_pad_push (aiffmux->srcpad, outbuf);
276 
277   if (ret != GST_FLOW_OK) {
278     GST_WARNING_OBJECT (aiffmux, "push header failed: flow = %s",
279         gst_flow_get_name (ret));
280   }
281 
282   return ret;
283 }
284 
285 static GstFlowReturn
gst_aiff_mux_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)286 gst_aiff_mux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
287 {
288   GstAiffMux *aiffmux = GST_AIFF_MUX (parent);
289   GstFlowReturn flow = GST_FLOW_OK;
290   guint64 cur_size;
291   gsize buf_size;
292 
293   if (!GST_AUDIO_INFO_CHANNELS (&aiffmux->info))
294     goto not_negotiated;
295 
296   if (G_UNLIKELY (aiffmux->overflow))
297     goto overflow;
298 
299   if (!aiffmux->sent_header) {
300     /* use bogus size initially, we'll write the real
301      * header when we get EOS and know the exact length */
302     flow = gst_aiff_mux_push_header (aiffmux, 0x7FFF0000);
303     if (flow != GST_FLOW_OK)
304       goto flow_error;
305 
306     GST_DEBUG_OBJECT (aiffmux, "wrote dummy header");
307     aiffmux->sent_header = TRUE;
308   }
309 
310   /* AIFF has an audio data size limit of slightly under 4 GB.
311      A value of audiosize + AIFF_HEADER_LEN - 8 is written, so
312      I'll error out if writing data that makes this overflow. */
313   cur_size = aiffmux->length + AIFF_HEADER_LEN - 8;
314   buf_size = gst_buffer_get_size (buf);
315 
316   if (G_UNLIKELY (cur_size + buf_size >= G_MAXUINT32)) {
317     GST_ERROR_OBJECT (aiffmux, "AIFF only supports about 4 GB worth of "
318         "audio data, dropping any further data on the floor");
319     GST_ELEMENT_WARNING (aiffmux, STREAM, MUX, ("AIFF has a 4GB size limit"),
320         ("AIFF only supports about 4 GB worth of audio data, "
321             "dropping any further data on the floor"));
322     aiffmux->overflow = TRUE;
323     goto overflow;
324   }
325 
326   GST_LOG_OBJECT (aiffmux,
327       "pushing %" G_GSIZE_FORMAT " bytes raw audio, ts=%" GST_TIME_FORMAT,
328       buf_size, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
329 
330   buf = gst_buffer_make_writable (buf);
331 
332   GST_BUFFER_OFFSET (buf) = AIFF_HEADER_LEN + aiffmux->length;
333   GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE;
334 
335   aiffmux->length += buf_size;
336 
337   flow = gst_pad_push (aiffmux->srcpad, buf);
338 
339   return flow;
340 
341 not_negotiated:
342   {
343     GST_WARNING_OBJECT (aiffmux, "no input format negotiated");
344     gst_buffer_unref (buf);
345     return GST_FLOW_NOT_NEGOTIATED;
346   }
347 overflow:
348   {
349     GST_WARNING_OBJECT (aiffmux, "output file too large, dropping buffer");
350     gst_buffer_unref (buf);
351     return GST_FLOW_OK;
352   }
353 flow_error:
354   {
355     GST_DEBUG_OBJECT (aiffmux, "got flow error %s", gst_flow_get_name (flow));
356     gst_buffer_unref (buf);
357     return flow;
358   }
359 }
360 
361 static gboolean
gst_aiff_mux_set_caps(GstAiffMux * aiffmux,GstCaps * caps)362 gst_aiff_mux_set_caps (GstAiffMux * aiffmux, GstCaps * caps)
363 {
364   GstCaps *outcaps;
365   GstAudioInfo info;
366 
367   if (aiffmux->sent_header) {
368     GST_WARNING_OBJECT (aiffmux, "cannot change format mid-stream");
369     return FALSE;
370   }
371 
372   GST_DEBUG_OBJECT (aiffmux, "got caps: %" GST_PTR_FORMAT, caps);
373 
374   if (!gst_audio_info_from_caps (&info, caps)) {
375     GST_WARNING_OBJECT (aiffmux, "caps incomplete");
376     return FALSE;
377   }
378 
379   aiffmux->info = info;
380 
381   GST_LOG_OBJECT (aiffmux,
382       "accepted caps: chans=%d depth=%d rate=%d",
383       GST_AUDIO_INFO_CHANNELS (&info), GST_AUDIO_INFO_DEPTH (&info),
384       GST_AUDIO_INFO_RATE (&info));
385 
386   outcaps = gst_static_pad_template_get_caps (&src_factory);
387   gst_pad_push_event (aiffmux->srcpad, gst_event_new_caps (outcaps));
388   gst_caps_unref (outcaps);
389 
390   return TRUE;
391 }
392 
393 
394 static gboolean
gst_aiff_mux_event(GstPad * pad,GstObject * parent,GstEvent * event)395 gst_aiff_mux_event (GstPad * pad, GstObject * parent, GstEvent * event)
396 {
397   gboolean res = TRUE;
398   GstAiffMux *aiffmux;
399 
400   aiffmux = GST_AIFF_MUX (parent);
401 
402   switch (GST_EVENT_TYPE (event)) {
403     case GST_EVENT_EOS:{
404       guint64 cur_size;
405       GST_DEBUG_OBJECT (aiffmux, "got EOS");
406 
407       cur_size = aiffmux->length + AIFF_HEADER_LEN - 8;
408 
409       /* ID3 chunk must be even aligned */
410       if ((aiffmux->length & 1) && cur_size + 1 < G_MAXUINT32) {
411         GstFlowReturn ret;
412         guint8 *data = g_new0 (guint8, 1);
413         GstBuffer *buffer = gst_buffer_new_wrapped (data, 1);
414         GST_BUFFER_OFFSET (buffer) = AIFF_HEADER_LEN + aiffmux->length;
415         GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE;
416         ret = gst_pad_push (aiffmux->srcpad, buffer);
417         if (ret != GST_FLOW_OK) {
418           GST_WARNING_OBJECT (aiffmux, "failed to push padding byte: %s",
419               gst_flow_get_name (ret));
420         }
421       }
422 
423       /* write header with correct length values */
424       gst_aiff_mux_push_header (aiffmux, aiffmux->length);
425 
426       /* and forward the EOS event */
427       res = gst_pad_event_default (pad, parent, event);
428       break;
429     }
430     case GST_EVENT_CAPS:
431     {
432       GstCaps *caps;
433 
434       gst_event_parse_caps (event, &caps);
435       res = gst_aiff_mux_set_caps (aiffmux, caps);
436       gst_event_unref (event);
437       break;
438     }
439     case GST_EVENT_SEGMENT:
440       /* Just drop it, it's probably in TIME format
441        * anyway. We'll send our own newsegment event */
442       gst_event_unref (event);
443       break;
444     default:
445       res = gst_pad_event_default (pad, parent, event);
446       break;
447   }
448   return res;
449 }
450 
451 static void
gst_aiff_mux_init(GstAiffMux * aiffmux)452 gst_aiff_mux_init (GstAiffMux * aiffmux)
453 {
454   aiffmux->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
455   gst_pad_set_chain_function (aiffmux->sinkpad,
456       GST_DEBUG_FUNCPTR (gst_aiff_mux_chain));
457   gst_pad_set_event_function (aiffmux->sinkpad,
458       GST_DEBUG_FUNCPTR (gst_aiff_mux_event));
459   gst_element_add_pad (GST_ELEMENT (aiffmux), aiffmux->sinkpad);
460 
461   aiffmux->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
462   gst_pad_use_fixed_caps (aiffmux->srcpad);
463   gst_element_add_pad (GST_ELEMENT (aiffmux), aiffmux->srcpad);
464 }
465