view src/cdrom/macosx/AudioFileReaderThread.c @ 1977:754847f19490

David Hedbor is no longer maintaining the Qtopia code.
author Sam Lantinga <slouken@libsdl.org>
date Thu, 10 Aug 2006 14:35:42 +0000
parents c121d94672cb
children e1da92da346c
line wrap: on
line source

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002  Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    slouken@libsdl.org

    This file based on Apple sample code. We haven't changed the file name, 
    so if you want to see the original search for it on apple.com/developer
*/
#include "SDL_config.h"

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   AudioFileManager.cpp
*/
#include "AudioFilePlayer.h"
#include <mach/mach.h>          /* used for setting policy of thread */
#include "SDLOSXCAGuard.h"
#include <pthread.h>

/*#include <list>*/

/*typedef void *FileData;*/
typedef struct S_FileData
{
    AudioFileManager *obj;
    struct S_FileData *next;
} FileData;


typedef struct S_FileReaderThread
{
/*public:*/
    SDLOSXCAGuard *(*GetGuard) (struct S_FileReaderThread * frt);
    void (*AddReader) (struct S_FileReaderThread * frt);
    void (*RemoveReader) (struct S_FileReaderThread * frt,
                          AudioFileManager * inItem);
    int (*TryNextRead) (struct S_FileReaderThread * frt,
                        AudioFileManager * inItem);

    int mThreadShouldDie;

/*private:*/
    /*typedef std::list<AudioFileManager*> FileData; */

    SDLOSXCAGuard *mGuard;
    UInt32 mThreadPriority;

    int mNumReaders;
    FileData *mFileData;


    void (*ReadNextChunk) (struct S_FileReaderThread * frt);
    int (*StartFixedPriorityThread) (struct S_FileReaderThread * frt);
    /*static */
      UInt32(*GetThreadBasePriority) (pthread_t inThread);
    /*static */
    void *(*DiskReaderEntry) (void *inRefCon);
} FileReaderThread;


static SDLOSXCAGuard *
FileReaderThread_GetGuard(FileReaderThread * frt)
{
    return frt->mGuard;
}

/* returns 1 if succeeded */
static int
FileReaderThread_TryNextRead(FileReaderThread * frt,
                             AudioFileManager * inItem)
{
    int didLock = 0;
    int succeeded = 0;
    if (frt->mGuard->Try(frt->mGuard, &didLock)) {
        /*frt->mFileData.push_back (inItem); */
        /* !!! FIXME: this could be faster with a "tail" member. --ryan. */
        FileData *i = frt->mFileData;
        FileData *prev = NULL;

        FileData *newfd = (FileData *) SDL_malloc(sizeof(FileData));
        newfd->obj = inItem;
        newfd->next = NULL;

        while (i != NULL) {
            prev = i;
            i = i->next;
        }
        if (prev == NULL)
            frt->mFileData = newfd;
        else
            prev->next = newfd;

        frt->mGuard->Notify(frt->mGuard);
        succeeded = 1;

        if (didLock)
            frt->mGuard->Unlock(frt->mGuard);
    }

    return succeeded;
}

static void
FileReaderThread_AddReader(FileReaderThread * frt)
{
    if (frt->mNumReaders == 0) {
        frt->mThreadShouldDie = 0;
        frt->StartFixedPriorityThread(frt);
    }
    frt->mNumReaders++;
}

static void
FileReaderThread_RemoveReader(FileReaderThread * frt,
                              AudioFileManager * inItem)
{
    if (frt->mNumReaders > 0) {
        int bNeedsRelease = frt->mGuard->Lock(frt->mGuard);

        /*frt->mFileData.remove (inItem); */
        FileData *i = frt->mFileData;
        FileData *prev = NULL;
        while (i != NULL) {
            FileData *next = i->next;
            if (i->obj != inItem)
                prev = i;
            else {
                if (prev == NULL)
                    frt->mFileData = next;
                else
                    prev->next = next;
                SDL_free(i);
            }
            i = next;
        }

        if (--frt->mNumReaders == 0) {
            frt->mThreadShouldDie = 1;
            frt->mGuard->Notify(frt->mGuard);   /* wake up thread so it will quit */
            frt->mGuard->Wait(frt->mGuard);     /* wait for thread to die */
        }

        if (bNeedsRelease)
            frt->mGuard->Unlock(frt->mGuard);
    }
}

