本篇我們介紹AudioFile和AudioFileStream。在第一篇技術(shù)棧的分析里,我們提到過AudioFile和AudioFileStream都可以用來解析采樣率、碼率、時長等信息,分離原始音頻數(shù)據(jù)中的音頻幀。這兩個都可以使用在流式播放中,當然,不僅限于流播,本地音頻也一樣可以使用。
??可能有的小伙伴會有疑問,既然它們倆功能相似,選擇其中一個不就可以了嗎?其實不然,AudioFile的功能遠比AudioFileStream強大,除了共同的解析音頻數(shù)據(jù)分離音頻幀之外,它還可以讀取音頻數(shù)據(jù),甚至可以寫音頻(生成音頻文件),而AudioFileStream本身沒有讀取音頻數(shù)據(jù)的功能??雌饋磉x擇AudioFile就OK了,不料AudioFile卻需要AudioFileStream來保證數(shù)據(jù)的完整性,否則會大大增加出錯的可能性(別急別急,且看下文一一道來)。
??下面我們就先認識一下Mr.AudioFileStream。
初始化AudioFileStream
首先當然是調(diào)用AudioFileStreamOpen()生成一個AudioFileStream實例,函數(shù)聲明如下:
OSStatus
AudioFileStreamOpen (
void * __nullable inClientData,
AudioFileStream_PropertyListenerProc inPropertyListenerProc,
AudioFileStream_PacketsProc inPacketsProc,
AudioFileTypeID inFileTypeHint,
AudioFileStreamID __nullable * __nonnull outAudioFileStream)
- 第一個參數(shù),inClientData是一個上下文對象,會回傳給回調(diào)函數(shù)。必須保證inClientData生命周期足夠長,否則在回調(diào)函數(shù)里使用的時候就是一個野指針了。一般我們會傳自身(self),在回調(diào)函數(shù)取出后再調(diào)用自身的實例方法(C和OC雖然可以混編,但是C函數(shù)并不是作為OC類的實例方法存在的,所以C函數(shù)里的self并不指代OC對象)。
- 第二個參數(shù),inPropertyListenerProc是歌曲信息解析的回調(diào),每解析出一個就會進行一次回調(diào)。
- 第三個參數(shù),inPacketsProc是分離音頻幀的回調(diào),每解析出一部分音頻幀就會進行一次回調(diào)。
- 第四個參數(shù),inFileTypeHint是音頻文件格式的描述信息,這個參數(shù)來幫助AudioFileStream對文件格式進行解析。這個參數(shù)在文件信息不完整(例如信息有缺陷)時尤其有用,它可以給與AudioFileStream一定的提示,幫助其繞過文件中的錯誤或者缺失從而成功解析文件,如果無法確定可以傳入0。
// AudioToolBox定義的AudioFileTypeID
CF_ENUM(AudioFileTypeID) {
kAudioFileAIFFType = 'AIFF',
kAudioFileAIFCType = 'AIFC',
kAudioFileWAVEType = 'WAVE',
kAudioFileSoundDesigner2Type = 'Sd2f',
kAudioFileNextType = 'NeXT',
kAudioFileMP3Type = 'MPG3', // mpeg layer 3
kAudioFileMP2Type = 'MPG2', // mpeg layer 2
kAudioFileMP1Type = 'MPG1', // mpeg layer 1
kAudioFileAC3Type = 'ac-3',
kAudioFileAAC_ADTSType = 'adts',
kAudioFileMPEG4Type = 'mp4f',
kAudioFileM4AType = 'm4af',
kAudioFileM4BType = 'm4bf',
kAudioFileCAFType = 'caff',
kAudioFile3GPType = '3gpp',
kAudioFile3GP2Type = '3gp2',
kAudioFileAMRType = 'amrf'
};
- 第五個參數(shù),outAudioFileStream代表生成的AudioFileStream實例,這個參數(shù)必須保存起來作為后續(xù)一些方法的參數(shù)使用。
- 返回值表示是否調(diào)用成功(status == noErr),關(guān)于OSStatus的解釋,可以參閱這里。
注意:在播放網(wǎng)絡(luò)音頻時,很多鏈接并沒有指明音頻格式,此時可以根據(jù)MIME type來確定音頻格式,而本地音頻可以根據(jù)文件擴展名確定。MIME type與擴展名有關(guān),用于確定文件的類型。 在HTTP請求中,MIME type通過請求頭中的 Content-Type 表示。iOS中可通過 <MobileCoreServices/UTType.h> 中定義的相關(guān)方法可以實現(xiàn) fileExtension <--> UTType <--> mimeType 的互轉(zhuǎn)。具體轉(zhuǎn)換方法可以看我之前的一篇博文。
解析音頻數(shù)據(jù)
上文AudioFileStream并沒有提供讀取音頻數(shù)據(jù)的接口,所以音頻數(shù)據(jù)的讀取需要自行實現(xiàn)。本地播放可以通過NSFileHandle提供的接口,流播時通過HTTP請求獲得。在得到音頻數(shù)據(jù)之后,調(diào)用AudioFileStreamParseBytes()就可以進行解析了。
OSStatus
AudioFileStreamParseBytes(
AudioFileStreamID inAudioFileStream,
UInt32 inDataByteSize,
const void * inData,
AudioFileStreamParseFlags inFlags)
- 第一個參數(shù),inAudioFileStream是初始化時得到的AudioFileStreamID。
- 第二個參數(shù),inDataByteSize是本次解析的數(shù)據(jù)長度。
- 第三個參數(shù),inData是本次解析的音頻數(shù)據(jù)。
- 第四個參數(shù),inFlags表示本次解析與上一次是否是連續(xù)關(guān)系。在第一篇中我們提到過形如MP3的數(shù)據(jù)都以幀的形式存在的,解析時也需要以幀為單位解析。但在解碼之前我們不可能知道每個幀的邊界在第幾個字節(jié),所以就會出現(xiàn)這樣的情況:我們傳給AudioFileStreamParseBytes的數(shù)據(jù)在解析完成之后會有一部分數(shù)據(jù)余下來,這部分數(shù)據(jù)是接下去那一幀的前半部分,如果再次有數(shù)據(jù)輸入需要繼續(xù)解析時就必須要用到前一次解析余下來的數(shù)據(jù)才能保證幀數(shù)據(jù)完整,所以在正常播放的情況下傳入0即可。需要傳入kAudioFileStreamParseFlag_Discontinuity的情況有兩個,一個是在seek完畢之后,顯然seek后的數(shù)據(jù)和之前的數(shù)據(jù)完全無關(guān);另一個和AudioFileStream的bug有關(guān),在回調(diào)得到kAudioFileStreamProperty_ReadyToProducePackets之后,在正常解析第一包之前最好都傳入kAudioFileStreamParseFlag_Discontinuity。
- 返回值表示本次解析是否成功(同樣status == noErr)。
注意:AudioFileStreamParseBytes()函數(shù)每次調(diào)用都必須檢查返回值,一旦出錯就沒有必要繼續(xù)解析了。注意一下若是返回這個kAudioFileStreamError_NotOptimized,說明這個音頻文件無法流播,只能下載完所有數(shù)據(jù)才能播放。
解析歌曲信息
調(diào)用AudioFileStreamParseBytes()之后首先會解析歌曲信息,每解析出一個,同步回調(diào)AudioFileStream_PropertyListenerProc。
typedef void (*AudioFileStream_PropertyListenerProc)(
void * inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
AudioFileStreamPropertyFlags * ioFlags)
- 第一個參數(shù),inClientData是初始化時指定的上下文信息。
- 第二個參數(shù),inAudioFileStream指代AudioFileStream對象。
- 第三個參數(shù),inPropertyID表示音頻的信息,可以通過AudioFileStreamGetProperty()函數(shù)取值。
- 第四個參數(shù),ioFlags表示這個property是否需要被緩存。
來看一下AudioFileStreamGetProperty()函數(shù)。
OSStatus
AudioFileStreamGetProperty(
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 * ioPropertyDataSize,
void * outPropertyData)
- 第一個參數(shù),inAudioFileStream指代AudioFileStream對象。
- 第二個參數(shù),inPropertyID表示想獲取哪個property。
- 第三個參數(shù),想要獲取的property所表示的數(shù)據(jù)結(jié)構(gòu)大小,對于大小不定的propertyID,需要先調(diào)用AudioFileStreamGetPropertyInfo()函數(shù)先獲取一下大小,比如kAudioFileStreamProperty_FormatList。
- 第四個參數(shù),outPropertyData是一個返回參數(shù),會返回獲取的property的值。
// AudioFileStream 定義的所有propertyID
CF_ENUM(AudioFileStreamPropertyID)
{
kAudioFileStreamProperty_ReadyToProducePackets = 'redy',
kAudioFileStreamProperty_FileFormat = 'ffmt',
kAudioFileStreamProperty_DataFormat = 'dfmt',
kAudioFileStreamProperty_FormatList = 'flst',
kAudioFileStreamProperty_MagicCookieData = 'mgic',
kAudioFileStreamProperty_AudioDataByteCount = 'bcnt',
kAudioFileStreamProperty_AudioDataPacketCount = 'pcnt',
kAudioFileStreamProperty_MaximumPacketSize = 'psze',
kAudioFileStreamProperty_DataOffset = 'doff',
kAudioFileStreamProperty_ChannelLayout = 'cmap',
kAudioFileStreamProperty_PacketToFrame = 'pkfr',
kAudioFileStreamProperty_FrameToPacket = 'frpk',
kAudioFileStreamProperty_PacketToByte = 'pkby',
kAudioFileStreamProperty_ByteToPacket = 'bypk',
kAudioFileStreamProperty_PacketTableInfo = 'pnfo',
kAudioFileStreamProperty_PacketSizeUpperBound = 'pkub',
kAudioFileStreamProperty_AverageBytesPerPacket = 'abpp',
kAudioFileStreamProperty_BitRate = 'brat',
kAudioFileStreamProperty_InfoDictionary = 'info'
};
幾個比較有用的propertyID
- kAudioFileStreamProperty_DataOffset:第一篇提到MP3文件有一個頭信息,之后才是真正的音頻數(shù)據(jù),這個屬性表示的就是頭信息的大小,在seek操作時有比較大的作用。對于用戶來講,seek操作操作的是時間,但對于編碼來講,我們seek的是文件位置,seek時會根據(jù)時間計算出音頻數(shù)據(jù)的字節(jié)offset然后需要再加上音頻數(shù)據(jù)的offset才能得到在文件中的真正offset。。
// 注意數(shù)據(jù)類型一定不能錯,否則會獲取不到想要的結(jié)果。
SInt64 dataOffset;
UInt32 offsetSize = sizeof(dataOffset);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &dataOffset);
if (status != noErr)
{
//錯誤處理
}
- kAudioFileStreamProperty_AudioDataByteCount:表示真正可播放的音頻數(shù)據(jù)大?。ǔヮ^信息),很明顯也可以這么計算audioDataByteCount = fileSize - dataOffset。
UInt64 audioDataByteCount;
UInt32 byteCountSize = sizeof(audioDataByteCount);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount);
if (status != noErr)
{
//錯誤處理
}
- kAudioFileStreamProperty_BitRate:獲取碼率可以用來計算音頻時長(AudioFileStream沒有提供直接獲取音頻時長的接口)。文件大小與碼率的關(guān)系在第一篇提到過,從而可得 duration = ((fileSize - dataOffset) * 8) / bitRate。
UInt32 bitRate;
UInt32 bitRateSize = sizeof(bitRate);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_BitRate, &bitRateSize, &bitRate);
if (status != noErr)
{
//錯誤處理
}
- kAudioFileStreamProperty_DataFormat:表示音頻文件結(jié)構(gòu)信息,是一個AudioStreamBasicDescription的結(jié)構(gòu)體。
struct AudioStreamBasicDescription
{
// 采樣率
Float64 mSampleRate;
// 音頻的類型,MP3 or WAV
AudioFormatID mFormatID;
// 隨mFormatID而定
AudioFormatFlags mFormatFlags;
// 每個數(shù)據(jù)包中的字節(jié)數(shù)
UInt32 mBytesPerPacket;
// 每個數(shù)據(jù)包的幀數(shù)(第一篇提到過,原始數(shù)據(jù)如PCM一包一幀,壓縮格式如MP3一包多幀)
UInt32 mFramesPerPacket;
// 每幀的字節(jié)數(shù)
UInt32 mBytesPerFrame;
// 每幀的聲道數(shù)
UInt32 mChannelsPerFrame;
// 每個聲道的采樣位數(shù)
UInt32 mBitsPerChannel;
// 與內(nèi)存對齊有關(guān)
UInt32 mReserved;
};
// 獲取format
AudioStreamBasicDescription asbd;
UInt32 asbdSize = sizeof(asbd);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
if (status != noErr)
{
//錯誤處理
}
- kAudioFileStreamProperty_FormatList:作用和kAudioFileStreamProperty_DataFormat是一樣的,區(qū)別在于用這個PropertyID獲取到是一個AudioStreamBasicDescription的數(shù)組,這個參數(shù)是用來支持AAC,SBR這樣的包含多個文件類型的音頻格式。由于到底有多少個format我們并不知曉,所以需要先獲取一下總數(shù)據(jù)大?。?/li>
//獲取數(shù)據(jù)大小
Boolean outWriteable;
UInt32 formatListSize;
OSStatus status = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);
if (status != noErr)
{
//錯誤處理
}
//獲取formatlist
AudioFormatListItem *formatList = malloc(formatListSize);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);
if (status != noErr)
{
//錯誤處理
}
//選擇需要的格式
for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i++)
{
AudioStreamBasicDescription pasbd = formatList[i].mASBD;
//選擇需要的格式。。
}
free(formatList);
- kAudioFileStreamProperty_ReadyToProducePackets:這個PropertyID可以不必獲取對應(yīng)的值,一旦回調(diào)中這個PropertyID出現(xiàn)就代表解析完成,接下來可以對音頻數(shù)據(jù)進行幀分離了。
分離音頻幀
歌曲信息讀取完整后,繼續(xù)調(diào)用AudioFileStreamParseBytes()方法可以對幀進行分離,并同步的進入AudioFileStream_PacketsProc回調(diào)方法。
typedef void (*AudioFileStream_PacketsProc)(void * inClientData,
UInt32 numberOfBytes,
UInt32 numberOfPackets,
const void * inInputData,
AudioStreamPacketDescription * inPacketDescriptions);
- 第一個參數(shù),inClientData是初始化時傳入的上下文對象。
- 第二個參數(shù),numberOfBytes表示本次處理的音頻數(shù)據(jù)總量。
- 第三個參數(shù),numberOfPackets表示本次處理的數(shù)據(jù)包數(shù)。
- 第四個參數(shù),inInputData表示本次處理的所有數(shù)據(jù)。
- 第五個參數(shù),AudioStreamPacketDescription數(shù)組,存儲了每一幀數(shù)據(jù)是從第幾個字節(jié)開始的,這一幀總共多少字節(jié)。
//這里的mVariableFramesInPacket是指實際的數(shù)據(jù)幀
//只有VBR的數(shù)據(jù)才能用到(像MP3這樣的壓縮數(shù)據(jù)一個幀里會有好幾個數(shù)據(jù)幀)
struct AudioStreamPacketDescription
{
// 音頻數(shù)據(jù)從哪里開始
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
// 這個數(shù)據(jù)包的大小
UInt32 mDataByteSize;
};
AudioFileStream的具體用法可以戳這里。在Xcode-->Edit Scheme中添加啟動參數(shù)為本地音頻文件的路徑即可。
關(guān)閉AudioFileStream
AudioFileStream使用完畢后需要調(diào)用AudioFileStreamClose()進行關(guān)閉。
extern OSStatus AudioFileStreamClose(AudioFileStreamID inAudioFileStream);
關(guān)于Mr.AudioFileStream的介紹到這里就差不多了,我們接著說它的大兄弟Mr.AudioFile。當然我們討論的是音頻播放相關(guān)的內(nèi)容,對于AudioFile僅會使用到它解析分離音頻幀的部分,對于寫音頻,這里就不討論啦。
初始化AudioFile
AudioFile提供了兩種讀取音頻文件的方法。第一種是通過文件路徑(因此僅能處理本地音頻,略過略過)。第二種是在AudioFile解析分離音頻幀的時候提供音頻數(shù)據(jù)給它。在流播的時候,音頻數(shù)據(jù)正好是一點一點通過HTTP請求返回的,所以我們把返回的數(shù)據(jù)一一提供給AudioFile就可以解析了;對于本地文件,可以用NSFIleHandle讀取數(shù)據(jù)提供給它,效果相同。
OSStatus AudioFileOpenWithCallbacks (void * inClientData,
AudioFile_ReadProc inReadFunc,
AudioFile_WriteProc inWriteFunc,
AudioFile_GetSizeProc inGetSizeFunc,
AudioFile_SetSizeProc inSetSizeFunc,
AudioFileTypeID inFileTypeHint,
AudioFileID * outAudioFile);
- 第一個參數(shù),inClientData是上下文對象。
- 第二個參數(shù),在AudioFile需要數(shù)據(jù)時由inReadFunc回調(diào)提供。
- 第三個參數(shù),inWriteFunc與寫音頻有關(guān),略過。
- 第四個參數(shù),通過inGetSizeFunc回調(diào)告訴AudioFile要解析的音頻文件大小,流播中通過請求頭的Content-Length獲得。
- 第五個參數(shù),inSetSizeFunc與寫音頻有關(guān),略過。
- 第六個參數(shù),inFileTypeHint同AudioFileStream,是文件格式的提示信息,同AudioFileStream。
- 第七個參數(shù),outAudioFile是生成的AudioFile實例,保存起來留給其它函數(shù)當參數(shù)使用。
注意:初始化AudioFile時就需要提供音頻數(shù)據(jù)給它,除此之外在調(diào)用AudioFileReadXXX()相關(guān)方法時也需要提供合適的音頻數(shù)據(jù)給它。
上面說到AudioFile在解析分離音頻幀時需要通過兩個回調(diào)函數(shù)通知它。先來看一下提供音頻數(shù)據(jù)的回調(diào) —— AudioFile_ReadProc。
typedef OSStatus (*AudioFile_ReadProc)(void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount);
- 第一個參數(shù),inClientData是初始化時傳遞的上下文對象。
- 第二個參數(shù),inPosition指明了AudioFile需要從什么位置開始讀取音頻數(shù)據(jù),也就是說從第幾個字節(jié)開始。
- 第三個參數(shù),requestCount指明AudioFile請求讀取的數(shù)據(jù)量,只是請求,不代表最后讀取的數(shù)據(jù)量。
- 第四個參數(shù),buffer是一個數(shù)據(jù)指針并且其空間已經(jīng)被分配,我們需要做的是把數(shù)據(jù)memcpy到buffer中。
- 第五個參數(shù),actualCount是實際提供的數(shù)據(jù)長度,即memcpy到buffer中的數(shù)據(jù)長度。
- 如果沒有出錯,返回noErr即可。
這里需要解釋一下這個回調(diào)方法的工作方式。AudioFile需要數(shù)據(jù)時會調(diào)用回調(diào)方法,需要數(shù)據(jù)的時間點有兩個:
- AudioFileOpenWithCallbacks()方法調(diào)用時,由于AudioFile的open方法調(diào)用過程中就會對音頻格式信息進行解析,只有符合要求的音頻格式才能被成功打開否則open方法就會返回錯誤碼(換句話說,open方法一旦調(diào)用成功就相當于AudioFileStream調(diào)用AudioFileStreamParseBytes()后返回ReadyToProducePackets
一樣,只要open成功就可以開始讀取音頻數(shù)據(jù),所以在open方法調(diào)用的過程中就需要提供一部分音頻數(shù)據(jù)來進行解析。 - AudioFileReadXXX()相關(guān)方法調(diào)用時,讀取數(shù)據(jù)時當然需要提供數(shù)據(jù)了。
通過回調(diào)提供數(shù)據(jù)時需要注意inPosition和requestCount參數(shù),這兩個參數(shù)指明了本次回調(diào)需要提供的數(shù)據(jù)范圍是從inPosition開始的 requestCount個連續(xù)字節(jié)的數(shù)據(jù)。這里又可以分為兩種情況:
- 有充足的數(shù)據(jù):那么我們需要把這個范圍內(nèi)的數(shù)據(jù)拷貝到buffer中,并且給actualCount賦值requestCount,最后返回noError;
- 數(shù)據(jù)不足:沒有充足數(shù)據(jù)的話就只能把手頭有的數(shù)據(jù)拷貝到buffer中,需要注意的是這部分被拷貝的數(shù)據(jù)必須是從inPosition開始的連續(xù)數(shù)據(jù),拷貝完成后給actualCount賦值實際拷貝進buffer中的數(shù)據(jù)長度后返回noErr,這個過程可以用下面的代碼來表示:
// totalData表示當前擁有的所有音頻數(shù)據(jù),NSData類型
static OSStatus mAudioFile_ReadProc(
void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount)
{
// 如果需要讀取的長度超過擁有的數(shù)據(jù)長度
if (inPosition + requestCount > [totalData length]) {
// 如果讀取起點的位置已經(jīng)超過或等于擁有的數(shù)據(jù)長度了
if (inPosition >= [totalData length]) {
// 此時真正讀取長度就沒有了
*actualCount = 0;
}else{
// 否則總共擁有的數(shù)據(jù)長度減去起點就是能讀到的所有數(shù)據(jù)了
*actualCount = (UInt32)([totalData length] - inPosition);
}
}else{
// 若是不比擁有的數(shù)據(jù)長度大
// 真正讀取的就是請求的長度
*actualCount = requestCount;
}
// EOF 整個文件讀取結(jié)束
if (*actualCount == 0) return noErr;
// 最后將從inPosition開始,長度為actualCount的數(shù)據(jù)拷貝到buffer中
memcpy(buffer, (uint8_t *)[totalData bytes] + inPosition, *actualCount);
// 返回noErr
return noErr;
}
說到這里又需要分兩種情況(oh-oh,媽媽再也不用擔心我不會分類了):
- AudioFileOpenWithCallbacks()方法調(diào)用時的回調(diào)數(shù)據(jù)不足:AudioFile的Open方法會根據(jù)音頻文件格式分幾步進行數(shù)據(jù)讀取,接著解析以確定是否是一個合法的文件格式,其中每一步的inPosition和requestCount都不一樣,如果某一步不成功就會直接進行下一步,如果幾部下來都失敗了,那么open方法就會失敗。簡單的說就是在調(diào)用open之前首先需要保證音頻文件的格式信息完整,這就意味著AudioFile并不能獨立用于音頻流的讀取,在流播放時首先需要使用AudioStreamFile來得到ReadyToProducePackets標志位來保證信息完整;
- AudioFileReadXXX()方法調(diào)用時的回調(diào)數(shù)據(jù)不足:這種情況下inPosition和requestCount的數(shù)值與AudioFileReadXXX()方法調(diào)用時傳入的參數(shù)有關(guān),數(shù)據(jù)不足對于Read方法本身沒有影響,只要回調(diào)返回noErr,AudioFileReadXXX()就成功,只是實際交給AudioFileReadXXX()方法的調(diào)用方的數(shù)據(jù)會不足,那么就把這個問題的處理交給了AudioFileReadXXX()的調(diào)用方,對應(yīng)播放器的狀態(tài)就是buffering;
解析音頻信息
讀數(shù)據(jù)時AudioFile和AudioFileStream差不多,成功打開AudioFile之后就可以獲取歌曲信息了,包括比特率,音頻時長等。
OSStatus AudioFileGetPropertyInfo(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * outDataSize,
UInt32 * isWritable);
OSStatus AudioFileGetProperty(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * ioDataSize,
void * outPropertyData);
AudioFileGetPropertyInfo方法用來獲取某個屬性對應(yīng)的數(shù)據(jù)的大?。╫utDataSize)以及該屬性是否可以被write(isWritable),而AudioFileGetProperty則用來獲取屬性對應(yīng)的數(shù)據(jù)。對于一些大小可變的屬性需要先使用AudioFileGetPropertyInfo獲取數(shù)據(jù)大小才能取獲取數(shù)據(jù)(例如formatList),而有些確定類型單個屬性則不必先調(diào)用AudioFileGetPropertyInfo直接調(diào)用AudioFileGetProperty即可。
- (BOOL)_fillFileFormat
{
UInt32 size;
OSStatus status;
// 支持AAC SBR類型的文件
// kAudioFilePropertyFormatList返回的是AudioFormatListItem數(shù)組
status = AudioFileGetPropertyInfo(_fileID, kAudioFilePropertyFormatList, &size, NULL);
if (status != noErr) {
return NO;
}
// 求出有多少個
UInt32 numFormats = size / sizeof(AudioFormatListItem);
// 分配好內(nèi)存
AudioFormatListItem *formatList = (AudioFormatListItem *)malloc(size);
// 獲取值
status = AudioFileGetProperty(_fileID, kAudioFilePropertyFormatList, &size, formatList);
if (status != noErr) {
free(formatList);
return NO;
}
// 只有一個的話直接取出來
if (numFormats == 1) {
_fileFormat = formatList[0].mASBD;
}
else {
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size);
if (status != noErr) {
free(formatList);
return NO;
}
UInt32 numDecoders = size / sizeof(OSType);
OSType *decoderIDS = (OSType *)malloc(size);
status = AudioFormatGetProperty(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &size, decoderIDS);
if (status != noErr) {
free(formatList);
free(decoderIDS);
return NO;
}
UInt32 i;
for (i = 0; i < numFormats; ++i) {
OSType decoderID = formatList[i].mASBD.mFormatID;
BOOL found = NO;
for (UInt32 j = 0; j < numDecoders; ++j) {
if (decoderID == decoderIDS[j]) {
found = YES;
break;
}
}
if (found) {
break;
}
}
free(decoderIDS);
if (i >= numFormats) {
free(formatList);
return NO;
}
_fileFormat = formatList[i].mASBD;
}
free(formatList);
return YES;
}
- (BOOL)_fillMiscProperties
{
UInt32 size;
OSStatus status;
UInt32 bitRate = 0;
size = sizeof(bitRate);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyBitRate, &size, &bitRate);
if (status != noErr) {
return NO;
}
_bitRate = bitRate;
SInt64 dataOffset = 0;
size = sizeof(dataOffset);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyDataOffset, &size, &dataOffset);
if (status != noErr) {
return NO;
}
_dataOffset = (NSUInteger)dataOffset;
Float64 estimatedDuration = 0.0;
size = sizeof(estimatedDuration);
status = AudioFileGetProperty(_fileID, kAudioFilePropertyEstimatedDuration, &size, &estimatedDuration);
if (status != noErr) {
return NO;
}
_estimatedDuration = estimatedDuration;
return YES;
}
讀取音頻數(shù)據(jù)
讀取音頻數(shù)據(jù)的方法分為兩類:
- 直接讀取音頻數(shù)據(jù):
OSStatus AudioFileReadBytes (AudioFileID inAudioFile,
Boolean inUseCache,
SInt64 inStartingByte,
UInt32 * ioNumBytes,
void * outBuffer);
- 第一個參數(shù),F(xiàn)ileID。
- 第二個參數(shù),是否需要cache,一般來說傳false。
- 第三個參數(shù),從第幾個byte開始讀取數(shù)據(jù)。
- 第四個參數(shù),這個參數(shù)在調(diào)用時作為輸入?yún)?shù)表示需要讀取讀取多少數(shù)據(jù),調(diào)用完成后作為輸出參數(shù)表示實際讀取了多少數(shù)據(jù)(即Read回調(diào)中的requestCount和actualCount)。
- 第五個參數(shù),buffer指針,需要事先分配好足夠大的內(nèi)存(ioNumBytes大,即Read回調(diào)中的buffer,所以Read回調(diào)中不需要再分配內(nèi)存)。
- 返回值表示是否讀取成功,EOF時會返回kAudioFileEndOfFileError。
注意:使用這個方法得到的數(shù)據(jù)都是沒有進行過幀分離的數(shù)據(jù),如果想要用來播放或者解碼還必須通過AudioFileStream進行幀分離。
- 按包(Packet)讀取音頻數(shù)據(jù):
OSStatus AudioFileReadPacketData (AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * ioNumBytes,
AudioStreamPacketDescription * outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * outBuffer);
OSStatus AudioFileReadPackets (AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * outNumBytes,
AudioStreamPacketDescription * outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * outBuffer);
按包讀取的方法有兩個,這兩個方法看上去差不多,就連參數(shù)也幾乎相同,但使用場景和效率上卻有所不同。只有當需要讀取固定時長音頻或者非壓縮音頻時才會用到AudioFileReadPackets(),其余時候使用AudioFileReadPacketData()會有更高的效率并且更省內(nèi)存(所以AudioFileReadPackets()已經(jīng)被標記為deprecated~~);
下面來看看這些參數(shù):
- 第一、二個參數(shù),同AudioFileReadBytes。
- 第三個參數(shù),對于AudioFileReadPacketData()來說ioNumBytes這個參數(shù)在輸入輸出時都要用到,在輸入時表示outBuffer的size,輸出時表示實際讀取了多少size的數(shù)據(jù)。而對AudioFileReadPackets()來說outNumBytes只在輸出時使用,表示實際讀取了多少size的數(shù)據(jù);
- 第四個參數(shù),幀信息數(shù)組指針,在輸入前需要分配內(nèi)存,大小必須足夠存儲ioNumPackets個幀信息(ioNumPackets * sizeof(AudioStreamPacketDescription))。
- 第五個參數(shù),從第幾幀開始讀取數(shù)據(jù)。
- 第六個參數(shù),在輸入時表示需要讀取多少個幀,在輸出時表示實際讀取了多少幀。
- 第七個參數(shù),outBuffer數(shù)據(jù)指針,在輸入前就需要分配好空間,這個參數(shù)看上去兩個方法一樣但其實并非如此。對于AudioFileReadPacketData()來說只要分配近似幀大小 * 幀數(shù)的內(nèi)存空間即可,方法本身會針對給定的內(nèi)存空間大小來決定最后輸出多少個幀,如果空間不夠會適當減少出的幀數(shù);而對于AudioFileReadPackets()來說則需要分配最大幀大小(或幀大小上界) * 幀數(shù)的內(nèi)存空間才行;這也就是為何第三個參數(shù)一個是輸入輸出雙向使用的,而另一個只是輸出時使用的原因。就這點來說兩個方法中前者在使用的過程中要比后者更省內(nèi)存。
- 返回值,同AudioFileReadBytes。
這兩個方法讀取后的數(shù)據(jù)為幀分離后的數(shù)據(jù),可以直接用來播放或者解碼。具體使用可以參考這里;
關(guān)閉AudioFile
AudioFile使用完畢后需要調(diào)用AudioFileClose進行關(guān)閉。
extern OSStatus AudioFileClose (AudioFileID inAudioFile);
下一篇會介紹AudioConverter。
說明:很多話我是直接從這里直接拿過來的,作者總結(jié)的非常好,可能我表述來表述去也就那么個意思,所以就直接拿來用了。有些地方,包括我自己遇到的坑,會做一些補充說明,大家知道就好。