iOS 音頻流式解碼器 - AudioFileStream

1 基礎(chǔ)知識(shí)

AudioFileStream將音頻文件流解析為音頻數(shù)據(jù)包的 API。

1.1 文件流錯(cuò)誤碼類型

音頻文件流可能出現(xiàn)的錯(cuò)誤類型,部分特殊場景,需要針對(duì)特定錯(cuò)誤碼做處理,完整錯(cuò)誤碼定義如下:

CF_ENUM(OSStatus)
{
    kAudioFileStreamError_UnsupportedFileType       = 'typ?',    // 不支持指定的文件類型
    kAudioFileStreamError_UnsupportedDataFormat     = 'fmt?',  // 指定的文件類型不支持?jǐn)?shù)據(jù)格式
    kAudioFileStreamError_UnsupportedProperty       = 'pty?',    // 不支持該屬性 
    kAudioFileStreamError_BadPropertySize           = '!siz',      // 屬性數(shù)據(jù)提供的緩沖區(qū)大小不正確
    kAudioFileStreamError_NotOptimized              = 'optm',    // 無法產(chǎn)生輸出數(shù)據(jù)包,因?yàn)榱魇揭纛l文件的數(shù)據(jù)包表或其他定義信息不存在或出現(xiàn)在音頻數(shù)據(jù)之后
    kAudioFileStreamError_InvalidPacketOffset       = 'pck?',   // 數(shù)據(jù)包偏移量小于0或超過文件末尾,或者在構(gòu)建數(shù)據(jù)包表時(shí)讀取了損壞的數(shù)據(jù)包大小
    kAudioFileStreamError_InvalidFile               = 'dta?',       // 文件格式錯(cuò)誤,不是其類型的音頻文件的有效實(shí)例,或未被識(shí)別為音頻文件
    kAudioFileStreamError_ValueUnknown              = 'unk?',     // 在音頻數(shù)據(jù)之前,此文件中不存在屬性值
    kAudioFileStreamError_DataUnavailable           = 'more',     // 提供給解析器的數(shù)據(jù)量不足以產(chǎn)生任何結(jié)果
    kAudioFileStreamError_IllegalOperation          = 'nope',   // 試圖進(jìn)行非法操作
    kAudioFileStreamError_UnspecifiedError          = 'wht?',    // 發(fā)生未指明的錯(cuò)誤
    kAudioFileStreamError_DiscontinuityCantRecover  = 'dsc!' // 音頻數(shù)據(jù)出現(xiàn)中斷,音頻文件流服務(wù)無法恢復(fù)
};

1.2 AudioFileStream Properties

AudioFileStream 中,支持從文件流中獲取以下 Property,但不支持給文件設(shè)置 Property。完整的 Property定義如下:

