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