iOS 音頻流式解碼器 - AudioFileStream

1 基礎知識

AudioFileStream將音頻文件流解析為音頻數據包的 API。

1.1 文件流錯誤碼類型

音頻文件流可能出現的錯誤類型,部分特殊場景滤奈,需要針對特定錯誤碼做處理突诬,完整錯誤碼定義如下:

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

1.2 AudioFileStream Properties

AudioFileStream 中蓄坏,支持從文件流中獲取以下 Property价捧,但不支持給文件設置 Property。完整的 Property定義如下:

CF_ENUM(AudioFileStreamPropertyID)
{
  // UInt32值涡戳,在解析器解析到音頻數據的開頭為止一直為0结蟋,當到達音頻數據即設置為1,為1時渔彰,所有可以知道的音頻文件流屬性都是已知的嵌屎。
    kAudioFileStreamProperty_ReadyToProducePackets          =   'redy',
  // 音頻文件的格式
    kAudioFileStreamProperty_FileFormat                     =   'ffmt',
  // 音頻文件數據格式的結構
    kAudioFileStreamProperty_DataFormat                     =   'dfmt',
  // 為了支持帶有SBR的AAC等格式,已編碼的數據流可以被解碼為多種目標格式恍涂,此屬性返回一個AudioFormatListItem結構數組宝惰,每個目標格式對應一個。
    kAudioFileStreamProperty_FormatList                     =   'flst',
  // 一個指向 magic cookie 的空指針
    kAudioFileStreamProperty_MagicCookieData                =   'mgic',
  // UInt64值再沧,表示流文件中音頻數據的字節(jié)數尼夺。僅當從標頭中解析的數據知道整個流的字節(jié)數時,此屬性才有效炒瘸。對于某些類型的流淤堵,此屬性可能沒有價值。
    kAudioFileStreamProperty_AudioDataByteCount             =   'bcnt',
  // UInt64值什燕,流文件中的音頻數據的數據包的數量的值粘勒。
    kAudioFileStreamProperty_AudioDataPacketCount           =   'pcnt',
  // UInt32值,表示所述數據的最大數據包大小值屎即。
    kAudioFileStreamProperty_MaximumPacketSize              =   'psze',
  // SInt64值庙睡,表示音頻數據開始的流文件中的字節(jié)偏移量事富。
    kAudioFileStreamProperty_DataOffset                     =   'doff',
  // 一個 AudioChannelLayout 數據結構 
    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值,表示指示在流文件中的理論上的最大數據包大小值乘陪。例如统台,此值可用于確定最小緩沖區(qū)大小。
    kAudioFileStreamProperty_PacketSizeUpperBound           =   'pkub',
  // Float64值啡邑,指示每個數據包的平均字節(jié)數贱勃。對于 CBR 和帶有數據包表的文件,這個數字是準確的谤逼。否則贵扰,它是解析的數據包的運行平均值。
    kAudioFileStreamProperty_AverageBytesPerPacket          =   'abpp',
  // UInt32值流部,表示每秒比特數表示流的比特率戚绕。
    kAudioFileStreamProperty_BitRate                        =   'brat',
    kAudioFileStreamProperty_InfoDictionary                 =   'info'
};

1.3 AudioFileStream Types

1.3.1 流屬性回調類型

解析器在音頻文件流中找到屬性值時調用。

typedef UInt32 AudioFileStreamPropertyID;
typedef struct OpaqueAudioFileStreamID  *AudioFileStreamID;

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

inClientData:調用函數時在參數中提供的值枝冀;

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

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

ioFlags:在輸入時果漾,如果設置了kAudioFileStreamPropertyFlag_PropertyIsCached值球切,解析器將緩存該屬性值。如果不是绒障,可以在輸出上設置kAudioFileStreamPropertyFlag_CacheProperty標志吨凑,以使解析器緩存該值。參見音頻文件流標志户辱。

1.3.2 流數據包回調類型

當音頻文件流解析器在音頻文件流中找到音頻數據時調用怀骤。對于恒定比特率 (CBR) 音頻數據,通常會使用與傳遞給函數的數據一樣多的數據調用回調焕妙。然而,有時由于輸入數據的邊界弓摘,可能只傳遞一個數據包焚鹊。對于可變比特率 (VBR) 音頻數據,每次調用該函數時可能會多次調用回調韧献。

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

inClientData:調用函數時在參數中提供的值末患;

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

inNumberPackets:緩沖區(qū)中音頻數據的包數锤窑;

inInputData:音頻數據璧针;

inPacketDescriptions:音頻文件流數據包描述結構數組。

1.4 AudioFileStream Flags

音頻文件流中標識類型集合:

typedef CF_OPTIONS(UInt32, AudioFileStreamPropertyFlags) {
  // 這個標志是在調用回調AudioFileStream_PropertyListenerProc時設置的渊啰,在這種情況下探橱,該屬性的值已經被緩存并且可以在以后獲得申屹。
    kAudioFileStreamPropertyFlag_PropertyIsCached = 1,
  // 屬性偵聽器設置此標志以指示解析器緩存屬性值,以便在回調返回后它仍然可用隧膏。
    kAudioFileStreamPropertyFlag_CacheProperty = 2
};

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

