1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 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;*/
38typedef struct S_FileData
39{
40    AudioFileManager *obj;
41    struct S_FileData *next;
42} FileData;
43
44
45typedef 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
73static SDLOSXCAGuard* FileReaderThread_GetGuard(FileReaderThread *frt)
74{
75    return frt->mGuard;
76}
77
78/* returns 1 if succeeded */
79static 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
110static 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
120static 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
155static 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
193static 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
234static 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
245static void    FileReaderThread_ReadNextChunk (FileReaderThread *frt)
246{
247    OSStatus result;
248    ByteCount 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
329void delete_FileReaderThread(FileReaderThread *frt)
330{
331    if (frt != NULL)
332    {
333        delete_SDLOSXCAGuard(frt->mGuard);
334        SDL_free(frt);
335    }
336}
337
338FileReaderThread *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
368static FileReaderThread *sReaderThread;
369
370
371static 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        ByteCount 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
409static void    AudioFileManager_Disconnect (AudioFileManager *afm)
410{
411    if (afm->mIsEngaged)
412    {
413        sReaderThread->RemoveReader (sReaderThread, afm);
414        afm->mIsEngaged = 0;
415    }
416}
417
418static OSStatus AudioFileManager_Read(AudioFileManager *afm, char *buffer, ByteCount *len)
419{
420    return FSReadFork (afm->mForkRefNum,
421                       fsFromStart,
422                       afm->mReadFilePosition + afm->mAudioDataOffset,
423                       *len,
424                       buffer,
425                       len);
426}
427
428static 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
458static 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
475static 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
486static 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
496static const char *AudioFileManager_GetFileBuffer(AudioFileManager *afm)
497{
498    return afm->mFileBuffer;
499}
500
501const AudioFilePlayer *AudioFileManager_GetParent(AudioFileManager *afm)
502{
503    return afm->mParent;
504}
505
506static int AudioFileManager_GetByteCounter(AudioFileManager *afm)
507{
508    return afm->mByteCounter;
509}
510
511static OSStatus    AudioFileManager_FileInputProc (void                  *inRefCon,
512                                         AudioUnitRenderActionFlags      *ioActionFlags,
513                                         const AudioTimeStamp            *inTimeStamp,
514                                         UInt32                          inBusNumber,
515                                         UInt32                          inNumberFrames,
516                                         AudioBufferList                 *ioData)
517{
518    AudioFileManager* afm = (AudioFileManager*)inRefCon;
519    return afm->Render(afm, ioData);
520}
521
522static OSStatus    AudioFileManager_Render (AudioFileManager *afm, AudioBufferList *ioData)
523{
524    OSStatus result = noErr;
525    AudioBuffer *abuf;
526    UInt32 i;
527
528    for (i = 0; i < ioData->mNumberBuffers; i++) {
529        abuf = &ioData->mBuffers[i];
530        if (afm->mBufferOffset >= afm->mBufferSize) {
531            result = afm->GetFileData(afm, &afm->mTmpBuffer, &afm->mBufferSize);
532            if (result) {
533                SDL_SetError ("AudioConverterFillBuffer:%ld\n", result);
534                afm->mParent->DoNotification(afm->mParent, result);
535                return result;
536            }
537
538            afm->mBufferOffset = 0;
539        }
540
541        if (abuf->mDataByteSize > afm->mBufferSize - afm->mBufferOffset)
542            abuf->mDataByteSize = afm->mBufferSize - afm->mBufferOffset;
543        abuf->mData = (char *)afm->mTmpBuffer + afm->mBufferOffset;
544        afm->mBufferOffset += abuf->mDataByteSize;
545
546        afm->mByteCounter += abuf->mDataByteSize;
547        afm->AfterRender(afm);
548    }
549    return result;
550}
551
552
553void delete_AudioFileManager (AudioFileManager *afm)
554{
555    if (afm != NULL) {
556        if (afm->mFileBuffer) {
557            free(afm->mFileBuffer);
558        }
559
560        SDL_free(afm);
561    }
562}
563
564
565AudioFileManager *new_AudioFileManager(AudioFilePlayer *inParent,
566                                       SInt16          inForkRefNum,
567                                       SInt64          inFileLength,
568                                       UInt32          inChunkSize)
569{
570    AudioFileManager *afm;
571
572    if (sReaderThread == NULL)
573    {
574        sReaderThread = new_FileReaderThread();
575        if (sReaderThread == NULL)
576            return NULL;
577    }
578
579    afm = (AudioFileManager *) SDL_malloc(sizeof (AudioFileManager));
580    if (afm == NULL)
581        return NULL;
582    SDL_memset(afm, '\0', sizeof (*afm));
583
584    #define SET_AUDIOFILEMANAGER_METHOD(m) afm->m = AudioFileManager_##m
585    SET_AUDIOFILEMANAGER_METHOD(Disconnect);
586    SET_AUDIOFILEMANAGER_METHOD(DoConnect);
587    SET_AUDIOFILEMANAGER_METHOD(Read);
588    SET_AUDIOFILEMANAGER_METHOD(GetFileBuffer);
589    SET_AUDIOFILEMANAGER_METHOD(GetParent);
590    SET_AUDIOFILEMANAGER_METHOD(SetPosition);
591    SET_AUDIOFILEMANAGER_METHOD(GetByteCounter);
592    SET_AUDIOFILEMANAGER_METHOD(SetEndOfFile);
593    SET_AUDIOFILEMANAGER_METHOD(Render);
594    SET_AUDIOFILEMANAGER_METHOD(GetFileData);
595    SET_AUDIOFILEMANAGER_METHOD(AfterRender);
596    SET_AUDIOFILEMANAGER_METHOD(FileInputProc);
597    #undef SET_AUDIOFILEMANAGER_METHOD
598
599    afm->mParent = inParent;
600    afm->mForkRefNum = inForkRefNum;
601    afm->mBufferSize = inChunkSize;
602    afm->mBufferOffset = inChunkSize;
603    afm->mChunkSize = inChunkSize;
604    afm->mFileLength = inFileLength;
605    afm->mFileBuffer = (char*) SDL_malloc(afm->mChunkSize * 2);
606    FSGetForkPosition(afm->mForkRefNum, &afm->mAudioDataOffset);
607    assert (afm->mFileBuffer != NULL);
608    return afm;
609}
610
611