CF_ENUM(AudioFileStreamPropertyID)
{
  // UInt32值,在解析器解析到音頻數(shù)據(jù)的開頭為止一直為0,當(dāng)?shù)竭_(dá)音頻數(shù)據(jù)即設(shè)置為1,為1時(shí),所有可以知道的音頻文件流屬性都是已知的。
    kAudioFileStreamProperty_ReadyToProducePackets          =   'redy',
  // 音頻文件的格式
    kAudioFileStreamProperty_FileFormat                     =   'ffmt',
  // 音頻文件數(shù)據(jù)格式的結(jié)構(gòu)
    kAudioFileStreamProperty_DataFormat                     =   'dfmt',
  // 為了支持帶有SBR的AAC等格式,已編碼的數(shù)據(jù)流可以被解碼為多種目標(biāo)格式,此屬性返回一個(gè)AudioFormatListItem結(jié)構(gòu)數(shù)組,每個(gè)目標(biāo)格式對(duì)應(yīng)一個(gè)。
    kAudioFileStreamProperty_FormatList                     =   'flst',
  // 一個(gè)指向 magic cookie 的空指針
    kAudioFileStreamProperty_MagicCookieData                =   'mgic',
  // UInt64值,表示流文件中音頻數(shù)據(jù)的字節(jié)數(shù)。僅當(dāng)從標(biāo)頭中解析的數(shù)據(jù)知道整個(gè)流的字節(jié)數(shù)時(shí),此屬性才有效。對(duì)于某些類型的流,此屬性可能沒有價(jià)值。
    kAudioFileStreamProperty_AudioDataByteCount             =   'bcnt',
  // UInt64值,流文件中的音頻數(shù)據(jù)的數(shù)據(jù)包的數(shù)量的值。
    kAudioFileStreamProperty_AudioDataPacketCount           =   'pcnt',
  // UInt32值,表示所述數(shù)據(jù)的最大數(shù)據(jù)包大小值。
    kAudioFileStreamProperty_MaximumPacketSize              =   'psze',
  // SInt64值,表示音頻數(shù)據(jù)開始的流文件中的字節(jié)偏移量。
    kAudioFileStreamProperty_DataOffset                     =   'doff',
  // 一個(gè) AudioChannelLayout 數(shù)據(jù)結(jié)構(gòu) 
    kAudioFileStreamProperty_ChannelLayout                  =   'cmap',
    kAudioFileStreamProperty_PacketToFrame                  =   'pkfr',
    kAudioFileStreamProperty_FrameToPacket                  =   'frpk',
    kAudioFileStreamProperty_RestrictsRandomAccess          =   'rrap',
    kAudioFileStreamProperty_PacketToRollDistance           =   'pkrl',
    kAudioFileStreamProperty_PreviousIndependentPacket      =   'pind',
    kAudioFileStreamProperty_NextIndependentPacket          =   'nind',
    kAudioFileStreamProperty_PacketToDependencyInfo         =   'pkdp',
    kAudioFileStreamProperty_PacketToByte                   =   'pkby',
    kAudioFileStreamProperty_ByteToPacket                   =   'bypk',
    kAudioFileStreamProperty_PacketTableInfo                =   'pnfo',
  // UInt32值,表示指示在流文件中的理論上的最大數(shù)據(jù)包大小值。例如,此值可用于確定最小緩沖區(qū)大小。
    kAudioFileStreamProperty_PacketSizeUpperBound           =   'pkub',
  // Float64值,指示每個(gè)數(shù)據(jù)包的平均字節(jié)數(shù)。對(duì)于 CBR 和帶有數(shù)據(jù)包表的文件,這個(gè)數(shù)字是準(zhǔn)確的。否則,它是解析的數(shù)據(jù)包的運(yùn)行平均值。
    kAudioFileStreamProperty_AverageBytesPerPacket          =   'abpp',
  // UInt32值,表示每秒比特?cái)?shù)表示流的比特率。
    kAudioFileStreamProperty_BitRate                        =   'brat',
    kAudioFileStreamProperty_InfoDictionary                 =   'info'
};

1.3 AudioFileStream Types

1.3.1 流屬性回調(diào)類型

解析器在音頻文件流中找到屬性值時(shí)調(diào)用。

typedef UInt32 AudioFileStreamPropertyID;
typedef struct OpaqueAudioFileStreamID  *AudioFileStreamID;

typedef void (*AudioFileStream_PropertyListenerProc)(
                                            void *                          inClientData,
                                            AudioFileStreamID               inAudioFileStream,
                                            AudioFileStreamPropertyID       inPropertyID,
                                            AudioFileStreamPropertyFlags *  ioFlags);

inClientData:調(diào)用函數(shù)時(shí)在參數(shù)中提供的值;

inAudioFileStream:音頻文件流解析器的 ID;

inPropertyID:解析器在音頻文件數(shù)據(jù)流中找到的屬性 ID;

ioFlags:在輸入時(shí),如果設(shè)置了kAudioFileStreamPropertyFlag_PropertyIsCached值,解析器將緩存該屬性值。如果不是,可以在輸出上設(shè)置kAudioFileStreamPropertyFlag_CacheProperty標(biāo)志,以使解析器緩存該值。參見音頻文件流標(biāo)志。

1.3.2 流數(shù)據(jù)包回調(diào)類型

當(dāng)音頻文件流解析器在音頻文件流中找到音頻數(shù)據(jù)時(shí)調(diào)用。對(duì)于恒定比特率 (CBR) 音頻數(shù)據(jù),通常會(huì)使用與傳遞給函數(shù)的數(shù)據(jù)一樣多的數(shù)據(jù)調(diào)用回調(diào)。然而,有時(shí)由于輸入數(shù)據(jù)的邊界,可能只傳遞一個(gè)數(shù)據(jù)包。對(duì)于可變比特率 (VBR) 音頻數(shù)據(jù),每次調(diào)用該函數(shù)時(shí)可能會(huì)多次調(diào)用回調(diào)。

