iOS音频(3):Audio File Stream(一)

Audio File Stream:它的作用是用来读取采样率、码率、时长等基本信息以及分离音频帧。

音频文件流服务支持以下音频数据类型:
  • AIFF
  • AFIC
  • WAVE
  • CAF
  • NeXT
  • ADTS
  • MPEG Audio Layer 3
  • AAC

一、初始化AudioFileStream

OSStatus AudioFileStreamOpen(void *inClientData, 
AudioFileStream_PropertyListenerProc inPropertyListenerProc, 
AudioFileStream_PacketsProc inPacketsProc, AudioFileTypeID inFileTypeHint, 
AudioFileStreamID  _Nullable *outAudioFileStream);

参数:
  • inClientData:指向要传递给回调函数的值或结构的指针。
  • inPropertyListenerProc:您的属性侦听器回调。只要解析器在数据流中找到属性的值,就会使用属性ID调用属性侦听器。然后您可以调用和函数来获取该属性的值。AudioFileStreamGetPropertyInfoAudioFileStreamGetProperty
  • inPacketsProc:您的音频数据回调。只要解析器在数据流中找到音频数据包,它就会将数据传递给您的音频数据回调。
  • inFileTypeHint:音频文件类型提示。如果您打算传递给解析器的音频文件流的类型是解析器无法轻松或唯一地根据数据(例如ADTS或AC3)确定的类型,则可以使用此参数指示类型。音频文件服务中的枚举中列出了可能的值。AudioFileTypeID
    无法确定音频文件类型,请通过0
  • outAudioFileStream:在输出上,表示音频文件流解析器的不透明对象。该对象在本文档中被称为音频文件流解析器ID。您需要将此ID传递给音频文件流API中的其他功能。

AudioFileTypeID:

CF_ENUM(AudioFileTypeID) {
        kAudioFileAIFFType              = 'AIFF',
        kAudioFileAIFCType              = 'AIFC',
        kAudioFileWAVEType              = 'WAVE',
        kAudioFileRF64Type              = 'RF64',
        kAudioFileSoundDesigner2Type    = 'Sd2f',
        kAudioFileNextType              = 'NeXT',
        kAudioFileMP3Type               = 'MPG3',   // mpeg layer 3
        kAudioFileMP2Type               = 'MPG2',   // mpeg layer 2
        kAudioFileMP1Type               = 'MPG1',   // mpeg layer 1
        kAudioFileAC3Type               = 'ac-3',
        kAudioFileAAC_ADTSType          = 'adts',
        kAudioFileMPEG4Type             = 'mp4f',
        kAudioFileM4AType               = 'm4af',
        kAudioFileM4BType               = 'm4bf',
        kAudioFileCAFType               = 'caff',
        kAudioFile3GPType               = '3gpp',
        kAudioFile3GP2Type              = '3gp2',       
        kAudioFileAMRType               = 'amrf',
        kAudioFileFLACType              = 'flac'
};

二、解析数据

获取一些流式数据。当有数据传递给解析器时调用该函数。按顺序将数据发送到解析器,如果可能的话,没有间隙。

OSStatus AudioFileStreamParseBytes(
AudioFileStreamID inAudioFileStream, 
UInt32 inDataByteSize, 
const void *inData, 
AudioFileStreamParseFlags inFlags);
参数
  • inAudioFileStream:传递数据的解析器的ID。解析器ID由初始化时返回。
  • inDataByteSize:本次解析的数据字节
  • inData:需要解析的数据
  • inFlags:本次的解析和上一次解析是否是连续的关系,如果是连续的传入0,不连续传kAudioFileStreamParseFlag_Discontinuity

这里需要插入解释一下何谓“连续”。在第一篇中我们提到过形如MP3的数据都以帧的形式存在的,解析时也需要以帧为单位解析。但在解码之前我们不可能知道每个帧的边界在第几个字节,所以就会出现这样的情况:我们传给AudioFileStreamParseBytes的数据在解析完成之后会有一部分数据余下来,这部分数据是接下去那一帧的前半部分,如果再次有数据输入需要继续解析时就必须要用到前一次解析余下来的数据才能保证帧数据完整,所以在正常播放的情况下传入0即可。目前知道的需要传入kAudioFileStreamParseFlag_Discontinuity的情况有两个,一个是在seek完毕之后显然seek后的数据和之前的数据完全无关;
AudioFileStreamParseBytes方法的返回值表示当前的数据是否被正常解析,如果OSStatus的值不是noErr则表示解析不成功,其中错误码包括:

enum
{
//指定的文件类型不受支持。
  kAudioFileStreamError_UnsupportedFileType        = 'typ?',
//数据格式不受指定文件类型的支持
  kAudioFileStreamError_UnsupportedDataFormat      = 'fmt?',
//该属性不受支持
  kAudioFileStreamError_UnsupportedProperty        = 'pty?',
//为属性数据提供的缓冲区大小不正确
  kAudioFileStreamError_BadPropertySize            = '!siz',
//由于流式音频文件的数据包表或其他定义信息不存在或出现在音频数据之后,因此不可能产生输出数据包。
  kAudioFileStreamError_NotOptimized               = 'optm',
//数据包偏移小于0或超过文件末尾,或者在构建数据包表时读取损坏的数据包大小。
  kAudioFileStreamError_InvalidPacketOffset        = 'pck?',
//该文件格式不正确,不是其类型的音频文件的有效实例,或者未被识别为音频文件。
  kAudioFileStreamError_InvalidFile                = 'dta?',
//音频数据之前,此文件中不存在属性值。
  kAudioFileStreamError_ValueUnknown               = 'unk?',
//提供给解析器的数据量不足以产生任何结果。
  kAudioFileStreamError_DataUnavailable            = 'more',
//企图进行非法行动。
  kAudioFileStreamError_IllegalOperation           = 'nope',
//发生未指定的错误。
  kAudioFileStreamError_UnspecifiedError           = 'wht?',
//音频数据中出现不连续性,音频文件流服务无法恢复。
  kAudioFileStreamError_DiscontinuityCantRecover   = 'dsc!'
};

kAudioFileStreamError_NotOptimized:它的含义是说这个音频文件的文件头不存在或者说文件头可能在文件的末尾,当前无法正常Parse,换句话说就是这个文件需要全部下载完才能播放,无法流播。

注意:AudioFileStreamParseBytes方法每一次调用都应该注意返回值,一旦出现错误就可以不必继续Parse了。

解析文件格式信息

在调用AudioFileStreamParseBytes方法进行解析时会首先读取格式信息,并同步的进入AudioFileStream_PropertyListenerProc回调方法

typedef void (*AudioFileStream_PropertyListenerProc)(void *inClientData,
                                                     AudioFileStreamID inAudioFileStream, 
                                                     AudioFileStreamPropertyID inPropertyID, 
                                                     AudioFileStreamPropertyFlags *ioFlags);
参数
  • inClientData:Open方法中的上下文对象
  • inAudioFileStream:inAudioFileStream是和Open方法中第四个返回参数AudioFileStreamID一样,表示当前FileStream的ID
  • inPropertyID:此次回调解析的信息ID。表示当前PropertyID对应的信息已经解析完成信息(例如数据格式、音频数据的偏移量等等),使用者可以通过AudioFileStreamGetProperty接口获取PropertyID对应的值或者数据结构;
extern OSStatus AudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream,
                                           AudioFileStreamPropertyID inPropertyID,
                                           UInt32 * ioPropertyDataSize,
                                           void * outPropertyData);
  • ioFlags:ioFlags是一个返回参数,表示这个property是否需要被缓存,如果需要赋值kAudioFileStreamPropertyFlag_PropertyIsCached否则不赋值(这个参数我也不知道应该在啥场景下使用。。。。);

这个回调会进来多次,但并不是每一次都需要进行处理,可以根据需求处理需要的PropertyID进行处理(PropertyID列表如下)。

enum
{
  kAudioFileStreamProperty_ReadyToProducePackets           =    'redy',
  kAudioFileStreamProperty_FileFormat                      =    'ffmt',
  kAudioFileStreamProperty_DataFormat                      =    'dfmt',
  kAudioFileStreamProperty_FormatList                      =    'flst',
  kAudioFileStreamProperty_MagicCookieData                 =    'mgic',
  kAudioFileStreamProperty_AudioDataByteCount              =    'bcnt',
  kAudioFileStreamProperty_AudioDataPacketCount            =    'pcnt',
  kAudioFileStreamProperty_MaximumPacketSize               =    'psze',
  kAudioFileStreamProperty_DataOffset                      =    'doff',
  kAudioFileStreamProperty_ChannelLayout                   =    'cmap',
  kAudioFileStreamProperty_PacketToFrame                   =    'pkfr',
  kAudioFileStreamProperty_FrameToPacket                   =    'frpk',
  kAudioFileStreamProperty_PacketToByte                    =    'pkby',
  kAudioFileStreamProperty_ByteToPacket                    =    'bypk',
  kAudioFileStreamProperty_PacketTableInfo                 =    'pnfo',
  kAudioFileStreamProperty_PacketSizeUpperBound            =    'pkub',
  kAudioFileStreamProperty_AverageBytesPerPacket           =    'abpp',
  kAudioFileStreamProperty_BitRate                         =    'brat',
  kAudioFileStreamProperty_InfoDictionary                  =    'info'
};

1、kAudioFileStreamProperty_BitRate:表示音频数据的码率,获取这个Property是为了计算音频的总时长Duration(因为AudioFileStream没有这样的接口。。)

