src/io/sink_apple_audio.c: avoid opening null path
[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
30 // CFURLRef, CFURLCreateWithFileSystemPath, ...
31 #include <CoreFoundation/CoreFoundation.h>
32 // ExtAudioFileRef, AudioStreamBasicDescription, AudioBufferList, ...
33 #include <AudioToolbox/AudioToolbox.h>
34
35 #define FLOAT_TO_SHORT(x) (short)(x * 32768)
36
37 extern int createAubioBufferList(AudioBufferList *bufferList, int channels, int segmentSize);
38 extern void freeAudioBufferList(AudioBufferList *bufferList);
39 extern CFURLRef getURLFromPath(const char * path);
40 char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
41
42 uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s);
43
44 #define MAX_SIZE 4096 // the maximum number of frames that can be written at a time
45
46 struct _aubio_sink_apple_audio_t {
47   uint_t samplerate;
48   uint_t channels;
49   char_t *path;
50
51   uint_t max_frames;
52
53   AudioBufferList bufferList;
54   ExtAudioFileRef audioFile;
55   bool async;
56 };
57
58 aubio_sink_apple_audio_t * new_aubio_sink_apple_audio(char_t * uri, uint_t samplerate) {
59   aubio_sink_apple_audio_t * s = AUBIO_NEW(aubio_sink_apple_audio_t);
60   s->path = uri;
61   s->max_frames = MAX_SIZE;
62   s->async = true;
63
64   if (uri == NULL) {
65     AUBIO_ERROR("sink_apple_audio: Aborted opening null path\n");
66     goto beach;
67   }
68
69   s->samplerate = 0;
70   s->channels = 0;
71
72   // negative samplerate given, abort
73   if ((sint_t)samplerate < 0) goto beach;
74   // zero samplerate given. do not open yet
75   if ((sint_t)samplerate == 0) return s;
76
77   s->samplerate = samplerate;
78   s->channels = 1;
79
80   if (aubio_sink_apple_audio_open(s) != AUBIO_OK) {
81     // open failed, abort
82     goto beach;
83   }
84
85   return s;
86 beach:
87   AUBIO_FREE(s);
88   return NULL;
89 }
90
91 uint_t aubio_sink_apple_audio_preset_samplerate(aubio_sink_apple_audio_t *s, uint_t samplerate)
92 {
93   if ((sint_t)(samplerate) <= 0) return AUBIO_FAIL;
94   s->samplerate = samplerate;
95   // automatically open when both samplerate and channels have been set
96   if (s->samplerate != 0 && s->channels != 0) {
97     return aubio_sink_apple_audio_open(s);
98   }
99   return AUBIO_OK;
100 }
101
102 uint_t aubio_sink_apple_audio_preset_channels(aubio_sink_apple_audio_t *s, uint_t channels)
103 {
104   if ((sint_t)(channels) <= 0) return AUBIO_FAIL;
105   s->channels = channels;
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_get_samplerate(aubio_sink_apple_audio_t *s)
114 {
115   return s->samplerate;
116 }
117
118 uint_t aubio_sink_apple_audio_get_channels(aubio_sink_apple_audio_t *s)
119 {
120   return s->channels;
121 }
122
123 uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s) {
124
125   if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL;
126
127   AudioStreamBasicDescription clientFormat;
128   memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription));
129   clientFormat.mFormatID         = kAudioFormatLinearPCM;
130   clientFormat.mSampleRate       = (Float64)(s->samplerate);
131   clientFormat.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
132   clientFormat.mChannelsPerFrame = s->channels;
133   clientFormat.mBitsPerChannel   = sizeof(short) * 8;
134   clientFormat.mFramesPerPacket  = 1;
135   clientFormat.mBytesPerFrame    = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8;
136   clientFormat.mBytesPerPacket   = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame;
137   clientFormat.mReserved         = 0;
138
139   AudioFileTypeID fileType = kAudioFileWAVEType;
140   CFURLRef fileURL = getURLFromPath(s->path);
141   bool overwrite = true;
142   OSStatus err = noErr;
143   err = ExtAudioFileCreateWithURL(fileURL, fileType, &clientFormat, NULL,
144      overwrite ? kAudioFileFlags_EraseFile : 0, &s->audioFile);
145   if (err) {
146     char_t errorstr[20];
147     AUBIO_ERR("sink_apple_audio: error when trying to create %s with "
148         "ExtAudioFileCreateWithURL (%s)\n", s->path,
149         getPrintableOSStatusError(errorstr, err));
150     goto beach;
151   }
152   if (createAubioBufferList(&s->bufferList, s->channels, s->max_frames * s->channels)) {
153     AUBIO_ERR("sink_apple_audio: error when creating buffer list for %s, "
154         "out of memory? \n", s->path);
155     goto beach;
156   }
157   return AUBIO_OK;
158
159 beach:
160   return AUBIO_FAIL;
161 }
162
163 void aubio_sink_apple_audio_do(aubio_sink_apple_audio_t * s, fvec_t * write_data, uint_t write) {
164   OSStatus err = noErr;
165   UInt32 c, v;
166   short *data = (short*)s->bufferList.mBuffers[0].mData;
167   if (write > s->max_frames) {
168     AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
169     write = s->max_frames;
170   }
171   smpl_t *buf = write_data->data;
172
173   if (buf) {
174       for (c = 0; c < s->channels; c++) {
175           for (v = 0; v < write; v++) {
176               data[v * s->channels + c] =
177                   FLOAT_TO_SHORT(buf[ v * s->channels + c]);
178           }
179       }
180   }
181   if (s->async) {
182     err = ExtAudioFileWriteAsync(s->audioFile, write, &s->bufferList);
183
184     if (err) {
185       char_t errorstr[20];
186       AUBIO_ERROR("sink_apple_audio: error while writing %s "
187           "in ExtAudioFileWriteAsync (%s), switching to sync\n", s->path,
188           getPrintableOSStatusError(errorstr, err));
189       s->async = false;
190     } else {
191       return;
192     }
193
194   } else {
195     err = ExtAudioFileWrite(s->audioFile, write, &s->bufferList);
196
197     if (err) {
198       char_t errorstr[20];
199       AUBIO_ERROR("sink_apple_audio: error while writing %s "
200           "in ExtAudioFileWrite (%s)\n", s->path,
201           getPrintableOSStatusError(errorstr, err));
202     }
203   }
204   return;
205 }
206
207 void aubio_sink_apple_audio_do_multi(aubio_sink_apple_audio_t * s, fmat_t * write_data, uint_t write) {
208   OSStatus err = noErr;
209   UInt32 c, v;
210   short *data = (short*)s->bufferList.mBuffers[0].mData;
211   if (write > s->max_frames) {
212     AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
213     write = s->max_frames;
214   }
215   smpl_t **buf = write_data->data;
216
217   if (buf) {
218       for (c = 0; c < s->channels; c++) {
219           for (v = 0; v < write; v++) {
220               data[v * s->channels + c] =
221                   FLOAT_TO_SHORT(buf[c][v]);
222           }
223       }
224   }
225   if (s->async) {
226     err = ExtAudioFileWriteAsync(s->audioFile, write, &s->bufferList);
227
228     if (err) {
229       char_t errorstr[20];
230       AUBIO_ERROR("sink_apple_audio: error while writing %s "
231           "in ExtAudioFileWriteAsync (%s), switching to sync\n", s->path,
232           getPrintableOSStatusError(errorstr, err));
233       s->async = false;
234     } else {
235       return;
236     }
237
238   } else {
239     err = ExtAudioFileWrite(s->audioFile, write, &s->bufferList);
240
241     if (err) {
242       char_t errorstr[20];
243       AUBIO_ERROR("sink_apple_audio: error while writing %s "
244           "in ExtAudioFileWrite (%s)\n", s->path,
245           getPrintableOSStatusError(errorstr, err));
246     }
247   }
248   return;
249 }
250
251 uint_t aubio_sink_apple_audio_close(aubio_sink_apple_audio_t * s) {
252   OSStatus err = noErr;
253   if (!s->audioFile) {
254     return AUBIO_FAIL;
255   }
256   err = ExtAudioFileDispose(s->audioFile);
257   if (err) {
258     char_t errorstr[20];
259     AUBIO_ERROR("sink_apple_audio: error while closing %s "
260         "in ExtAudioFileDispose (%s)\n", s->path,
261         getPrintableOSStatusError(errorstr, err));
262   }
263   s->audioFile = NULL;
264   return err;
265 }
266
267 void del_aubio_sink_apple_audio(aubio_sink_apple_audio_t * s) {
268   if (s->audioFile) aubio_sink_apple_audio_close (s);
269   freeAudioBufferList(&s->bufferList);
270   AUBIO_FREE(s);
271   return;
272 }
273
274 #endif /* HAVE_SINK_APPLE_AUDIO */