static int
FileReaderThread_StartFixedPriorityThread(FileReaderThread * frt)
{
    pthread_attr_t theThreadAttrs;
    pthread_t pThread;

    OSStatus result = pthread_attr_init(&theThreadAttrs);
    if (result)
        return 0;               /*THROW_RESULT("pthread_attr_init - Thread attributes could not be created.") */

    result =
        pthread_attr_setdetachstate(&theThreadAttrs, PTHREAD_CREATE_DETACHED);
    if (result)
        return 0;               /*THROW_RESULT("pthread_attr_setdetachstate - Thread attributes could not be detached.") */

    result =
        pthread_create(&pThread, &theThreadAttrs, frt->DiskReaderEntry, frt);
    if (result)
        return 0;               /*THROW_RESULT("pthread_create - Create and start the thread.") */

    pthread_attr_destroy(&theThreadAttrs);

    /* we've now created the thread and started it
       we'll now set the priority of the thread to the nominated priority
       and we'll also make the thread fixed */
    thread_extended_policy_data_t theFixedPolicy;
    thread_precedence_policy_data_t thePrecedencePolicy;
    SInt32 relativePriority;

    /* make thread fixed */
    theFixedPolicy.timeshare = 0;       /* set to 1 for a non-fixed thread */
    result =
        thread_policy_set(pthread_mach_thread_np(pThread),
                          THREAD_EXTENDED_POLICY,
                          (thread_policy_t) & theFixedPolicy,
                          THREAD_EXTENDED_POLICY_COUNT);
    if (result)
        return 0;               /*THROW_RESULT("thread_policy - Couldn't set thread as fixed priority.") */
    /* set priority */
    /* precedency policy's "importance" value is relative to spawning thread's priority */
    relativePriority =
        frt->mThreadPriority - frt->GetThreadBasePriority(pthread_self());

    thePrecedencePolicy.importance = relativePriority;
    result =
        thread_policy_set(pthread_mach_thread_np(pThread),
                          THREAD_PRECEDENCE_POLICY,
                          (thread_policy_t) & thePrecedencePolicy,
                          THREAD_PRECEDENCE_POLICY_COUNT);
    if (result)
        return 0;               /*THROW_RESULT("thread_policy - Couldn't set thread priority.") */

    return 1;
}

static UInt32
FileReaderThread_GetThreadBasePriority(pthread_t inThread)
{
    thread_basic_info_data_t threadInfo;
    policy_info_data_t thePolicyInfo;
    unsigned int count;

    /* get basic info */
    count = THREAD_BASIC_INFO_COUNT;
    thread_info(pthread_mach_thread_np(inThread), THREAD_BASIC_INFO,
                (integer_t *) & threadInfo, &count);

    switch (threadInfo.policy) {
    case POLICY_TIMESHARE:
        count = POLICY_TIMESHARE_INFO_COUNT;
        thread_info(pthread_mach_thread_np(inThread),
                    THREAD_SCHED_TIMESHARE_INFO,
                    (integer_t *) & (thePolicyInfo.ts), &count);
        return thePolicyInfo.ts.base_priority;
        break;

    case POLICY_FIFO:
        count = POLICY_FIFO_INFO_COUNT;
        thread_info(pthread_mach_thread_np(inThread),
                    THREAD_SCHED_FIFO_INFO,
                    (integer_t *) & (thePolicyInfo.fifo), &count);
        if (thePolicyInfo.fifo.depressed) {
            return thePolicyInfo.fifo.depress_priority;
        } else {
            return thePolicyInfo.fifo.base_priority;
        }
        break;

    case POLICY_RR:
        count = POLICY_RR_INFO_COUNT;
        thread_info(pthread_mach_thread_np(inThread),
                    THREAD_SCHED_RR_INFO,
                    (integer_t *) & (thePolicyInfo.rr), &count);
        if (thePolicyInfo.rr.depressed) {
            return thePolicyInfo.rr.depress_priority;
        } else {
            return thePolicyInfo.rr.base_priority;
        }
        break;
    }

    return 0;
}

static void *
FileReaderThread_DiskReaderEntry(void *inRefCon)
{
    FileReaderThread *frt = (FileReaderThread *) inRefCon;
    frt->ReadNextChunk(frt);
#if DEBUG
    printf("finished with reading file\n");
#endif

    return 0;
}

