AudioFileStream介紹
AudioFileStreamer時(shí)提到它的作用是用來讀取采樣率队他、碼率输吏、時(shí)長等基本信息以及分離音頻幀余爆。那么在官方文檔中Apple是這樣描述的:
To play streamed audio content, such as from a network connection, use Audio File Stream Services in concert with Audio Queue Services. Audio File Stream Services parses audio packets and metadata from common audio file container formats in a network bitstream. You can also use it to parse packets and metadata from on-disk files
根據(jù)Apple的描述AudioFileStreamer用在流播放中澎迎,當(dāng)然不僅限于網(wǎng)絡(luò)流送爸,本地文件同樣可以用它來讀取信息和分離音頻幀。AudioFileStreamer的主要數(shù)據(jù)是文件數(shù)據(jù)而不是文件路徑咙好,所以數(shù)據(jù)的讀取需要使用者自行實(shí)現(xiàn)篡腌,
支持的文件格式有:
MPEG-1 Audio Layer 3, used for .mp3 files
MPEG-2 ADTS, used for the .aac audio data format
AIFC
AIFF
CAF
MPEG-4, used for .m4a, .mp4, and .3gp files
NeXT
WAVE
上述格式是iOS、MacOSX所支持的音頻格式勾效,這類格式可以被系統(tǒng)提供的API解碼嘹悼,如果想要解碼其他的音頻格式(如OGG叛甫、APE、FLAC)就需要自己實(shí)現(xiàn)解碼器了杨伙。
初始化AudioFileStream
第一步其监,自然是要生成一個(gè)AudioFileStream的實(shí)例:
12345externOSStatusAudioFileStreamOpen(void*inClientData,AudioFileStream_PropertyListenerProcinPropertyListenerProc,AudioFileStream_PacketsProcinPacketsProc,AudioFileTypeIDinFileTypeHint,AudioFileStreamID*outAudioFileStream);
第一個(gè)參數(shù)和之前的AudioSession的初始化方法一樣是一個(gè)上下文對(duì)象;
第二個(gè)參數(shù)AudioFileStream_PropertyListenerProc是歌曲信息解析的回調(diào)限匣,每解析出一個(gè)歌曲信息都會(huì)進(jìn)行一次回調(diào)抖苦;
第三個(gè)參數(shù)AudioFileStream_PacketsProc是分離幀的回調(diào),每解析出一部分幀就會(huì)進(jìn)行一次回調(diào)米死;
第四個(gè)參數(shù)AudioFileTypeID是文件類型的提示锌历,這個(gè)參數(shù)來幫助AudioFileStream對(duì)文件格式進(jìn)行解析。這個(gè)參數(shù)在文件信息不完整(例如信息有缺陷)時(shí)尤其有用峦筒,它可以給與AudioFileStream一定的提示究西,幫助其繞過文件中的錯(cuò)誤或者缺失從而成功解析文件。所以在確定文件類型的情況下建議各位還是填上這個(gè)參數(shù)物喷,如果無法確定可以傳入0(原理上應(yīng)該和這篇博文近似)卤材;
1234567891011121314151617181920//AudioFileTypeID枚舉enum{kAudioFileAIFFType='AIFF',kAudioFileAIFCType='AIFC',kAudioFileWAVEType='WAVE',kAudioFileSoundDesigner2Type='Sd2f',kAudioFileNextType='NeXT',kAudioFileMP3Type='MPG3',// mpeg layer 3kAudioFileMP2Type='MPG2',// mpeg layer 2kAudioFileMP1Type='MPG1',// mpeg layer 1kAudioFileAC3Type='ac-3',kAudioFileAAC_ADTSType='adts',kAudioFileMPEG4Type='mp4f',kAudioFileM4AType='m4af',kAudioFileM4BType='m4bf',kAudioFileCAFType='caff',kAudioFile3GPType='3gpp',kAudioFile3GP2Type='3gp2',kAudioFileAMRType='amrf'};
第五個(gè)參數(shù)是返回的AudioFileStream實(shí)例對(duì)應(yīng)的AudioFileStreamID,這個(gè)ID需要保存起來作為后續(xù)一些方法的參數(shù)使用峦失;
返回值用來判斷是否成功初始化(OSStatus == noErr)扇丛。
解析數(shù)據(jù)
在初始化完成之后,只要拿到文件數(shù)據(jù)就可以進(jìn)行解析了尉辑。解析時(shí)調(diào)用方法:
1234externOSStatusAudioFileStreamParseBytes(AudioFileStreamIDinAudioFileStream,UInt32inDataByteSize,constvoid*inData,UInt32inFlags);
第一個(gè)參數(shù)AudioFileStreamID帆精,即初始化時(shí)返回的ID;
第二個(gè)參數(shù)inDataByteSize材蹬,本次解析的數(shù)據(jù)長度实幕;
第三個(gè)參數(shù)inData,本次解析的數(shù)據(jù)堤器;
第四個(gè)參數(shù)是說本次的解析和上一次解析是否是連續(xù)的關(guān)系昆庇,如果是連續(xù)的傳入0,否則傳入kAudioFileStreamParseFlag_Discontinuity闸溃。
這里需要插入解釋一下何謂“連續(xù)”整吆。在第一篇中我們提到過形如MP3的數(shù)據(jù)都以幀的形式存在的,解析時(shí)也需要以幀為單位解析辉川。但在解碼之前我們不可能知道每個(gè)幀的邊界在第幾個(gè)字節(jié)表蝙,所以就會(huì)出現(xiàn)這樣的情況:我們傳給AudioFileStreamParseBytes的數(shù)據(jù)在解析完成之后會(huì)有一部分?jǐn)?shù)據(jù)余下來,這部分?jǐn)?shù)據(jù)是接下去那一幀的前半部分乓旗,如果再次有數(shù)據(jù)輸入需要繼續(xù)解析時(shí)就必須要用到前一次解析余下來的數(shù)據(jù)才能保證幀數(shù)據(jù)完整府蛇,所以在正常播放的情況下傳入0即可。目前知道的需要傳入kAudioFileStreamParseFlag_Discontinuity的情況有兩個(gè)屿愚,一個(gè)是在seek完畢之后顯然seek后的數(shù)據(jù)和之前的數(shù)據(jù)完全無關(guān)汇跨;另一個(gè)是開源播放器AudioStreamer的作者@Matt Gallagher曾在自己的blog中提到過的:
the Audio File Stream Services hit me with a nasty bug: AudioFileStreamParseBytes will crash when trying to parse a streaming MP3.
In this case, if we pass the kAudioFileStreamParseFlag_Discontinuity flag to AudioFileStreamParseBytes on every invocation between receiving kAudioFileStreamProperty_ReadyToProducePackets and the first successful call to MyPacketsProc, then AudioFileStreamParseBytes will be extra cautious in its approach and won't crash.
Matt發(fā)布這篇blog是在2008年务荆,這個(gè)Bug年代相當(dāng)久遠(yuǎn)了,而且原因未知穷遂,究竟是否修復(fù)也不得而知函匕,而且由于環(huán)境不同(比如測(cè)試用的mp3文件和所處的iOS系統(tǒng))無法重現(xiàn)這個(gè)問題,所以我個(gè)人覺得還是按照Matt的work around在回調(diào)得到kAudioFileStreamProperty_ReadyToProducePackets之后蚪黑,在正常解析第一幀之前都傳入kAudioFileStreamParseFlag_Discontinuity比較好盅惜。
回到之前的內(nèi)容,AudioFileStreamParseBytes方法的返回值表示當(dāng)前的數(shù)據(jù)是否被正常解析忌穿,如果OSStatus的值不是noErr則表示解析不成功抒寂,其中錯(cuò)誤碼包括:
123456789101112131415enum{kAudioFileStreamError_UnsupportedFileType='typ?',kAudioFileStreamError_UnsupportedDataFormat='fmt?',kAudioFileStreamError_UnsupportedProperty='pty?',kAudioFileStreamError_BadPropertySize='!siz',kAudioFileStreamError_NotOptimized='optm',kAudioFileStreamError_InvalidPacketOffset='pck?',kAudioFileStreamError_InvalidFile='dta?',kAudioFileStreamError_ValueUnknown='unk?',kAudioFileStreamError_DataUnavailable='more',kAudioFileStreamError_IllegalOperation='nope',kAudioFileStreamError_UnspecifiedError='wht?',kAudioFileStreamError_DiscontinuityCantRecover='dsc!'};
大多數(shù)都可以從字面上理解,需要提一下的是kAudioFileStreamError_NotOptimized掠剑,文檔上是這么說的:
It is not possible to produce output packets because the file's packet table or other defining info is either not present or is after the audio data.
它的含義是說這個(gè)音頻文件的文件頭不存在或者說文件頭可能在文件的末尾蓬推,當(dāng)前無法正常Parse,換句話說就是這個(gè)文件需要全部下載完才能播放澡腾,無法流播。
注意AudioFileStreamParseBytes方法每一次調(diào)用都應(yīng)該注意返回值糕珊,一旦出現(xiàn)錯(cuò)誤就可以不必繼續(xù)Parse了动分。
解析文件格式信息
在調(diào)用AudioFileStreamParseBytes方法進(jìn)行解析時(shí)會(huì)首先讀取格式信息,并同步的進(jìn)入AudioFileStream_PropertyListenerProc回調(diào)方法
來看一下這個(gè)回調(diào)方法的定義
1234typedefvoid(*AudioFileStream_PropertyListenerProc)(void*inClientData,AudioFileStreamIDinAudioFileStream,AudioFileStreamPropertyIDinPropertyID,UInt32*ioFlags);
回調(diào)的第一個(gè)參數(shù)是Open方法中的上下文對(duì)象红选;
第二個(gè)參數(shù)inAudioFileStream是和Open方法中第四個(gè)返回參數(shù)AudioFileStreamID一樣澜公,表示當(dāng)前FileStream的ID;
第三個(gè)參數(shù)是此次回調(diào)解析的信息ID喇肋。表示當(dāng)前PropertyID對(duì)應(yīng)的信息已經(jīng)解析完成信息(例如數(shù)據(jù)格式坟乾、音頻數(shù)據(jù)的偏移量等等),使用者可以通過AudioFileStreamGetProperty接口獲取PropertyID對(duì)應(yīng)的值或者數(shù)據(jù)結(jié)構(gòu)蝶防;
1234externOSStatusAudioFileStreamGetProperty(AudioFileStreamIDinAudioFileStream,AudioFileStreamPropertyIDinPropertyID,UInt32*ioPropertyDataSize,void*outPropertyData);
第四個(gè)參數(shù)ioFlags是一個(gè)返回參數(shù)甚侣,表示這個(gè)property是否需要被緩存,如果需要賦值kAudioFileStreamPropertyFlag_PropertyIsCached否則不賦值(這個(gè)參數(shù)我也不知道應(yīng)該在啥場(chǎng)景下使用间学。殷费。一直都沒去理他);
這個(gè)回調(diào)會(huì)進(jìn)來多次低葫,但并不是每一次都需要進(jìn)行處理详羡,可以根據(jù)需求處理需要的PropertyID進(jìn)行處理(PropertyID列表如下)。
1234567891011121314151617181920212223//AudioFileStreamProperty枚舉enum{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è)我認(rèn)為比較重要的PropertyID:
1嘿悬、kAudioFileStreamProperty_BitRate:
表示音頻數(shù)據(jù)的碼率实柠,獲取這個(gè)Property是為了計(jì)算音頻的總時(shí)長Duration(因?yàn)锳udioFileStream沒有這樣的接口。善涨。)窒盐。
1234567UInt32bitRate;UInt32bitRateSize=sizeof(bitRate);OSStatusstatus=AudioFileStreamGetProperty(inAudioFileStream,kAudioFileStreamProperty_BitRate,&bitRateSize,&bitRate);if(status!=noErr){//錯(cuò)誤處理}
2014.8.2 補(bǔ)充:發(fā)現(xiàn)在流播放的情況下草则,有時(shí)數(shù)據(jù)流量比較小時(shí)會(huì)出現(xiàn)ReadyToProducePackets還是沒有獲取到bitRate的情況,這時(shí)就需要分離一些拼音幀然后計(jì)算平均bitRate登钥,計(jì)算公式如下:
1UInt32averageBitRate=totalPackectByteCount/totalPacketCout;
2畔师、kAudioFileStreamProperty_DataOffset:
表示音頻數(shù)據(jù)在整個(gè)音頻文件中的offset(因?yàn)榇蠖鄶?shù)音頻文件都會(huì)有一個(gè)文件頭之后才使真正的音頻數(shù)據(jù)),這個(gè)值在seek時(shí)會(huì)發(fā)揮比較大的作用牧牢,音頻的seek并不是直接seek文件位置而seek時(shí)間(比如seek到2分10秒的位置)看锉,seek時(shí)會(huì)根據(jù)時(shí)間計(jì)算出音頻數(shù)據(jù)的字節(jié)offset然后需要再加上音頻數(shù)據(jù)的offset才能得到在文件中的真正offset。
1234567SInt64dataOffset;UInt32offsetSize=sizeof(dataOffset);OSStatusstatus=AudioFileStreamGetProperty(inAudioFileStream,kAudioFileStreamProperty_DataOffset,&offsetSize,&dataOffset);if(status!=noErr){//錯(cuò)誤處理}
3塔鳍、kAudioFileStreamProperty_DataFormat
表示音頻文件結(jié)構(gòu)信息伯铣,是一個(gè)AudioStreamBasicDescription的結(jié)構(gòu)
1234567891011121314151617181920structAudioStreamBasicDescription{Float64mSampleRate;UInt32mFormatID;UInt32mFormatFlags;UInt32mBytesPerPacket;UInt32mFramesPerPacket;UInt32mBytesPerFrame;UInt32mChannelsPerFrame;UInt32mBitsPerChannel;UInt32mReserved;};AudioStreamBasicDescriptionasbd;UInt32asbdSize=sizeof(asbd);OSStatusstatus=AudioFileStreamGetProperty(inAudioFileStream,kAudioFileStreamProperty_DataFormat,&asbdSize,&asbd);if(status!=noErr){//錯(cuò)誤處理}
4、kAudioFileStreamProperty_FormatList
作用和kAudioFileStreamProperty_DataFormat是一樣的轮纫,區(qū)別在于用這個(gè)PropertyID獲取到是一個(gè)AudioStreamBasicDescription的數(shù)組腔寡,這個(gè)參數(shù)是用來支持AAC SBR這樣的包含多個(gè)文件類型的音頻格式。由于到底有多少個(gè)format我們并不知曉掌唾,所以需要先獲取一下總數(shù)據(jù)大蟹徘啊:
123456789101112131415161718192021222324//獲取數(shù)據(jù)大小BooleanoutWriteable;UInt32formatListSize;OSStatusstatus=AudioFileStreamGetPropertyInfo(inAudioFileStream,kAudioFileStreamProperty_FormatList,&formatListSize,&outWriteable);if(status!=noErr){//錯(cuò)誤處理}//獲取formatlistAudioFormatListItem*formatList=malloc(formatListSize);OSStatusstatus=AudioFileStreamGetProperty(inAudioFileStream,kAudioFileStreamProperty_FormatList,&formatListSize,formatList);if(status!=noErr){//錯(cuò)誤處理}//選擇需要的格式for(inti=0;i*sizeof(AudioFormatListItem)
5、kAudioFileStreamProperty_AudioDataByteCount
顧名思義糯彬,音頻文件中音頻數(shù)據(jù)的總量凭语。這個(gè)Property的作用一是用來計(jì)算音頻的總時(shí)長,二是可以在seek時(shí)用來計(jì)算時(shí)間對(duì)應(yīng)的字節(jié)offset撩扒。
1234567UInt64audioDataByteCount;UInt32byteCountSize=sizeof(audioDataByteCount);OSStatusstatus=AudioFileStreamGetProperty(inAudioFileStream,kAudioFileStreamProperty_AudioDataByteCount,&byteCountSize,&audioDataByteCount);if(status!=noErr){//錯(cuò)誤處理}
2014.8.2 補(bǔ)充:發(fā)現(xiàn)在流播放的情況下似扔,有時(shí)數(shù)據(jù)流量比較小時(shí)會(huì)出現(xiàn)ReadyToProducePackets還是沒有獲取到audioDataByteCount的情況,這時(shí)就需要近似計(jì)算audioDataByteCount搓谆。一般來說音頻文件的總大小一定是可以得到的(利用文件系統(tǒng)或者Http請(qǐng)求中的contentLength)炒辉,那么計(jì)算方法如下:
123UInt32dataOffset=...;//kAudioFileStreamProperty_DataOffsetUInt32fileLength=...;//音頻文件大小UInt32audioDataByteCount=fileLength-dataOffset;
5、kAudioFileStreamProperty_ReadyToProducePackets
這個(gè)PropertyID可以不必獲取對(duì)應(yīng)的值泉手,一旦回調(diào)中這個(gè)PropertyID出現(xiàn)就代表解析完成黔寇,接下來可以對(duì)音頻數(shù)據(jù)進(jìn)行幀分離了。
計(jì)算時(shí)長Duration
獲取時(shí)長的最佳方法是從ID3信息中去讀取斩萌,那樣是最準(zhǔn)確的啡氢。如果ID3信息中沒有存,那就依賴于文件頭中的信息去計(jì)算了术裸。
計(jì)算duration的公式如下:
1doubleduration=(audioDataByteCount*8)/bitRate
音頻數(shù)據(jù)的字節(jié)總量audioDataByteCount可以通過kAudioFileStreamProperty_AudioDataByteCount獲取倘是,碼率bitRate可以通過kAudioFileStreamProperty_BitRate獲取也可以通過Parse一部分?jǐn)?shù)據(jù)后計(jì)算平均碼率來得到。
對(duì)于CBR數(shù)據(jù)來說用這樣的計(jì)算方法的duration會(huì)比較準(zhǔn)確袭艺,對(duì)于VBR數(shù)據(jù)就不好說了搀崭。所以對(duì)于VBR數(shù)據(jù)來說,最好是能夠從ID3信息中獲取到duration,獲取不到再想辦法通過計(jì)算平均碼率的途徑來計(jì)算duration瘤睹。
分離音頻幀
讀取格式信息完成之后繼續(xù)調(diào)用AudioFileStreamParseBytes方法可以對(duì)幀進(jìn)行分離升敲,并同步的進(jìn)入AudioFileStream_PacketsProc回調(diào)方法。
回調(diào)的定義:
12345typedefvoid(*AudioFileStream_PacketsProc)(void*inClientData,UInt32numberOfBytes,UInt32numberOfPackets,constvoid*inInputData,AudioStreamPacketDescription*inPacketDescriptions);
第一個(gè)參數(shù)轰传,一如既往的上下文對(duì)象驴党;
第二個(gè)參數(shù),本次處理的數(shù)據(jù)大谢癫纭港庄;
第三個(gè)參數(shù),本次總共處理了多少幀(即代碼里的Packet)恕曲;
第四個(gè)參數(shù)鹏氧,本次處理的所有數(shù)據(jù);
第五個(gè)參數(shù)佩谣,AudioStreamPacketDescription數(shù)組把还,存儲(chǔ)了每一幀數(shù)據(jù)是從第幾個(gè)字節(jié)開始的,這一幀總共多少字節(jié)茸俭。
12345678//AudioStreamPacketDescription結(jié)構(gòu)//這里的mVariableFramesInPacket是指實(shí)際的數(shù)據(jù)幀只有VBR的數(shù)據(jù)才能用到(像MP3這樣的壓縮數(shù)據(jù)一個(gè)幀里會(huì)有好幾個(gè)數(shù)據(jù)幀)structAudioStreamPacketDescription{SInt64mStartOffset;UInt32mVariableFramesInPacket;UInt32mDataByteSize;};
下面是我按照自己的理解實(shí)現(xiàn)的回調(diào)方法片段:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051staticvoidMyAudioFileStreamPacketsCallBack(void*inClientData,UInt32numberOfBytes,UInt32numberOfPackets,constvoid*inInputData,AudioStreamPacketDescription*inPacketDescriptions){//處理discontinuous..if(numberOfBytes==0||numberOfPackets==0){return;}BOOLdeletePackDesc=NO;if(packetDescriptioins==NULL){//如果packetDescriptioins不存在吊履,就按照CBR處理,平均每一幀的數(shù)據(jù)后生成packetDescriptioinsdeletePackDesc=YES;UInt32packetSize=numberOfBytes/numberOfPackets;packetDescriptioins=(AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription)*numberOfPackets);for(inti=0;i
inPacketDescriptions這個(gè)字段為空時(shí)需要按CBR的數(shù)據(jù)處理调鬓。但其實(shí)在解析CBR數(shù)據(jù)時(shí)inPacketDescriptions一般也會(huì)有返回率翅,因?yàn)榧词故荂BR數(shù)據(jù)幀的大小也不是恒定不變的,例如CBR的MP3會(huì)在每一幀的數(shù)據(jù)后放1 byte的填充位袖迎,這個(gè)填充位也并非時(shí)時(shí)刻刻存在,所以幀的大小會(huì)有1 byte的浮動(dòng)腺晾。(比如采樣率44.1KHZ燕锥,碼率160kbps的CBR MP3文件每一幀的大小在522字節(jié)和523字節(jié)浮動(dòng)。所以不能因?yàn)橛衖nPacketDescriptions沒有返回NULL而判定音頻數(shù)據(jù)就是VBR編碼的)悯蝉。
Seek
就音頻的角度來seek功能描述為“我要拖到xx分xx秒”归形,而實(shí)際操作時(shí)我們需要操作的是文件,所以我們需要知道的是“我要拖到xx分xx秒”這個(gè)操作對(duì)應(yīng)到文件上是要從第幾個(gè)字節(jié)開始讀取音頻數(shù)據(jù)鼻由。
對(duì)于原始的PCM數(shù)據(jù)來說每一個(gè)PCM幀都是固定長度的暇榴,對(duì)應(yīng)的播放時(shí)長也是固定的,但一旦轉(zhuǎn)換成壓縮后的音頻數(shù)據(jù)就會(huì)因?yàn)榫幋a形式的不同而不同了蕉世。對(duì)于CBR而言每個(gè)幀中所包含的PCM數(shù)據(jù)幀是恒定的蔼紧,所以每一幀對(duì)應(yīng)的播放時(shí)長也是恒定的;而VBR則不同狠轻,為了保證數(shù)據(jù)最優(yōu)并且文件大小最小奸例,VBR的每一幀中所包含的PCM數(shù)據(jù)幀是不固定的,這就導(dǎo)致在流播放的情況下VBR的數(shù)據(jù)想要做seek并不容易向楼。這里我們也只討論CBR下的seek查吊。
CBR數(shù)據(jù)的seek一般是這樣實(shí)現(xiàn)的(參考并修改自matt的blog):
1谐区、近似地計(jì)算應(yīng)該seek到哪個(gè)字節(jié)
1234567doubleseekToTime=...;//需要seek到哪個(gè)時(shí)間,秒為單位UInt64audioDataByteCount=...;//通過kAudioFileStreamProperty_AudioDataByteCount獲取的值SInt64dataOffset=...;//通過kAudioFileStreamProperty_DataOffset獲取的值doubledurtion=...;//通過公式(AudioDataByteCount * 8) / BitRate計(jì)算得到的時(shí)長//近似seekOffset = 數(shù)據(jù)偏移 + seekToTime對(duì)應(yīng)的近似字節(jié)數(shù)SInt64approximateSeekOffset=dataOffset+(seekToTime/duration)*audioDataByteCount;
2逻卖、計(jì)算seekToTime對(duì)應(yīng)的是第幾個(gè)幀(Packet)
我們可以利用之前Parse得到的音頻格式信息來計(jì)算PacketDuration宋列。audioItem.fileFormat.mFramesPerPacket /audioItem.fileFormat.mSampleRate;
123456//首先需要計(jì)算每個(gè)packet對(duì)應(yīng)的時(shí)長AudioStreamBasicDescriptionasbd=...;////通過kAudioFileStreamProperty_DataFormat或者kAudioFileStreamProperty_FormatList獲取的值doublepacketDuration=asbd.mFramesPerPacket/asbd.mSampleRate//然后計(jì)算packet位置SInt64seekToPacket=floor(seekToTime/packetDuration);
3、使用AudioFileStreamSeek計(jì)算精確的字節(jié)偏移和時(shí)間
AudioFileStreamSeek可以用來尋找某一個(gè)幀(Packet)對(duì)應(yīng)的字節(jié)偏移(byte offset):
如果ioFlags里有kAudioFileStreamSeekFlag_OffsetIsEstimated說明給出的outDataByteOffset是估算的评也,并不準(zhǔn)確炼杖,那么還是應(yīng)該用第1步計(jì)算出來的approximateSeekOffset來做seek;
如果ioFlags里沒有kAudioFileStreamSeekFlag_OffsetIsEstimated說明給出了準(zhǔn)確的outDataByteOffset仇参,就是輸入的seekToPacket對(duì)應(yīng)的字節(jié)偏移量嘹叫,我們可以根據(jù)outDataByteOffset來計(jì)算出精確的seekOffset和seekToTime;
1234567891011121314SInt64seekByteOffset;UInt32ioFlags=0;SInt64outDataByteOffset;OSStatusstatus=AudioFileStreamSeek(audioFileStreamID,seekToPacket,&outDataByteOffset,&ioFlags);if(status==noErr&&!(ioFlags&kAudioFileStreamSeekFlag_OffsetIsEstimated)){//如果AudioFileStreamSeek方法找到了準(zhǔn)確的幀字節(jié)偏移诈乒,需要修正一下時(shí)間seekToTime-=((approximateSeekOffset-dataOffset)-outDataByteOffset)*8.0/bitRate;seekByteOffset=outDataByteOffset+dataOffset;}else{seekByteOffset=approximateSeekOffset;}
4罩扇、按照seekByteOffset讀取對(duì)應(yīng)的數(shù)據(jù)繼續(xù)使用AudioFileStreamParseByte進(jìn)行解析
如果是網(wǎng)絡(luò)流可以通過設(shè)置range頭來獲取字節(jié),本地文件的話直接seek就好了怕磨。調(diào)用AudioFileStreamParseByte時(shí)注意剛seek完第一次Parse數(shù)據(jù)需要加參數(shù)kAudioFileStreamParseFlag_Discontinuity喂饥。
關(guān)閉AudioFileStream
AudioFileStream使用完畢后需要調(diào)用AudioFileStreamClose進(jìn)行關(guān)閉,沒啥特別需要注意的肠鲫。
1externOSStatusAudioFileStreamClose(AudioFileStreamIDinAudioFileStream);
小結(jié)
本篇關(guān)于AudioFileStream做了詳細(xì)介紹员帮,小結(jié)一下:
使用AudioFileStream首先需要調(diào)用AudioFileStreamOpen,需要注意的是盡量提供inFileTypeHint參數(shù)幫助AudioFileStream解析數(shù)據(jù)导饲,調(diào)用完成后記錄AudioFileStreamID捞高;
當(dāng)有數(shù)據(jù)時(shí)調(diào)用AudioFileStreamParseBytes進(jìn)行解析,每一次解析都需要注意返回值渣锦,返回值一旦出現(xiàn)noErr以外的值就代表Parse出錯(cuò)硝岗,其中kAudioFileStreamError_NotOptimized代表該文件缺少頭信息或者其頭信息在文件尾部不適合流播放;
使用AudioFileStreamParseBytes需要注意第四個(gè)參數(shù)在需要合適的時(shí)候傳入kAudioFileStreamParseFlag_Discontinuity袋毙;
調(diào)用AudioFileStreamParseBytes后會(huì)首先同步進(jìn)入AudioFileStream_PropertyListenerProc回調(diào)來解析文件格式信息型檀,如果回調(diào)得到kAudioFileStreamProperty_ReadyToProducePackets表示解析格式信息完成;
解析格式信息完成后繼續(xù)調(diào)用AudioFileStreamParseBytes會(huì)進(jìn)入MyAudioFileStreamPacketsCallBack回調(diào)來分離音頻幀听盖,在回調(diào)中應(yīng)該將分離出來的幀信息保存到自己的buffer中
seek時(shí)需要先近似的計(jì)算seekTime對(duì)應(yīng)的seekByteOffset胀溺,然后利用AudioFileStreamSeek計(jì)算精確的offset,如果能得到精確的offset就修正一下seektime皆看,如果無法得到精確的offset就用之前的近似結(jié)果
AudioFileStream使用完畢后需要調(diào)用AudioFileStreamClose進(jìn)行關(guān)閉仓坞;