typedef void (*AudioFileStream_PacketsProc)(
                                            void *                                        inClientData,
                                            UInt32                                        inNumberBytes,
                                            UInt32                                        inNumberPackets,
                                            const void *                                inInputData,
                                            AudioStreamPacketDescription * __nullable   inPacketDescriptions);

inClientData:調(diào)用函數(shù)時(shí)在參數(shù)中提供的值;

inNumberBytes:緩沖區(qū)中數(shù)據(jù)的字節(jié)數(shù);

inNumberPackets:緩沖區(qū)中音頻數(shù)據(jù)的包數(shù);

inInputData:音頻數(shù)據(jù);

inPacketDescriptions:音頻文件流數(shù)據(jù)包描述結(jié)構(gòu)數(shù)組。

1.4 AudioFileStream Flags

音頻文件流中標(biāo)識(shí)類型集合:

typedef CF_OPTIONS(UInt32, AudioFileStreamPropertyFlags) {
  // 這個(gè)標(biāo)志是在調(diào)用回調(diào)AudioFileStream_PropertyListenerProc時(shí)設(shè)置的,在這種情況下,該屬性的值已經(jīng)被緩存并且可以在以后獲得。
    kAudioFileStreamPropertyFlag_PropertyIsCached = 1,
  // 屬性偵聽器設(shè)置此標(biāo)志以指示解析器緩存屬性值,以便在回調(diào)返回后它仍然可用。
    kAudioFileStreamPropertyFlag_CacheProperty = 2
};

typedef CF_OPTIONS(UInt32, AudioFileStreamParseFlags) {
  // AudioFileStreamParseBytes方法中,將此標(biāo)志傳遞給函數(shù)以表示音頻數(shù)據(jù)的不連續(xù)性。
    kAudioFileStreamParseFlag_Discontinuity = 1
};

typedef CF_OPTIONS(UInt32, AudioFileStreamSeekFlags) {
  // AudioFileStreamSeek 方法,如果字節(jié)偏移量只是一個(gè)估計(jì)值,則此標(biāo)志由函數(shù)返回。
    kAudioFileStreamSeekFlag_OffsetIsEstimated = 1
};

1.5 AudioFileStream Functions

1.5.1 初始化與釋放文件流服務(wù)

  1. 創(chuàng)建并打開一個(gè)新的音頻文件流解析器。
extern OSStatus 
AudioFileStreamOpen (
                            void * __nullable                                    inClientData,
                            AudioFileStream_PropertyListenerProc       inPropertyListenerProc,
                            AudioFileStream_PacketsProc                    inPacketsProc,
              AudioFileTypeID                                        inFileTypeHint,
              AudioFileStreamID __nullable * __nonnull outAudioFileStream);

inClientData:傳遞給回調(diào)函數(shù)的值或結(jié)構(gòu)的指針;

inPropertyListenerProc:屬性監(jiān)聽器回調(diào),當(dāng)解析器在數(shù)據(jù)流中找到Property的值時(shí)回調(diào);

inPacketsProc:音頻數(shù)據(jù)回調(diào),當(dāng)解析器在數(shù)據(jù)流中找到音頻數(shù)據(jù)包時(shí)回調(diào);

inFileTypeHint:音頻文件類型,如果不知道音頻文件類型,則設(shè)置為 0;

outAudioFileStream:音頻文件流解析器的 ID,需要將其保存,供其它音頻文件流 API 使用。

  1. 關(guān)閉并釋放指定的音頻文件流解析器。
extern OSStatus 
AudioFileStreamClose(AudioFileStreamID inAudioFileStream);

inAudioFileStream:指定的音頻文件流解析器的 ID。

1.5.2 解析數(shù)據(jù)