typedef CF_OPTIONS(UInt32, AudioFileStreamSeekFlags) {
  // AudioFileStreamSeek 方法胞枕,如果字節(jié)偏移量只是一個估計值杆煞,則此標志由函數返回。
    kAudioFileStreamSeekFlag_OffsetIsEstimated = 1
};

1.5 AudioFileStream Functions

1.5.1 初始化與釋放文件流服務

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

inClientData:傳遞給回調函數的值或結構的指針决乎;

inPropertyListenerProc:屬性監(jiān)聽器回調,當解析器在數據流中找到Property的值時回調派桩;

inPacketsProc:音頻數據回調构诚,當解析器在數據流中找到音頻數據包時回調;

inFileTypeHint:音頻文件類型窄坦,如果不知道音頻文件類型唤反,則設置為 0;

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

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

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

1.5.2 解析數據

將音頻文件流數據傳遞給解析器。當向解析器提供數據時闻书,解析器將查找屬性數據和音頻數據包名斟,當數據準備好時,將調用AudioFileStream_PropertyListenerProc和AudioFileStream_PacketsProc回調函數來處理數據魄眉。實際提供的數據量至少多于一個包的音頻文件數據砰盐,但最好一次提供幾個包到幾秒鐘的數據。

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

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

inDataByteSize:要解析的數據的字節(jié)數岩梳;

inData:要解析的數據;

inFlags:音頻文件流標志晃择。如果傳遞給解析器的最后一個數據存在不連續(xù)性冀值,請設置該標志為:kAudioFileStreamParseFlag_Discontinuity

1.5.3 Seek

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

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

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

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

outAbsoluteByteOffset:在輸出時浪蹂,參數中指定其偏移量的數據包的絕對字節(jié)偏移量抵栈。對于不包含數據包表的音頻文件格式告材,返回的偏移量可能是一個估計值;

ioFlags:在輸出中竭讳,如果outAbsoluteByteOffset參數返回一個估計值创葡,則該參數返回常量kAudioFileStreamSeekFlag_OffsetIsEstimated

1.5.4 獲取屬性

獲取有關屬性值的信息绢慢。

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

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

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

outPropertyDataSize:在輸出時胰舆,指定屬性的當前值的大猩丁(以字節(jié)為單位)。

outWritable:在輸出時缚窿,true如果可以寫入屬性棘幸,但目前沒有可寫的音頻文件流屬性。

1.5.5 獲取屬性值

檢索指定屬性的值倦零。

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

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

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

ioPropertyDataSize:參數中緩沖區(qū)的大小扫茅√G叮可能通過調用AudioFileStreamGetPropertyInfo獲取屬性值的大小葫隙;

outPropertyData:輸出指定屬性的值栽烂。

1.5.6 設置屬性

設置指定屬性的值。目前音頻文件流中恋脚,沒有可以設置的屬性腺办。

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

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

inPropertyID:要設置其值的音頻文件流的PropertyID糟描;

inPropertyDataSize:屬性數據的大谢澈怼(以字節(jié)為單位);

inPropertyData:屬性數據船响。

2 實踐與應用

為了驗證AudioFileStream能力磺送,這里僅通過 API,實現一個簡化版本的 AudioFileParser灿意,目標實現創(chuàng)建、解碼崇呵、Seek缤剧、關閉能力。

2.1 主體框架

主體框架僅包含必要的定義域慷,未實現任何功能荒辕,在下文汗销,會針對每個功能補充必要的能力,完善 AudioFileParser抵窒。

@interface AudioFileParser () {
    AudioFileStreamID _audioFileStreamID;
}
/// 是否不連續(xù)
@property (nonatomic, assign) BOOL discontinuous;
/// 解析出來的packets
@property (nonatomic, strong) NSMutableArray *packets;
/// 音頻數據在文件中的偏移
@property (nonatomic, assign) SInt64 dataOffset;
/// 已讀數據在數據源文件中的偏移
@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;
}
/// 解析數據
- (BOOL)parse:(NSData *)data error:(NSError **)error {
}
/// 音頻文件解析器Seek
- (BOOL)seek:(UInt32)packetCount error:(NSError **)error {
}
/// 關閉解析器
- (void)close {
}
/// 處理音頻文件流的Property
/// @param propertyID Property 對應的 ID
- (void)handleAudioFileStreamProperty:(AudioFileStreamPropertyID)propertyID {
}
/// 處理音頻文件流的 packets
/// @param packets 音頻包數據
/// @param numberOfBytes 緩沖區(qū)中數據的字節(jié)數
/// @param numberOfPackets 緩沖區(qū)中音頻數據的包數
/// @param packetDescriptions 描述數據的音頻文件流數據包描述結構數組
- (void)handleAudioFileStreamPackets:(const void *)packets
                       numberOfBytes:(UInt32)numberOfBytes
                     numberOfPackets:(UInt32)numberOfPackets
                   packetDescription:(AudioStreamPacketDescription *)packetDescriptions {
    
}
@end