static void
FileReaderThread_ReadNextChunk(FileReaderThread * frt)
{
    OSStatus result;
    UInt32 dataChunkSize;
    AudioFileManager *theItem = 0;

    for (;;) {
        {                       /* this is a scoped based lock */
            int bNeedsRelease = frt->mGuard->Lock(frt->mGuard);

            if (frt->mThreadShouldDie) {
                frt->mGuard->Notify(frt->mGuard);
                if (bNeedsRelease)
                    frt->mGuard->Unlock(frt->mGuard);
                return;
            }

            /*if (frt->mFileData.empty()) */
            if (frt->mFileData == NULL) {
                frt->mGuard->Wait(frt->mGuard);
            }

            /* kill thread */
            if (frt->mThreadShouldDie) {

                frt->mGuard->Notify(frt->mGuard);
                if (bNeedsRelease)
                    frt->mGuard->Unlock(frt->mGuard);
                return;
            }

            /*theItem = frt->mFileData.front(); */
            /*frt->mFileData.pop_front(); */
            theItem = NULL;
            if (frt->mFileData != NULL) {
                FileData *next = frt->mFileData->next;
                theItem = frt->mFileData->obj;
                SDL_free(frt->mFileData);
                frt->mFileData = next;
            }

            if (bNeedsRelease)
                frt->mGuard->Unlock(frt->mGuard);
        }

        if ((theItem->mFileLength - theItem->mReadFilePosition) <
            theItem->mChunkSize)
            dataChunkSize = theItem->mFileLength - theItem->mReadFilePosition;
        else
            dataChunkSize = theItem->mChunkSize;

        /* this is the exit condition for the thread */
        if (dataChunkSize <= 0) {
            theItem->mFinishedReadingData = 1;
            continue;
        }
        /* construct pointer */
        char *writePtr = (char *) (theItem->GetFileBuffer(theItem) +
                                   (theItem->
                                    mWriteToFirstBuffer ? 0 : theItem->
                                    mChunkSize));

        /* read data */
        result = theItem->Read(theItem, writePtr, &dataChunkSize);
        if (result != noErr && result != eofErr) {
            AudioFilePlayer *afp =
                (AudioFilePlayer *) theItem->GetParent(theItem);
            afp->DoNotification(afp, result);
            continue;
        }

        if (dataChunkSize != theItem->mChunkSize) {
            writePtr += dataChunkSize;

            /* can't exit yet.. we still have to pass the partial buffer back */
            SDL_memset(writePtr, 0, (theItem->mChunkSize - dataChunkSize));
        }

        theItem->mWriteToFirstBuffer = !theItem->mWriteToFirstBuffer;   /* switch buffers */

        if (result == eofErr)
            theItem->mReadFilePosition = theItem->mFileLength;
        else
            theItem->mReadFilePosition += dataChunkSize;        /* increment count */
    }
}

void
delete_FileReaderThread(FileReaderThread * frt)
{
    if (frt != NULL) {
        delete_SDLOSXCAGuard(frt->mGuard);
        SDL_free(frt);
    }
}

FileReaderThread *
new_FileReaderThread()
{
    FileReaderThread *frt =
        (FileReaderThread *) SDL_malloc(sizeof(FileReaderThread));
    if (frt == NULL)
        return NULL;
    SDL_memset(frt, '\0', sizeof(*frt));

    frt->mGuard = new_SDLOSXCAGuard();
    if (frt->mGuard == NULL) {
        SDL_free(frt);
        return NULL;
    }
#define SET_FILEREADERTHREAD_METHOD(m) frt->m = FileReaderThread_##m
    SET_FILEREADERTHREAD_METHOD(GetGuard);
    SET_FILEREADERTHREAD_METHOD(AddReader);
    SET_FILEREADERTHREAD_METHOD(RemoveReader);
    SET_FILEREADERTHREAD_METHOD(TryNextRead);
    SET_FILEREADERTHREAD_METHOD(ReadNextChunk);
    SET_FILEREADERTHREAD_METHOD(StartFixedPriorityThread);
    SET_FILEREADERTHREAD_METHOD(GetThreadBasePriority);
    SET_FILEREADERTHREAD_METHOD(DiskReaderEntry);
#undef SET_FILEREADERTHREAD_METHOD

    frt->mThreadPriority = 62;
    return frt;
}


