Mercurial > almixer_isolated
comparison Isolated/coreaudio.c @ 38:71b465ff0622
Added support files.
author | Eric Wing <ewing@anscamobile.com> |
---|---|
date | Thu, 28 Apr 2011 16:22:30 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
37:b346b6608eab | 38:71b465ff0622 |
---|---|
1 #ifdef ALMIXER_COMPILE_WITHOUT_SDL | |
2 | |
3 /* | |
4 * SDL_sound Core Audio backend | |
5 * Copyright (C) 2010 Eric Wing <ewing . public @ playcontrol.net> | |
6 * | |
7 * This library is free software; you can redistribute it and/or | |
8 * modify it under the terms of the GNU Lesser General Public | |
9 * License as published by the Free Software Foundation; either | |
10 * version 2.1 of the License, or (at your option) any later version. | |
11 * | |
12 * This library is distributed in the hope that it will be useful, | |
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Lesser General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Lesser General Public | |
18 * License along with this library; if not, write to the Free Software | |
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 */ | |
21 | |
22 #if HAVE_CONFIG_H | |
23 # include <config.h> | |
24 #endif | |
25 | |
26 #ifdef __APPLE__ | |
27 | |
28 #include <stddef.h> /* NULL */ | |
29 #include <stdio.h> /* printf */ | |
30 #include <arpa/inet.h> /* htonl */ | |
31 #include <AudioToolbox/AudioToolbox.h> | |
32 | |
33 #include "SoundDecoder.h" | |
34 | |
35 #include "SoundDecoder_Internal.h" | |
36 | |
37 typedef struct CoreAudioFileContainer | |
38 { | |
39 AudioFileID* audioFileID; | |
40 ExtAudioFileRef extAudioFileRef; | |
41 AudioStreamBasicDescription* outputFormat; | |
42 } CoreAudioFileContainer; | |
43 | |
44 static int CoreAudio_init(void); | |
45 static void CoreAudio_quit(void); | |
46 static int CoreAudio_open(Sound_Sample *sample, const char *ext); | |
47 static void CoreAudio_close(Sound_Sample *sample); | |
48 static size_t CoreAudio_read(Sound_Sample *sample); | |
49 static int CoreAudio_rewind(Sound_Sample *sample); | |
50 static int CoreAudio_seek(Sound_Sample *sample, size_t ms); | |
51 | |
52 static const char *extensions_coreaudio[] = | |
53 { | |
54 "aif", | |
55 "aiff", | |
56 "aifc", | |
57 "wav", | |
58 "wave", | |
59 "mp3", | |
60 "mp4", | |
61 "m4a", | |
62 "aac", | |
63 "adts", | |
64 "caf", | |
65 "Sd2f", | |
66 "Sd2", | |
67 "au", | |
68 "snd", | |
69 "next", | |
70 "mp2", | |
71 "mp1", | |
72 "ac3", | |
73 "3gpp", | |
74 "3gp", | |
75 "3gp2", | |
76 "3g2", | |
77 "amrf", | |
78 "amr", | |
79 "ima4", | |
80 "ima", | |
81 NULL | |
82 }; | |
83 const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio = | |
84 { | |
85 { | |
86 extensions_coreaudio, | |
87 "Decode audio through Core Audio through", | |
88 "Eric Wing <ewing . public @ playcontrol.net>", | |
89 "http://playcontrol.net" | |
90 }, | |
91 | |
92 CoreAudio_init, /* init() method */ | |
93 CoreAudio_quit, /* quit() method */ | |
94 CoreAudio_open, /* open() method */ | |
95 CoreAudio_close, /* close() method */ | |
96 CoreAudio_read, /* read() method */ | |
97 CoreAudio_rewind, /* rewind() method */ | |
98 CoreAudio_seek /* seek() method */ | |
99 }; | |
100 | |
101 | |
102 static int CoreAudio_init(void) | |
103 { | |
104 return(1); /* always succeeds. */ | |
105 } /* CoreAudio_init */ | |
106 | |
107 | |
108 static void CoreAudio_quit(void) | |
109 { | |
110 /* it's a no-op. */ | |
111 } /* CoreAudio_quit */ | |
112 | |
113 /* | |
114 http://developer.apple.com/library/ios/#documentation/MusicAudio/Reference/AudioFileConvertRef/Reference/reference.html | |
115 kAudioFileAIFFType = 'AIFF', | |
116 kAudioFileAIFCType = 'AIFC', | |
117 kAudioFileWAVEType = 'WAVE', | |
118 kAudioFileSoundDesigner2Type = 'Sd2f', | |
119 kAudioFileNextType = 'NeXT', | |
120 kAudioFileMP3Type = 'MPG3', | |
121 kAudioFileMP2Type = 'MPG2', | |
122 kAudioFileMP1Type = 'MPG1', | |
123 kAudioFileAC3Type = 'ac-3', | |
124 kAudioFileAAC_ADTSType = 'adts', | |
125 kAudioFileMPEG4Type = 'mp4f', | |
126 kAudioFileM4AType = 'm4af', | |
127 kAudioFileCAFType = 'caff', | |
128 kAudioFile3GPType = '3gpp', | |
129 kAudioFile3GP2Type = '3gp2', | |
130 kAudioFileAMRType = 'amrf' | |
131 */ | |
132 static AudioFileTypeID CoreAudio_GetAudioTypeForExtension(const char* file_extension) | |
133 { | |
134 if( (__Sound_strcasecmp(file_extension, "aif") == 0) | |
135 || (__Sound_strcasecmp(file_extension, "aiff") == 0) | |
136 || (__Sound_strcasecmp(file_extension, "aifc") == 0) | |
137 ) | |
138 { | |
139 return kAudioFileAIFCType; | |
140 } | |
141 else if( (__Sound_strcasecmp(file_extension, "wav") == 0) | |
142 || (__Sound_strcasecmp(file_extension, "wave") == 0) | |
143 ) | |
144 { | |
145 return kAudioFileWAVEType; | |
146 } | |
147 else if( (__Sound_strcasecmp(file_extension, "mp3") == 0) | |
148 ) | |
149 { | |
150 return kAudioFileMP3Type; | |
151 } | |
152 else if( (__Sound_strcasecmp(file_extension, "mp4") == 0) | |
153 ) | |
154 { | |
155 return kAudioFileMPEG4Type; | |
156 } | |
157 else if( (__Sound_strcasecmp(file_extension, "m4a") == 0) | |
158 ) | |
159 { | |
160 return kAudioFileM4AType; | |
161 } | |
162 else if( (__Sound_strcasecmp(file_extension, "aac") == 0) | |
163 ) | |
164 { | |
165 return kAudioFileAAC_ADTSType; | |
166 } | |
167 else if( (__Sound_strcasecmp(file_extension, "adts") == 0) | |
168 ) | |
169 { | |
170 return kAudioFileAAC_ADTSType; | |
171 } | |
172 else if( (__Sound_strcasecmp(file_extension, "caf") == 0) | |
173 || (__Sound_strcasecmp(file_extension, "caff") == 0) | |
174 ) | |
175 { | |
176 return kAudioFileCAFType; | |
177 } | |
178 else if( (__Sound_strcasecmp(file_extension, "Sd2f") == 0) | |
179 || (__Sound_strcasecmp(file_extension, "sd2") == 0) | |
180 ) | |
181 { | |
182 return kAudioFileSoundDesigner2Type; | |
183 } | |
184 else if( (__Sound_strcasecmp(file_extension, "au") == 0) | |
185 || (__Sound_strcasecmp(file_extension, "snd") == 0) | |
186 || (__Sound_strcasecmp(file_extension, "next") == 0) | |
187 ) | |
188 { | |
189 return kAudioFileNextType; | |
190 } | |
191 else if( (__Sound_strcasecmp(file_extension, "mp2") == 0) | |
192 ) | |
193 { | |
194 return kAudioFileMP2Type; | |
195 } | |
196 else if( (__Sound_strcasecmp(file_extension, "mp1") == 0) | |
197 ) | |
198 { | |
199 return kAudioFileMP1Type; | |
200 } | |
201 else if( (__Sound_strcasecmp(file_extension, "ac3") == 0) | |
202 ) | |
203 { | |
204 return kAudioFileAC3Type; | |
205 } | |
206 else if( (__Sound_strcasecmp(file_extension, "3gpp") == 0) | |
207 || (__Sound_strcasecmp(file_extension, "3gp") == 0) | |
208 ) | |
209 { | |
210 return kAudioFile3GPType; | |
211 } | |
212 else if( (__Sound_strcasecmp(file_extension, "3gp2") == 0) | |
213 || (__Sound_strcasecmp(file_extension, "3g2") == 0) | |
214 ) | |
215 { | |
216 return kAudioFile3GP2Type; | |
217 } | |
218 else if( (__Sound_strcasecmp(file_extension, "amrf") == 0) | |
219 || (__Sound_strcasecmp(file_extension, "amr") == 0) | |
220 ) | |
221 { | |
222 return kAudioFileAMRType; | |
223 } | |
224 else if( (__Sound_strcasecmp(file_extension, "ima4") == 0) | |
225 || (__Sound_strcasecmp(file_extension, "ima") == 0) | |
226 ) | |
227 { | |
228 /* not sure about this one */ | |
229 return kAudioFileCAFType; | |
230 } | |
231 else | |
232 { | |
233 return 0; | |
234 } | |
235 | |
236 } | |
237 | |
238 static const char* CoreAudio_FourCCToString(int32_t error_code) | |
239 { | |
240 static char return_string[16]; | |
241 uint32_t big_endian_code = htonl(error_code); | |
242 char* big_endian_str = (char*)&big_endian_code; | |
243 // see if it appears to be a 4-char-code | |
244 if(isprint(big_endian_str[0]) | |
245 && isprint(big_endian_str[1]) | |
246 && isprint(big_endian_str[2]) | |
247 && isprint (big_endian_str[3])) | |
248 { | |
249 return_string[0] = '\''; | |
250 return_string[1] = big_endian_str[0]; | |
251 return_string[2] = big_endian_str[1]; | |
252 return_string[3] = big_endian_str[2]; | |
253 return_string[4] = big_endian_str[3]; | |
254 return_string[5] = '\''; | |
255 return_string[6] = '\0'; | |
256 } | |
257 else if(error_code > -200000 && error_code < 200000) | |
258 { | |
259 // no, format it as an integer | |
260 snprintf(return_string, 16, "%d", error_code); | |
261 } | |
262 else | |
263 { | |
264 // no, format it as an integer but in hex | |
265 snprintf(return_string, 16, "0x%x", error_code); | |
266 } | |
267 return return_string; | |
268 } | |
269 | |
270 | |
271 | |
272 SInt64 CoreAudio_SizeCallback(void* inClientData) | |
273 { | |
274 ALmixer_RWops* rw_ops = (ALmixer_RWops*)inClientData; | |
275 SInt64 current_position = ALmixer_RWtell(rw_ops); | |
276 SInt64 end_position = ALmixer_RWseek(rw_ops, 0, SEEK_END); | |
277 ALmixer_RWseek(rw_ops, current_position, SEEK_SET); | |
278 // fprintf(stderr, "CoreAudio_SizeCallback:%d\n", end_position); | |
279 | |
280 return end_position; | |
281 } | |
282 | |
283 OSStatus CoreAudio_ReadCallback( | |
284 void* inClientData, | |
285 SInt64 inPosition, | |
286 UInt32 requestCount, | |
287 void* data_buffer, | |
288 UInt32* actualCount | |
289 ) | |
290 { | |
291 ALmixer_RWops* rw_ops = (ALmixer_RWops*)inClientData; | |
292 ALmixer_RWseek(rw_ops, inPosition, SEEK_SET); | |
293 size_t bytes_actually_read = ALmixer_RWread(rw_ops, data_buffer, 1, requestCount); | |
294 // Not sure how to test for a read error with ALmixer_RWops | |
295 // fprintf(stderr, "CoreAudio_ReadCallback:%d, %d\n", requestCount, bytes_actually_read); | |
296 | |
297 *actualCount = bytes_actually_read; | |
298 return noErr; | |
299 } | |
300 | |
301 | |
302 static int CoreAudio_open(Sound_Sample *sample, const char *ext) | |
303 { | |
304 CoreAudioFileContainer* core_audio_file_container; | |
305 AudioFileID* audio_file_id; | |
306 OSStatus error_result; | |
307 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
308 AudioStreamBasicDescription actual_format; | |
309 AudioStreamBasicDescription output_format; | |
310 Float64 estimated_duration; | |
311 UInt32 format_size; | |
312 | |
313 | |
314 core_audio_file_container = (CoreAudioFileContainer*)malloc(sizeof(CoreAudioFileContainer)); | |
315 BAIL_IF_MACRO(core_audio_file_container == NULL, ERR_OUT_OF_MEMORY, 0); | |
316 | |
317 | |
318 audio_file_id = (AudioFileID*)malloc(sizeof(AudioFileID)); | |
319 BAIL_IF_MACRO(audio_file_id == NULL, ERR_OUT_OF_MEMORY, 0); | |
320 | |
321 error_result = AudioFileOpenWithCallbacks( | |
322 internal->rw, | |
323 CoreAudio_ReadCallback, | |
324 NULL, | |
325 CoreAudio_SizeCallback, | |
326 NULL, | |
327 CoreAudio_GetAudioTypeForExtension(ext), | |
328 audio_file_id | |
329 ); | |
330 if (error_result != noErr) | |
331 { | |
332 AudioFileClose(*audio_file_id); | |
333 free(audio_file_id); | |
334 free(core_audio_file_container); | |
335 SNDDBG(("Core Audio: can't grok data. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); | |
336 BAIL_MACRO("Core Audio: Not valid audio data.", 0); | |
337 } /* if */ | |
338 | |
339 format_size = sizeof(actual_format); | |
340 error_result = AudioFileGetProperty( | |
341 *audio_file_id, | |
342 kAudioFilePropertyDataFormat, | |
343 &format_size, | |
344 &actual_format | |
345 ); | |
346 if (error_result != noErr) | |
347 { | |
348 AudioFileClose(*audio_file_id); | |
349 free(audio_file_id); | |
350 free(core_audio_file_container); | |
351 SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s]", CoreAudio_FourCCToString(error_result))); | |
352 BAIL_MACRO("Core Audio: Not valid audio data.", 0); | |
353 } /* if */ | |
354 | |
355 format_size = sizeof(estimated_duration); | |
356 error_result = AudioFileGetProperty( | |
357 *audio_file_id, | |
358 kAudioFilePropertyEstimatedDuration, | |
359 &format_size, | |
360 &estimated_duration | |
361 ); | |
362 if (error_result != noErr) | |
363 { | |
364 AudioFileClose(*audio_file_id); | |
365 free(audio_file_id); | |
366 free(core_audio_file_container); | |
367 SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); | |
368 BAIL_MACRO("Core Audio: Not valid audio data.", 0); | |
369 } /* if */ | |
370 | |
371 | |
372 core_audio_file_container->audioFileID = audio_file_id; | |
373 | |
374 internal->decoder_private = core_audio_file_container; | |
375 | |
376 sample->flags = SOUND_SAMPLEFLAG_CANSEEK; | |
377 sample->actual.rate = (UInt32) actual_format.mSampleRate; | |
378 sample->actual.channels = (UInt8)actual_format.mChannelsPerFrame; | |
379 internal->total_time = (SInt32)(estimated_duration * 1000.0 + 0.5); | |
380 | |
381 #if 0 | |
382 /* FIXME: Both Core Audio and SDL 1.3 support float and 32-bit formats */ | |
383 if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian) | |
384 { | |
385 if(16 == actual_format.mBitsPerChannel) | |
386 { | |
387 if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) | |
388 { | |
389 sample->actual.format = AUDIO_S16MSB; | |
390 } | |
391 else | |
392 { | |
393 sample->actual.format = AUDIO_U16MSB; | |
394 } | |
395 } | |
396 else if(8 == actual_format.mBitsPerChannel) | |
397 { | |
398 if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) | |
399 { | |
400 sample->actual.format = AUDIO_S8; | |
401 } | |
402 else | |
403 { | |
404 sample->actual.format = AUDIO_U8; | |
405 } | |
406 } | |
407 else // might be 0 for undefined? | |
408 { | |
409 // This case seems to come up a lot for me. Maybe for file types like .m4a? | |
410 sample->actual.format = AUDIO_S16SYS; | |
411 SNDDBG(("Core Audio: Unsupported actual_format.mBitsPerChannel: [%d].\n", actual_format.mBitsPerChannel)); | |
412 | |
413 } | |
414 } | |
415 else // little endian | |
416 { | |
417 if(16 == actual_format.mBitsPerChannel) | |
418 { | |
419 if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) | |
420 { | |
421 sample->actual.format = AUDIO_S16LSB; | |
422 } | |
423 else | |
424 { | |
425 sample->actual.format = AUDIO_U16LSB; | |
426 } | |
427 } | |
428 else if(8 == actual_format.mBitsPerChannel) | |
429 { | |
430 if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) | |
431 { | |
432 sample->actual.format = AUDIO_S8; | |
433 } | |
434 else | |
435 { | |
436 sample->actual.format = AUDIO_U8; | |
437 } | |
438 } | |
439 else // might be 0 for undefined? | |
440 { | |
441 sample->actual.format = AUDIO_S16SYS; | |
442 | |
443 SNDDBG(("Core Audio: Unsupported actual_format.mBitsPerChannel: [%d].\n", actual_format.mBitsPerChannel)); | |
444 } | |
445 | |
446 } | |
447 #else | |
448 | |
449 | |
450 | |
451 /* | |
452 * I want to use Core Audio to do conversion and decoding for performance reasons. | |
453 * This is particularly important on mobile devices like iOS. | |
454 * Taking from the Ogg Vorbis decode, I pretend the "actual" format is the same | |
455 * as the desired format. | |
456 */ | |
457 sample->actual.format = (sample->desired.format == 0) ? | |
458 AUDIO_S16SYS : sample->desired.format; | |
459 #endif | |
460 | |
461 | |
462 SNDDBG(("CoreAudio: channels == (%d).\n", sample->actual.channels)); | |
463 SNDDBG(("CoreAudio: sampling rate == (%d).\n",sample->actual.rate)); | |
464 SNDDBG(("CoreAudio: total seconds of sample == (%d).\n", internal->total_time)); | |
465 SNDDBG(("CoreAudio: sample->actual.format == (%d).\n", sample->actual.format)); | |
466 | |
467 | |
468 | |
469 error_result = ExtAudioFileWrapAudioFileID(*audio_file_id, | |
470 false, // set to false for read-only | |
471 &core_audio_file_container->extAudioFileRef | |
472 ); | |
473 if(error_result != noErr) | |
474 { | |
475 AudioFileClose(*audio_file_id); | |
476 free(audio_file_id); | |
477 free(core_audio_file_container); | |
478 SNDDBG(("Core Audio: can't wrap data. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); | |
479 BAIL_MACRO("Core Audio: Failed to wrap data.", 0); | |
480 } /* if */ | |
481 | |
482 | |
483 /* The output format must be linear PCM because that's the only type OpenAL knows how to deal with. | |
484 * Set the client format to 16 bit signed integer (native-endian) data because that is the most | |
485 * optimal format on iPhone/iPod Touch hardware. | |
486 * Maintain the channel count and sample rate of the original source format. | |
487 */ | |
488 output_format.mSampleRate = actual_format.mSampleRate; // preserve the original sample rate | |
489 output_format.mChannelsPerFrame = actual_format.mChannelsPerFrame; // preserve the number of channels | |
490 output_format.mFormatID = kAudioFormatLinearPCM; // We want linear PCM data | |
491 output_format.mFramesPerPacket = 1; // We know for linear PCM, the definition is 1 frame per packet | |
492 | |
493 if(sample->desired.format == 0) | |
494 { | |
495 // do AUDIO_S16SYS | |
496 output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; // I seem to read failures problems without kAudioFormatFlagIsPacked. From a mailing list post, this seems to be a Core Audio bug. | |
497 output_format.mBitsPerChannel = 16; // We know we want 16-bit | |
498 } | |
499 else | |
500 { | |
501 output_format.mFormatFlags = 0; // clear flags | |
502 output_format.mFormatFlags |= kAudioFormatFlagIsPacked; // I seem to read failures problems without kAudioFormatFlagIsPacked. From a mailing list post, this seems to be a Core Audio bug. | |
503 // Mask against bitsize | |
504 if(0xFF & sample->desired.format) | |
505 { | |
506 output_format.mBitsPerChannel = 16; /* 16-bit */ | |
507 } | |
508 else | |
509 { | |
510 output_format.mBitsPerChannel = 8; /* 8-bit */ | |
511 } | |
512 | |
513 // Mask for signed/unsigned | |
514 if((1<<15) & sample->desired.format) | |
515 { | |
516 output_format.mFormatFlags = output_format.mFormatFlags | kAudioFormatFlagIsSignedInteger; | |
517 | |
518 } | |
519 else | |
520 { | |
521 // no flag set for unsigned | |
522 } | |
523 // Mask for big/little endian | |
524 if((1<<12) & sample->desired.format) | |
525 { | |
526 output_format.mFormatFlags = output_format.mFormatFlags | kAudioFormatFlagIsBigEndian; | |
527 } | |
528 else | |
529 { | |
530 // no flag set for little endian | |
531 } | |
532 } | |
533 | |
534 output_format.mBytesPerPacket = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // e.g. 16-bits/8 * channels => so 2-bytes per channel per frame | |
535 output_format.mBytesPerFrame = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // For PCM, since 1 frame is 1 packet, it is the same as mBytesPerPacket | |
536 | |
537 | |
538 /* | |
539 output_format.mSampleRate = actual_format.mSampleRate; // preserve the original sample rate | |
540 output_format.mChannelsPerFrame = actual_format.mChannelsPerFrame; // preserve the number of channels | |
541 output_format.mFormatID = kAudioFormatLinearPCM; // We want linear PCM data | |
542 // output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; | |
543 output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; | |
544 output_format.mFramesPerPacket = 1; // We know for linear PCM, the definition is 1 frame per packet | |
545 output_format.mBitsPerChannel = 16; // We know we want 16-bit | |
546 output_format.mBytesPerPacket = 2 * output_format.mChannelsPerFrame; // We know we are using 16-bit, so 2-bytes per channel per frame | |
547 output_format.mBytesPerFrame = 2 * output_format.mChannelsPerFrame; // For PCM, since 1 frame is 1 packet, it is the same as mBytesPerPacket | |
548 */ | |
549 SNDDBG(("output_format: mSampleRate: %lf\n", output_format.mSampleRate)); | |
550 SNDDBG(("output_format: mChannelsPerFrame: %d\n", output_format.mChannelsPerFrame)); | |
551 SNDDBG(("output_format: mFormatID: %d\n", output_format.mFormatID)); | |
552 SNDDBG(("output_format: mFormatFlags: %d\n", output_format.mFormatFlags)); | |
553 SNDDBG(("output_format: mFramesPerPacket: %d\n", output_format.mFramesPerPacket)); | |
554 SNDDBG(("output_format: mBitsPerChannel: %d\n", output_format.mBitsPerChannel)); | |
555 SNDDBG(("output_format: mBytesPerPacket: %d\n", output_format.mBytesPerPacket)); | |
556 SNDDBG(("output_format: mBytesPerFrame: %d\n", output_format.mBytesPerFrame)); | |
557 | |
558 | |
559 /* Set the desired client (output) data format */ | |
560 error_result = ExtAudioFileSetProperty(core_audio_file_container->extAudioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(output_format), &output_format); | |
561 if(noErr != error_result) | |
562 { | |
563 ExtAudioFileDispose(core_audio_file_container->extAudioFileRef); | |
564 AudioFileClose(*audio_file_id); | |
565 free(audio_file_id); | |
566 free(core_audio_file_container); | |
567 SNDDBG(("Core Audio: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) failed, reason: [%s].\n", CoreAudio_FourCCToString(error_result))); | |
568 BAIL_MACRO("Core Audio: Not valid audio data.", 0); | |
569 } | |
570 | |
571 | |
572 core_audio_file_container->outputFormat = (AudioStreamBasicDescription*)malloc(sizeof(AudioStreamBasicDescription)); | |
573 BAIL_IF_MACRO(core_audio_file_container->outputFormat == NULL, ERR_OUT_OF_MEMORY, 0); | |
574 | |
575 | |
576 | |
577 /* Copy the output format to the audio_description that was passed in so the | |
578 * info will be returned to the user. | |
579 */ | |
580 memcpy(core_audio_file_container->outputFormat, &output_format, sizeof(AudioStreamBasicDescription)); | |
581 | |
582 | |
583 | |
584 return(1); | |
585 } /* CoreAudio_open */ | |
586 | |
587 | |
588 static void CoreAudio_close(Sound_Sample *sample) | |
589 { | |
590 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
591 CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; | |
592 | |
593 free(core_audio_file_container->outputFormat); | |
594 ExtAudioFileDispose(core_audio_file_container->extAudioFileRef); | |
595 AudioFileClose(*core_audio_file_container->audioFileID); | |
596 free(core_audio_file_container->audioFileID); | |
597 free(core_audio_file_container); | |
598 | |
599 } /* CoreAudio_close */ | |
600 | |
601 | |
602 static size_t CoreAudio_read(Sound_Sample *sample) | |
603 { | |
604 OSStatus error_result = noErr; | |
605 /* Documentation/example shows SInt64, but is problematic for big endian | |
606 * on 32-bit cast for ExtAudioFileRead() because it takes the upper | |
607 * bits which turn to 0. | |
608 */ | |
609 UInt32 buffer_size_in_frames = 0; | |
610 UInt32 buffer_size_in_frames_remaining = 0; | |
611 UInt32 total_frames_read = 0; | |
612 UInt32 data_buffer_size = 0; | |
613 UInt32 bytes_remaining = 0; | |
614 size_t total_bytes_read = 0; | |
615 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
616 CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; | |
617 UInt32 max_buffer_size = internal->buffer_size; | |
618 | |
619 // printf("internal->buffer_size=%d, internal->buffer=0x%x, sample->buffer_size=%d\n", internal->buffer_size, internal->buffer, sample->buffer_size); | |
620 // printf("internal->max_buffer_size=%d\n", max_buffer_size); | |
621 | |
622 /* Compute how many frames will fit into our max buffer size */ | |
623 /* Warning: If this is not evenly divisible, the buffer will not be completely filled which violates the SDL_sound assumption. */ | |
624 buffer_size_in_frames = max_buffer_size / core_audio_file_container->outputFormat->mBytesPerFrame; | |
625 // printf("buffer_size_in_frames=%ld, internal->buffer_size=%d, internal->buffer=0x%x outputFormat->mBytesPerFrame=%d, sample->buffer_size=%d\n", buffer_size_in_frames, internal->buffer_size, internal->buffer, core_audio_file_container->outputFormat->mBytesPerFrame, sample->buffer_size); | |
626 | |
627 | |
628 // void* temp_buffer = malloc(max_buffer_size); | |
629 | |
630 AudioBufferList audio_buffer_list; | |
631 audio_buffer_list.mNumberBuffers = 1; | |
632 audio_buffer_list.mBuffers[0].mDataByteSize = max_buffer_size; | |
633 audio_buffer_list.mBuffers[0].mNumberChannels = core_audio_file_container->outputFormat->mChannelsPerFrame; | |
634 audio_buffer_list.mBuffers[0].mData = internal->buffer; | |
635 | |
636 | |
637 bytes_remaining = max_buffer_size; | |
638 buffer_size_in_frames_remaining = buffer_size_in_frames; | |
639 | |
640 // oops. Due to the kAudioFormatFlagIsPacked bug, | |
641 // I was misled to believe that Core Audio | |
642 // was not always filling my entire requested buffer. | |
643 // So this while-loop might be unnecessary. | |
644 // However, I have not exhaustively tested all formats, | |
645 // so maybe it is possible this loop is useful. | |
646 // It might also handle the not-evenly disvisible case above. | |
647 while(buffer_size_in_frames_remaining > 0 && !(sample->flags & SOUND_SAMPLEFLAG_EOF)) | |
648 { | |
649 | |
650 data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame); | |
651 // printf("data_buffer_size=%d\n", data_buffer_size); | |
652 | |
653 buffer_size_in_frames = buffer_size_in_frames_remaining; | |
654 | |
655 // printf("reading buffer_size_in_frames=%"PRId64"\n", buffer_size_in_frames); | |
656 | |
657 | |
658 audio_buffer_list.mBuffers[0].mDataByteSize = bytes_remaining; | |
659 audio_buffer_list.mBuffers[0].mData = &(((UInt8*)internal->buffer)[total_bytes_read]); | |
660 | |
661 | |
662 /* Read the data into an AudioBufferList */ | |
663 error_result = ExtAudioFileRead(core_audio_file_container->extAudioFileRef, &buffer_size_in_frames, &audio_buffer_list); | |
664 if(error_result == noErr) | |
665 { | |
666 | |
667 | |
668 /* Success */ | |
669 | |
670 total_frames_read += buffer_size_in_frames; | |
671 buffer_size_in_frames_remaining = buffer_size_in_frames_remaining - buffer_size_in_frames; | |
672 | |
673 // printf("read buffer_size_in_frames=%"PRId64", buffer_size_in_frames_remaining=%"PRId64"\n", buffer_size_in_frames, buffer_size_in_frames_remaining); | |
674 | |
675 /* ExtAudioFileRead returns the number of frames actually read. Need to convert back to bytes. */ | |
676 data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame); | |
677 // printf("data_buffer_size=%d\n", data_buffer_size); | |
678 | |
679 total_bytes_read += data_buffer_size; | |
680 bytes_remaining = bytes_remaining - data_buffer_size; | |
681 | |
682 /* Note: 0 == buffer_size_in_frames is a legitimate value meaning we are EOF. */ | |
683 if(0 == buffer_size_in_frames) | |
684 { | |
685 sample->flags |= SOUND_SAMPLEFLAG_EOF; | |
686 } | |
687 | |
688 } | |
689 else | |
690 { | |
691 SNDDBG(("Core Audio: ExtAudioFileReadfailed, reason: [%s].\n", CoreAudio_FourCCToString(error_result))); | |
692 | |
693 sample->flags |= SOUND_SAMPLEFLAG_ERROR; | |
694 break; | |
695 | |
696 } | |
697 } | |
698 | |
699 if( (!(sample->flags & SOUND_SAMPLEFLAG_EOF)) && (total_bytes_read < max_buffer_size)) | |
700 { | |
701 SNDDBG(("Core Audio: ExtAudioFileReadfailed SOUND_SAMPLEFLAG_EAGAIN, reason: [total_bytes_read < max_buffer_size], %d, %d.\n", total_bytes_read , max_buffer_size)); | |
702 | |
703 /* Don't know what to do here. */ | |
704 sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; | |
705 } | |
706 return total_bytes_read; | |
707 } /* CoreAudio_read */ | |
708 | |
709 | |
710 static int CoreAudio_rewind(Sound_Sample *sample) | |
711 { | |
712 OSStatus error_result = noErr; | |
713 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
714 CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; | |
715 | |
716 error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, 0); | |
717 if(error_result != noErr) | |
718 { | |
719 sample->flags |= SOUND_SAMPLEFLAG_ERROR; | |
720 } | |
721 return(1); | |
722 } /* CoreAudio_rewind */ | |
723 | |
724 /* Note: I found this tech note: | |
725 http://developer.apple.com/library/mac/#qa/qa2009/qa1678.html | |
726 I don't know if this applies to us. So far, I haven't noticed the problem, | |
727 so I haven't applied any of the techniques. | |
728 */ | |
729 static int CoreAudio_seek(Sound_Sample *sample, size_t ms) | |
730 { | |
731 OSStatus error_result = noErr; | |
732 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; | |
733 CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; | |
734 SInt64 frame_offset = 0; | |
735 | |
736 /* I'm confused. The Apple documentation says this: | |
737 "Seek position is specified in the sample rate and frame count of the file’s audio data format | |
738 — not your application’s audio data format." | |
739 My interpretation is that I want to get the "actual format of the file and compute the frame offset. | |
740 But when I try that, seeking goes to the wrong place. | |
741 When I use outputFormat, things seem to work correctly. | |
742 I must be misinterpreting the documentation or doing something wrong. | |
743 */ | |
744 #if 0 /* not working */ | |
745 AudioStreamBasicDescription actual_format; | |
746 UInt32 format_size; | |
747 format_size = sizeof(AudioStreamBasicDescription); | |
748 error_result = AudioFileGetProperty( | |
749 *core_audio_file_container->audioFileID, | |
750 kAudioFilePropertyDataFormat, | |
751 &format_size, | |
752 &actual_format | |
753 ); | |
754 if(error_result != noErr) | |
755 { | |
756 sample->flags |= SOUND_SAMPLEFLAG_ERROR; | |
757 BAIL_MACRO("Core Audio: Could not GetProperty for kAudioFilePropertyDataFormat.", 0); | |
758 } /* if */ | |
759 | |
760 // packetIndex = (pos * sampleRate) / framesPerPacket | |
761 frame_offset = (SInt64)((ms/1000.0 * actual_format.mSampleRate) / actual_format.mFramesPerPacket); | |
762 // computed against actual format and not the client format | |
763 | |
764 // packetIndex = (pos * sampleRate) / framesPerPacket | |
765 // frame_offset = (SInt64)((ms/1000.0 * actual_format.mSampleRate) / actual_format.mFramesPerPacket); | |
766 #else /* seems to work, but I'm confused */ | |
767 // packetIndex = (pos * sampleRate) / framesPerPacket | |
768 frame_offset = (SInt64)((ms/1000.0 * core_audio_file_container->outputFormat->mSampleRate) / core_audio_file_container->outputFormat->mFramesPerPacket); | |
769 #endif | |
770 | |
771 // computed against actual format and not the client format | |
772 error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, frame_offset); | |
773 if(error_result != noErr) | |
774 { | |
775 sample->flags |= SOUND_SAMPLEFLAG_ERROR; | |
776 } | |
777 return(1); | |
778 } /* CoreAudio_seek */ | |
779 | |
780 #endif /* __APPLE__ */ | |
781 | |
782 | |
783 #endif /* ALMIXER_COMPILE_WITHOUT_SDL */ | |
784 |