4.1.3 读取流式音频数据
在App的使用中,有的时候会遇到这样的场景,音频文件不是本地的文件,而是从网络下载的流文件,比如现在的FM 类APP,或者音乐播放从网上搜索到语音点击播放后,不等到先下载完再播放,而是下载了部分数据后就开始播放了。对于这种场景,最麻烦的是音频数据的处理需要按照Packet来处理,甚至要根据不同过的文件格式解析头上的Meaa信息,还好Apple为这种场景提供了一套处理读取流式音频数据的接口。
这套接口首先往"AuidoFileStream"里面塞源文件数据,然后其会对数据做相关的处理,当得到完整的Meta信息或者完整的Packet数据后,做相关回调将解析的内容给到用户在做和从文件读出Packet一样的操作:
图中函数即为塞入数据和回调的接口。
创建流处理器对象
创建流处理器对象需要先提供上门说的得到Meta和数据信息的回调,这个我们列在后面,先来看创建:
OSStatus AudioFileStreamOpen ( void *inClientData, AudioFileStream_PropertyListenerProc inPropertyListenerProc, AudioFileStream_PacketsProc inPacketsProc, AudioFileTypeID inFileTypeHint, AudioFileStreamID _Nullable *outAudioFileStream );
第一个void 参数是C里一般使用模式,相当于C++里面的self最后会透传给回调(注意后面的回调第一个参数也都是这个void )。然后是两个回调的实现,接着是一个前面有结果的hint文件类型提示,一般传递0就可以了,最后对象结果在outAudioFileStream中返回。
比如:
AudioFileTypeID hint = kAudioFileAIFFType;
OSStatus stts;
stts = AudioFileStreamOpen((__bridge void * _Nullable)(self), audioFileStream_PropertyListenerProc, audioFileStream_PacketsProc , hint, &streamID_);
VStatus(stts, @"AudioFileStreamOpen");
这里VSStatus依旧是个检查的返回值的宏。
填充数据
有了流处理器对象,就可以往里面填充数据了,比如这里我们填充一个本地文件:
NSFileHandle *audioFD;
NSString *audioURL = [[NSBundle mainBundle] pathForResource:@"01" ofType:@"caf"];
audioFD = [NSFileHandle fileHandleForReadingAtPath:audioURL];
if (nil == audioFD) {
NSLog(@"audio fd is null");
return ;
}
NSLog(@"open audio fd success ");
for (;;) {
NSData *cntnt = [audioFD readDataOfLength:1024];
if (NULL == cntnt || 0 == [cntnt length]) {
NSLog(@"Read EOF!");
break ;
}
NSLog(@"[%@]have read %lu", [NSThread currentThread], (unsigned long)[cntnt length]);
OSStatus stts;
stts = AudioFileStreamParseBytes(streamID_, (UInt32) [cntnt length], [cntnt bytes], 0);
VStatus(stts, @"AudioFileStreamParseBytes");
}
这里读取文件内容后,调用了:
OSStatus AudioFileStreamParseBytes ( AudioFileStreamID inAudioFileStream, UInt32 inDataByteSize, const void *inData, AudioFileStreamParseFlags inFlags );
塞数据,函数原型比较好理解,就是往流处理器inAudioFileStream里面塞入inDataByteSize字节数据,这里有个flag控制比较关键。正常情况下传0就可以了,比如我们从文件里面读数据的时候,但是比如我们在处理网络流的时候,中间断了一截数据,现在要 从后一段数据来开始播放,那这个flag就有作用了,将其设置成"kAudioFileStreamParseFlag_Discontinuity"就可以正常播放了。
处理回调
经过了这么多处理,最终的数据会通过回调给我们,就如同在用"AudioFileReadPacketData"读取文件一样。主要有两个回调
AudioFileStream_PropertyListenerProc
typedef void (*AudioFileStream_PropertyListenerProc) ( void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags );
流处理器从源数据涨处理出了AudioFileStreamPropertyID指向的内容,此时可以通过调用“AudioFileStreamGetPropertyInfo”和“AudioFileStreamGetProperty”来获取相关的属性。
这里flags别标记为io,当其为0时表示不缓存属性值,此时通过设置其为kAudioFileStreamPropertyFlag_CacheProperty,这样来控制缓存策略。
AudioFileStream_PacketsProc
typedef void (*AudioFileStream_PacketsProc) ( void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions );
流处理器从源数据中处理出来一个个Packet数据,其总共有inNumberPackets个包,总长inNumberBytes个字节,数据放在inInputData指向的内存中,每个Packet的数据格式依次在inPacketDescriptions数组中。按照和"AudioFileReadPacketData"一样的处理音频数据就可以了。
文中Demo参见GitHub
获取属性
属性值的操作和“AudioFileService”里面基本是完全一样的,分成两个接口,一个获取属性大小等信息:
OSStatus AudioFileStreamGetPropertyInfo ( AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *outPropertyDataSize, Boolean *outWritable );
获取属性inPropertyID的大小和是否可写值。然后通过:
OSStatus AudioFileStreamGetProperty ( AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioPropertyDataSize, void *outPropertyData );
得到属性inPropertyID的值放在outPropertyData中。这里有点点不同的是这个函数可能返回“kAudioFileStreamError_DataUnavailable”表示说这个属性的值还没有读完全,要等下AudioFileStream_PropertyListenerProc再次回调了再去尝试。
属性大概有如下一些:
AudioFileStreamPropertyID | 意义 | 值类型 |
---|---|---|
kAudioFileStreamProperty_ReadyToProducePackets | 是否解析完了属性部分 | UInt32 0为还没有解析,1为已经到数据部分了 |
kAudioFileStreamProperty_FileFormat | 文件类型ID | UInt32 |
kAudioFileStreamProperty_DataFormat | 数据内容格式 | AudioStreamBasicDescription |
kAudioFileStreamProperty_FormatList | 容纳的编码格式 | AudioFormatListItem 数组 |
kAudioFileStreamProperty_MagicCookieData | Magic数据 | void * |
kAudioFileStreamProperty_AudioDataByteCount | 数据字节数目 | UInt64 |
kAudioFileStreamProperty_AudioDataPacketCount | 数据的包数目 | UInt64 |
kAudioFileStreamProperty_MaximumPacketSize | 最大包的字节数 | UInt64 |
kAudioFileStreamProperty_DataOffset | 单前数据便宜 | SInt64 |
kAudioFileStreamProperty_ChannelLayout | 通道结构 | AudioFormatListItem |
kAudioFileStreamProperty_PacketToFrame | 将包数转换成帧数 | AudioFramePacketTranslation中mPacket做输入,mFrame做输出 |
kAudioFileStreamProperty_FrameToPacket | 将帧数转换成包数 | AudioFramePacketTranslation中mFrame做输入,mFrameOffsetInPacket,mPacket做输出 |
kAudioFileStreamProperty_PacketToByte | 将包数转换成字节数 | AudioFramePacketTranslation中mPacket做输入,mByte做输出 |
kAudioFileStreamProperty_ByteToPacket | 将字节数转换成包数 | AudioFramePacketTranslation中mByte做输入,mPacket和mByteOffsetInPacket做输出 |
kAudioFileStreamProperty_PacketTableInfo | 音频的Table信息 | AudioFilePacketTableInfo |
kAudioFileStreamProperty_PacketSizeUpperBound | 理论上最大的Packet大小 | UInt32 |
kAudioFileStreamProperty_AverageBytesPerPacket | 平均Packet大小 | UInt32 |
kAudioFileStreamProperty_BitRate | 码率 | UInt32 |
总结
Audio File Stream Service提供了一种将二进制文件做音频文件解析并提供Meta信息和音频包信息的途径,通过这个工具,我们既可以播放网络上的音频流数据,同时也为对本地文件异步读取先加载再处理提供了一种解决方案,基于此可以封装一套读取所有种类音频的接口。