static FileReaderThread *sReaderThread;


static int
AudioFileManager_DoConnect(AudioFileManager * afm)
{
    if (!afm->mIsEngaged) {
        OSStatus result;

        /*afm->mReadFilePosition = 0; */
        afm->mFinishedReadingData = 0;

        afm->mNumTimesAskedSinceFinished = 0;
        afm->mLockUnsuccessful = 0;

        UInt32 dataChunkSize;

        if ((afm->mFileLength - afm->mReadFilePosition) < afm->mChunkSize)
            dataChunkSize = afm->mFileLength - afm->mReadFilePosition;
        else
            dataChunkSize = afm->mChunkSize;

        result = afm->Read(afm, afm->mFileBuffer, &dataChunkSize);
        if (result)
            return 0;           /*THROW_RESULT("AudioFileManager::DoConnect(): Read") */

        afm->mReadFilePosition += dataChunkSize;

        afm->mWriteToFirstBuffer = 0;
        afm->mReadFromFirstBuffer = 1;

        sReaderThread->AddReader(sReaderThread);

        afm->mIsEngaged = 1;
    }
    /*
       else
       throw static_cast<OSStatus>(-1); *//* thread has already been started */

    return 1;
}

static void
AudioFileManager_Disconnect(AudioFileManager * afm)
{
    if (afm->mIsEngaged) {
        sReaderThread->RemoveReader(sReaderThread, afm);
        afm->mIsEngaged = 0;
    }
}

static OSStatus
AudioFileManager_Read(AudioFileManager * afm, char *buffer, UInt32 * len)
{
    return FSReadFork(afm->mForkRefNum,
                      fsFromStart,
                      afm->mReadFilePosition + afm->mAudioDataOffset,
                      *len, buffer, len);
}

static OSStatus
AudioFileManager_GetFileData(AudioFileManager * afm, void **inOutData,
                             UInt32 * inOutDataSize)
{
    if (afm->mFinishedReadingData) {
        ++afm->mNumTimesAskedSinceFinished;
        *inOutDataSize = 0;
        *inOutData = 0;
        return noErr;
    }

    if (afm->mReadFromFirstBuffer == afm->mWriteToFirstBuffer) {
#if DEBUG
        printf("* * * * * * * Can't keep up with reading file\n");
#endif

        afm->mParent->DoNotification(afm->mParent,
                                     kAudioFilePlayErr_FilePlayUnderrun);
        *inOutDataSize = 0;
        *inOutData = 0;
    } else {
        *inOutDataSize = afm->mChunkSize;
        *inOutData =
            afm->mReadFromFirstBuffer ? afm->mFileBuffer : (afm->
                                                            mFileBuffer +
                                                            afm->mChunkSize);
    }

    afm->mLockUnsuccessful = !sReaderThread->TryNextRead(sReaderThread, afm);

    afm->mReadFromFirstBuffer = !afm->mReadFromFirstBuffer;

    return noErr;
}

static void
AudioFileManager_AfterRender(AudioFileManager * afm)
{
    if (afm->mNumTimesAskedSinceFinished > 0) {
        int didLock = 0;
        SDLOSXCAGuard *guard = sReaderThread->GetGuard(sReaderThread);
        if (guard->Try(guard, &didLock)) {
            afm->mParent->DoNotification(afm->mParent,
                                         kAudioFilePlay_FileIsFinished);
            if (didLock)
                guard->Unlock(guard);
        }
    }

    if (afm->mLockUnsuccessful)
        afm->mLockUnsuccessful =
            !sReaderThread->TryNextRead(sReaderThread, afm);
}

static void
AudioFileManager_SetPosition(AudioFileManager * afm, SInt64 pos)
{
    if (pos < 0 || pos >= afm->mFileLength) {
        SDL_SetError
            ("AudioFileManager::SetPosition - position invalid: %d filelen=%d\n",
             (unsigned int) pos, (unsigned int) afm->mFileLength);
        pos = 0;
    }

    afm->mReadFilePosition = pos;
}

static void
AudioFileManager_SetEndOfFile(AudioFileManager * afm, SInt64 pos)
{
    if (pos <= 0 || pos > afm->mFileLength) {
        SDL_SetError
            ("AudioFileManager::SetEndOfFile - position beyond actual eof\n");
        pos = afm->mFileLength;
    }

    afm->mFileLength = pos;
}