將音頻文件流數(shù)據(jù)傳遞給解析器。當(dāng)向解析器提供數(shù)據(jù)時(shí),解析器將查找屬性數(shù)據(jù)和音頻數(shù)據(jù)包,當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),將調(diào)用AudioFileStream_PropertyListenerProc和AudioFileStream_PacketsProc回調(diào)函數(shù)來處理數(shù)據(jù)。實(shí)際提供的數(shù)據(jù)量至少多于一個(gè)包的音頻文件數(shù)據(jù),但最好一次提供幾個(gè)包到幾秒鐘的數(shù)據(jù)。

extern OSStatus
AudioFileStreamParseBytes(  
                                AudioFileStreamID                   inAudioFileStream,
                                UInt32                                  inDataByteSize,
                                const void * __nullable         inData,
                                AudioFileStreamParseFlags       inFlags);

inAudioFileStream:音頻文件流解析器的 ID;

inDataByteSize:要解析的數(shù)據(jù)的字節(jié)數(shù);

inData:要解析的數(shù)據(jù);

inFlags:音頻文件流標(biāo)志。如果傳遞給解析器的最后一個(gè)數(shù)據(jù)存在不連續(xù)性,請(qǐng)?jiān)O(shè)置該標(biāo)志為:kAudioFileStreamParseFlag_Discontinuity。

1.5.3 Seek

為數(shù)據(jù)流中的指定數(shù)據(jù)包提供字節(jié)偏移量。

extern OSStatus
AudioFileStreamSeek(    
                                AudioFileStreamID                  inAudioFileStream,
                                SInt64                                 inPacketOffset,
                                SInt64 *                               outDataByteOffset,
                                AudioFileStreamSeekFlags * ioFlags);

inAudioFileStream:音頻文件流解析器的 ID;

inAbsolutePacketOffset:希望返回其字節(jié)偏移量的數(shù)據(jù)包文件開頭的數(shù)據(jù)包數(shù);

outAbsoluteByteOffset:在輸出時(shí),參數(shù)中指定其偏移量的數(shù)據(jù)包的絕對(duì)字節(jié)偏移量。對(duì)于不包含數(shù)據(jù)包表的音頻文件格式,返回的偏移量可能是一個(gè)估計(jì)值;

ioFlags:在輸出中,如果outAbsoluteByteOffset參數(shù)返回一個(gè)估計(jì)值,則該參數(shù)返回常量kAudioFileStreamSeekFlag_OffsetIsEstimated。

1.5.4 獲取屬性

獲取有關(guān)屬性值的信息。

extern OSStatus
AudioFileStreamGetPropertyInfo( 
                                AudioFileStreamID                   inAudioFileStream,
                                AudioFileStreamPropertyID       inPropertyID,
                                UInt32 * __nullable               outPropertyDataSize,
                                Boolean * __nullable              outWritable);

inAudioFileStream:音頻文件流解析器的 ID;

inPropertyID:需要其信息的音頻文件流PropertyID

outPropertyDataSize:在輸出時(shí),指定屬性的當(dāng)前值的大?。ㄒ宰止?jié)為單位)。

outWritable:在輸出時(shí),true如果可以寫入屬性,但目前沒有可寫的音頻文件流屬性。

1.5.5 獲取屬性值

檢索指定屬性的值。

extern OSStatus
AudioFileStreamGetProperty( 
                            AudioFileStreamID                     inAudioFileStream,
                            AudioFileStreamPropertyID     inPropertyID,
                            UInt32 *                                  ioPropertyDataSize,
                            void *                                    outPropertyData);

inAudioFileStream:音頻文件流解析器的 ID;

inPropertyID:讀取其值的音頻文件流屬性;

ioPropertyDataSize:參數(shù)中緩沖區(qū)的大小??赡芡ㄟ^調(diào)用AudioFileStreamGetPropertyInfo獲取屬性值的大??;

outPropertyData:輸出指定屬性的值。

1.5.6 設(shè)置屬性

設(shè)置指定屬性的值。目前音頻文件流中,沒有可以設(shè)置的屬性。

extern OSStatus
AudioFileStreamSetProperty( 
                            AudioFileStreamID                     inAudioFileStream,
                            AudioFileStreamPropertyID     inPropertyID,
                            UInt32                                    inPropertyDataSize,
                            const void *                            inPropertyData);

inAudioFileStream:音頻文件流解析器的 ID;

inPropertyID:要設(shè)置其值的音頻文件流的PropertyID;

inPropertyDataSize:屬性數(shù)據(jù)的大小(以字節(jié)為單位);

