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