📄 soundengine.cpp
字号:
if (xFlags & yFlags & kAudioFormatFlagIsFloat) { xFlags = xFlags & ~kAudioFormatFlagIsSignedInteger; yFlags = yFlags & ~kAudioFormatFlagIsSignedInteger; } // if the bit depth is 8 bits or less and the format is packed, we don't care about endianness if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) { xFlags = xFlags & ~kAudioFormatFlagIsBigEndian; } if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) { yFlags = yFlags & ~kAudioFormatFlagIsBigEndian; } // if the number of channels is 0 or 1, we don't care about non-interleavedness if (x.mChannelsPerFrame <= 1 && y.mChannelsPerFrame <= 1) { xFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; yFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; } } return xFlags == yFlags;}Boolean FormatIsEqual(AudioStreamBasicDescription x, AudioStreamBasicDescription y){ // the semantics for equality are: // 1) Values must match exactly // 2) wildcard's are ignored in the comparison #define MATCH(name) ((x.name) == 0 || (y.name) == 0 || (x.name) == (y.name)) return ((x.mSampleRate==0.) || (y.mSampleRate==0.) || (x.mSampleRate==y.mSampleRate)) && MATCH(mFormatID) && MatchFormatFlags(x, y) && MATCH(mBytesPerPacket) && MATCH(mFramesPerPacket) && MATCH(mBytesPerFrame) && MATCH(mChannelsPerFrame) && MATCH(mBitsPerChannel) ;}#pragma mark ***** BackgroundTrackMgr *****//==================================================================================================// BackgroundTrackMgr class//==================================================================================================class BackgroundTrackMgr{ #define CurFileInfo THIS->mBGFileInfo[THIS->mCurrentFileIndex] public: typedef struct BG_FileInfo { const char* mFilePath; AudioFileID mAFID; AudioStreamBasicDescription mFileFormat; UInt64 mFileDataSize; //UInt64 mFileNumPackets; // this is only used if loading file to memory Boolean mLoadAtOnce; Boolean mFileDataInQueue; } BackgroundMusicFileInfo; BackgroundTrackMgr() : mQueue(0), mBufferByteSize(0), mCurrentPacket(0), mNumPacketsToRead(0), mVolume(1.0), mPacketDescs(NULL), mCurrentFileIndex(0), mMakeNewQueueWhenStopped(false), mStopAtEnd(false) { } ~BackgroundTrackMgr() { Teardown(); } void Teardown() { if (mQueue) AudioQueueDispose(mQueue, true); for (UInt32 i=0; i < mBGFileInfo.size(); i++) if (mBGFileInfo[i]->mAFID) AudioFileClose(mBGFileInfo[i]->mAFID); if (mPacketDescs) delete mPacketDescs; } AudioStreamPacketDescription *GetPacketDescsPtr() { return mPacketDescs; } UInt32 GetNumPacketsToRead(BackgroundTrackMgr::BG_FileInfo *inFileInfo) { return mNumPacketsToRead; } static OSStatus AttachNewCookie(AudioQueueRef inQueue, BackgroundTrackMgr::BG_FileInfo *inFileInfo) { OSStatus result = noErr; UInt32 size = sizeof(UInt32); result = AudioFileGetPropertyInfo (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, NULL); if (!result && size) { char* cookie = new char [size]; result = AudioFileGetProperty (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, cookie); AssertNoError("Error getting cookie data", end); result = AudioQueueSetProperty(inQueue, kAudioQueueProperty_MagicCookie, cookie, size); delete [] cookie; AssertNoError("Error setting cookie data for queue", end); } return noErr; end: return noErr; } static void QueueStoppedProc( void * inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID) { UInt32 isRunning; UInt32 propSize = sizeof(isRunning); BackgroundTrackMgr *THIS = (BackgroundTrackMgr*)inUserData; OSStatus result = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &isRunning, &propSize); if ((!isRunning) && (THIS->mMakeNewQueueWhenStopped)) { result = AudioQueueDispose(inAQ, true); AssertNoError("Error disposing queue", end); result = THIS->SetupQueue(CurFileInfo); AssertNoError("Error setting up new queue", end); result = THIS->SetupBuffers(CurFileInfo); AssertNoError("Error setting up new queue buffers", end); result = THIS->Start(); AssertNoError("Error starting queue", end); } end: return; } static Boolean DisposeBuffer(AudioQueueRef inAQ, std::vector<AudioQueueBufferRef> inDisposeBufferList, AudioQueueBufferRef inBufferToDispose) { for (unsigned int i=0; i < inDisposeBufferList.size(); i++) { if (inBufferToDispose == inDisposeBufferList[i]) { OSStatus result = AudioQueueFreeBuffer(inAQ, inBufferToDispose); if (result == noErr) inDisposeBufferList.pop_back(); return true; } } return false; } enum { kQueueState_DoNothing = 0, kQueueState_ResizeBuffer = 1, kQueueState_NeedNewCookie = 2, kQueueState_NeedNewBuffers = 3, kQueueState_NeedNewQueue = 4, }; static SInt8 GetQueueStateForNextBuffer(BackgroundTrackMgr::BG_FileInfo *inFileInfo, BackgroundTrackMgr::BG_FileInfo *inNextFileInfo) { inFileInfo->mFileDataInQueue = false; // unless the data formats are the same, we need a new queue if (!FormatIsEqual(inFileInfo->mFileFormat, inNextFileInfo->mFileFormat)) return kQueueState_NeedNewQueue; // if going from a load-at-once file to streaming or vice versa, we need new buffers if (inFileInfo->mLoadAtOnce != inNextFileInfo->mLoadAtOnce) return kQueueState_NeedNewBuffers; // if the next file is smaller than the current, we just need to resize if (inNextFileInfo->mLoadAtOnce) return (inFileInfo->mFileDataSize >= inNextFileInfo->mFileDataSize) ? kQueueState_ResizeBuffer : kQueueState_NeedNewBuffers; return kQueueState_NeedNewCookie; } static void QueueCallback( void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer) { // dispose of the buffer if no longer in use OSStatus result = noErr; BackgroundTrackMgr *THIS = (BackgroundTrackMgr*)inUserData; if (DisposeBuffer(inAQ, THIS->mBuffersToDispose, inCompleteAQBuffer)) return; UInt32 nPackets = 0; // loop the current buffer if the following: // 1. file was loaded into the buffer previously // 2. only one file in the queue // 3. we have not been told to stop at playlist completion if ((CurFileInfo->mFileDataInQueue) && (THIS->mBGFileInfo.size() == 1) && (!THIS->mStopAtEnd)) nPackets = THIS->GetNumPacketsToRead(CurFileInfo); else { UInt32 numBytes; while (nPackets == 0) { // if loadAtOnce, get all packets in the file, otherwise ~.5 seconds of data nPackets = THIS->GetNumPacketsToRead(CurFileInfo); result = AudioFileReadPackets(CurFileInfo->mAFID, false, &numBytes, THIS->mPacketDescs, THIS->mCurrentPacket, &nPackets, inCompleteAQBuffer->mAudioData); AssertNoError("Error reading file data", end); inCompleteAQBuffer->mAudioDataByteSize = numBytes; if (nPackets == 0) // no packets were read, this file has ended. { if (CurFileInfo->mLoadAtOnce) CurFileInfo->mFileDataInQueue = true; THIS->mCurrentPacket = 0; UInt32 theNextFileIndex = (THIS->mCurrentFileIndex < THIS->mBGFileInfo.size()-1) ? THIS->mCurrentFileIndex+1 : 0; // we have gone through the playlist. if mStopAtEnd, stop the queue here if (theNextFileIndex == 0 && THIS->mStopAtEnd) { result = AudioQueueStop(inAQ, false); AssertNoError("Error stopping queue", end); return; } SInt8 theQueueState = GetQueueStateForNextBuffer(CurFileInfo, THIS->mBGFileInfo[theNextFileIndex]); if (theNextFileIndex != THIS->mCurrentFileIndex) { // if were are not looping the same file. Close the old one and open the new result = AudioFileClose(CurFileInfo->mAFID); AssertNoError("Error closing file", end); THIS->mCurrentFileIndex = theNextFileIndex; result = LoadFileDataInfo(CurFileInfo->mFilePath, CurFileInfo->mAFID, CurFileInfo->mFileFormat, CurFileInfo->mFileDataSize); AssertNoError("Error opening file", end); } switch (theQueueState) { // if we need to resize the buffer, set the buffer's audio data size to the new file's size // we will also need to get the new file cookie case kQueueState_ResizeBuffer: inCompleteAQBuffer->mAudioDataByteSize = CurFileInfo->mFileDataSize; // if the data format is the same but we just need a new cookie, attach a new cookie case kQueueState_NeedNewCookie: result = AttachNewCookie(inAQ, CurFileInfo); AssertNoError("Error attaching new file cookie data to queue", end); break; // we can keep the same queue, but not the same buffer(s) case kQueueState_NeedNewBuffers: THIS->mBuffersToDispose.push_back(inCompleteAQBuffer); THIS->SetupBuffers(CurFileInfo); break; // if the data formats are not the same, we need to dispose the current queue and create a new one case kQueueState_NeedNewQueue: THIS->mMakeNewQueueWhenStopped = true; result = AudioQueueStop(inAQ, false); AssertNoError("Error stopping queue", end); return; default: break; } } } } result = AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, (THIS->mPacketDescs ? nPackets : 0), THIS->mPacketDescs); AssertNoError("Error enqueuing new buffer", end); if (CurFileInfo->mLoadAtOnce) CurFileInfo->mFileDataInQueue = true; THIS->mCurrentPacket += nPackets; end: return; } OSStatus SetupQueue(BG_FileInfo *inFileInfo) { UInt32 size = 0; OSStatus result = AudioQueueNewOutput(&inFileInfo->mFileFormat, QueueCallback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &mQueue); AssertNoError("Error creating queue", end); // (2) If the file has a cookie, we should get it and set it on the AQ size = sizeof(UInt32); result = AudioFileGetPropertyInfo (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, NULL); if (!result && size) { char* cookie = new char [size]; result = AudioFileGetProperty (inFileInfo->mAFID, kAudioFilePropertyMagicCookieData, &size, cookie); AssertNoError("Error getting magic cookie", end); result = AudioQueueSetProperty(mQueue, kAudioQueueProperty_MagicCookie, cookie, size); delete [] cookie; AssertNoError("Error setting magic cookie", end); } // channel layout OSStatus err = AudioFileGetPropertyInfo(inFileInfo->mAFID, kAudioFilePropertyChannelLayout, &size, NULL); if (err == noErr && size > 0) { AudioChannelLayout *acl = (AudioChannelLayout *)malloc(size); result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyChannelLayout, &size, acl); AssertNoError("Error getting channel layout from file", end); result = AudioQueueSetProperty(mQueue, kAudioQueueProperty_ChannelLayout, acl, size); free(acl); AssertNoError("Error setting channel layout on queue", end); } // add a notification proc for when the queue stops result = AudioQueueAddPropertyListener(mQueue, kAudioQueueProperty_IsRunning, QueueStoppedProc, this); AssertNoError("Error adding isRunning property listener to queue", end); // we need to reset this variable so that if the queue is stopped mid buffer we don't dispose it mMakeNewQueueWhenStopped = false; // volume result = SetVolume(mVolume); end: return result; } OSStatus SetupBuffers(BG_FileInfo *inFileInfo) { int numBuffersToQueue = kNumberBuffers; UInt32 maxPacketSize; UInt32 size = sizeof(maxPacketSize); // we need to calculate how many packets we read at a time, and how big a buffer we need // we base this on the size of the packets in the file and an approximate duration for each buffer // first check to see what the max size of a packet is - if it is bigger // than our allocation default size, that needs to become larger OSStatus result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize); AssertNoError("Error getting packet upper bound size", end); bool isFormatVBR = (inFileInfo->mFileFormat.mBytesPerPacket == 0 || inFileInfo->mFileFormat.mFramesPerPacket == 0); CalculateBytesForTime(inFileInfo->mFileFormat, maxPacketSize, 0.5/*seconds*/, &mBufferByteSize, &mNumPacketsToRead); // if the file is smaller than the capacity of all the buffer queues, always load it at once if ((mBufferByteSize * numBuffersToQueue) > inFileInfo->mFileDataSize) inFileInfo->mLoadAtOnce = true; if (inFileInfo->mLoadAtOnce) { UInt64 theFileNumPackets; size = sizeof(UInt64); result = AudioFileGetProperty(inFileInfo->mAFID, kAudioFilePropertyAudioDataPacketCount, &size, &theFileNumPackets); AssertNoError("Error getting packet count for file", end); mNumPacketsToRead = (UInt32)theFileNumPackets; mBufferByteSize = inFileInfo->mFileDataSize; numBuffersToQueue = 1; } else { mNumPacketsToRead = mBufferByteSize / maxPacketSize; } if (isFormatVBR) mPacketDescs = new AudioStreamPacketDescription [mNumPacketsToRead]; else mPacketDescs = NULL; // we don't provide packet descriptions for constant bit rate formats (like linear PCM) // allocate the queue's buffers for (int i = 0; i < numBuffersToQueue; ++i) { result = AudioQueueAllocateBuffer(mQueue, mBufferByteSize, &mBuffers[i]); AssertNoError("Error allocating buffer for queue", end); QueueCallback (this, mQueue, mBuffers[i]); if (inFileInfo->mLoadAtOnce) inFileInfo->mFileDataInQueue = true; } end: return result; } OSStatus LoadTrack(const char* inFilePath, Boolean inAddToQueue, Boolean inLoadAtOnce) { BG_FileInfo *fileInfo = new BG_FileInfo; fileInfo->mFilePath = inFilePath; OSStatus result = LoadFileDataInfo(fileInfo->mFilePath, fileInfo->mAFID, fileInfo->mFileFormat, fileInfo->mFileDataSize); AssertNoError("Error getting file data info", fail); fileInfo->mLoadAtOnce = inLoadAtOnce; fileInfo->mFileDataInQueue = false; // if not adding to the queue, clear the current file vector if (!inAddToQueue) mBGFileInfo.clear(); mBGFileInfo.push_back(fileInfo); // setup the queue if this is the first (or only) file if (mBGFileInfo.size() == 1) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -