上一篇我介紹了AudioFileStream,這一篇我來介紹一下AudioFile。
AudioFile跟AudioFileStream一樣,也能讀取音頻格式信息和進行幀的分離,但是功能比AudioFileStream強大
AudioFile官方文檔介紹:
a C programming interface that enables you to read or write a wide variety of audio data to or from disk or a memory buffer.With Audio File Services you can:
- Create, initialize, open, and close audio files
- Read and write audio files
- Optimize audio files
- Work with user data and global information
由文檔我們知道,這個類可以用來創建、初始化音頻文件;讀寫音頻數據,對音頻進行優化;讀取和寫入音頻信息。所以它不止可以用來支持音頻播放,還可以生成音頻文件(本篇暫不涉及)
初始化AudioFile
AudioFile有兩個創建方式
1.AudioFileOpenURL
extern OSStatus
AudioFileOpenURL ( CFURLRef inFileRef,
AudioFilePermissions inPermissions,
AudioFileTypeID inFileTypeHint,
AudioFileID __nullable * __nonnull outAudioFile) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
第一個參數是文件路徑
第二個參數是文件的允許使用方式,一共三種,讀、寫和讀寫,如果打開文件后使用了權限外的操作,就會報錯
typedef CF_ENUM(SInt8, AudioFilePermissions) {
kAudioFileReadPermission = 0x01,
kAudioFileWritePermission = 0x02,
kAudioFileReadWritePermission = 0x03
};
第三個參數跟AudioFileStream一樣,傳入一個參數幫助解析文件
第四個參數返回AudioFile實例對應的一個ID,需要保存起來作為一些參數的查詢
返回值返回noErr則成功
2.AudioFileOpenWithCallbacks
extern OSStatus
AudioFileOpenWithCallbacks (
void * inClientData,
AudioFile_ReadProc inReadFunc,
AudioFile_WriteProc __nullable inWriteFunc,
AudioFile_GetSizeProc inGetSizeFunc,
AudioFile_SetSizeProc __nullable inSetSizeFunc,
AudioFileTypeID inFileTypeHint,
AudioFileID __nullable * __nonnull outAudioFile) __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
第一個參數,上下文對象,一般為AudioFile實例
第二個參數,當AudioFile需要讀音頻數據時進行的回調(同步回調)
第三個參數,當AudioFile需要寫音頻數據時進行的回調(暫不討論,傳Null)
第四個參數,當AudioFile需要用到文件的總大小時的回調(同步回調)
第五個參數,當AudioFile需要設置文件大小時的回調(寫音頻文件功能時使用,傳Null,暫不討論)
第六,第七同AudioFileOpenURL方法
這個方法的重點在于AudioFile_ReadProc這個回調。這個方法比第一個自由度更高,AudioFile只需要一個數據源,無論是磁盤或者內存的數據甚至是網絡流只要能在AudioFile需要時即open和read時,通過AudioFile_ReadProc回調給AudioFile提供合適的數據就可以了,即可以讀取網絡流和本地文件
我們來看這兩個回調
typedef OSStatus (*AudioFile_ReadProc)(
void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount);
typedef SInt64 (*AudioFile_GetSizeProc)(
void * inClientData);
AudioFile_GetSizeProc
這個回調比較簡單,就是返回文件總長度
AudioFile_ReadProc
第一個參數,上下文對象,一般為AudioFile實例
第二個參數,需要讀取第幾個字節開始的數據
第三個參數,需要讀取的數據長度
第四個參數,返回參數,是一個數據指針且其空間已經被分配了,我們需要做的是把數據memcpy到buffer中
第五個參數,實際提供的數據長度,即memcpy到buffer的數據長度
返回值,如果沒有異常的話就直接返回noErr。
AudioFile需要數據時會調用回調方法,需要數據的時間點有兩個
- Open方法調用時,由于AudioFile的Open方法調用過程中就會對音頻格式信息進行解析,只有復合要求的音頻格式才能被成功打開否則Open方法就會返回碼
- Read相關方法調用時;
這個回調函數需要注意inPosition和requestCount參數,這兩個參數指明了本次回調需要提供的數據范圍是從inPosition和requestCount個字節的數據。這里就有兩種情況 - 有充足的數據:那么我們需要把這個范圍內的數據拷貝到buffer中,并且給actualCount賦值給requestCount,最后返回noErr
- 數據不足: 沒有充足數據的話就只能把手頭的數據拷貝到buffer中,拷貝的數據必須是從inPosition開始的連續數據,拷貝完成后給actualCount賦值實際拷貝進buffer中的數據長度,然后返回noErr,如下代碼
static OSStatus ZJAudioFileReadCallBack(void *inclientData,SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount){
ZJAudioFile *audioFile = (__bridge ZJAudioFile *)inclientData;
*actualCount = [audioFile availableDataLengthAtOffset:inPosition maxLength:requestCount];
if (*actualCount>0) {
NSData *data = [audioFile dataAtOffset:inPosition length:*actualCount];
memcpy(buffer, [data bytes], [data length]);
}
return noErr;
}
- 當Open方法調用時的回調數據不足:AudioFile的Open方法會根據文件格式類型分幾步進行數據讀取以解析確定是否是一個合法的文件格式,其中每一步的inPosition和requestCount都不一樣,如果某一步不成會直接進行下一步,如果幾步下來都失敗了,那么Open方法就會失敗。即在調用Open方法之前需要保證音頻文件的格式信息完整,所以AudioFile并不能獨立應用于音頻流的讀取,在流播放的時候需要使用AudioFileStream得到ReadyToProducePackets標志位來保證信息完整;
- Read方法調用時回調數據不足:這種情況下inPosition和requestCount的數值與Read方法調用時傳入的參數有關,數據不足對于Read方法本身沒有影響,只要回調返回noErr,Read就成功,只是實際交給Read方法的調用方的數據會不足,所以需要去處理;
讀取音頻格式信息
像AudioFileStream一樣,AudioFile會有兩個getProperty的方法去獲取音頻格式信息
extern OSStatus
AudioFileGetPropertyInfo( AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * __nullable outDataSize,
UInt32 * __nullable isWritable) __OSX_AVAILABLE_STARTING(__MAC_10_2,__IPHONE_2_0);
extern OSStatus
AudioFileGetProperty( AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 *ioDataSize,
void *outPropertyData) __OSX_AVAILABLE_STARTING(__MAC_10_2,__IPHONE_2_0);
AudioFileGetPropertyInfo方法用來獲取某個屬性對應的數據大小(outDataSize)以及該屬性能否被write(isWritable),而AudioFileGetProperty則用來獲取屬性對應的數據。對于一些大小可變的屬性需要先使用AudioFileGetPropertyInfo獲取數據大小才能獲取數據(例如formatList),而有些確定類型單個則不必先調用AudioFileGetPropertyInfo直接調用AudioFileGetProperty則可(比如bitRate)
UInt32 formatListSize;
OSStatus status = AudioFileGetPropertyInfo(_audioFileID, kAudioFilePropertyFormatList, &formatListSize, NULL);
if (status == noErr) {
BOOL found = NO;
AudioFormatListItem *formatList = malloc(formatListSize);
OSStatus status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyFormatList, &formatListSize, formatList);
if (status == noErr) {
UInt32 supportedFormatsSize;
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &supportedFormatsSize);
if (status != noErr) {
free(formatList);
[self _closeAudioFile];
return;
}
UInt32 supportedFormatCount = supportedFormatsSize/sizeof(OSType);
OSType *supportedFormats = (OSType *)malloc(supportedFormatsSize);
status = AudioFormatGetProperty(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &supportedFormatsSize, supportedFormats);
if (status != noErr) {
free(formatList);
free(supportedFormats);
[self _closeAudioFile];
return;
}
for (int i = 0; i*sizeof(AudioFormatListItem)<formatListSize; i++) {
AudioStreamBasicDescription format = formatList[i].mASBD;
for (UInt32 j = 0; j < supportedFormatCount; j++) {
if (format.mFormatID == supportedFormats[j]) {
NSLog(@"i -- %u j -- %u",i,j);
_format = format;
found = YES;
break;
}
}
}
free(supportedFormats);
}
free(formatList);
if (!found) {
[self _closeAudioFile];
return;
}else{
[self _calculatePacketsDuration];
}
}
UInt32 size = sizeof(_bitRate);
status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyBitRate, &size, &_bitRate);
if (status != noErr) {
[self _closeAudioFile];
return;
}
size = sizeof(_dataOffset);
status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyDataOffset, &size, &_dataOffset);
if (status != noErr) {
[self _closeAudioFile];
return;
}
_audioDataByteCount = _fileSize-_dataOffset;
size = sizeof(_duration);
status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyEstimatedDuration, &size, &_duration);
if (status != noErr) {
[self _calculateDuration];
}
size = sizeof(_maxPacketSize);
status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyPacketSizeUpperBound, &size, &_maxPacketSize);
if (status != noErr || _maxPacketSize == 0) {
status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyMaximumPacketSize, &size, &_maxPacketSize);
if (status != noErr) {
[self _closeAudioFile];
return;
}
}
以下是可以獲取到的屬性
CF_ENUM(AudioFilePropertyID)
{
kAudioFilePropertyFileFormat = 'ffmt',
kAudioFilePropertyDataFormat = 'dfmt',
kAudioFilePropertyIsOptimized = 'optm',
kAudioFilePropertyMagicCookieData = 'mgic',
kAudioFilePropertyAudioDataByteCount = 'bcnt',
kAudioFilePropertyAudioDataPacketCount = 'pcnt',
kAudioFilePropertyMaximumPacketSize = 'psze',
kAudioFilePropertyDataOffset = 'doff',
kAudioFilePropertyChannelLayout = 'cmap',
kAudioFilePropertyDeferSizeUpdates = 'dszu',
kAudioFilePropertyDataFormatName = 'fnme',
kAudioFilePropertyMarkerList = 'mkls',
kAudioFilePropertyRegionList = 'rgls',
kAudioFilePropertyPacketToFrame = 'pkfr',
kAudioFilePropertyFrameToPacket = 'frpk',
kAudioFilePropertyPacketToByte = 'pkby',
kAudioFilePropertyByteToPacket = 'bypk',
kAudioFilePropertyChunkIDs = 'chid',
kAudioFilePropertyInfoDictionary = 'info',
kAudioFilePropertyPacketTableInfo = 'pnfo',
kAudioFilePropertyFormatList = 'flst',
kAudioFilePropertyPacketSizeUpperBound = 'pkub',
kAudioFilePropertyReserveDuration = 'rsrv',
kAudioFilePropertyEstimatedDuration = 'edur',
kAudioFilePropertyBitRate = 'brat',
kAudioFilePropertyID3Tag = 'id3t',
kAudioFilePropertySourceBitDepth = 'sbtd',
kAudioFilePropertyAlbumArtwork = 'aart',
kAudioFilePropertyAudioTrackCount = 'atct',
kAudioFilePropertyUseAudioTrack = 'uatk'
};
里面的有EstimatedDuration和bitRate,可以直接獲取duration和bitRate了
讀取音頻數據
讀取音頻數據分為直接讀取音頻數據和按幀(packet)讀取音頻數據
先來看直接讀取音頻數據
extern OSStatus AudioFileReadBytes (AudioFileID inAudioFile,
Boolean inUseCache,
SInt64 inStartingByte,
UInt32 * ioNumBytes,
void * outBuffer);
第一個參數fileID,即Open方法里面得到的實例id;
第二個參數,是否需要cache,一般都傳false;
第三個參數,從第幾個bytes開始讀取數據;
第四個參數,這個參數在調用時作為輸入參數表示需要讀取多少數據,調用完成后作為輸出參數表示實際讀取了多少數據(Read回調中的requestCount和actualCount);
第五個參數,buffer指針,需要事先分配好足夠大的內存(ioNumBytes大,即Read回調中的buffer,所以Read回調不需要再分配內存);
返回值表示是否讀取成功,如果失敗則返回kAudioFileEndOfFileError;
這個方法得到的數據都是沒有幀分離的數據,如果想要用來播放或者解碼還必須通過AudioFileStream進行幀分離;
我們繼續看按幀(packet)讀取音頻數據
extern OSStatus
AudioFileReadPackets ( AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * outNumBytes,
AudioStreamPacketDescription * __nullable outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * __nullable outBuffer) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_2,__MAC_10_10, __IPHONE_2_0,__IPHONE_8_0);//iOS8已經deprecated
extern OSStatus
AudioFileReadPacketData ( AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * ioNumBytes,
AudioStreamPacketDescription * __nullable outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * __nullable outBuffer) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_2_2);
第一個回調函數在iOS8以后已經deprecated了,其實兩個函數差別不大,只是前者之前更多用來讀取固定時長音頻或者非壓縮音頻時,現在一般采用第二種,且效率更高并且省內存
我們來看參數
第一,二個參數跟AudioFileReadBytes一致
第三個參數,AudioFileReadPacketData中,ioNumBytes這個參數在輸入輸出中都要用到,在輸入表示outBuffer的size,輸出時表示實際讀取了多少size的數據。
第四個參數,幀信息數組指針,在輸入前需要分配內存,大小必須足夠存在ioNumPackets個幀(sizeof(AudioStreamPacketDescription)*ioNumPackets);
第五個參數,從第幾幀開始讀取數據
第六個參數,在輸入時表示需要讀取多少個幀,在輸出時表示實際讀取了多少幀
第七個參數,outBuffer數據指針,在輸入前就需要分配好空間,這個參數看上去兩個方法一樣但其實并非這樣。對于AudioFileReadPacketData來說只要分配近似幀*幀數
的內存空間即可,方法本身會對給定的內存空間大小來決定最后輸出多少個幀,如果空間不夠會適當減少出的幀數;而對于AudioFileReadPackets來說則需分配最大幀大小(或幀大小上界)*幀數
的內存空間才行;第三個參數后者是輸入輸出雙向使用的,而前者只是作為輸出使用,這也是后者省內存的原因
返回值,同AudioFileReadBytes
這兩個方法讀取后的數據為幀分離后的數據,可以用來直接播放或者解碼
- (NSArray *)parseData:(BOOL *)isErr
{
UInt32 ioNumPackets = packetPerRead;//要讀取多少個packet
UInt32 ioNumBytes = ioNumPackets * _maxPacketSize;獲取輸出輸入的size
void *outBuffer = (void *)malloc(ioNumBytes);
AudioStreamPacketDescription *outPacketDescriptions = NULL;
OSStatus status = noErr;
UInt32 descSize = sizeof(AudioStreamPacketDescription) *ioNumPackets;
outPacketDescriptions = (AudioStreamPacketDescription *)malloc(descSize);
status = AudioFileReadPacketData(_audioFileID, false, &ioNumBytes, outPacketDescriptions, _packetOffset, &ioNumPackets, outBuffer);
if (status != noErr) {
*isErr = status == kAudioFileEndOfFileError;
free(outBuffer);
return nil;
}
if (ioNumBytes == 0) {
*isErr = YES;
}
_packetOffset += ioNumPackets;
if (ioNumPackets > 0) {
NSMutableArray *parsedDataArray = [[NSMutableArray alloc]init];
for (int i = 0; i < ioNumPackets; i++) {
AudioStreamPacketDescription packetDescription;
if (outPacketDescriptions) {
packetDescription = outPacketDescriptions[i];
}else{
packetDescription.mStartOffset = i*_format.mBytesPerPacket;
packetDescription.mDataByteSize = _format.mBytesPerPacket;
packetDescription.mVariableFramesInPacket = _format.mFramesPerPacket;
}
ZJParsedAudioData *parsedData = [ZJParsedAudioData parsedAudioDataWithBytes:outBuffer+packetDescription.mStartOffset packetDescription:packetDescription];
if (parsedData) {
[parsedDataArray addObject:parsedData];
}
}
return parsedDataArray;
}
return nil;
}
同樣AudioFile也需要關閉
extern OSStatus AudioFileClose (AudioFileID inAudioFile);
到此基本完結了,小結
- AudioFile有兩種初始化方法,一種只能打開本地文件,一種則使用場景更多
- 必須保證音頻信息的完整,才能使用AudioFile的Open方法,AudioFile需要配合AudioFileStream來判斷文件格式可讀再調用AudioFile的Open方法
- 使用AudioFileGetProperty讀取格式信息時需要判斷所讀取的信息是否需要先調用AudioFileGetPropertyInfo獲得數據大小后再進行讀取
最后demo地址奉上https://github.com/chanbendong/ZJAudioFile