本篇我們介紹AudioFile和AudioFileStream项栏。在第一篇技術(shù)棧的分析里,我們提到過(guò)AudioFile和AudioFileStream都可以用來(lái)解析采樣率嫂用、碼率型凳、時(shí)長(zhǎng)等信息,分離原始音頻數(shù)據(jù)中的音頻幀嘱函。這兩個(gè)都可以使用在流式播放中甘畅,當(dāng)然,不僅限于流播,本地音頻也一樣可以使用疏唾。
??可能有的小伙伴會(huì)有疑問(wèn)蓄氧,既然它們倆功能相似,選擇其中一個(gè)不就可以了嗎槐脏?其實(shí)不然喉童,AudioFile的功能遠(yuǎn)比AudioFileStream強(qiáng)大,除了共同的解析音頻數(shù)據(jù)分離音頻幀之外顿天,它還可以讀取音頻數(shù)據(jù)堂氯,甚至可以寫(xiě)音頻(生成音頻文件),而AudioFileStream本身沒(méi)有讀取音頻數(shù)據(jù)的功能露氮∽婊遥看起來(lái)選擇AudioFile就OK了,不料AudioFile卻需要AudioFileStream來(lái)保證數(shù)據(jù)的完整性畔规,否則會(huì)大大增加出錯(cuò)的可能性(別急別急局扶,且看下文一一道來(lái))。
??下面我們就先認(rèn)識(shí)一下Mr.AudioFileStream叁扫。
初始化AudioFileStream
首先當(dāng)然是調(diào)用AudioFileStreamOpen()生成一個(gè)AudioFileStream實(shí)例三妈,函數(shù)聲明如下:
OSStatus
AudioFileStreamOpen (
void * __nullable inClientData,
AudioFileStream_PropertyListenerProc inPropertyListenerProc,
AudioFileStream_PacketsProc inPacketsProc,
AudioFileTypeID inFileTypeHint,
AudioFileStreamID __nullable * __nonnull outAudioFileStream)
- 第一個(gè)參數(shù),inClientData是一個(gè)上下文對(duì)象莫绣,會(huì)回傳給回調(diào)函數(shù)畴蒲。必須保證inClientData生命周期足夠長(zhǎng),否則在回調(diào)函數(shù)里使用的時(shí)候就是一個(gè)野指針了对室。一般我們會(huì)傳自身(self)模燥,在回調(diào)函數(shù)取出后再調(diào)用自身的實(shí)例方法(C和OC雖然可以混編,但是C函數(shù)并不是作為OC類的實(shí)例方法存在的掩宜,所以C函數(shù)里的self并不指代OC對(duì)象)蔫骂。
- 第二個(gè)參數(shù),inPropertyListenerProc是歌曲信息解析的回調(diào)牺汤,每解析出一個(gè)就會(huì)進(jìn)行一次回調(diào)辽旋。
- 第三個(gè)參數(shù),inPacketsProc是分離音頻幀的回調(diào)檐迟,每解析出一部分音頻幀就會(huì)進(jìn)行一次回調(diào)补胚。
- 第四個(gè)參數(shù),inFileTypeHint是音頻文件格式的描述信息追迟,這個(gè)參數(shù)來(lái)幫助AudioFileStream對(duì)文件格式進(jìn)行解析溶其。這個(gè)參數(shù)在文件信息不完整(例如信息有缺陷)時(shí)尤其有用,它可以給與AudioFileStream一定的提示怔匣,幫助其繞過(guò)文件中的錯(cuò)誤或者缺失從而成功解析文件握联,如果無(wú)法確定可以傳入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'
};
- 第五個(gè)參數(shù),outAudioFileStream代表生成的AudioFileStream實(shí)例金闽,這個(gè)參數(shù)必須保存起來(lái)作為后續(xù)一些方法的參數(shù)使用纯露。
- 返回值表示是否調(diào)用成功(status == noErr),關(guān)于OSStatus的解釋,可以參閱這里代芜。
注意:在播放網(wǎng)絡(luò)音頻時(shí)埠褪,很多鏈接并沒(méi)有指明音頻格式,此時(shí)可以根據(jù)MIME type來(lái)確定音頻格式挤庇,而本地音頻可以根據(jù)文件擴(kuò)展名確定钞速。MIME type與擴(kuò)展名有關(guān),用于確定文件的類型嫡秕。 在HTTP請(qǐng)求中渴语,MIME type通過(guò)請(qǐng)求頭中的 Content-Type 表示。iOS中可通過(guò) <MobileCoreServices/UTType.h> 中定義的相關(guān)方法可以實(shí)現(xiàn) fileExtension <--> UTType <--> mimeType 的互轉(zhuǎn)昆咽。具體轉(zhuǎn)換方法可以看我之前的一篇博文驾凶。
解析音頻數(shù)據(jù)
上文AudioFileStream并沒(méi)有提供讀取音頻數(shù)據(jù)的接口,所以音頻數(shù)據(jù)的讀取需要自行實(shí)現(xiàn)掷酗。本地播放可以通過(guò)NSFileHandle提供的接口调违,流播時(shí)通過(guò)HTTP請(qǐng)求獲得。在得到音頻數(shù)據(jù)之后泻轰,調(diào)用AudioFileStreamParseBytes()就可以進(jìn)行解析了技肩。
OSStatus
AudioFileStreamParseBytes(
AudioFileStreamID inAudioFileStream,
UInt32 inDataByteSize,
const void * inData,
AudioFileStreamParseFlags inFlags)
- 第一個(gè)參數(shù),inAudioFileStream是初始化時(shí)得到的AudioFileStreamID浮声。
- 第二個(gè)參數(shù)虚婿,inDataByteSize是本次解析的數(shù)據(jù)長(zhǎng)度。
- 第三個(gè)參數(shù)泳挥,inData是本次解析的音頻數(shù)據(jù)雳锋。
- 第四個(gè)參數(shù),inFlags表示本次解析與上一次是否是連續(xù)關(guān)系羡洁。在第一篇中我們提到過(guò)形如MP3的數(shù)據(jù)都以幀的形式存在的,解析時(shí)也需要以幀為單位解析爽丹。但在解碼之前我們不可能知道每個(gè)幀的邊界在第幾個(gè)字節(jié)筑煮,所以就會(huì)出現(xiàn)這樣的情況:我們傳給AudioFileStreamParseBytes的數(shù)據(jù)在解析完成之后會(huì)有一部分?jǐn)?shù)據(jù)余下來(lái),這部分?jǐn)?shù)據(jù)是接下去那一幀的前半部分粤蝎,如果再次有數(shù)據(jù)輸入需要繼續(xù)解析時(shí)就必須要用到前一次解析余下來(lái)的數(shù)據(jù)才能保證幀數(shù)據(jù)完整真仲,所以在正常播放的情況下傳入0即可。需要傳入kAudioFileStreamParseFlag_Discontinuity的情況有兩個(gè)初澎,一個(gè)是在seek完畢之后秸应,顯然seek后的數(shù)據(jù)和之前的數(shù)據(jù)完全無(wú)關(guān)虑凛;另一個(gè)和AudioFileStream的bug有關(guān),在回調(diào)得到kAudioFileStreamProperty_ReadyToProducePackets之后软啼,在正常解析第一包之前最好都傳入kAudioFileStreamParseFlag_Discontinuity桑谍。
- 返回值表示本次解析是否成功(同樣status == noErr)。
注意:AudioFileStreamParseBytes()函數(shù)每次調(diào)用都必須檢查返回值祸挪,一旦出錯(cuò)就沒(méi)有必要繼續(xù)解析了锣披。注意一下若是返回這個(gè)kAudioFileStreamError_NotOptimized,說(shuō)明這個(gè)音頻文件無(wú)法流播贿条,只能下載完所有數(shù)據(jù)才能播放雹仿。
解析歌曲信息
調(diào)用AudioFileStreamParseBytes()之后首先會(huì)解析歌曲信息,每解析出一個(gè)整以,同步回調(diào)AudioFileStream_PropertyListenerProc胧辽。
typedef void (*AudioFileStream_PropertyListenerProc)(
void * inClientData,
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
AudioFileStreamPropertyFlags * ioFlags)
- 第一個(gè)參數(shù),inClientData是初始化時(shí)指定的上下文信息公黑。
- 第二個(gè)參數(shù)邑商,inAudioFileStream指代AudioFileStream對(duì)象。
- 第三個(gè)參數(shù)帆调,inPropertyID表示音頻的信息奠骄,可以通過(guò)AudioFileStreamGetProperty()函數(shù)取值。
- 第四個(gè)參數(shù)番刊,ioFlags表示這個(gè)property是否需要被緩存含鳞。
來(lái)看一下AudioFileStreamGetProperty()函數(shù)。
OSStatus
AudioFileStreamGetProperty(
AudioFileStreamID inAudioFileStream,
AudioFileStreamPropertyID inPropertyID,
UInt32 * ioPropertyDataSize,
void * outPropertyData)
- 第一個(gè)參數(shù)芹务,inAudioFileStream指代AudioFileStream對(duì)象蝉绷。
- 第二個(gè)參數(shù),inPropertyID表示想獲取哪個(gè)property枣抱。
- 第三個(gè)參數(shù)熔吗,想要獲取的property所表示的數(shù)據(jù)結(jié)構(gòu)大小,對(duì)于大小不定的propertyID佳晶,需要先調(diào)用AudioFileStreamGetPropertyInfo()函數(shù)先獲取一下大小桅狠,比如kAudioFileStreamProperty_FormatList。
- 第四個(gè)參數(shù)轿秧,outPropertyData是一個(gè)返回參數(shù)中跌,會(huì)返回獲取的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'
};
幾個(gè)比較有用的propertyID
- kAudioFileStreamProperty_DataOffset:第一篇提到MP3文件有一個(gè)頭信息菇篡,之后才是真正的音頻數(shù)據(jù)漩符,這個(gè)屬性表示的就是頭信息的大小,在seek操作時(shí)有比較大的作用驱还。對(duì)于用戶來(lái)講嗜暴,seek操作操作的是時(shí)間凸克,但對(duì)于編碼來(lái)講,我們seek的是文件位置闷沥,seek時(shí)會(huì)根據(jù)時(shí)間計(jì)算出音頻數(shù)據(jù)的字節(jié)offset然后需要再加上音頻數(shù)據(jù)的offset才能得到在文件中的真正offset萎战。。
// 注意數(shù)據(jù)類型一定不能錯(cuò)狐赡,否則會(huì)獲取不到想要的結(jié)果撞鹉。
SInt64 dataOffset;
UInt32 offsetSize = sizeof(dataOffset);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &dataOffset);
if (status != noErr)
{
//錯(cuò)誤處理
}
- kAudioFileStreamProperty_AudioDataByteCount:表示真正可播放的音頻數(shù)據(jù)大小(除去頭信息)颖侄,很明顯也可以這么計(jì)算audioDataByteCount = fileSize - dataOffset鸟雏。
UInt64 audioDataByteCount;
UInt32 byteCountSize = sizeof(audioDataByteCount);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount);
if (status != noErr)
{
//錯(cuò)誤處理
}
- kAudioFileStreamProperty_BitRate:獲取碼率可以用來(lái)計(jì)算音頻時(shí)長(zhǎng)(AudioFileStream沒(méi)有提供直接獲取音頻時(shí)長(zhǎng)的接口)。文件大小與碼率的關(guān)系在第一篇提到過(guò)览祖,從而可得 duration = ((fileSize - dataOffset) * 8) / bitRate孝鹊。
UInt32 bitRate;
UInt32 bitRateSize = sizeof(bitRate);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_BitRate, &bitRateSize, &bitRate);
if (status != noErr)
{
//錯(cuò)誤處理
}
- kAudioFileStreamProperty_DataFormat:表示音頻文件結(jié)構(gòu)信息,是一個(gè)AudioStreamBasicDescription的結(jié)構(gòu)體展蒂。
struct AudioStreamBasicDescription
{
// 采樣率
Float64 mSampleRate;
// 音頻的類型又活,MP3 or WAV
AudioFormatID mFormatID;
// 隨mFormatID而定
AudioFormatFlags mFormatFlags;
// 每個(gè)數(shù)據(jù)包中的字節(jié)數(shù)
UInt32 mBytesPerPacket;
// 每個(gè)數(shù)據(jù)包的幀數(shù)(第一篇提到過(guò),原始數(shù)據(jù)如PCM一包一幀锰悼,壓縮格式如MP3一包多幀)
UInt32 mFramesPerPacket;
// 每幀的字節(jié)數(shù)
UInt32 mBytesPerFrame;
// 每幀的聲道數(shù)
UInt32 mChannelsPerFrame;
// 每個(gè)聲道的采樣位數(shù)
UInt32 mBitsPerChannel;
// 與內(nèi)存對(duì)齊有關(guān)
UInt32 mReserved;
};
// 獲取format
AudioStreamBasicDescription asbd;
UInt32 asbdSize = sizeof(asbd);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
if (status != noErr)
{
//錯(cuò)誤處理
}
- kAudioFileStreamProperty_FormatList:作用和kAudioFileStreamProperty_DataFormat是一樣的柳骄,區(qū)別在于用這個(gè)PropertyID獲取到是一個(gè)AudioStreamBasicDescription的數(shù)組,這個(gè)參數(shù)是用來(lái)支持AAC箕般,SBR這樣的包含多個(gè)文件類型的音頻格式耐薯。由于到底有多少個(gè)format我們并不知曉,所以需要先獲取一下總數(shù)據(jù)大兴坷铩:
//獲取數(shù)據(jù)大小
Boolean outWriteable;
UInt32 formatListSize;
OSStatus status = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);
if (status != noErr)
{
//錯(cuò)誤處理
}
//獲取formatlist
AudioFormatListItem *formatList = malloc(formatListSize);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);
if (status != noErr)
{
//錯(cuò)誤處理
}
//選擇需要的格式
for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i++)
{
AudioStreamBasicDescription pasbd = formatList[i].mASBD;
//選擇需要的格式曲初。。
}
free(formatList);
- kAudioFileStreamProperty_ReadyToProducePackets:這個(gè)PropertyID可以不必獲取對(duì)應(yīng)的值杯聚,一旦回調(diào)中這個(gè)PropertyID出現(xiàn)就代表解析完成臼婆,接下來(lái)可以對(duì)音頻數(shù)據(jù)進(jìn)行幀分離了。
分離音頻幀
歌曲信息讀取完整后幌绍,繼續(xù)調(diào)用AudioFileStreamParseBytes()方法可以對(duì)幀進(jìn)行分離颁褂,并同步的進(jìn)入AudioFileStream_PacketsProc回調(diào)方法。
typedef void (*AudioFileStream_PacketsProc)(void * inClientData,
UInt32 numberOfBytes,
UInt32 numberOfPackets,
const void * inInputData,
AudioStreamPacketDescription * inPacketDescriptions);
- 第一個(gè)參數(shù)傀广,inClientData是初始化時(shí)傳入的上下文對(duì)象痢虹。
- 第二個(gè)參數(shù),numberOfBytes表示本次處理的音頻數(shù)據(jù)總量主儡。
- 第三個(gè)參數(shù),numberOfPackets表示本次處理的數(shù)據(jù)包數(shù)惨缆。
- 第四個(gè)參數(shù)糜值,inInputData表示本次處理的所有數(shù)據(jù)丰捷。
- 第五個(gè)參數(shù),AudioStreamPacketDescription數(shù)組寂汇,存儲(chǔ)了每一幀數(shù)據(jù)是從第幾個(gè)字節(jié)開(kāi)始的病往,這一幀總共多少字節(jié)。
//這里的mVariableFramesInPacket是指實(shí)際的數(shù)據(jù)幀
//只有VBR的數(shù)據(jù)才能用到(像MP3這樣的壓縮數(shù)據(jù)一個(gè)幀里會(huì)有好幾個(gè)數(shù)據(jù)幀)
struct AudioStreamPacketDescription
{
// 音頻數(shù)據(jù)從哪里開(kāi)始
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
// 這個(gè)數(shù)據(jù)包的大小
UInt32 mDataByteSize;
};
AudioFileStream的具體用法可以戳這里骄瓣。在Xcode-->Edit Scheme中添加啟動(dòng)參數(shù)為本地音頻文件的路徑即可停巷。
關(guān)閉AudioFileStream
AudioFileStream使用完畢后需要調(diào)用AudioFileStreamClose()進(jìn)行關(guān)閉。
extern OSStatus AudioFileStreamClose(AudioFileStreamID inAudioFileStream);
關(guān)于Mr.AudioFileStream的介紹到這里就差不多了榕栏,我們接著說(shuō)它的大兄弟Mr.AudioFile畔勤。當(dāng)然我們討論的是音頻播放相關(guān)的內(nèi)容,對(duì)于AudioFile僅會(huì)使用到它解析分離音頻幀的部分扒磁,對(duì)于寫(xiě)音頻庆揪,這里就不討論啦。
初始化AudioFile
AudioFile提供了兩種讀取音頻文件的方法妨托。第一種是通過(guò)文件路徑(因此僅能處理本地音頻缸榛,略過(guò)略過(guò))。第二種是在AudioFile解析分離音頻幀的時(shí)候提供音頻數(shù)據(jù)給它兰伤。在流播的時(shí)候内颗,音頻數(shù)據(jù)正好是一點(diǎn)一點(diǎn)通過(guò)HTTP請(qǐng)求返回的,所以我們把返回的數(shù)據(jù)一一提供給AudioFile就可以解析了敦腔;對(duì)于本地文件均澳,可以用NSFIleHandle讀取數(shù)據(jù)提供給它,效果相同会烙。
OSStatus AudioFileOpenWithCallbacks (void * inClientData,
AudioFile_ReadProc inReadFunc,
AudioFile_WriteProc inWriteFunc,
AudioFile_GetSizeProc inGetSizeFunc,
AudioFile_SetSizeProc inSetSizeFunc,
AudioFileTypeID inFileTypeHint,
AudioFileID * outAudioFile);
- 第一個(gè)參數(shù)负懦,inClientData是上下文對(duì)象。
- 第二個(gè)參數(shù)柏腻,在AudioFile需要數(shù)據(jù)時(shí)由inReadFunc回調(diào)提供纸厉。
- 第三個(gè)參數(shù),inWriteFunc與寫(xiě)音頻有關(guān)五嫂,略過(guò)颗品。
- 第四個(gè)參數(shù),通過(guò)inGetSizeFunc回調(diào)告訴AudioFile要解析的音頻文件大小沃缘,流播中通過(guò)請(qǐng)求頭的Content-Length獲得躯枢。
- 第五個(gè)參數(shù),inSetSizeFunc與寫(xiě)音頻有關(guān)槐臀,略過(guò)锄蹂。
- 第六個(gè)參數(shù),inFileTypeHint同AudioFileStream水慨,是文件格式的提示信息得糜,同AudioFileStream敬扛。
- 第七個(gè)參數(shù),outAudioFile是生成的AudioFile實(shí)例朝抖,保存起來(lái)留給其它函數(shù)當(dāng)參數(shù)使用啥箭。
注意:初始化AudioFile時(shí)就需要提供音頻數(shù)據(jù)給它,除此之外在調(diào)用AudioFileReadXXX()相關(guān)方法時(shí)也需要提供合適的音頻數(shù)據(jù)給它治宣。
上面說(shuō)到AudioFile在解析分離音頻幀時(shí)需要通過(guò)兩個(gè)回調(diào)函數(shù)通知它急侥。先來(lái)看一下提供音頻數(shù)據(jù)的回調(diào) —— AudioFile_ReadProc。
typedef OSStatus (*AudioFile_ReadProc)(void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount);
- 第一個(gè)參數(shù)侮邀,inClientData是初始化時(shí)傳遞的上下文對(duì)象坏怪。
- 第二個(gè)參數(shù),inPosition指明了AudioFile需要從什么位置開(kāi)始讀取音頻數(shù)據(jù)豌拙,也就是說(shuō)從第幾個(gè)字節(jié)開(kāi)始陕悬。
- 第三個(gè)參數(shù),requestCount指明AudioFile請(qǐng)求讀取的數(shù)據(jù)量按傅,只是請(qǐng)求捉超,不代表最后讀取的數(shù)據(jù)量。
- 第四個(gè)參數(shù)唯绍,buffer是一個(gè)數(shù)據(jù)指針并且其空間已經(jīng)被分配拼岳,我們需要做的是把數(shù)據(jù)memcpy到buffer中。
- 第五個(gè)參數(shù)况芒,actualCount是實(shí)際提供的數(shù)據(jù)長(zhǎng)度惜纸,即memcpy到buffer中的數(shù)據(jù)長(zhǎng)度。
- 如果沒(méi)有出錯(cuò)绝骚,返回noErr即可耐版。
這里需要解釋一下這個(gè)回調(diào)方法的工作方式。AudioFile需要數(shù)據(jù)時(shí)會(huì)調(diào)用回調(diào)方法压汪,需要數(shù)據(jù)的時(shí)間點(diǎn)有兩個(gè):
- AudioFileOpenWithCallbacks()方法調(diào)用時(shí)粪牲,由于AudioFile的open方法調(diào)用過(guò)程中就會(huì)對(duì)音頻格式信息進(jìn)行解析,只有符合要求的音頻格式才能被成功打開(kāi)否則open方法就會(huì)返回錯(cuò)誤碼(換句話說(shuō)止剖,open方法一旦調(diào)用成功就相當(dāng)于AudioFileStream調(diào)用AudioFileStreamParseBytes()后返回ReadyToProducePackets
一樣腺阳,只要open成功就可以開(kāi)始讀取音頻數(shù)據(jù),所以在open方法調(diào)用的過(guò)程中就需要提供一部分音頻數(shù)據(jù)來(lái)進(jìn)行解析穿香。 - AudioFileReadXXX()相關(guān)方法調(diào)用時(shí)亭引,讀取數(shù)據(jù)時(shí)當(dāng)然需要提供數(shù)據(jù)了。
通過(guò)回調(diào)提供數(shù)據(jù)時(shí)需要注意inPosition和requestCount參數(shù)皮获,這兩個(gè)參數(shù)指明了本次回調(diào)需要提供的數(shù)據(jù)范圍是從inPosition開(kāi)始的 requestCount個(gè)連續(xù)字節(jié)的數(shù)據(jù)焙蚓。這里又可以分為兩種情況:
- 有充足的數(shù)據(jù):那么我們需要把這個(gè)范圍內(nèi)的數(shù)據(jù)拷貝到buffer中,并且給actualCount賦值requestCount,最后返回noError;
- 數(shù)據(jù)不足:沒(méi)有充足數(shù)據(jù)的話就只能把手頭有的數(shù)據(jù)拷貝到buffer中,需要注意的是這部分被拷貝的數(shù)據(jù)必須是從inPosition開(kāi)始的連續(xù)數(shù)據(jù)滞项,拷貝完成后給actualCount賦值實(shí)際拷貝進(jìn)buffer中的數(shù)據(jù)長(zhǎng)度后返回noErr筹麸,這個(gè)過(guò)程可以用下面的代碼來(lái)表示:
// totalData表示當(dāng)前擁有的所有音頻數(shù)據(jù),NSData類型
static OSStatus mAudioFile_ReadProc(
void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount)
{
// 如果需要讀取的長(zhǎng)度超過(guò)擁有的數(shù)據(jù)長(zhǎng)度
if (inPosition + requestCount > [totalData length]) {
// 如果讀取起點(diǎn)的位置已經(jīng)超過(guò)或等于擁有的數(shù)據(jù)長(zhǎng)度了
if (inPosition >= [totalData length]) {
// 此時(shí)真正讀取長(zhǎng)度就沒(méi)有了
*actualCount = 0;
}else{
// 否則總共擁有的數(shù)據(jù)長(zhǎng)度減去起點(diǎn)就是能讀到的所有數(shù)據(jù)了
*actualCount = (UInt32)([totalData length] - inPosition);
}
}else{
// 若是不比擁有的數(shù)據(jù)長(zhǎng)度大
// 真正讀取的就是請(qǐng)求的長(zhǎng)度
*actualCount = requestCount;
}
// EOF 整個(gè)文件讀取結(jié)束
if (*actualCount == 0) return noErr;
// 最后將從inPosition開(kāi)始将宪,長(zhǎng)度為actualCount的數(shù)據(jù)拷貝到buffer中
memcpy(buffer, (uint8_t *)[totalData bytes] + inPosition, *actualCount);
// 返回noErr
return noErr;
}
說(shuō)到這里又需要分兩種情況(oh-oh绘闷,媽媽再也不用擔(dān)心我不會(huì)分類了):
- AudioFileOpenWithCallbacks()方法調(diào)用時(shí)的回調(diào)數(shù)據(jù)不足:AudioFile的Open方法會(huì)根據(jù)音頻文件格式分幾步進(jìn)行數(shù)據(jù)讀取,接著解析以確定是否是一個(gè)合法的文件格式较坛,其中每一步的inPosition和requestCount都不一樣印蔗,如果某一步不成功就會(huì)直接進(jìn)行下一步,如果幾部下來(lái)都失敗了丑勤,那么open方法就會(huì)失敗华嘹。簡(jiǎn)單的說(shuō)就是在調(diào)用open之前首先需要保證音頻文件的格式信息完整,這就意味著AudioFile并不能獨(dú)立用于音頻流的讀取法竞,在流播放時(shí)首先需要使用AudioStreamFile來(lái)得到ReadyToProducePackets標(biāo)志位來(lái)保證信息完整耙厚;
- AudioFileReadXXX()方法調(diào)用時(shí)的回調(diào)數(shù)據(jù)不足:這種情況下inPosition和requestCount的數(shù)值與AudioFileReadXXX()方法調(diào)用時(shí)傳入的參數(shù)有關(guān),數(shù)據(jù)不足對(duì)于Read方法本身沒(méi)有影響岔霸,只要回調(diào)返回noErr薛躬,AudioFileReadXXX()就成功,只是實(shí)際交給AudioFileReadXXX()方法的調(diào)用方的數(shù)據(jù)會(huì)不足呆细,那么就把這個(gè)問(wèn)題的處理交給了AudioFileReadXXX()的調(diào)用方型宝,對(duì)應(yīng)播放器的狀態(tài)就是buffering;
解析音頻信息
讀數(shù)據(jù)時(shí)AudioFile和AudioFileStream差不多絮爷,成功打開(kāi)AudioFile之后就可以獲取歌曲信息了趴酣,包括比特率,音頻時(shí)長(zhǎng)等坑夯。
OSStatus AudioFileGetPropertyInfo(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * outDataSize,
UInt32 * isWritable);
OSStatus AudioFileGetProperty(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * ioDataSize,
void * outPropertyData);
AudioFileGetPropertyInfo方法用來(lái)獲取某個(gè)屬性對(duì)應(yīng)的數(shù)據(jù)的大嗅(outDataSize)以及該屬性是否可以被write(isWritable),而AudioFileGetProperty則用來(lái)獲取屬性對(duì)應(yīng)的數(shù)據(jù)渊涝。對(duì)于一些大小可變的屬性需要先使用AudioFileGetPropertyInfo獲取數(shù)據(jù)大小才能取獲取數(shù)據(jù)(例如formatList)慎璧,而有些確定類型單個(gè)屬性則不必先調(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;
}
// 求出有多少個(gè)
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;
}
// 只有一個(gè)的話直接取出來(lái)
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);
- 第一個(gè)參數(shù)跨释,F(xiàn)ileID胸私。
- 第二個(gè)參數(shù),是否需要cache鳖谈,一般來(lái)說(shuō)傳false岁疼。
- 第三個(gè)參數(shù),從第幾個(gè)byte開(kāi)始讀取數(shù)據(jù)。
- 第四個(gè)參數(shù)捷绒,這個(gè)參數(shù)在調(diào)用時(shí)作為輸入?yún)?shù)表示需要讀取讀取多少數(shù)據(jù)瑰排,調(diào)用完成后作為輸出參數(shù)表示實(shí)際讀取了多少數(shù)據(jù)(即Read回調(diào)中的requestCount和actualCount)。
- 第五個(gè)參數(shù)暖侨,buffer指針椭住,需要事先分配好足夠大的內(nèi)存(ioNumBytes大,即Read回調(diào)中的buffer字逗,所以Read回調(diào)中不需要再分配內(nèi)存)京郑。
- 返回值表示是否讀取成功,EOF時(shí)會(huì)返回kAudioFileEndOfFileError葫掉。
注意:使用這個(gè)方法得到的數(shù)據(jù)都是沒(méi)有進(jìn)行過(guò)幀分離的數(shù)據(jù)些举,如果想要用來(lái)播放或者解碼還必須通過(guò)AudioFileStream進(jìn)行幀分離。
- 按包(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);
按包讀取的方法有兩個(gè)俭厚,這兩個(gè)方法看上去差不多户魏,就連參數(shù)也幾乎相同,但使用場(chǎng)景和效率上卻有所不同挪挤。只有當(dāng)需要讀取固定時(shí)長(zhǎng)音頻或者非壓縮音頻時(shí)才會(huì)用到AudioFileReadPackets()叼丑,其余時(shí)候使用AudioFileReadPacketData()會(huì)有更高的效率并且更省內(nèi)存(所以AudioFileReadPackets()已經(jīng)被標(biāo)記為deprecated~~);
下面來(lái)看看這些參數(shù):
- 第一电禀、二個(gè)參數(shù)幢码,同AudioFileReadBytes。
- 第三個(gè)參數(shù)尖飞,對(duì)于AudioFileReadPacketData()來(lái)說(shuō)ioNumBytes這個(gè)參數(shù)在輸入輸出時(shí)都要用到症副,在輸入時(shí)表示outBuffer的size,輸出時(shí)表示實(shí)際讀取了多少size的數(shù)據(jù)政基。而對(duì)AudioFileReadPackets()來(lái)說(shuō)outNumBytes只在輸出時(shí)使用贞铣,表示實(shí)際讀取了多少size的數(shù)據(jù);
- 第四個(gè)參數(shù)沮明,幀信息數(shù)組指針辕坝,在輸入前需要分配內(nèi)存,大小必須足夠存儲(chǔ)ioNumPackets個(gè)幀信息(ioNumPackets * sizeof(AudioStreamPacketDescription))荐健。
- 第五個(gè)參數(shù)酱畅,從第幾幀開(kāi)始讀取數(shù)據(jù)。
- 第六個(gè)參數(shù)江场,在輸入時(shí)表示需要讀取多少個(gè)幀纺酸,在輸出時(shí)表示實(shí)際讀取了多少幀。
- 第七個(gè)參數(shù)址否,outBuffer數(shù)據(jù)指針餐蔬,在輸入前就需要分配好空間,這個(gè)參數(shù)看上去兩個(gè)方法一樣但其實(shí)并非如此。對(duì)于AudioFileReadPacketData()來(lái)說(shuō)只要分配近似幀大小 * 幀數(shù)的內(nèi)存空間即可樊诺,方法本身會(huì)針對(duì)給定的內(nèi)存空間大小來(lái)決定最后輸出多少個(gè)幀仗考,如果空間不夠會(huì)適當(dāng)減少出的幀數(shù);而對(duì)于AudioFileReadPackets()來(lái)說(shuō)則需要分配最大幀大小(或幀大小上界) * 幀數(shù)的內(nèi)存空間才行词爬;這也就是為何第三個(gè)參數(shù)一個(gè)是輸入輸出雙向使用的秃嗜,而另一個(gè)只是輸出時(shí)使用的原因。就這點(diǎn)來(lái)說(shuō)兩個(gè)方法中前者在使用的過(guò)程中要比后者更省內(nèi)存顿膨。
- 返回值痪寻,同AudioFileReadBytes。
這兩個(gè)方法讀取后的數(shù)據(jù)為幀分離后的數(shù)據(jù)虽惭,可以直接用來(lái)播放或者解碼。具體使用可以參考這里;
關(guān)閉AudioFile
AudioFile使用完畢后需要調(diào)用AudioFileClose進(jìn)行關(guān)閉蛇尚。
extern OSStatus AudioFileClose (AudioFileID inAudioFile);
下一篇會(huì)介紹AudioConverter芽唇。
說(shuō)明:很多話我是直接從這里直接拿過(guò)來(lái)的,作者總結(jié)的非常好取劫,可能我表述來(lái)表述去也就那么個(gè)意思匆笤,所以就直接拿來(lái)用了。有些地方谱邪,包括我自己遇到的坑炮捧,會(huì)做一些補(bǔ)充說(shuō)明,大家知道就好惦银。