UInt32 bitRate;
UInt32 bitRateSize = sizeof(bitRate);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_BitRate, &bitRateSize, &bitRate);
if (status != noErr)
{
    //错误处理
}

//在流播放的情况下,有时数据流量比较小时会出现ReadyToProducePackets还是没有获取到bitRate的情况,这时就需要分离一些拼音帧然后计算平均bitRate,计算公式如下:
UInt32 averageBitRate = totalPackectByteCount / totalPacketCout;

2、kAudioFileStreamProperty_DataOffset:表示音频数据在整个音频文件中的offset(因为大多数音频文件都会有一个文件头之后才使真正的音频数据),这个值在seek时会发挥比较大的作用,音频的seek并不是直接seek文件位置而seek时间(比如seek到2分10秒的位置),seek时会根据时间计算出音频数据的字节offset然后需要再加上音频数据的offset才能得到在文件中的真正offset。

SInt64 dataOffset;
UInt32 offsetSize = sizeof(dataOffset);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &dataOffset);
if (status != noErr)
{
    //错误处理
}

3、kAudioFileStreamProperty_DataFormat:表示音频文件结构信息,是一个AudioStreamBasicDescription的结构

struct AudioStreamBasicDescription
{
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;
};

AudioStreamBasicDescription asbd;
UInt32 asbdSize = sizeof(asbd);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
if (status != noErr)
{
    //错误处理
}

4、kAudioFileStreamProperty_FormatList:作用和kAudioFileStreamProperty_DataFormat是一样的,区别在于用这个PropertyID获取到是一个AudioStreamBasicDescription的数组,这个参数是用来支持AAC SBR这样的包含多个文件类型的音频格式。由于到底有多少个format我们并不知晓,所以需要先获取一下总数据大小

//获取数据大小
Boolean outWriteable;
UInt32 formatListSize;
OSStatus status = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);
if (status != noErr)
{
    //错误处理
}

//获取formatlist
AudioFormatListItem *formatList = malloc(formatListSize);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);
if (status != noErr)
{
    //错误处理
}

//选择需要的格式
for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i++)
{
    AudioStreamBasicDescription pasbd = formatList[i].mASBD;
    //选择需要的格式。。                             
}
free(formatList);

5、kAudioFileStreamProperty_AudioDataByteCount:顾名思义,音频文件中音频数据的总量。这个Property的作用一是用来计算音频的总时长,二是可以在seek时用来计算时间对应的字节offset。

UInt64 audioDataByteCount;
UInt32 byteCountSize = sizeof(audioDataByteCount);
OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount);
if (status != noErr)
{
    //错误处理
}
//有时数据流量比较小时会出现ReadyToProducePackets还是没有获取到audioDataByteCount的情况,这时就需要近似计算audioDataByteCount。一般来说音频文件的总大小一定是可以得到的(利用文件系统或者Http请求中的contentLength),那么计算方法如下:
UInt32 dataOffset = ...; //kAudioFileStreamProperty_DataOffset
UInt32 fileLength = ...; //音频文件大小
UInt32 audioDataByteCount = fileLength - dataOffset;

6、kAudioFileStreamProperty_ReadyToProducePackets:这个PropertyID可以不必获取对应的值,一旦回调中这个PropertyID出现就代表解析完成,接下来可以对音频数据进行帧分离了。

计算时长Duration

获取时长的最佳方法是从ID3信息中去读取,那样是最准确的。如果ID3信息中没有存,那就依赖于文件头中的信息去计算了。
double duration = (audioDataByteCount * 8) / bitRate
音频数据的字节总量audioDataByteCount可以通过kAudioFileStreamProperty_AudioDataByteCount获取,码率bitRate可以通过kAudioFileStreamProperty_BitRate获取也可以通过Parse一部分数据后计算平均码率来得到。
对于CBR数据来说用这样的计算方法的duration会比较准确,对于VBR数据就不好说了。所以对于VBR数据来说,最好是能够从ID3信息中获取到duration,获取不到再想办法通过计算平均码率的途径来计算duration。

分离音频帧和关闭AudioFileStream下一篇再写,这篇写的内容有点多,消化消化~
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 音乐一直是我的爱好,作为一名开发,同时我也想知道这些音乐是怎么播放的,音效是如何改变的,如何升降调,一个音乐播放器...
    程序狗阅读 5,334评论 1 8
  • AudioFileStream介绍 AudioFileStreamer时提到它的作用是用来读取采样率、码率、时长等...
    VD2012阅读 552评论 0 2
  • 本篇我们介绍AudioFile和AudioFileStream。在第一篇技术栈的分析里,我们提到过AudioFil...
    anyoptional阅读 2,630评论 0 5
  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,695评论 0 3
  • 《ilua》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 1...
    叶染柒丶阅读 10,696评论 0 11