src/io/sink_apple_audio.c: avoid crash on empty file name
[aubio.git] / src / io / sink_apple_audio.c
1 /*
2   Copyright (C) 2012-2014 Paul Brossier <piem@aubio.org>
3
4   This file is part of aubio.
5
6   aubio is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   aubio 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
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with aubio.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "config.h"
22
23 #ifdef HAVE_SINK_APPLE_AUDIO
24
25 #include "aubio_priv.h"
26 #include "fvec.h"
27 #include "fmat.h"
28 #include "io/sink_apple_audio.h"
29 #include "io/ioutils.h"
30
31 // CFURLRef, CFURLCreateWithFileSystemPath, ...
32 #include <CoreFoundation/CoreFoundation.h>
33 // ExtAudioFileRef, AudioStreamBasicDescription, AudioBufferList, ...
34 #include <AudioToolbox/AudioToolbox.h>
35
36 #define FLOAT_TO_SHORT(x) (short)(x * 32768)
37
38 extern int createAubioBufferList(AudioBufferList *bufferList, int channels, int segmentSize);
39 extern void freeAudioBufferList(AudioBufferList *bufferList);
40 extern CFURLRef createURLFromPath(const char * path);
41 char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
42
43 uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s);
44
45 #define MAX_SIZE 4096 // the maximum number of frames that can be written at a time
46
47 void aubio_sink_apple_audio_write(aubio_sink_apple_audio_t *s, uint_t write);
48
49 struct _aubio_sink_apple_audio_t {
50   uint_t samplerate;
51   uint_t channels;
52   char_t *path;
53
54   uint_t max_frames;
55
56   AudioBufferList bufferList;
57   ExtAudioFileRef audioFile;
58   bool async;
59 };
60
61 aubio_sink_apple_audio_t * new_aubio_sink_apple_audio(const char_t * uri, uint_t samplerate) {
62   aubio_sink_apple_audio_t * s = AUBIO_NEW(aubio_sink_apple_audio_t);
63   s->max_frames = MAX_SIZE;
64   s->async = false;
65
66   if ( (uri == NULL) || (strlen(uri) < 1) ) {
67     AUBIO_ERROR("sink_apple_audio: Aborted opening null path\n");
68     goto beach;
69   }
70   if (s->path != NULL) AUBIO_FREE(s->path);
71   s->path = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX) + 1);
72   strncpy(s->path, uri, strnlen(uri, PATH_MAX) + 1);
73
74   s->samplerate = 0;
75   s->channels = 0;
76
77   // zero samplerate given. do not open yet
78   if ((sint_t)samplerate == 0) {
79     return s;
80   }
81   // invalid samplerate given, abort
82   if (aubio_io_validate_samplerate("sink_apple_audio", s->path, samplerate)) {
83     goto beach;
84   }
85
86   s->samplerate = samplerate;
87   s->channels = 1;
88
89   if (aubio_sink_apple_audio_open(s) != AUBIO_OK) {
90     // open failed, abort
91     goto beach;
92   }
93
94   return s;
95 beach:
96   AUBIO_FREE(s);
97   return NULL;
98 }
99
100 uint_t aubio_sink_apple_audio_preset_samplerate(aubio_sink_apple_audio_t *s, uint_t samplerate)
101 {
102   if (aubio_io_validate_samplerate("sink_apple_audio", s->path, samplerate)) {
103     return AUBIO_FAIL;
104   }
105   s->samplerate = samplerate;
106   // automatically open when both samplerate and channels have been set
107   if (s->samplerate != 0 && s->channels != 0) {
108     return aubio_sink_apple_audio_open(s);
109   }
110   return AUBIO_OK;
111 }
112
113 uint_t aubio_sink_apple_audio_preset_channels(aubio_sink_apple_audio_t *s, uint_t channels)
114 {
115   if (aubio_io_validate_channels("sink_apple_audio", s->path, channels)) {
116     return AUBIO_FAIL;
117   }
118   s->channels = channels;
119   // automatically open when both samplerate and channels have been set
120   if (s->samplerate != 0 && s->channels != 0) {
121     return aubio_sink_apple_audio_open(s);
122   }
123   return AUBIO_OK;
124 }
125
126 uint_t aubio_sink_apple_audio_get_samplerate(const aubio_sink_apple_audio_t *s)
127 {
128   return s->samplerate;
129 }
130
131 uint_t aubio_sink_apple_audio_get_channels(const aubio_sink_apple_audio_t *s)
132 {
133   return s->channels;
134 }
135
136 uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s) {
137
138   if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL;
139
140   AudioStreamBasicDescription clientFormat;
141   memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription));
142   clientFormat.mFormatID         = kAudioFormatLinearPCM;
143   clientFormat.mSampleRate       = (Float64)(s->samplerate);
144   clientFormat.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
145   clientFormat.mChannelsPerFrame = s->channels;
146   clientFormat.mBitsPerChannel   = sizeof(short) * 8;
147   clientFormat.mFramesPerPacket  = 1;
148   clientFormat.mBytesPerFrame    = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8;
149   clientFormat.mBytesPerPacket   = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame;
150   clientFormat.mReserved         = 0;
151
152   AudioFileTypeID fileType = kAudioFileWAVEType;
153   CFURLRef fileURL = createURLFromPath(s->path);
154   bool overwrite = true;
155   OSStatus err = noErr;
156   err = ExtAudioFileCreateWithURL(fileURL, fileType, &clientFormat, NULL,
157      overwrite ? kAudioFileFlags_EraseFile : 0, &s->audioFile);
158   CFRelease(fileURL);
159   if (err) {
160     char_t errorstr[20];
161     AUBIO_ERR("sink_apple_audio: error when trying to create %s with "
162         "ExtAudioFileCreateWithURL (%s)\n", s->path,
163         getPrintableOSStatusError(errorstr, err));
164     goto beach;
165   }
166   if (createAubioBufferList(&s->bufferList, s->channels, s->max_frames * s->channels)) {
167     AUBIO_ERR("sink_apple_audio: error when creating buffer list for %s, "
168         "out of memory? \n", s->path);
169     goto beach;
170   }
171   return AUBIO_OK;
172
173 beach:
174   return AUBIO_FAIL;
175 }
176
177 void aubio_sink_apple_audio_do(aubio_sink_apple_audio_t * s, fvec_t * write_data, uint_t write) {
178   UInt32 c, v;
179   short *data = (short*)s->bufferList.mBuffers[0].mData;
180   if (write > s->max_frames) {
181     AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
182     write = s->max_frames;
183   }
184   smpl_t *buf = write_data->data;
185
186   if (buf) {
187       for (c = 0; c < s->channels; c++) {
188           for (v = 0; v < write; v++) {
189               data[v * s->channels + c] =
190                   FLOAT_TO_SHORT(buf[ v * s->channels + c]);
191           }
192       }
193   }
194   aubio_sink_apple_audio_write(s, write);
195 }
196
197 void aubio_sink_apple_audio_do_multi(aubio_sink_apple_audio_t * s, fmat_t * write_data, uint_t write) {
198   UInt32 c, v;
199   short *data = (short*)s->bufferList.mBuffers[0].mData;
200   if (write > s->max_frames) {
201     AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
202     write = s->max_frames;
203   }
204   smpl_t **buf = write_data->data;
205
206   if (buf) {
207       for (c = 0; c < s->channels; c++) {
208           for (v = 0; v < write; v++) {
209               data[v * s->channels + c] =
210                   FLOAT_TO_SHORT(buf[c][v]);
211           }
212       }
213   }
214   aubio_sink_apple_audio_write(s, write);
215 }
216
217 void aubio_sink_apple_audio_write(aubio_sink_apple_audio_t *s, uint_t write) {
218   OSStatus err = noErr;
219   if (s->async) {
220     err = ExtAudioFileWriteAsync(s->audioFile, write, &s->bufferList);
221     if (err) {
222       char_t errorstr[20];
223       if (err == kExtAudioFileError_AsyncWriteBufferOverflow) {
224         sprintf(errorstr,"buffer overflow");
225       } else if (err == kExtAudioFileError_AsyncWriteTooLarge) {
226         sprintf(errorstr,"write too large");
227       } else {
228         // unknown error
229         getPrintableOSStatusError(errorstr, err);
230       }
231       AUBIO_ERROR("sink_apple_audio: error while writing %s "
232                   "in ExtAudioFileWriteAsync (%s)\n", s->path, errorstr);
233     }
234   } else {
235     err = ExtAudioFileWrite(s->audioFile, write, &s->bufferList);
236     if (err) {
237       char_t errorstr[20];
238       AUBIO_ERROR("sink_apple_audio: error while writing %s "
239           "in ExtAudioFileWrite (%s)\n", s->path,
240           getPrintableOSStatusError(errorstr, err));
241     }
242   }
243 }
244
245 uint_t aubio_sink_apple_audio_close(aubio_sink_apple_audio_t * s) {
246   OSStatus err = noErr;
247   if (!s->audioFile) {
248     return AUBIO_FAIL;
249   }
250   err = ExtAudioFileDispose(s->audioFile);
251   if (err) {
252     char_t errorstr[20];
253     AUBIO_ERROR("sink_apple_audio: error while closing %s "
254         "in ExtAudioFileDispose (%s)\n", s->path,
255         getPrintableOSStatusError(errorstr, err));
256   }
257   s->audioFile = NULL;
258   return err;
259 }
260
261 void del_aubio_sink_apple_audio(aubio_sink_apple_audio_t * s) {
262   if (s->audioFile) aubio_sink_apple_audio_close (s);
263   if (s->path) AUBIO_FREE(s->path);
264   freeAudioBufferList(&s->bufferList);
265   AUBIO_FREE(s);
266   return;
267 }
268
269 #endif /* HAVE_SINK_APPLE_AUDIO */