1 /*
2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19 Sam Lantinga
20 slouken@libsdl.org
21
22 This file based on Apple sample code. We haven't changed the file name,
23 so if you want to see the original search for it on apple.com/developer
24 */
25 #include "SDL_config.h"
26
27 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28 AudioFileManager.cpp
29 */
30 #include "AudioFilePlayer.h"
31 #include <mach/mach.h> /* used for setting policy of thread */
32 #include "SDLOSXCAGuard.h"
33 #include <pthread.h>
34
35 /*#include <list>*/
36
37 /*typedef void *FileData;*/
38 typedef struct S_FileData
39 {
40 AudioFileManager *obj;
41 struct S_FileData *next;
42 } FileData;
43
44
45 typedef struct S_FileReaderThread {
46 /*public:*/
47 SDLOSXCAGuard* (*GetGuard)(struct S_FileReaderThread *frt);
48 void (*AddReader)(struct S_FileReaderThread *frt);
49 void (*RemoveReader)(struct S_FileReaderThread *frt, AudioFileManager* inItem);
50 int (*TryNextRead)(struct S_FileReaderThread *frt, AudioFileManager* inItem);
51
52 int mThreadShouldDie;
53
54 /*private:*/
55 /*typedef std::list<AudioFileManager*> FileData;*/
56
57 SDLOSXCAGuard *mGuard;
58 UInt32 mThreadPriority;
59
60 int mNumReaders;
61 FileData *mFileData;
62
63
64 void (*ReadNextChunk)(struct S_FileReaderThread *frt);
65 int (*StartFixedPriorityThread)(struct S_FileReaderThread *frt);
66 /*static*/
67 UInt32 (*GetThreadBasePriority)(pthread_t inThread);
68 /*static*/
69 void* (*DiskReaderEntry)(void *inRefCon);
70 } FileReaderThread;
71
72
FileReaderThread_GetGuard(FileReaderThread * frt)73 static SDLOSXCAGuard* FileReaderThread_GetGuard(FileReaderThread *frt)
74 {
75 return frt->mGuard;
76 }
77
78 /* returns 1 if succeeded */
FileReaderThread_TryNextRead(FileReaderThread * frt,AudioFileManager * inItem)79 static int FileReaderThread_TryNextRead (FileReaderThread *frt, AudioFileManager* inItem)
80 {
81 int didLock = 0;
82 int succeeded = 0;
83 if (frt->mGuard->Try(frt->mGuard, &didLock))
84 {
85 /*frt->mFileData.push_back (inItem);*/
86 /* !!! FIXME: this could be faster with a "tail" member. --ryan. */
87 FileData *i = frt->mFileData;
88 FileData *prev = NULL;
89
90 FileData *newfd = (FileData *) SDL_malloc(sizeof (FileData));
91 newfd->obj = inItem;
92 newfd->next = NULL;
93
94 while (i != NULL) { prev = i; i = i->next; }
95 if (prev == NULL)
96 frt->mFileData = newfd;
97 else
98 prev->next = newfd;
99
100 frt->mGuard->Notify(frt->mGuard);
101 succeeded = 1;
102
103 if (didLock)
104 frt->mGuard->Unlock(frt->mGuard);
105 }
106
107 return succeeded;
108 }
109
FileReaderThread_AddReader(FileReaderThread * frt)110 static void FileReaderThread_AddReader(FileReaderThread *frt)
111 {
112 if (frt->mNumReaders == 0)
113 {
114 frt->mThreadShouldDie = 0;
115 frt->StartFixedPriorityThread (frt);
116 }
117 frt->mNumReaders++;
118 }
119
FileReaderThread_RemoveReader(FileReaderThread * frt,AudioFileManager * inItem)120 static void FileReaderThread_RemoveReader (FileReaderThread *frt, AudioFileManager* inItem)
121 {
122 if (frt->mNumReaders > 0)
123 {
124 int bNeedsRelease = frt->mGuard->Lock(frt->mGuard);
125
126 /*frt->mFileData.remove (inItem);*/
127 FileData *i = frt->mFileData;
128 FileData *prev = NULL;
129 while (i != NULL)
130 {
131 FileData *next = i->next;
132 if (i->obj != inItem)
133 prev = i;
134 else
135 {
136 if (prev == NULL)
137 frt->mFileData = next;
138 else
139 prev->next = next;
140 SDL_free(i);
141 }
142 i = next;
143 }
144
145 if (--frt->mNumReaders == 0) {
146 frt->mThreadShouldDie = 1;
147 frt->mGuard->Notify(frt->mGuard); /* wake up thread so it will quit */
148 frt->mGuard->Wait(frt->mGuard); /* wait for thread to die */
149 }
150
151 if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard);
152 }
153 }
154
FileReaderThread_StartFixedPriorityThread(FileReaderThread * frt)155 static int FileReaderThread_StartFixedPriorityThread (FileReaderThread *frt)
156 {
157 pthread_attr_t theThreadAttrs;
158 pthread_t pThread;
159
160 OSStatus result = pthread_attr_init(&theThreadAttrs);
161 if (result) return 0; /*THROW_RESULT("pthread_attr_init - Thread attributes could not be created.")*/
162
163 result = pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED);
164 if (result) return 0; /*THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.")*/
165
166 result = pthread_create (&pThread, &theThreadAttrs, frt->DiskReaderEntry, frt);
167 if (result) return 0; /*THROW_RESULT("pthread_create - Create and start the thread.")*/
168
169 pthread_attr_destroy(&theThreadAttrs);
170
171 /* we've now created the thread and started it
172 we'll now set the priority of the thread to the nominated priority
173 and we'll also make the thread fixed */
174 thread_extended_policy_data_t theFixedPolicy;
175 thread_precedence_policy_data_t thePrecedencePolicy;
176 SInt32 relativePriority;
177
178 /* make thread fixed */
179 theFixedPolicy.timeshare = 0; /* set to 1 for a non-fixed thread */
180 result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT);
181 if (result) return 0; /*THROW_RESULT("thread_policy - Couldn't set thread as fixed priority.")*/
182 /* set priority */
183 /* precedency policy's "importance" value is relative to spawning thread's priority */
184 relativePriority = frt->mThreadPriority - frt->GetThreadBasePriority(pthread_self());
185
186 thePrecedencePolicy.importance = relativePriority;
187 result = thread_policy_set (pthread_mach_thread_np(pThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT);
188 if (result) return 0; /*THROW_RESULT("thread_policy - Couldn't set thread priority.")*/
189
190 return 1;
191 }
192
FileReaderThread_GetThreadBasePriority(pthread_t inThread)193 static UInt32 FileReaderThread_GetThreadBasePriority (pthread_t inThread)
194 {
195 thread_basic_info_data_t threadInfo;
196 policy_info_data_t thePolicyInfo;
197 unsigned int count;
198
199 /* get basic info */
200 count = THREAD_BASIC_INFO_COUNT;
201 thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (integer_t*)&threadInfo, &count);
202
203 switch (threadInfo.policy) {
204 case POLICY_TIMESHARE:
205 count = POLICY_TIMESHARE_INFO_COUNT;
206 thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (integer_t*)&(thePolicyInfo.ts), &count);
207 return thePolicyInfo.ts.base_priority;
208 break;
209
210 case POLICY_FIFO:
211 count = POLICY_FIFO_INFO_COUNT;
212 thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (integer_t*)&(thePolicyInfo.fifo), &count);
213 if (thePolicyInfo.fifo.depressed) {
214 return thePolicyInfo.fifo.depress_priority;
215 } else {
216 return thePolicyInfo.fifo.base_priority;
217 }
218 break;
219
220 case POLICY_RR:
221 count = POLICY_RR_INFO_COUNT;
222 thread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (integer_t*)&(thePolicyInfo.rr), &count);
223 if (thePolicyInfo.rr.depressed) {
224 return thePolicyInfo.rr.depress_priority;
225 } else {
226 return thePolicyInfo.rr.base_priority;
227 }
228 break;
229 }
230
231 return 0;
232 }
233
FileReaderThread_DiskReaderEntry(void * inRefCon)234 static void *FileReaderThread_DiskReaderEntry (void *inRefCon)
235 {
236 FileReaderThread *frt = (FileReaderThread *)inRefCon;
237 frt->ReadNextChunk(frt);
238 #if DEBUG
239 printf ("finished with reading file\n");
240 #endif
241
242 return 0;
243 }
244
FileReaderThread_ReadNextChunk(FileReaderThread * frt)245 static void FileReaderThread_ReadNextChunk (FileReaderThread *frt)
246 {
247 OSStatus result;
248 UInt32 dataChunkSize;
249 AudioFileManager* theItem = 0;
250
251 for (;;)
252 {
253 { /* this is a scoped based lock */
254 int bNeedsRelease = frt->mGuard->Lock(frt->mGuard);
255
256 if (frt->mThreadShouldDie) {
257 frt->mGuard->Notify(frt->mGuard);
258 if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard);
259 return;
260 }
261
262 /*if (frt->mFileData.empty())*/
263 if (frt->mFileData == NULL)
264 {
265 frt->mGuard->Wait(frt->mGuard);
266 }
267
268 /* kill thread */
269 if (frt->mThreadShouldDie) {
270
271 frt->mGuard->Notify(frt->mGuard);
272 if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard);
273 return;
274 }
275
276 /*theItem = frt->mFileData.front();*/
277 /*frt->mFileData.pop_front();*/
278 theItem = NULL;
279 if (frt->mFileData != NULL)
280 {
281 FileData *next = frt->mFileData->next;
282 theItem = frt->mFileData->obj;
283 SDL_free(frt->mFileData);
284 frt->mFileData = next;
285 }
286
287 if (bNeedsRelease) frt->mGuard->Unlock(frt->mGuard);
288 }
289
290 if ((theItem->mFileLength - theItem->mReadFilePosition) < theItem->mChunkSize)
291 dataChunkSize = theItem->mFileLength - theItem->mReadFilePosition;
292 else
293 dataChunkSize = theItem->mChunkSize;
294
295 /* this is the exit condition for the thread */
296 if (dataChunkSize <= 0) {
297 theItem->mFinishedReadingData = 1;
298 continue;
299 }
300 /* construct pointer */
301 char* writePtr = (char *) (theItem->GetFileBuffer(theItem) +
302 (theItem->mWriteToFirstBuffer ? 0 : theItem->mChunkSize));
303
304 /* read data */
305 result = theItem->Read(theItem, writePtr, &dataChunkSize);
306 if (result != noErr && result != eofErr) {
307 AudioFilePlayer *afp = (AudioFilePlayer *) theItem->GetParent(theItem);
308 afp->DoNotification(afp, result);
309 continue;
310 }
311
312 if (dataChunkSize != theItem->mChunkSize)
313 {
314 writePtr += dataChunkSize;
315
316 /* can't exit yet.. we still have to pass the partial buffer back */
317 SDL_memset(writePtr, 0, (theItem->mChunkSize - dataChunkSize));
318 }
319
320 theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer; /* switch buffers */
321
322 if (result == eofErr)
323 theItem->mReadFilePosition = theItem->mFileLength;
324 else
325 theItem->mReadFilePosition += dataChunkSize; /* increment count */
326 }
327 }
328
delete_FileReaderThread(FileReaderThread * frt)329 void delete_FileReaderThread(FileReaderThread *frt)
330 {
331 if (frt != NULL)
332 {
333 delete_SDLOSXCAGuard(frt->mGuard);
334 SDL_free(frt);
335 }
336 }
337
new_FileReaderThread()338 FileReaderThread *new_FileReaderThread ()
339 {
340 FileReaderThread *frt = (FileReaderThread *) SDL_malloc(sizeof (FileReaderThread));
341 if (frt == NULL)
342 return NULL;
343 SDL_memset(frt, '\0', sizeof (*frt));
344
345 frt->mGuard = new_SDLOSXCAGuard();
346 if (frt->mGuard == NULL)
347 {
348 SDL_free(frt);
349 return NULL;
350 }
351
352 #define SET_FILEREADERTHREAD_METHOD(m) frt->m = FileReaderThread_##m
353 SET_FILEREADERTHREAD_METHOD(GetGuard);
354 SET_FILEREADERTHREAD_METHOD(AddReader);
355 SET_FILEREADERTHREAD_METHOD(RemoveReader);
356 SET_FILEREADERTHREAD_METHOD(TryNextRead);
357 SET_FILEREADERTHREAD_METHOD(ReadNextChunk);
358 SET_FILEREADERTHREAD_METHOD(StartFixedPriorityThread);
359 SET_FILEREADERTHREAD_METHOD(GetThreadBasePriority);
360 SET_FILEREADERTHREAD_METHOD(DiskReaderEntry);
361 #undef SET_FILEREADERTHREAD_METHOD
362
363 frt->mThreadPriority = 62;
364 return frt;
365 }
366
367
368 static FileReaderThread *sReaderThread;
369
370
AudioFileManager_DoConnect(AudioFileManager * afm)371 static int AudioFileManager_DoConnect (AudioFileManager *afm)
372 {
373 if (!afm->mIsEngaged)
374 {
375 OSStatus result;
376
377 /*afm->mReadFilePosition = 0;*/
378 afm->mFinishedReadingData = 0;
379
380 afm->mNumTimesAskedSinceFinished = 0;
381 afm->mLockUnsuccessful = 0;
382
383 UInt32 dataChunkSize;
384
385 if ((afm->mFileLength - afm->mReadFilePosition) < afm->mChunkSize)
386 dataChunkSize = afm->mFileLength - afm->mReadFilePosition;
387 else
388 dataChunkSize = afm->mChunkSize;
389
390 result = afm->Read(afm, afm->mFileBuffer, &dataChunkSize);
391 if (result) return 0; /*THROW_RESULT("AudioFileManager::DoConnect(): Read")*/
392
393 afm->mReadFilePosition += dataChunkSize;
394
395 afm->mWriteToFirstBuffer = 0;
396 afm->mReadFromFirstBuffer = 1;
397
398 sReaderThread->AddReader(sReaderThread);
399
400 afm->mIsEngaged = 1;
401 }
402 /*
403 else
404 throw static_cast<OSStatus>(-1); */ /* thread has already been started */
405
406 return 1;
407 }
408
AudioFileManager_Disconnect(AudioFileManager * afm)409 static void AudioFileManager_Disconnect (AudioFileManager *afm)
410 {
411 if (afm->mIsEngaged)
412 {
413 sReaderThread->RemoveReader (sReaderThread, afm);
414 afm->mIsEngaged = 0;
415 }
416 }
417
AudioFileManager_Read(AudioFileManager * afm,char * buffer,UInt32 * len)418 static OSStatus AudioFileManager_Read(AudioFileManager *afm, char *buffer, UInt32 *len)
419 {
420 return FSReadFork (afm->mForkRefNum,
421 fsFromStart,
422 afm->mReadFilePosition + afm->mAudioDataOffset,
423 *len,
424 buffer,
425 len);
426 }
427
AudioFileManager_GetFileData(AudioFileManager * afm,void ** inOutData,UInt32 * inOutDataSize)428 static OSStatus AudioFileManager_GetFileData (AudioFileManager *afm, void** inOutData, UInt32 *inOutDataSize)
429 {
430 if (afm->mFinishedReadingData)
431 {
432 ++afm->mNumTimesAskedSinceFinished;
433 *inOutDataSize = 0;
434 *inOutData = 0;
435 return noErr;
436 }
437
438 if (afm->mReadFromFirstBuffer == afm->mWriteToFirstBuffer) {
439 #if DEBUG
440 printf ("* * * * * * * Can't keep up with reading file\n");
441 #endif
442
443 afm->mParent->DoNotification (afm->mParent, kAudioFilePlayErr_FilePlayUnderrun);
444 *inOutDataSize = 0;
445 *inOutData = 0;
446 } else {
447 *inOutDataSize = afm->mChunkSize;
448 *inOutData = afm->mReadFromFirstBuffer ? afm->mFileBuffer : (afm->mFileBuffer + afm->mChunkSize);
449 }
450
451 afm->mLockUnsuccessful = !sReaderThread->TryNextRead (sReaderThread, afm);
452
453 afm->mReadFromFirstBuffer = !afm->mReadFromFirstBuffer;
454
455 return noErr;
456 }
457
AudioFileManager_AfterRender(AudioFileManager * afm)458 static void AudioFileManager_AfterRender (AudioFileManager *afm)
459 {
460 if (afm->mNumTimesAskedSinceFinished > 0)
461 {
462 int didLock = 0;
463 SDLOSXCAGuard *guard = sReaderThread->GetGuard(sReaderThread);
464 if (guard->Try(guard, &didLock)) {
465 afm->mParent->DoNotification (afm->mParent, kAudioFilePlay_FileIsFinished);
466 if (didLock)
467 guard->Unlock(guard);
468 }
469 }
470
471 if (afm->mLockUnsuccessful)
472 afm->mLockUnsuccessful = !sReaderThread->TryNextRead (sReaderThread, afm);
473 }
474
AudioFileManager_SetPosition(AudioFileManager * afm,SInt64 pos)475 static void AudioFileManager_SetPosition (AudioFileManager *afm, SInt64 pos)
476 {
477 if (pos < 0 || pos >= afm->mFileLength) {
478 SDL_SetError ("AudioFileManager::SetPosition - position invalid: %d filelen=%d\n",
479 (unsigned int)pos, (unsigned int)afm->mFileLength);
480 pos = 0;
481 }
482
483 afm->mReadFilePosition = pos;
484 }
485
AudioFileManager_SetEndOfFile(AudioFileManager * afm,SInt64 pos)486 static void AudioFileManager_SetEndOfFile (AudioFileManager *afm, SInt64 pos)
487 {
488 if (pos <= 0 || pos > afm->mFileLength) {
489 SDL_SetError ("AudioFileManager::SetEndOfFile - position beyond actual eof\n");
490 pos = afm->mFileLength;
491 }
492
493 afm->mFileLength = pos;
494 }
495
AudioFileManager_GetFileBuffer(AudioFileManager * afm)496 static const char *AudioFileManager_GetFileBuffer(AudioFileManager *afm)
497 {
498 return afm->mFileBuffer;
499 }
500
AudioFileManager_GetParent(AudioFileManager * afm)501 const AudioFilePlayer *AudioFileManager_GetParent(AudioFileManager *afm)
502 {
503 return afm->mParent;
504 }
505
AudioFileManager_GetByteCounter(AudioFileManager * afm)506 static int AudioFileManager_GetByteCounter(AudioFileManager *afm)
507 {
508 return afm->mByteCounter;
509 }
510
511
AudioFileManager_FileInputProc(void * inRefCon,AudioUnitRenderActionFlags inActionFlags,const AudioTimeStamp * inTimeStamp,UInt32 inBusNumber,AudioBuffer * ioData)512 static OSStatus AudioFileManager_FileInputProc (void *inRefCon,
513 AudioUnitRenderActionFlags inActionFlags,
514 const AudioTimeStamp *inTimeStamp,
515 UInt32 inBusNumber,
516 AudioBuffer *ioData)
517 {
518 AudioFileManager* afm = (AudioFileManager*)inRefCon;
519 return afm->Render(afm, ioData);
520 }
521
AudioFileManager_Render(AudioFileManager * afm,AudioBuffer * ioData)522 static OSStatus AudioFileManager_Render (AudioFileManager *afm, AudioBuffer *ioData)
523 {
524 OSStatus result = noErr;
525
526 if (afm->mBufferOffset >= afm->mBufferSize) {
527 result = afm->GetFileData(afm, &afm->mTmpBuffer, &afm->mBufferSize);
528 if (result) {
529 SDL_SetError ("AudioConverterFillBuffer:%ld\n", result);
530 afm->mParent->DoNotification(afm->mParent, result);
531 return result;
532 }
533
534 afm->mBufferOffset = 0;
535 }
536
537 if (ioData->mDataByteSize > afm->mBufferSize - afm->mBufferOffset)
538 ioData->mDataByteSize = afm->mBufferSize - afm->mBufferOffset;
539 ioData->mData = (char *)afm->mTmpBuffer + afm->mBufferOffset;
540 afm->mBufferOffset += ioData->mDataByteSize;
541
542 afm->mByteCounter += ioData->mDataByteSize;
543 afm->AfterRender(afm);
544 return result;
545 }
546
547
delete_AudioFileManager(AudioFileManager * afm)548 void delete_AudioFileManager (AudioFileManager *afm)
549 {
550 if (afm != NULL) {
551 if (afm->mFileBuffer) {
552 free(afm->mFileBuffer);
553 }
554
555 SDL_free(afm);
556 }
557 }
558
559
new_AudioFileManager(AudioFilePlayer * inParent,SInt16 inForkRefNum,SInt64 inFileLength,UInt32 inChunkSize)560 AudioFileManager *new_AudioFileManager(AudioFilePlayer *inParent,
561 SInt16 inForkRefNum,
562 SInt64 inFileLength,
563 UInt32 inChunkSize)
564 {
565 AudioFileManager *afm;
566
567 if (sReaderThread == NULL)
568 {
569 sReaderThread = new_FileReaderThread();
570 if (sReaderThread == NULL)
571 return NULL;
572 }
573
574 afm = (AudioFileManager *) SDL_malloc(sizeof (AudioFileManager));
575 if (afm == NULL)
576 return NULL;
577 SDL_memset(afm, '\0', sizeof (*afm));
578
579 #define SET_AUDIOFILEMANAGER_METHOD(m) afm->m = AudioFileManager_##m
580 SET_AUDIOFILEMANAGER_METHOD(Disconnect);
581 SET_AUDIOFILEMANAGER_METHOD(DoConnect);
582 SET_AUDIOFILEMANAGER_METHOD(Read);
583 SET_AUDIOFILEMANAGER_METHOD(GetFileBuffer);
584 SET_AUDIOFILEMANAGER_METHOD(GetParent);
585 SET_AUDIOFILEMANAGER_METHOD(SetPosition);
586 SET_AUDIOFILEMANAGER_METHOD(GetByteCounter);
587 SET_AUDIOFILEMANAGER_METHOD(SetEndOfFile);
588 SET_AUDIOFILEMANAGER_METHOD(Render);
589 SET_AUDIOFILEMANAGER_METHOD(GetFileData);
590 SET_AUDIOFILEMANAGER_METHOD(AfterRender);
591 SET_AUDIOFILEMANAGER_METHOD(FileInputProc);
592 #undef SET_AUDIOFILEMANAGER_METHOD
593
594 afm->mParent = inParent;
595 afm->mForkRefNum = inForkRefNum;
596 afm->mBufferSize = inChunkSize;
597 afm->mBufferOffset = inChunkSize;
598 afm->mChunkSize = inChunkSize;
599 afm->mFileLength = inFileLength;
600 afm->mFileBuffer = (char*) SDL_malloc(afm->mChunkSize * 2);
601 FSGetForkPosition(afm->mForkRefNum, &afm->mAudioDataOffset);
602 assert (afm->mFileBuffer != NULL);
603 return afm;
604 }
605
606