38
|
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
|