2.2 核心能力

2.2.1 初始化與關閉

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

2.2.3 解析數據

初始化文件流解析器后,通過AudioFileStreamParseBytes對數據進行解碼哪亿,數據由外部傳遞進來粥烁。我們通過 fileReadOffset 來標識,當前我們訪問的數據在原始文件中的偏移蝇棉。需要注意讨阻,在未解析到音頻數據包前或者 Seek 之后,AudioFileStreamParseFlags 需要設置為 kAudioFileStreamParseFlag_Discontinuity银萍。

- (BOOL)parse:(NSData *)data error:(NSError **)error {
    BOOL bResult = YES;
    do {
        if (!data || !data.length) {
            bResult = NO;
            break;
        }
        // 已讀偏移加上實際讀取到的數據量变勇,有可能讀取到的數據要比要讀的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 本質上是對數據流的處理,并不特指是流媒體的資源贴唇,即使數據是本地文件搀绣,也是可以正常工作的,估這里命名為 AudioFileParser 而不是 AudioFileStreamParser戳气。

2.2.3 獲取音頻文件信息

通過解析音頻數據链患,解析器會解析并獲取音頻文件的頭文件,會通過AudioFileStream_PropertyListenerProc回調(多次回調)瓶您,這里重點關注關注:

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

2.2.3 處理音頻數據包

在解析到音頻文件信息之后,當解析器接收到足夠的數據夜赵,會將解析到的音頻數據包明棍,通過AudioFileStream_PacketsProc回調出來,我們需要在該回調中寇僧,保存音頻數據包的格式數據及音頻包數據摊腋,提供給后繼的轉碼器或者處理器使用沸版。

- (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該初始化方法底層默認copy一份數據
        NSData *data = [[NSData alloc] initWithBytes:packets+packetOffset length:packetSize];
        [_packets addObject:data];
    }
    
    if (deletePackDesc) {
        free(packetDescriptions);
        packetDescriptions = NULL;
    }
}

2.2.4 Seek 實現

AudioFileStream 中,Seek 本身只是獲取音頻文件在文件中偏移值兴蒸,然后通過計算出在原始音頻文件中偏移视粮,通過讀取新的數據包,實現 Seek 能力橙凳,需要注意的是在 Seek 之后蕾殴,需要將 discontinuous 設置 YES,否則可能會遇到數據解碼異常痕惋,同時需要把已經緩存的音頻數據包清空区宇,避免出現串數據而出現雜音。

- (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 后需要移除已經解析出來的包
    [_packets removeAllObjects];
    return YES;
}

Note:如果使用了轉碼器值戳,Seek 之后议谷,需要刷新其緩沖區(qū)。

2.3 小結

AudioFileParser 中僅實現簡化版本的文件流解碼器堕虹,比如音頻文件格式卧晓、時長、總幀數赴捞。最大包大小等數據逼裆,需要讀者去擴展其能力。這里僅介紹 AudioFileStream赦政,實際應用中胜宇,AudioFileStream 很少單獨應該,一般會結合 AudioConverter 恢着、Audio Unit 或者更高級的音頻 API 一起實現桐愉,實現解碼器、轉碼器掰派、處理器从诲、播放器之間的聯動。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末靡羡,一起剝皮案震驚了整個濱河市系洛,隨后出現的幾起案子,更是在濱河造成了極大的恐慌略步,老刑警劉巖描扯,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異趟薄,居然都是意外死亡绽诚,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憔购,“玉大人,你說我怎么就攤上這事岔帽∶的瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵犀勒,是天一觀的道長屎飘。 經常有香客問我,道長贾费,這世上最難降的妖魔是什么钦购? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮褂萧,結果婚禮上押桃,老公的妹妹穿的比我還像新娘。我一直安慰自己导犹,他們只是感情好唱凯,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谎痢,像睡著了一般磕昼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上节猿,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天票从,我揣著相機與錄音,去河邊找鬼滨嘱。 笑死峰鄙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的九孩。 我是一名探鬼主播先馆,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躺彬!你這毒婦竟也來了煤墙?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宪拥,失蹤者是張志新(化名)和其女友劉穎仿野,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體她君,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡脚作,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片球涛。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡劣针,死狀恐怖,靈堂內的尸體忽然破棺而出亿扁,到底是詐尸還是另有隱情捺典,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布从祝,位于F島的核電站襟己,受9級特大地震影響,放射性物質發(fā)生泄漏牍陌。R本人自食惡果不足惜擎浴,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毒涧。 院中可真熱鬧贮预,春花似錦、人聲如沸链嘀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怀泊。三九已至茫藏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霹琼,已是汗流浹背务傲。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枣申,地道東北人售葡。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像忠藤,于是被迫代替她去往敵國和親挟伙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容