inPropertyData:屬性數(shù)據(jù)。

2 實(shí)踐與應(yīng)用

為了驗(yàn)證AudioFileStream能力,這里僅通過 API,實(shí)現(xiàn)一個(gè)簡化版本的 AudioFileParser,目標(biāo)實(shí)現(xiàn)創(chuàng)建、解碼、Seek、關(guān)閉能力。

2.1 主體框架

主體框架僅包含必要的定義,未實(shí)現(xiàn)任何功能,在下文,會(huì)針對(duì)每個(gè)功能補(bǔ)充必要的能力,完善 AudioFileParser。

@interface AudioFileParser () {
    AudioFileStreamID _audioFileStreamID;
}
/// 是否不連續(xù)
@property (nonatomic, assign) BOOL discontinuous;
/// 解析出來的packets
@property (nonatomic, strong) NSMutableArray *packets;
/// 音頻數(shù)據(jù)在文件中的偏移
@property (nonatomic, assign) SInt64 dataOffset;
/// 已讀數(shù)據(jù)在數(shù)據(jù)源文件中的偏移
@property (nonatomic, assign) SInt64 fileReadOffset;
/// 文件頭解析完畢
@property (nonatomic, assign) BOOL readyToProducePackets;
@end
  
static void KSKitAudioFileStreamPropertyListener(void *inClientData,AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *inFlags) {
    AudioFileParser *parser = (__bridge AudioFileParser *)inClientData;
    [parser handleAudioFileStreamProperty:inPropertyID];
}

static void KSKitAudioFileStreamPacketCallBack(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescrrptions) {
    AudioFileParser *parser = (__bridge AudioFileParser *)inClientData;
    [parser handleAudioFileStreamPackets:inInputData
                           numberOfBytes:inNumberBytes
                         numberOfPackets:inNumberPackets
                       packetDescription:inPacketDescrrptions];
}

@implementation AudioFileParser
/// 初始化
- (instancetype)init {
    if (self = [super init]) {
    }
    return self;
}
/// 解析數(shù)據(jù)
- (BOOL)parse:(NSData *)data error:(NSError **)error {
}
/// 音頻文件解析器Seek
- (BOOL)seek:(UInt32)packetCount error:(NSError **)error {
}
/// 關(guān)閉解析器
- (void)close {
}
/// 處理音頻文件流的Property
/// @param propertyID Property 對(duì)應(yīng)的 ID
- (void)handleAudioFileStreamProperty:(AudioFileStreamPropertyID)propertyID {
}
/// 處理音頻文件流的 packets
/// @param packets 音頻包數(shù)據(jù)
/// @param numberOfBytes 緩沖區(qū)中數(shù)據(jù)的字節(jié)數(shù)
/// @param numberOfPackets 緩沖區(qū)中音頻數(shù)據(jù)的包數(shù)
/// @param packetDescriptions 描述數(shù)據(jù)的音頻文件流數(shù)據(jù)包描述結(jié)構(gòu)數(shù)組
- (void)handleAudioFileStreamPackets:(const void *)packets
                       numberOfBytes:(UInt32)numberOfBytes
                     numberOfPackets:(UInt32)numberOfPackets
                   packetDescription:(AudioStreamPacketDescription *)packetDescriptions {
    
}
@end

2.2 核心能力

2.2.1 初始化與關(guān)閉

  1. 在初始化AudioFileParser時(shí),通過AudioFileStreamOpen創(chuàng)建音頻文件流服務(wù)。readyToProducePackets 用來標(biāo)識(shí)是否已經(jīng)解析出音頻文件頭信息,discontinuous 用來標(biāo)識(shí)是否連續(xù),會(huì)在 Seek 實(shí)現(xiàn)中詳細(xì)講解。這里需要重點(diǎn)關(guān)注的是 KSKitAudioFileStreamPropertyListener 與 KSKitAudioFileStreamPacketCallBack,負(fù)責(zé)了音頻數(shù)據(jù)回調(diào)與屬性監(jiān)聽器回調(diào)。
