iOS 音頻流播(三)

本篇我們介紹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ō)明,大家知道就好惦银。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咆课,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扯俱,更是在濱河造成了極大的恐慌书蚪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迅栅,死亡現(xiàn)場(chǎng)離奇詭異殊校,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)读存,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)为流,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人让簿,你說(shuō)我怎么就攤上這事敬察。” “怎么了拜英?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵静汤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)虫给,這世上最難降的妖魔是什么藤抡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮抹估,結(jié)果婚禮上缠黍,老公的妹妹穿的比我還像新娘。我一直安慰自己药蜻,他們只是感情好瓷式,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著语泽,像睡著了一般贸典。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上踱卵,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天廊驼,我揣著相機(jī)與錄音,去河邊找鬼惋砂。 笑死妒挎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的西饵。 我是一名探鬼主播酝掩,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼眷柔!你這毒婦竟也來(lái)了期虾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闯割,失蹤者是張志新(化名)和其女友劉穎彻消,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宙拉,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宾尚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谢澈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煌贴。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖锥忿,靈堂內(nèi)的尸體忽然破棺而出牛郑,到底是詐尸還是另有隱情,我是刑警寧澤敬鬓,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布淹朋,位于F島的核電站笙各,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏础芍。R本人自食惡果不足惜杈抢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仑性。 院中可真熱鬧惶楼,春花似錦、人聲如沸诊杆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晨汹。三九已至豹储,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淘这,已是汗流浹背颂翼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慨灭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓球及,卻偏偏與公主長(zhǎng)得像氧骤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吃引,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容