1 基础知识

AudioFileStream将音频文件流解析为音频数据包的 API。

1.1 文件流过错码类型

音频文件流或许出现的过错类型,部分特殊场景,需求针对特定过错码做处理,完好过错码界说如下:

CF_ENUM(OSStatus)
{
	kAudioFileStreamError_UnsupportedFileType		= 'typ?',    // 不支撑指定的文件类型
	kAudioFileStreamError_UnsupportedDataFormat		= 'fmt?',  // 指定的文件类型不支撑数据格局
	kAudioFileStreamError_UnsupportedProperty		= 'pty?',    // 不支撑该特点 
	kAudioFileStreamError_BadPropertySize			= '!siz',      // 特点数据供给的缓冲区巨细不正确
	kAudioFileStreamError_NotOptimized				= 'optm',    // 无法发生输出数据包,因为流式音频文件的数据包表或其他界说信息不存在或出现在音频数据之后
	kAudioFileStreamError_InvalidPacketOffset		= 'pck?',   // 数据包偏移量小于0或超过文件结尾,或许在构建数据包表时读取了损坏的数据包巨细
	kAudioFileStreamError_InvalidFile				= 'dta?',       // 文件格局过错,不是其类型的音频文件的有用实例,或未被识别为音频文件
	kAudioFileStreamError_ValueUnknown				= 'unk?',     // 在音频数据之前,此文件中不存在特点值
	kAudioFileStreamError_DataUnavailable			= 'more',     // 供给给解析器的数据量不足以发生任何结果
	kAudioFileStreamError_IllegalOperation			= 'nope',   // 企图进行非法操作
	kAudioFileStreamError_UnspecifiedError			= 'wht?',    // 发生未指明的过错
	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值,表明流文件中音频数据的字节数。仅当从标头中解析的数据知道整个流的字节数时,此特点才有用。关于某些类型的流,此特点或许没有价值。
	kAudioFileStreamProperty_AudioDataByteCount				=	'bcnt',
  // UInt64值,流文件中的音频数据的数据包的数量的值。
	kAudioFileStreamProperty_AudioDataPacketCount			=	'pcnt',
  // UInt32值,表明所述数据的最大数据包巨细值。
	kAudioFileStreamProperty_MaximumPacketSize				=	'psze',
  // SInt64值,表明音频数据开始的流文件中的字节偏移量。
	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值,表明指示在流文件中的理论上的最大数据包巨细值。例如,此值可用于确定最小缓冲区巨细。
	kAudioFileStreamProperty_PacketSizeUpperBound  			=	'pkub',
  // Float64值,指示每个数据包的平均字节数。关于 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:缓冲区中数据的字节数;

inNumberPackets:缓冲区中音频数据的包数;

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办法中,将此标志传递给函数以表明音频数据的不接连性。
	kAudioFileStreamParseFlag_Discontinuity = 1
};
typedef CF_OPTIONS(UInt32, AudioFileStreamSeekFlags) {
  // AudioFileStreamSeek 办法,假如字节偏移量仅仅一个估计值,则此标志由函数回来。
	kAudioFileStreamSeekFlag_OffsetIsEstimated = 1
};

1.5 AudioFileStream Functions

1.5.1 初始化与开释文件流服务

  1. 创立并翻开一个新的音频文件流解析器。
extern OSStatus
AudioFileStreamOpen (
							void * __nullable						             inClientData,
							AudioFileStream_PropertyListenerProc	   inPropertyListenerProc,
							AudioFileStream_PacketsProc				       inPacketsProc,
              AudioFileTypeID							             inFileTypeHint,
              AudioFileStreamID __nullable * __nonnull outAudioFileStream);

inClientData:传递给回调函数的值或结构的指针;

inPropertyListenerProc:特点监听器回调,当解析器在数据流中找到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:要解析的数据的字节数;

inData:要解析的数据;

inFlags:音频文件流标志。假如传递给解析器的最后一个数据存在不接连性,请设置该标志为:kAudioFileStreamParseFlag_Discontinuity

1.5.3 Seek

为数据流中的指定数据包供给字节偏移量。

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

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

inAbsolutePacketOffset:期望回来其字节偏移量的数据包文件开头的数据包数;

outAbsoluteByteOffset:在输出时,参数中指定其偏移量的数据包的绝对字节偏移量。关于不包含数据包表的音频文件格局,回来的偏移量或许是一个估计值;

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:在输出时,指定特点的当时值的巨细(以字节为单位)。

outWritable:在输出时,true假如能够写入特点,但现在没有可写的音频文件流特点。

1.5.5 获取特点值

检索指定特点的值。

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

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

inPropertyID:读取其值的音频文件流特点;

ioPropertyDataSize:参数中缓冲区的巨细。或许经过调用AudioFileStreamGetPropertyInfo获取特点值的巨细;

outPropertyData:输出指定特点的值。

1.5.6 设置特点

设置指定特点的值。现在音频文件流中,没有能够设置的特点。

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

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

inPropertyID:要设置其值的音频文件流的PropertyID;

inPropertyDataSize:特点数据的巨细(以字节为单位);

inPropertyData:特点数据。

2 实践与使用

为了验证AudioFileStream能力,这儿仅经过 API,完成一个简化版别的 AudioFileParser,方针完成创立、解码、Seek、封闭能力。

2.1 主体结构

主体结构仅包含必要的界说,未完成任何功用,在下文,会针对每个功用弥补必要的能力,完善 AudioFileParser。

@interface AudioFileParser () {
    AudioFileStreamID _audioFileStreamID;
}
/// 是否不接连
@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 缓冲区中数据的字节数
/// @param numberOfPackets 缓冲区中音频数据的包数
/// @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创立音频文件流服务。readyToProducePackets 用来标识是否现已解析出音频文件头信息,discontinuous 用来标识是否接连,会在 Seek 完成中具体讲解。这儿需求要点重视的是 KSKitAudioFileStreamPropertyListener 与 KSKitAudioFileStreamPacketCallBack,担任了音频数据回调与特点监听器回调。
- (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 原始数据偏移的差异
/// 处理音频文件流的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 之后,需求刷新其缓冲区。

2.3 小结

AudioFileParser 中仅完成简化版别的文件流解码器,比如音频文件格局、时长、总帧数。最大包巨细等数据,需求读者去扩展其能力。这儿仅介绍 AudioFileStream,实践使用中,AudioFileStream 很少单独应该,一般会结合 AudioConverter 、Audio Unit 或许更高级的音频 API 一起完成,完成解码器、转码器、处理器、播放器之间的联动。