- (instancetype)init {
    if (self = [super init]) {
        _readyToProducePackets = NO;
        _discontinuous = NO;
        _packets = [[NSMutableArray alloc] init];
        // inFileTypeHint 可以根據(jù)實(shí)際的傳或者不指定
        OSStatus status = AudioFileStreamOpen((__bridge void *)self, KSKitAudioFileStreamPropertyListener, KSKitAudioFileStreamPacketCallBack, kAudioFileM4AType, &_audioFileStreamID);
        if (status != noErr) {
            return nil;
        }
    }
    return self;
}
  1. 音頻文件流服務(wù),需要手機(jī)關(guān)閉,通過AudioFileStreamClose關(guān)閉指定的解析器。
- (void)close {
    if (_audioFileStreamID) {
        AudioFileStreamClose(_audioFileStreamID);
        _audioFileStreamID = NULL;
    }
}

2.2.3 解析數(shù)據(jù)

初始化文件流解析器后,通過AudioFileStreamParseBytes對(duì)數(shù)據(jù)進(jìn)行解碼,數(shù)據(jù)由外部傳遞進(jìn)來。我們通過 fileReadOffset 來標(biāo)識(shí),當(dāng)前我們?cè)L問的數(shù)據(jù)在原始文件中的偏移。需要注意,在未解析到音頻數(shù)據(jù)包前或者 Seek 之后,AudioFileStreamParseFlags 需要設(shè)置為 kAudioFileStreamParseFlag_Discontinuity。

- (BOOL)parse:(NSData *)data error:(NSError **)error {
    BOOL bResult = YES;
    do {
        if (!data || !data.length) {
            bResult = NO;
            break;
        }
        // 已讀偏移加上實(shí)際讀取到的數(shù)據(jù)量,有可能讀取到的數(shù)據(jù)要比要讀的size少
        _fileReadOffset += data.length;
        OSStatus status;
        if (_discontinuous) {
            status = AudioFileStreamParseBytes(_audioFileStreamID, (UInt32)data.length, data.bytes, kAudioFileStreamParseFlag_Discontinuity);
        } else {
            status = AudioFileStreamParseBytes(_audioFileStreamID, (UInt32)data.length, data.bytes, 0);
        }
        if (status != noErr) {
            // handle error
            bResult = NO;
        }
    } while (NO);
    return bResult;
}

Note:AudioFileStream 本質(zhì)上是對(duì)數(shù)據(jù)流的處理,并不特指是流媒體的資源,即使數(shù)據(jù)是本地文件,也是可以正常工作的,估這里命名為 AudioFileParser 而不是 AudioFileStreamParser。

2.2.3 獲取音頻文件信息

通過解析音頻數(shù)據(jù),解析器會(huì)解析并獲取音頻文件的頭文件,會(huì)通過AudioFileStream_PropertyListenerProc回調(diào)(多次回調(diào)),這里重點(diǎn)關(guān)注關(guān)注:

  1. kAudioFileStreamProperty_ReadyToProducePackets 成功獲取頭信息會(huì)回調(diào),回調(diào)后,discontinuous 與 readyToProducePackets 可以標(biāo)識(shí)為 YES;
  2. kAudioFileStreamProperty_DataOffset 獲取音頻真實(shí)數(shù)據(jù)在音頻文件的偏移值,Seek 時(shí)使用,這里注意上文說到的 fileReadOffset 原始數(shù)據(jù)偏移的區(qū)別
/// 處理音頻文件流的Property
/// @param propertyID Property 對(duì)應(yīng)的 ID
- (void)handleAudioFileStreamProperty:(AudioFileStreamPropertyID)propertyID {
    if (propertyID == kAudioFileStreamProperty_ReadyToProducePackets) {
        // 成功獲取頭部信息
        _readyToProducePackets = YES;
        _discontinuous = YES;
    } else if (propertyID == kAudioFileStreamProperty_DataOffset) {
        UInt32 offsetSize = sizeof(_dataOffset);
        // 獲取音頻真實(shí)數(shù)據(jù)在音頻文件的偏移值
        OSStatus status = AudioFileStreamGetProperty(_audioFileStreamID, kAudioFileStreamProperty_DataOffset, &offsetSize, &_dataOffset);
        if(status != noErr) {
            NSLog(@"Parser get dataOffset error: %d", (int)status);
        }
    } 
}

2.2.3 處理音頻數(shù)據(jù)包

