Vuo  2.3.2
VuoAudioFile.c
Go to the documentation of this file.
1 
10 #include "VuoAudioFile.h"
11 #include "VuoUrlFetch.h"
12 #include "VuoOsStatus.h"
13 #include "VuoEventLoop.h"
14 
15 #include <AudioToolbox/AudioToolbox.h>
16 //#include <CoreServices/CoreServices.h>
17 //#include <CoreAudio/CoreAudioTypes.h>
18 //#include <AudioToolbox/AudioFile.h>
19 //#include <AudioToolbox/AudioFormat.h>
20 
21 #include "module.h"
22 
23 #include "VuoApp.h"
24 
25 #ifdef VUO_COMPILER
27  "title" : "VuoAudioFile",
28  "dependencies" : [
29  "VuoAudioSamples",
30  "VuoInteger",
31  "VuoLoopType",
32  "VuoOsStatus",
33  "VuoReal",
34  "VuoText",
35  "VuoUrlFetch",
36  "VuoList_VuoAudioSamples",
37  "AudioToolbox.framework",
38  "CoreFoundation.framework"
39  ]
40  });
41 #endif
42 
46 typedef struct VuoAudioFileInternal
47 {
48  ExtAudioFileRef audioFile;
49  dispatch_queue_t audioFileQueue;
50  AudioStreamBasicDescription inputFormat;
51  AudioStreamBasicDescription outputFormat;
52  SInt64 totalFrameCount;
53  SInt64 headerFrames;
54 
55  bool playing;
57 
59  void (*finishedPlayback)(void);
60  dispatch_source_t playbackTimer;
61  dispatch_semaphore_t playbackTimerCanceled;
63 
68 {
69  if (!afi->playing)
70  return;
71 
72  VuoInteger bufferSize = sizeof(float) * afi->outputFormat.mChannelsPerFrame * VuoAudioSamples_bufferSize;
73  float *buffer = (float*)malloc(bufferSize);
74  bzero(buffer, bufferSize);
75 
76  dispatch_sync(afi->audioFileQueue, ^{
77  VuoInteger framesRemaining = VuoAudioSamples_bufferSize;
78  float *subBuffer = buffer;
79 
80  while (framesRemaining)
81  {
82  AudioBufferList bufferList;
83  bufferList.mNumberBuffers = 1;
84  bufferList.mBuffers[0].mNumberChannels = afi->outputFormat.mChannelsPerFrame;
85  bufferList.mBuffers[0].mDataByteSize = sizeof(float) * afi->outputFormat.mChannelsPerFrame * framesRemaining;
86  bufferList.mBuffers[0].mData = (void *)subBuffer;
87 
88  UInt32 numFrames = framesRemaining;
89  OSStatus err = ExtAudioFileRead(afi->audioFile, &numFrames, &bufferList);
90  if (err != noErr)
91  {
92  free(buffer);
93  char *errStr = VuoOsStatus_getText(err);
94  VUserLog("Error reading samples: %s", errStr);
95  free(errStr);
96  afi->playing = false;
97  if (afi->finishedPlayback)
98  afi->finishedPlayback();
99  return;
100  }
101 
102  if (!numFrames)
103  {
104 // VLog("didn't get any more frames; breaking");
105  break;
106  }
107 
108  framesRemaining -= numFrames;
109  subBuffer += numFrames * afi->outputFormat.mChannelsPerFrame;
110  }
111 
112  if (framesRemaining)
113  {
114 // VLog("couldn't get all the frames; stopping playback");
115 
116  // even if LoopType is looping, still fire the finishedPlaybac event to let
117  // user know one cycle is complete
118  if (afi->finishedPlayback)
119  afi->finishedPlayback();
120 
121  switch(afi->loop)
122  {
123  case VuoLoopType_Loop:
124  {
125  const SInt64 targetFrame = 0;
126 
127  OSStatus err = ExtAudioFileSeek(afi->audioFile, targetFrame + afi->headerFrames);
128 
129  if (err != noErr)
130  {
131  char *errStr = VuoOsStatus_getText(err);
132  VUserLog("Error seeking to sample: %s", errStr);
133  free(errStr);
134  afi->playing = false;
135  break;
136  }
137 
138  break;
139  }
140 
141  default:
142  afi->playing = false;
143  break;
144  }
145  }
146  });
147 
149 
150  for (VuoInteger channel = 0; channel < afi->outputFormat.mChannelsPerFrame; ++channel)
151  {
154 
155  for (VuoInteger sample = 0; sample < VuoAudioSamples_bufferSize; ++sample)
156  samples.samples[sample] = buffer[sample*afi->outputFormat.mChannelsPerFrame + channel];
157 
158  VuoListAppendValue_VuoAudioSamples(channels, samples);
159  }
160  free(buffer);
161 
162  dispatch_sync(afi->audioFileQueue, ^{
163  if (afi->decodedChannels)
164  afi->decodedChannels(channels);
165  else
166  {
167  VuoRetain(channels);
168  VuoRelease(channels);
169  }
170  });
171 }
172 
176 void VuoAudioFile_free(void *af)
177 {
179 
180  if (afi->playbackTimer)
181  {
182  dispatch_source_cancel(afi->playbackTimer);
183  dispatch_release(afi->playbackTimer);
184  }
185 
186  if (afi->playbackTimerCanceled)
187  {
188  dispatch_semaphore_wait(afi->playbackTimerCanceled, DISPATCH_TIME_FOREVER);
189  dispatch_release(afi->playbackTimerCanceled);
190  }
191 
192  if (afi->audioFileQueue)
193  dispatch_release(afi->audioFileQueue);
194 
195  if (afi->audioFile)
196  ExtAudioFileDispose(afi->audioFile);
197 
198  free(afi);
199 }
200 
205 {
206  if (VuoText_isEmpty(url))
207  return NULL;
208 
209  VuoAudioFileInternal afi = calloc(1, sizeof(struct VuoAudioFileInternal));
211 
212  afi->playing = false;
213  afi->loop = VuoLoopType_None;
214  afi->decodedChannels = NULL;
215  afi->finishedPlayback = NULL;
216 
217  {
218  VuoText normalizedURL = VuoUrl_normalize(url, VuoUrlNormalize_default);
219  VuoRetain(normalizedURL);
220  CFStringRef urlCFS = CFStringCreateWithCString(NULL, normalizedURL, kCFStringEncodingUTF8);
221  CFURLRef url = CFURLCreateWithString(NULL, urlCFS, NULL);
222  if (!url)
223  {
224  VUserLog("Couldn't open '%s': Invalid URL.", normalizedURL);
225  VuoRelease(normalizedURL);
226  CFRelease(urlCFS);
227  goto fail;
228  }
229 
230  // https://b33p.net/kosada/node/7971
231  VuoApp_init(false);
232 
233  OSStatus err = ExtAudioFileOpenURL(url, &afi->audioFile);
234  CFRelease(urlCFS);
235  CFRelease(url);
236  if (err != noErr)
237  {
238  char *errStr = VuoOsStatus_getText(err);
239  VUserLog("Couldn't open '%s': %s", normalizedURL, errStr);
240  free(errStr);
241  VuoRelease(normalizedURL);
242  goto fail;
243  }
244  VuoRelease(normalizedURL);
245  }
246 
247  {
248  UInt32 size = sizeof(afi->inputFormat);
249  OSStatus err = ExtAudioFileGetProperty(afi->audioFile, kExtAudioFileProperty_FileDataFormat, &size, &afi->inputFormat);
250  if (err != noErr)
251  {
252  char *errStr = VuoOsStatus_getText(err);
253  VUserLog("Error getting file format for '%s': %s", url, errStr);
254  free(errStr);
255  goto fail;
256  }
257  }
258 
259  {
260  UInt32 size = sizeof(afi->totalFrameCount);
261  OSStatus err = ExtAudioFileGetProperty(afi->audioFile, kExtAudioFileProperty_FileLengthFrames, &size, &afi->totalFrameCount);
262  if (err != noErr)
263  {
264  char *errStr = VuoOsStatus_getText(err);
265  VUserLog("Error getting frame count for '%s': %s", url, errStr);
266  free(errStr);
267  goto fail;
268  }
269  }
270 
271  {
272  afi->outputFormat = afi->inputFormat;
273  afi->outputFormat.mFormatID = kAudioFormatLinearPCM;
274  afi->outputFormat.mSampleRate = VuoAudioSamples_sampleRate;
275  afi->outputFormat.mFormatFlags = kAudioFormatFlagIsFloat;
276  afi->outputFormat.mBytesPerFrame = sizeof(float) * afi->outputFormat.mChannelsPerFrame;
277  afi->outputFormat.mBitsPerChannel = sizeof(float) * 8;
278  afi->outputFormat.mFramesPerPacket = 1;
279  afi->outputFormat.mBytesPerPacket = afi->outputFormat.mBytesPerFrame * afi->outputFormat.mFramesPerPacket;
280  UInt32 size = sizeof(afi->outputFormat);
281  OSStatus err = ExtAudioFileSetProperty(afi->audioFile, kExtAudioFileProperty_ClientDataFormat, size, &afi->outputFormat);
282  if (err != noErr)
283  {
284  char *errStr = VuoOsStatus_getText(err);
285  VUserLog("Error setting output format for '%s': %s", url, errStr);
286  free(errStr);
287  goto fail;
288  }
289  }
290 
291  {
292  AudioConverterRef ac;
293  UInt32 size = sizeof(AudioConverterRef);
294  bzero(&ac, size);
295  OSStatus err = ExtAudioFileGetProperty(afi->audioFile, kExtAudioFileProperty_AudioConverter, &size, &ac);
296  if (err != noErr)
297  {
298  char *errStr = VuoOsStatus_getText(err);
299  VUserLog("Error getting audio converter info for '%s': %s", url, errStr);
300  free(errStr);
301  }
302  else
303  {
304  AudioConverterPrimeInfo pi;
305  UInt32 size = sizeof(AudioConverterPrimeInfo);
306  bzero(&pi, size);
307  OSStatus err = AudioConverterGetProperty(ac, kAudioConverterPrimeInfo, &size, &pi);
308  if (err != noErr)
309  {
310  if (err != kAudioFormatUnsupportedPropertyError)
311  {
312  char *errStr = VuoOsStatus_getText(err);
313  VUserLog("Error getting header info for '%s': %s", url, errStr);
314  free(errStr);
315  }
316  }
317  else
318  afi->headerFrames = pi.leadingFrames;
319  }
320  }
321 
322  afi->audioFileQueue = dispatch_queue_create("org.vuo.audiofile", VuoEventLoop_getDispatchInteractiveAttribute());
323  dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
324  afi->playbackTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, q);
325  afi->playbackTimerCanceled = dispatch_semaphore_create(0);
326 
327  uint64_t nanoseconds = (float)VuoAudioSamples_bufferSize/VuoAudioSamples_sampleRate * NSEC_PER_SEC;
328  dispatch_source_set_timer(afi->playbackTimer, dispatch_time(DISPATCH_TIME_NOW, nanoseconds), nanoseconds, 0);
329  dispatch_source_set_event_handler(afi->playbackTimer, ^{
330  VuoAudioFile_decodeChannels(afi);
331  });
332  dispatch_source_set_cancel_handler(afi->playbackTimer, ^{
333  dispatch_semaphore_signal(afi->playbackTimerCanceled);
334  });
335 
336  // Seek past the header.
338 
339  dispatch_resume(afi->playbackTimer);
340 
341  return (VuoAudioFile)afi;
342 
343 fail:
344  VuoRetain(afi);
345  VuoRelease(afi);
346  return NULL;
347 }
348 
354 bool VuoAudioFile_getInfo(VuoText url, VuoReal *duration, VuoInteger *channelCount, VuoReal *sampleRate)
355 {
357  if (!afi)
358  return false;
359  VuoRetain(afi);
360 
361  *duration = (float)afi->totalFrameCount / afi->inputFormat.mSampleRate;
362  *channelCount = afi->inputFormat.mChannelsPerFrame;
363  *sampleRate = afi->inputFormat.mSampleRate;
364 
365  VuoRelease(afi);
366 
367  return true;
368 }
369 
375 void VuoAudioFile_enableTriggers(VuoAudioFile af, void (*decodedChannels)(VuoList_VuoAudioSamples), void (*finishedPlayback)(void))
376 {
377  if (!af)
378  return;
380  dispatch_sync(afi->audioFileQueue, ^{
381  afi->decodedChannels = decodedChannels;
382  afi->finishedPlayback = finishedPlayback;
383  });
384 }
385 
392 {
393  if (!af)
394  return;
396  dispatch_sync(afi->audioFileQueue, ^{
397  afi->decodedChannels = NULL;
398  afi->finishedPlayback = NULL;
399  });
400 }
401 
406 {
407  if (!af)
408  return;
410  afi->loop = loop;
411 }
412 
417 {
418  if (!af)
419  return;
421  dispatch_sync(afi->audioFileQueue, ^{
422  SInt64 targetFrame = time * afi->inputFormat.mSampleRate;
423 // VLog("seeking to frame %lld (of %lld)", targetFrame, afi->totalFrameCount);
424  OSStatus err = ExtAudioFileSeek(afi->audioFile, targetFrame + afi->headerFrames);
425  if (err != noErr)
426  {
427  char *errStr = VuoOsStatus_getText(err);
428  VUserLog("Error seeking to sample: %s", errStr);
429  free(errStr);
430  return;
431  }
432  });
433 }
434 
439 {
440  if (!af)
441  return;
443  afi->playing = true;
444 }
445 
450 {
451  if (!af)
452  return;
454  afi->playing = false;
455 }
456 
463 {
464  if (!af)
465  return false;
467  return afi->playing;
468 }