static const char *
AudioFileManager_GetFileBuffer(AudioFileManager * afm)
{
    return afm->mFileBuffer;
}

const AudioFilePlayer *
AudioFileManager_GetParent(AudioFileManager * afm)
{
    return afm->mParent;
}

static int
AudioFileManager_GetByteCounter(AudioFileManager * afm)
{
    return afm->mByteCounter;
}


static OSStatus
AudioFileManager_FileInputProc(void *inRefCon,
                               AudioUnitRenderActionFlags inActionFlags,
                               const AudioTimeStamp * inTimeStamp,
                               UInt32 inBusNumber, AudioBuffer * ioData)
{
    AudioFileManager *afm = (AudioFileManager *) inRefCon;
    return afm->Render(afm, ioData);
}

static OSStatus
AudioFileManager_Render(AudioFileManager * afm, AudioBuffer * ioData)
{
    OSStatus result = noErr;

    if (afm->mBufferOffset >= afm->mBufferSize) {
        result = afm->GetFileData(afm, &afm->mTmpBuffer, &afm->mBufferSize);
        if (result) {
            SDL_SetError("AudioConverterFillBuffer:%ld\n", result);
            afm->mParent->DoNotification(afm->mParent, result);
            return result;
        }

        afm->mBufferOffset = 0;
    }

    if (ioData->mDataByteSize > afm->mBufferSize - afm->mBufferOffset)
        ioData->mDataByteSize = afm->mBufferSize - afm->mBufferOffset;
    ioData->mData = (char *) afm->mTmpBuffer + afm->mBufferOffset;
    afm->mBufferOffset += ioData->mDataByteSize;

    afm->mByteCounter += ioData->mDataByteSize;
    afm->AfterRender(afm);
    return result;
}


void
delete_AudioFileManager(AudioFileManager * afm)
{
    if (afm != NULL) {
        if (afm->mFileBuffer) {
            free(afm->mFileBuffer);
        }

        SDL_free(afm);
    }
}


AudioFileManager *
new_AudioFileManager(AudioFilePlayer * inParent,
                     SInt16 inForkRefNum,
                     SInt64 inFileLength, UInt32 inChunkSize)
{
    AudioFileManager *afm;

    if (sReaderThread == NULL) {
        sReaderThread = new_FileReaderThread();
        if (sReaderThread == NULL)
            return NULL;
    }

    afm = (AudioFileManager *) SDL_malloc(sizeof(AudioFileManager));
    if (afm == NULL)
        return NULL;
    SDL_memset(afm, '\0', sizeof(*afm));

#define SET_AUDIOFILEMANAGER_METHOD(m) afm->m = AudioFileManager_##m
    SET_AUDIOFILEMANAGER_METHOD(Disconnect);
    SET_AUDIOFILEMANAGER_METHOD(DoConnect);
    SET_AUDIOFILEMANAGER_METHOD(Read);
    SET_AUDIOFILEMANAGER_METHOD(GetFileBuffer);
    SET_AUDIOFILEMANAGER_METHOD(GetParent);
    SET_AUDIOFILEMANAGER_METHOD(SetPosition);
    SET_AUDIOFILEMANAGER_METHOD(GetByteCounter);
    SET_AUDIOFILEMANAGER_METHOD(SetEndOfFile);
    SET_AUDIOFILEMANAGER_METHOD(Render);
    SET_AUDIOFILEMANAGER_METHOD(GetFileData);
    SET_AUDIOFILEMANAGER_METHOD(AfterRender);
    SET_AUDIOFILEMANAGER_METHOD(FileInputProc);
#undef SET_AUDIOFILEMANAGER_METHOD

    afm->mParent = inParent;
    afm->mForkRefNum = inForkRefNum;
    afm->mBufferSize = inChunkSize;
    afm->mBufferOffset = inChunkSize;
    afm->mChunkSize = inChunkSize;
    afm->mFileLength = inFileLength;
    afm->mFileBuffer = (char *) SDL_malloc(afm->mChunkSize * 2);
    FSGetForkPosition(afm->mForkRefNum, &afm->mAudioDataOffset);
    assert(afm->mFileBuffer != NULL);
    return afm;
}

/* vi: set ts=4 sw=4 expandtab: */