#include #include #include #include #include #include "FilePlayer.h" #import #import "VirtualRingBuffer.h" // // Parameters to play with: // #define RING_BUFFER_SIZE 15 // Size (in seconds) of ring buffer. #define RING_BUFFER_WRITE_CHUNK_SIZE (16 * 1024) // Size (in bytes) of chunks to write into the ring buffer. Should probably be at least a page. // Too big and we cause writes to take too long (and the buffer may run dry while we are writing). // Too small and we waste CPU by waking up and writing too often. #define FILE_SAMPLE_BUFFER_SIZE (256 * 1024) // Size (in bytes) of chunks of sample data to read from the file at one time. This is uncompressed, unconverted data. // This is the amount we pass to GetMediaSample(), but that doesn't mean that the actual filesystem reads will necessarily be this size. // constructor FilePlayer::FilePlayer(unsigned long Num) { connected = false; disconnect = false; quitFiller = false; outputSource = NULL; UID = 0; fillerThread = NULL; type = pType_audio_file; status = status_empty; position = 0.0; bus_assignment = 0; fade = 0.0; balance = 0.0; next = -1; segout = 0.0; bufftime = 0; movie = NULL; ringBuffer = NULL; ringSize = 0L; bytesConverted = 0; soundConverter = NULL; converterSourceSampleDataHandle = NULL; path = NULL; Managed = false; pNum = Num; pthread_mutex_init(&timeMutex, NULL); pthread_mutex_init(&fillMutex, NULL); pthread_cond_init(&fillSemaphore, NULL); } FilePlayer::~FilePlayer(void) { if(fillerThread){ // set the quit flag for the filler thread quitFiller = true; // wake the thread so it can quit pthread_mutex_lock( &fillMutex ); pthread_cond_signal( &fillSemaphore ); pthread_mutex_unlock( &fillMutex ); // wait here for the thread to end if(Debug){ fprintf(stdout, "FilePlayer Deallocate: pNum=%d waiting for filler thread to cancel\n", pNum); } pthread_join(fillerThread, NULL); if(Debug){ fprintf(stdout, "FilePlayer Deallocate: pNum=%d filler thread has canceled!\n", pNum); } quitFiller = false; } if (connected == true) disconnect = true; // need to wait here until the render is finished using our outputSource Audio Unit. do{ sched_yield(); }while(connected == true); if(soundConverter) SoundConverterClose(soundConverter); AudioUnitUninitialize(outputSource); CloseComponent(outputSource); DisposeSoundConverterFillBufferDataUPP(soundConverterBufferFillerUPP); if (movie) DisposeMovie(movie); if (converterSourceSampleDataHandle) DisposeHandle(converterSourceSampleDataHandle); if(path) delete [] path; if(ringBuffer) delete ringBuffer; deleteMetaRecord(UID); // see if the metaData record can be deleted from the metaData List DeleteFromLists(this); pthread_mutex_destroy(&timeMutex); pthread_mutex_destroy(&fillMutex); pthread_cond_destroy(&fillSemaphore); // send out notifications struct notifyData data; data.pNum = pNum; data.iVal = 0; data.fVal = 0.0; data.type = nType_vol; Notifier->MakeEntry(data); data.type = nType_bal; Notifier->MakeEntry(data); data.type = nType_bus; Notifier->MakeEntry(data); data.type = nType_stat; Notifier->MakeEntry(data); } unsigned char FilePlayer::load(const char *thePath) { OSStatus err; SoundComponentData outputSoundConverterFormat; SoundComponentData sourceSoundConverterFormat; AudioFormatAtomPtr audioFormatAtom; status = status_loading; // store the path locally path = new char [strlen(thePath)+1]; strcpy(path, thePath); err = EnterMovies(); if (err != noErr) goto errorReturn; if (!openFileAsMovie()) goto errorReturn; soundConverterBufferFillerUPP = NewSoundConverterFillBufferDataUPP(&FilePlayer::fillSoundConverterBuffer); converterSourceSampleDataHandle = NewHandle(FILE_SAMPLE_BUFFER_SIZE); // Get the format of the input file, along with any additional decompression parameters if (!getMovieSoundFormat(&sourceSoundConverterFormat, &audioFormatAtom)) goto errorReturn; if (!setUpAudioOutput()) goto errorReturn; // Get the AUConverters input format, in a structure that the SoundConverter understands if (!getOutputSoundConverterFormat(&outputSoundConverterFormat)) goto errorReturn; ringSize = (unsigned long)(BufferBytesPerSec * RING_BUFFER_SIZE); ringBuffer = new VirtualRingBuffer(ringSize); if (!ringBuffer) goto errorReturn; if (!startSoundConverter(&sourceSoundConverterFormat, &outputSoundConverterFormat, audioFormatAtom)){ stopSoundConverter(); goto errorReturn; } // update the duration variable getDuration(); // create filler thread pthread_create(&fillerThread, NULL, &FilePlayer::fillRingBufferInThread, this); if (!startAudioOutput()){ goto errorReturn; }else{ connected = true; } status = status_standby; // send out notifications struct notifyData data; data.pNum = pNum; data.type = nType_vol; data.iVal = 0; data.fVal = getMixerInputVolume(pNum); Notifier->MakeEntry(data); data.type = nType_bal; data.iVal = 0; data.fVal = balance; Notifier->MakeEntry(data); data.type = nType_bus; data.iVal = bus_assignment; data.fVal = 0.0; Notifier->MakeEntry(data); data.type = nType_stat; data.iVal = status; data.fVal = 0.0; Notifier->MakeEntry(data); return true; errorReturn: status = status_empty; return false; } void FilePlayer::start(void) { TaskItem *task; status = status | status_playing; task = new TaskItem("Update Mute Groups", RefreshMutesGroups, NULL, 0L, 0L, false); if(!(bus_assignment & 2L)){ // not in cue if(!(status & status_hasPlayed)){ // first time playing status = status | status_hasPlayed; // create log entry ProgramLoger->UIDEntry(UID, false, true); } // send out notifications struct notifyData data; data.pNum = pNum; data.type = nType_stat; data.iVal = status; data.fVal = 0.0; Notifier->MakeEntry(data); } } void FilePlayer::stop(void) { status = status & ~status_playing; TaskItem *task; task = new TaskItem("Update Mute Groups", RefreshMutesGroups, NULL, 0L, 0L, false); // send out notifications struct notifyData data; data.pNum = pNum; data.type = nType_stat; data.iVal = status; data.fVal = 0.0; Notifier->MakeEntry(data); } void FilePlayer::volChange(float vol) { } void FilePlayer::setPosition(float time) { TimeValue newTime; newTime = (TimeValue)floor(time * (double)mediaTimeScale); if (newTime < 0) newTime = 0; else if (newTime > mediaDuration) newTime = mediaDuration; pthread_mutex_lock(&timeMutex); // flag a time change status = status | status_timeChange; status = status & ~status_finished; ringBuffer->clear(); currentMediaTime = newTime; startTime = currentMediaTime; bytesConverted = 0; playbackPosition(); pthread_mutex_unlock(&timeMutex); } void FilePlayer::setStatus(unsigned long newStat) { status = newStat; // send out notifications struct notifyData data; data.pNum = pNum; data.type = nType_stat; data.iVal = status; data.fVal = 0.0; Notifier->MakeEntry(data); } void FilePlayer::getDuration(void) { char durStr[16]; double duration; // convert mediaDuration to seconds if (mediaTimeScale == 0){ duration = 0.0; }else{ duration = (double)mediaDuration / (double)mediaTimeScale; } snprintf(durStr, sizeof(durStr), "%.2f", duration); SetMetaData(UID, "Duration", string(durStr)); } void FilePlayer::bufferTime(void) { unsigned long bytesAvailable; void *readPointer; bytesAvailable = ringBuffer->lengthAvailableToReadReturningPointer(&readPointer); bufftime = (float)bytesAvailable / BufferBytesPerSec; } void FilePlayer::playbackPosition(void) { unsigned long bytesAvailable; void *readPointer; // No need to lock mediaTimeLock; reading currentMediaTime is atomic if (mediaTimeScale == 0){ position = 0.0; }else{ bytesAvailable = ringBuffer->lengthAvailableToReadReturningPointer(&readPointer); position = ((double)startTime / (double)mediaTimeScale) + ((bytesConverted - (double)bytesAvailable) / BufferBytesPerSec); } } unsigned char FilePlayer::openFileAsMovie(void) { FSRef fsRef; FSSpec fsSpec; OSErr err; short fileRefNum; short resID = 0; unsigned char wasChanged; Track track; // Convert our file name to an FSRef if (FSPathMakeRef ((const unsigned char *)path, &fsRef, NULL) != noErr) return false; // Convert FSRef to an FSSpec if (FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL) != noErr) return false; // Open the movie file from FSSpec if (OpenMovieFile(&fsSpec, &fileRefNum, fsRdPerm) != noErr) return false; // Instantiate the movie and close the file err = NewMovieFromFile(&movie, fileRefNum, &resID, NULL, newMovieActive, &wasChanged); CloseMovieFile(fileRefNum); if (err != noErr) return false; // Get the first sound track track = GetMovieIndTrackType(movie, 1, SoundMediaType, movieTrackMediaType); if (!track) return false; // Get the sound track's media media = GetTrackMedia(track); if (!media) return false; // Start reading the movie from the beginning, and remember how long it is mediaTimeScale = GetMediaTimeScale(media); mediaDuration = GetMediaDuration(media); return true; } unsigned char FilePlayer::getMovieSoundFormat(SoundComponentData *outSoundFormat, AudioFormatAtomPtr *outAtom) { OSErr err; SoundDescriptionV1Handle sourceSoundDescription; Handle extension; // Get the description of the sample data sourceSoundDescription = (SoundDescriptionV1Handle)NewHandle(0); GetMediaSampleDescription(media, 1, (SampleDescriptionHandle)sourceSoundDescription); err = GetMoviesError(); if (err != noErr) { DisposeHandle((Handle)sourceSoundDescription); return false; } // Get the "magic" decompression atom. // This extension to the SoundDescription information stores data specific to a given audio decompressor. // Some audio decompression algorithms require a set of out-of-stream values to configure the decompressor. extension = NewHandle(0); err = GetSoundDescriptionExtension((SoundDescriptionHandle)sourceSoundDescription, &extension, siDecompressionParams); if (noErr == err) { // Copy the atom Size size; size = GetHandleSize(extension); HLock(extension); *outAtom = (AudioFormatAtomPtr)malloc(size); memcpy(*outAtom, *extension, size); HUnlock(extension); } else { // If it doesn't have an atom, that's OK *outAtom = NULL; } // Remember the format of the audio in the movie // (converting from a SoundDescription to a SoundComponentData) outSoundFormat->flags = 0; outSoundFormat->format = (*sourceSoundDescription)->desc.dataFormat; outSoundFormat->numChannels = (*sourceSoundDescription)->desc.numChannels; outSoundFormat->sampleSize = (*sourceSoundDescription)->desc.sampleSize; outSoundFormat->sampleRate = (*sourceSoundDescription)->desc.sampleRate; outSoundFormat->sampleCount = 0; outSoundFormat->buffer = 0; outSoundFormat->reserved = 0; sampleRate = (float)(outSoundFormat->sampleRate) / 65536.0; DisposeHandle(extension); DisposeHandle((Handle)sourceSoundDescription); return true; } unsigned char FilePlayer::startSoundConverter(const SoundComponentData *source, const SoundComponentData *dest, AudioFormatAtomPtr compression) { OSErr err; AudioFormatAtomPtr audioFormatAtom; audioFormatAtom = compression; // Create the sound converter err = SoundConverterOpen(source, dest, &soundConverter); if (err != noErr || soundConverter == NULL) goto errorReturn; // If we have a decompression atom, give it to the SoundConverter if (audioFormatAtom) { err = SoundConverterSetInfo(soundConverter, siDecompressionParams, audioFormatAtom); // and get rid of it free(audioFormatAtom); audioFormatAtom = NULL; // Sometimes we get the error siUnknownInfoType. (I've seen this in AIFF files which contain a chunk of type 'wave'.) // However, we can still continue on and decode successfully. if (err != noErr && err != siUnknownInfoType) goto errorReturn; } currentMediaTime = 0; startTime = 0; // Fill in converterSourceSoundComponentData so as little of it needs to change as possible later on converterSourceSoundComponentData.desc.flags = kExtendedSoundData; converterSourceSoundComponentData.desc.format = source->format; converterSourceSoundComponentData.desc.numChannels = source->numChannels; converterSourceSoundComponentData.desc.sampleSize = source->sampleSize; converterSourceSoundComponentData.desc.sampleRate = source->sampleRate; converterSourceSoundComponentData.desc.flags = kExtendedSoundData; converterSourceSoundComponentData.recordSize = sizeof(ExtendedSoundComponentData); converterSourceSoundComponentData.extendedFlags = kExtendedSoundSampleCountNotValid | kExtendedSoundBufferSizeValid; // Finally, begin the conversion err = SoundConverterBeginConversion(soundConverter); if (err != noErr) goto errorReturn; return true; errorReturn: if (soundConverter) { SoundConverterClose(soundConverter); soundConverter = NULL; } if (audioFormatAtom) free(audioFormatAtom); return false; } void FilePlayer::stopSoundConverter(void) { if (soundConverter) SoundConverterClose(soundConverter); soundConverter = NULL; } unsigned char FilePlayer::fillSoundConverterBuffer(SoundComponentDataPtr *data, void *refCon) { long sourceBytesReturned; long numberOfSamples; TimeValue sourceReturnedTime, durationPerSample; OSErr err; unsigned char success; FilePlayer *parent = NULL; parent = (FilePlayer *)(refCon); if (parent->currentMediaTime >= parent->mediaDuration) { return false; } // HUnlock(parent->converterSourceSampleDataHandle); err = GetMediaSample( parent->media, parent->converterSourceSampleDataHandle, // sample data is returned into this handle FILE_SAMPLE_BUFFER_SIZE, // maximum number of bytes of sample data to be returned &sourceBytesReturned, // the number of bytes of sample data returned parent->currentMediaTime, // starting time of the sample to be retrieved &sourceReturnedTime, // indicates the actual time of the returned sample data &durationPerSample, // duration of each sample in the media NULL, // sample description corresponding to the returned sample data (NULL to ignore) NULL, // index value to the sample description that corresponds to the returned sample data (NULL to ignore) 0, // maximum number of samples to be returned (0 to use a value that is appropriate for the media) &numberOfSamples, // number of samples it actually returned NULL); // flags that describe the sample (NULL to ignore) // HLock(parent->converterSourceSampleDataHandle); if (noErr == err && sourceBytesReturned > 0) { parent->currentMediaTime = sourceReturnedTime + (durationPerSample * numberOfSamples); parent->converterSourceSoundComponentData.bufferSize = sourceBytesReturned; parent->converterSourceSoundComponentData.desc.buffer = (Byte *)*(parent->converterSourceSampleDataHandle); *data = (SoundComponentDataPtr)&parent->converterSourceSoundComponentData; success = true; } else { success = false; } return success; } unsigned char FilePlayer::getOutputSoundConverterFormat(SoundComponentData *outputSoundConverterFormat) { OSErr err; AudioStreamBasicDescription outputCoreAudioFormat; unsigned long size; // Get the format that the converter AudioUnit expects us to give it size = sizeof(AudioStreamBasicDescription); err = AudioUnitGetProperty(outputSource, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputCoreAudioFormat, &size); if (err) return false; // Translate the format to a SoundComponentData for the sound converter. Yuck. outputSoundConverterFormat->flags = 0; if (outputCoreAudioFormat.mFormatID != kAudioFormatLinearPCM) return false; SampleSize = (outputCoreAudioFormat.mBitsPerChannel/8); // sample size in bytes if (outputCoreAudioFormat.mFormatFlags & kLinearPCMFormatFlagIsFloat) { if (outputCoreAudioFormat.mBitsPerChannel == 32) outputSoundConverterFormat->format = kFloat32Format; else if (outputCoreAudioFormat.mBitsPerChannel == 64) outputSoundConverterFormat->format = kFloat64Format; else return false; } else { unsigned char isBigEndian = (outputCoreAudioFormat.mFormatFlags & kLinearPCMFormatFlagIsBigEndian); if (outputCoreAudioFormat.mBitsPerChannel == 16) outputSoundConverterFormat->format = (isBigEndian ? k16BitBigEndianFormat : k16BitLittleEndianFormat); else if (outputCoreAudioFormat.mBitsPerChannel == 32) outputSoundConverterFormat->format = (isBigEndian ? k32BitFormat : k32BitLittleEndianFormat); else return false; } outputSoundConverterFormat->numChannels = outputCoreAudioFormat.mChannelsPerFrame; outputSoundConverterFormat->sampleSize = outputCoreAudioFormat.mBitsPerChannel; outputSoundConverterFormat->sampleRate = ConvertdoubleToUnsignedFixed(outputCoreAudioFormat.mSampleRate); outputSoundConverterFormat->sampleCount = 0; outputSoundConverterFormat->buffer = NULL; outputSoundConverterFormat->reserved = 0; BufferBytesPerSec = outputCoreAudioFormat.mSampleRate * outputCoreAudioFormat.mChannelsPerFrame * (outputCoreAudioFormat.mBitsPerChannel / 8); return true; } unsigned char FilePlayer::setUpAudioOutput(void) { AURenderCallbackStruct rcbs; OSStatus result; AudioStreamBasicDescription format; unsigned long size; ComponentDescription desc; Component comp; desc.componentType = kAudioUnitType_FormatConverter; desc.componentSubType = kAudioUnitSubType_AUConverter; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; if(comp = FindNextComponent(0, &desc)){ result = OpenAComponent(comp, &outputSource); if (result) return false; }else{ return false; } size = sizeof(format); result = AudioUnitGetProperty( mixer, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, pNum, &format, &size ); if (result) return false; // set converter output format to the mixer input format result = AudioUnitSetProperty( outputSource, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &format, sizeof(format) ); if (result) return false; // set converter input format to interleaved and the audio file sample rate if(format.mFormatFlags & kAudioFormatFlagIsNonInterleaved) format.mFormatFlags = format.mFormatFlags - kAudioFormatFlagIsNonInterleaved; format.mBytesPerFrame = format.mBytesPerFrame * format.mChannelsPerFrame; format.mBytesPerPacket = format.mBytesPerFrame; format.mSampleRate = sampleRate; result = AudioUnitSetProperty( outputSource, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(format) ); if (result) return false; // set sound-converter input to our local input callback rcbs.inputProc = &FilePlayer::renderCallback; rcbs.inputProcRefCon = this; result = AudioUnitSetProperty( outputSource, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &rcbs, sizeof(rcbs) ); if (result) return false; result = AudioUnitInitialize(outputSource); if (result){ return false; }else{ return true; } } unsigned char FilePlayer::startAudioOutput(void) { // connect our converter to the mixer input pthread_mutex_lock(&sourceList[pNum].countLock); sourceList[pNum].playerPtr = this; connected = true; pthread_mutex_unlock(&sourceList[pNum].countLock); return true; } UnsignedFixed FilePlayer::ConvertdoubleToUnsignedFixed(double doubleValue) { UnsignedFixed fixedValue; // High 2 bytes is the integer part of the value // Low 2 bytes is the floating point part of the value fixedValue = (unsigned long)(doubleValue * 65536.0); // integer part fixedValue = fixedValue + (unsigned long)((doubleValue - floor(doubleValue)) * 65536.0); // fractional part return fixedValue; } void *FilePlayer::fillRingBufferInThread(void* refCon) { FilePlayer *parent = (FilePlayer *)(refCon); do{ if(!(parent->status & status_finished)){ parent->convertIntoRingBuffer(); parent->bufferTime(); } // Wait for the audio thread to signal us that it could use more data pthread_mutex_lock( &parent->fillMutex ); pthread_cond_wait( &parent->fillSemaphore, &parent->fillMutex ); pthread_mutex_unlock( &parent->fillMutex ); }while(!parent->quitFiller); pthread_exit(0); } void FilePlayer::convertIntoRingBuffer(void) { // Check if there is a reasonable amount of space available to write to the ring buffer. // If there is, ask the SoundConverter to put a chunk of converted data into the ring buffer. // Repeat this until the available space to write is too small, or we run out of data to convert. void *writePointer; unsigned char continueReading; do { unsigned long bytesToWrite, bytesAvailableToWrite; continueReading = false; bytesToWrite = RING_BUFFER_WRITE_CHUNK_SIZE; bytesAvailableToWrite = ringBuffer->lengthAvailableToWriteReturningPointer(&writePointer); if (bytesAvailableToWrite >= bytesToWrite) { OSErr err = noErr; unsigned long bytesWritten; unsigned long framesWritten; unsigned long outputFlags; pthread_mutex_lock(&timeMutex); if (status & status_timeChange) { // Throw away any data that may have been buffered inside the SoundConverter, // and restart the conversion at the new time. unsigned long discardBytesWritten, discardFramesWritten; err = SoundConverterEndConversion(soundConverter, NULL, &discardFramesWritten, &discardBytesWritten); if (err == noErr) err = SoundConverterBeginConversion(soundConverter); } if (err == noErr) { // Request the sound converter to convert one buffer of data. err = SoundConverterFillBuffer(soundConverter, soundConverterBufferFillerUPP, this, writePointer, bytesToWrite, &bytesWritten, &framesWritten, &outputFlags); } pthread_mutex_unlock(&timeMutex); if (err != noErr) { // Act like there's no more data bytesWritten = 0; outputFlags = kSoundConverterDidntFillBuffer; } if (bytesWritten > 0){ ringBuffer->didWriteLength(bytesWritten); bytesConverted = bytesConverted + bytesWritten; } if (outputFlags & kSoundConverterDidntFillBuffer) { // We have read the last of the file (or hit an error in reading it, or EOF). So now we're done. status = status | status_finished; continueReading = false; } else { status = status & ~status_timeChange; // Immediately go back around for another chunk. continueReading = true; } } } while (continueReading); } OSStatus FilePlayer::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, unsigned long inBusNumber, unsigned long inNumberFrames, AudioBufferList *ioData) { ARPlayer *next = NULL; FilePlayer *parent = NULL; unsigned long bytesAvailable =0; unsigned long bytesToRead = 0; unsigned long locStatus = 0; void *readPointer = NULL; struct notifyData data; parent = (FilePlayer *)(inRefCon); // make a local copy of the player status, that way we don't need to worry about it changing while we are using it locStatus = parent->status; // note: This callback feeds interleaved data to a SINGLE buffer so only the first buffer pass [0] will be used. // Really, no other buffers should have been passed in the first place. // If playback is stopped, or if we have played all the sound there is to play, just play silence if (!(locStatus & status_playing)) { bzero(ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize); if (!(locStatus & status_timeChange)){ *inActionFlags = kAudioUnitRenderAction_OutputIsSilence; return noErr; } } // Normal case: we want to read some audio data from the ring buffer. How much is available? bytesAvailable = parent->ringBuffer->lengthAvailableToReadReturningPointer(&readPointer); if (bytesAvailable >= ioData->mBuffers[0].mDataByteSize) { bytesToRead = ioData->mBuffers[0].mDataByteSize; } else { // The ring buffer has run dry. Just read as much as possible from the ring buffer, and fill the result of the audio buffer with zero. // make sure it is a whole number of samples & channels bytesToRead = bytesAvailable; // zero the tail end of the buffers bzero((void *)((unsigned long)ioData->mBuffers[0].mData + bytesToRead), (unsigned long)ioData->mBuffers[0].mDataByteSize - bytesToRead); } if (bytesToRead > 0) { // Finally read from the ring buffer. if(!(locStatus & status_timeChange) && (locStatus & status_playing)){ // copy from the ring buffer into the passed buffer memcpy(ioData->mBuffers[0].mData, readPointer, bytesToRead); }else{ // or fill with zeros if there are no bytes to read bzero(ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize); *inActionFlags = kAudioUnitRenderAction_OutputIsSilence; } // say we read all the bytes, even if the media time was changed, that way we // try to add more to the buffer, finallizing the new media time. parent->ringBuffer->didReadLength(bytesToRead); } // If there is no more data to be read, tell the feeder thread that we are done playing. if (bytesAvailable == 0 && (locStatus & status_finished)){ if(parent->next == -1){ // no segue pending... parent->status = parent->status & ~status_playing; data.pNum = parent->pNum; data.type = nType_stat; data.iVal = parent->status; data.fVal = 0.0; Notifier->MakeEntry(data); }else{ // segue pending... force a segue NOW. parent->segout = 0.0; } } // If there is now enough space available to write into the ring buffer, wake up the feeder thread. if (bytesAvailable < (parent->ringSize - RING_BUFFER_WRITE_CHUNK_SIZE)){ if ((locStatus & status_finished) == 0){ pthread_cond_signal(&parent->fillSemaphore); } } // update position and bufferTime variables parent->bufferTime(); parent->playbackPosition(); // this is a good, reliable, periodicly accessed place to fade out and segue if( (parent->position > parent->fade) && (parent->fade > 0.0) ) // fadeout check { parent->segout = parent->fade; // segout on fade too float volume = 1.0; // default AudioUnitGetParameter(mixer, kMatrixMixerParam_Volume, kAudioUnitScope_Global, ((parent->pNum*inChNum)<<16)+0xffff, &volume); volume = volume - 0.002; if (volume < 0.002){ bytesAvailable = 0; parent->status = parent->status | status_finished; parent->status = parent->status & ~status_playing; volume = 0.0; parent->fade = 0.0; setMixerInputVolume(parent->pNum, volume); data.pNum = parent->pNum; data.type = nType_stat; data.iVal = parent->status; data.fVal = 0.0; Notifier->MakeEntry(data); }else setMixerInputVolume(parent->pNum, volume); data.pNum = parent->pNum; data.type = nType_vol; data.iVal = 0; data.fVal = volume; Notifier->MakeEntry(data); } if( (parent->position > parent->segout) && (parent->next >= 0) ) // segout check { if(pMutex->readLock(false)){ // the playerlist mutex is not locked... we locked it and can proceed. Other wise skip until next time so there are no audio drop outs. if(next = pList[parent->next]){ next->start(); } parent->next = -1; pMutex->readUnlock(); } } return noErr; }