在解析到音頻文件信息之后,當(dāng)解析器接收到足夠的數(shù)據(jù),會(huì)將解析到的音頻數(shù)據(jù)包,通過AudioFileStream_PacketsProc回調(diào)出來,我們需要在該回調(diào)中,保存音頻數(shù)據(jù)包的格式數(shù)據(jù)及音頻包數(shù)據(jù),提供給后繼的轉(zhuǎn)碼器或者處理器使用。

- (void)handleAudioFileStreamPackets:(const void *)packets
                       numberOfBytes:(UInt32)numberOfBytes
                     numberOfPackets:(UInt32)numberOfPackets
                   packetDescription:(AudioStreamPacketDescription *)packetDescriptions {
    _discontinuous = NO;
    if (numberOfBytes == 0 || numberOfPackets == 0) {
        return;
    }
    
    BOOL deletePackDesc = NO;
    if (packetDescriptions == NULL) {
        deletePackDesc = YES;
        UInt32 packetSize = numberOfBytes / numberOfPackets;
        AudioStreamPacketDescription *descriptions = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription)*numberOfPackets);
        for (int i = 0; i < numberOfPackets; i++) {
            UInt32 packetOffset = packetSize * i;
            descriptions[i].mStartOffset  = packetOffset;
            descriptions[i].mVariableFramesInPacket = 0;
            if (i == numberOfPackets - 1) {
                descriptions[i].mDataByteSize = numberOfPackets-packetOffset;
            }else{
                descriptions[i].mDataByteSize = packetSize;
            }
        }
        packetDescriptions = descriptions;
    }
    
    for (int i = 0; i < numberOfPackets; i++) {
        SInt64 packetOffset = packetDescriptions[i].mStartOffset;
        AudioStreamPacketDescription aspd = packetDescriptions[i];
        UInt32 packetSize = aspd.mDataByteSize;
        // data該初始化方法底層默認(rèn)copy一份數(shù)據(jù)
        NSData *data = [[NSData alloc] initWithBytes:packets+packetOffset length:packetSize];
        [_packets addObject:data];
    }
    
    if (deletePackDesc) {
        free(packetDescriptions);
        packetDescriptions = NULL;
    }
}

2.2.4 Seek 實(shí)現(xiàn)

AudioFileStream 中,Seek 本身只是獲取音頻文件在文件中偏移值,然后通過計(jì)算出在原始音頻文件中偏移,通過讀取新的數(shù)據(jù)包,實(shí)現(xiàn) Seek 能力,需要注意的是在 Seek 之后,需要將 discontinuous 設(shè)置 YES,否則可能會(huì)遇到數(shù)據(jù)解碼異常,同時(shí)需要把已經(jīng)緩存的音頻數(shù)據(jù)包清空,避免出現(xiàn)串?dāng)?shù)據(jù)而出現(xiàn)雜音。

- (BOOL)seek:(UInt32)packetCount error:(NSError **)error; {
    SInt64 outDataByteOffset;
    UInt32 ioFlags;
    OSStatus status = AudioFileStreamSeek(_audioFileStreamID, packetCount, &outDataByteOffset, &ioFlags);
    if ((status == noErr) && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated)) {
        _fileReadOffset = _dataOffset + outDataByteOffset;
    } else {
        // handle error
        return NO;
    }
    _discontinuous = YES;
    // seek 后需要移除已經(jīng)解析出來的包
    [_packets removeAllObjects];
    return YES;
}

Note:如果使用了轉(zhuǎn)碼器,Seek 之后,需要刷新其緩沖區(qū)。

2.3 小結(jié)

AudioFileParser 中僅實(shí)現(xiàn)簡化版本的文件流解碼器,比如音頻文件格式、時(shí)長、總幀數(shù)。最大包大小等數(shù)據(jù),需要讀者去擴(kuò)展其能力。這里僅介紹 AudioFileStream,實(shí)際應(yīng)用中,AudioFileStream 很少單獨(dú)應(yīng)該,一般會(huì)結(jié)合 AudioConverter 、Audio Unit 或者更高級(jí)的音頻 API 一起實(shí)現(xiàn),實(shí)現(xiàn)解碼器、轉(zhuǎn)碼器、處理器、播放器之間的聯(lián)動(dòng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容