原文出处 : http://msching.github.io/blog/2014/07/19/audio-in-ios-4/

前言

接着第三篇AudioStreamFile这一篇要来聊一下AudioFile。和AudioStreamFile一样AudioFileAudioToolBox framework中的一员,它也可以完毕第一篇所述的第2步,读取音频格式信息和进行帧分离。但其实它的功能远不止如此。


AudioFile介绍

依照官方文档的描写叙述:

a C programming interface that enables you to read or write a wide variety of audio data
to or from disk or a memory buffer.With Audio File Services you can:

  • Create, initialize, open, and close audio files
  • Read and write audio files
  • Optimize audio files
  • Work with user data and global information

这个类能够用来创建、初始化音频文件;读写音频数据;对音频文件进行优化;读取和写入音频格式信息等等。功能十分强大。可见它不但能够用来支持音频播放。甚至能够用来生成音频文件。当然,在本篇文章中仅仅会涉及一些和音频播放相关的内容(打开音频文件、读取格式信息、读取音频数据,事实上我也仅仅对这些方法有一点了解。其余的功能没用过。

。>_<).


AudioFile的打开“姿势”

AudioFile提供了两个打开文件的方法:

1、 AudioFileOpenURL

1
2
3
4
5
6
7
8
9
10
enum {
kAudioFileReadPermission = 0x01,
kAudioFileWritePermission = 0x02,
kAudioFileReadWritePermission = 0x03
}; extern OSStatus AudioFileOpenURL (CFURLRef inFileRef,
SInt8 inPermissions,
AudioFileTypeID inFileTypeHint,
AudioFileID * outAudioFile);

从方法的定义上来看是用来读取本地文件的:

第一个參数,文件路径;

第二个參数,文件的同意使用方式,是读、写还是读写。假设打开文件后进行了同意使用方式以外的操作。就得到kAudioFilePermissionsError错误码(比方Open时声明是kAudioFileReadPermission但却调用了AudioFileWriteBytes);

第三个參数,和AudioFileStream的open方法中一样是一个帮助AudioFile解析文件的类型提示。假设文件类型确定的话应当传入;

第四个參数,返回AudioFile实例相应的AudioFileID。这个ID须要保存起来作为兴许一些方法的參数使用;

返回值用来推断是否成功打开文件(OSSStatus == noErr)。


2、 AudioFileOpenWithCallbacks

1
2
3
4
5
6
7
extern OSStatus AudioFileOpenWithCallbacks (void * inClientData,
AudioFile_ReadProc inReadFunc,
AudioFile_WriteProc inWriteFunc,
AudioFile_GetSizeProc inGetSizeFunc,
AudioFile_SetSizeProc inSetSizeFunc,
AudioFileTypeID inFileTypeHint,
AudioFileID * outAudioFile);

看过第一个Open方法后,这种方法乍看上去让人有点迷茫,没有URL的參数怎样告诉AudioFile该打开哪个文件?还是先来看一下參数的说明吧:

第一个參数。上下文信息,不再多做解释;

第二个參数。当AudioFile须要读音频数据时进行的回调(调用Open和Read方式后同步回调);

第三个參数。当AudioFile须要写音频数据时进行的回调(写音频文件功能时使用,暂不讨论)。

第四个參数,当AudioFile须要用到文件的总大小时回调(调用Open和Read方式后同步回调)。

第五个參数。当AudioFile须要设置文件的大小时回调(写音频文件功能时使用。暂不讨论)。

第六、七个參数和返回值同AudioFileOpenURL方法。

这种方法的重点在于AudioFile_ReadProc这个回调。换一个角度理解。这种方法相比于第一个方法自由度更高,AudioFile须要的仅仅是一个数据源,不管是磁盘上的文件、内存里的数据甚至是网络流仅仅要能在AudioFile须要数据时(Open和Read时)通过AudioFile_ReadProc回调为AudioFile提供合适的数据就能够了,也就是说用法不仅仅能够读取本地文件也能够如AudioFileStream一样以流的形式读取数据。


以下来看一下AudioFile_GetSizeProcAudioFile_ReadProc这两个读取功能相关的回调

1
2
3
4
5
6
7
typedef SInt64 (*AudioFile_GetSizeProc)(void * inClientData);

typedef OSStatus (*AudioFile_ReadProc)(void * inClientData,
SInt64 inPosition,
UInt32 requestCount,
void * buffer,
UInt32 * actualCount);

首先是AudioFile_GetSizeProc回调。这个回调非常好理解,返回文件总长度就可以,总长度的获取途径自然是文件系统或者httpResponse等等。

接下来是AudioFile_ReadProc回调:

第一个參数,上下文对象,不再赘述;

第二个參数,须要读取第几个字节開始的数据;

第三个參数,须要读取的数据长度;

第四个參数,返回參数,是一个数据指针而且其空间已经被分配。我们须要做的是把数据memcpy到buffer中。

第五个參数。实际提供的数据长度,即memcpy到buffer中的数据长度;

返回值,假设没有不论什么异常产生就返回noErr,假设有异常能够依据异常类型选择须要的error常量返回(一般用不到其它返回值,返回noErr就足够了)。

这里须要解释一下这个回调方法的工作方式。AudioFile须要数据时会调用回调方法,须要数据的时间点有两个:

  1. Open方法调用时,因为AudioFile的Open方法调用过程中就会对音频格式信息进行解析。仅仅有符合要求的音频格式才干被成功打开否则Open方法就会返回错误码(换句话说。Open方法一旦调用成功就相当于AudioStreamFile在Parse后返回ReadyToProducePackets一样,仅仅要Open成功就能够開始读取音频数据。详见第三篇),所以在Open方法调用的过程中就须要提供一部分音频数据来进行解析;

  2. Read相关方法调用时,这个不须要多说非常好理解;

通过回调提供数据时须要注意inPosition和requestCount參数,这两个參数指明了本次回调须要提供的数据范围是从inPosition開始requestCount个字节的数据。这里又能够分为两种情况:

  1. 有充足的数据:那么我们须要把这个范围内的数据复制到buffer中,而且给actualCount赋值requestCount,最后返回noError;

  2. 数据不足:没有充足数据的话就仅仅能把手头有的数据复制到buffer中。须要注意的是这部分被拷贝的数据必须是从inPosition開始的连续数据,拷贝完毕后给actualCount赋值实际拷贝进buffer中的数据长度后返回noErr。这个过程能够用以下的代码来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static OSStatus MyAudioFileReadCallBack(void *inClientData,
SInt64 inPosition,
UInt32 requestCount,
void *buffer,
UInt32 *actualCount)
{
__unsafe_unretained MyContext *context = (__bridge MyContext *)inClientData; *actualCount = [context availableDataLengthAtOffset:inPosition maxLength:requestCount];
if (*actualCount > 0)
{
NSData *data = [context dataAtOffset:inPosition length:*actualCount];
memcpy(buffer, [data bytes], [data length]);
} return noErr;
}

讲到这里又须要分两种情况:

2.1. Open方法调用时的回调数据不足:AudioFile的Open方法会依据文件格式类型分几步进行数据读取以解析确定是否是一个合法的文件格式,当中每一步的inPosition和requestCount都不一样。假设某一步不成功就会直接进行下一步,假设几部下来都失败了。那么Open方法就会失败。简单的说就是在调用Open之前首先须要保证音频文件的格式信息完整。这就意味着AudioFile并不能独立用于音频流的读取,在流播放时首先须要使用AudioStreamFile来得到ReadyToProducePackets标志位来保证信息完整。

2.2. Read方法调用时的回调数据不足:这样的情况下inPosition和requestCount的数值与Read方法调用时传入的參数有关,数据不足对于Read方法本身没有影响。仅仅要回调返回noErr,Read就成功,仅仅是实际交给Read方法的调用方的数据会不足,那么就把这个问题的处理交给了Read的调用方;


读取音频格式信息

成功打开音频文件后就能够读取当中的格式信息了,读取用到的方法例如以下:

1
2
3
4
5
6
7
8
9
extern OSStatus AudioFileGetPropertyInfo(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * outDataSize,
UInt32 * isWritable); extern OSStatus AudioFileGetProperty(AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 * ioDataSize,
void * outPropertyData);

AudioFileGetPropertyInfo方法用来获取某个属性相应的数据的大小(outDataSize)以及该属性能否够被write(isWritable),而AudioFileGetProperty则用来获取属性相应的数据。对于一些大小可变的属性须要先使用AudioFileGetPropertyInfo获取数据大小才干取获取数据(比如formatList),而有些确定类型单个属性则不必先调用AudioFileGetPropertyInfo直接调用AudioFileGetProperty就可以(比方BitRate)。样例例如以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
AudioFileID fileID; //Open方法返回的AudioFileID

//获取格式信息
UInt32 formatListSize = 0;
OSStatus status = AudioFileGetPropertyInfo(_fileID, kAudioFilePropertyFormatList, &formatListSize, NULL);
if (status == noErr)
{
AudioFormatListItem *formatList = (AudioFormatListItem *)malloc(formatListSize);
status = AudioFileGetProperty(fileID, kAudioFilePropertyFormatList, &formatListSize, formatList);
if (status == noErr)
{
for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem))
{
AudioStreamBasicDescription pasbd = formatList[i].mASBD;
//选择须要的格式。。
}
}
free(formatList);
} //获取码率
UInt32 bitRate;
UInt32 bitRateSize = sizeof(bitRate);
status = AudioFileGetProperty(fileID, kAudioFilePropertyBitRate, &size, &bitRate);
if (status != noErr)
{
//错误处理
}

能够获取的属性有以下这些,大家能够參考文档来获取自己须要的信息(注意到这里有EstimatedDuration,能够得到Duration了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
enum
{
kAudioFilePropertyFileFormat = 'ffmt',
kAudioFilePropertyDataFormat = 'dfmt',
kAudioFilePropertyIsOptimized = 'optm',
kAudioFilePropertyMagicCookieData = 'mgic',
kAudioFilePropertyAudioDataByteCount = 'bcnt',
kAudioFilePropertyAudioDataPacketCount = 'pcnt',
kAudioFilePropertyMaximumPacketSize = 'psze',
kAudioFilePropertyDataOffset = 'doff',
kAudioFilePropertyChannelLayout = 'cmap',
kAudioFilePropertyDeferSizeUpdates = 'dszu',
kAudioFilePropertyMarkerList = 'mkls',
kAudioFilePropertyRegionList = 'rgls',
kAudioFilePropertyChunkIDs = 'chid',
kAudioFilePropertyInfoDictionary = 'info',
kAudioFilePropertyPacketTableInfo = 'pnfo',
kAudioFilePropertyFormatList = 'flst',
kAudioFilePropertyPacketSizeUpperBound = 'pkub',
kAudioFilePropertyReserveDuration = 'rsrv',
kAudioFilePropertyEstimatedDuration = 'edur',
kAudioFilePropertyBitRate = 'brat',
kAudioFilePropertyID3Tag = 'id3t',
kAudioFilePropertySourceBitDepth = 'sbtd',
kAudioFilePropertyAlbumArtwork = 'aart',
kAudioFilePropertyAudioTrackCount = 'atct',
kAudioFilePropertyUseAudioTrack = 'uatk'
};

读取音频数据

读取音频数据的方法分为两类:

1、直接读取音频数据:

1
2
3
4
5
extern OSStatus AudioFileReadBytes (AudioFileID inAudioFile,
Boolean inUseCache,
SInt64 inStartingByte,
UInt32 * ioNumBytes,
void * outBuffer);

第一个參数。FileID。

第二个參数,是否须要cache,一般来说传false。

第三个參数,从第几个byte開始读取数据

第四个參数。这个參数在调用时作为输入參数表示须要读取读取多少数据。调用完毕后作为输出參数表示实际读取了多少数据(即Read回调中的requestCount和actualCount);

第五个參数。buffer指针,须要事先分配好足够大的内存(ioNumBytes大,即Read回调中的buffer,所以Read回调中不须要再分配内存);

返回值表示是否读取成功,EOF时会返回kAudioFileEndOfFileError

使用这种方法得到的数据都是没有进行过帧分离的数据,假设想要用来播放或者解码还必须通过AudioFileStream进行帧分离。

2、按帧(Packet)读取音频数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern OSStatus AudioFileReadPacketData (AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * ioNumBytes,
AudioStreamPacketDescription * outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * outBuffer); extern OSStatus AudioFileReadPackets (AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * outNumBytes,
AudioStreamPacketDescription * outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * outBuffer);

按帧读取的方法有两个,这两个方法看上去几乎相同,就连參数也差点儿同样,但使用场景和效率上却有所不同,官方文档中如此描写叙述这两个方法:

  • AudioFileReadPacketData is memory efficient
    when reading variable bit-rate (VBR) audio data;
  • AudioFileReadPacketData is more efficient
    than AudioFileReadPackets when reading compressed file formats that do not have packet tables, such as MP3 or ADTS. This function is a good choice for reading either CBR
    (constant bit-rate) or VBR data if you do not need to read a fixed duration of audio.
  • Use AudioFileReadPackets only when you need
    to read a fixed duration of audio data, or when you are reading only uncompressed audio.

仅仅有当须要读取固定时长音频或者非压缩音频时才会用到AudioFileReadPackets。其余时候使用AudioFileReadPacketData会有更高的效率而且更省内存。

以下来看看这些參数:

第一、二个參数,同AudioFileReadBytes

第三个參数,对于AudioFileReadPacketData来说ioNumBytes这个參数在输入输出时都要用到。在输入时表示outBuffer的size。输出时表示实际读取了多少size的数据。而对AudioFileReadPackets来说outNumBytes仅仅在输出时使用,表示实际读取了多少size的数据;

第四个參数,帧信息数组指针,在输入前须要分配内存,大小必须足够存在ioNumPackets个帧信息(ioNumPackets * sizeof(AudioStreamPacketDescription));

第五个參数,在输入时表示须要读取多少个帧,在输出时表示实际读取了多少帧。

第六个參数。outBuffer数据指针,在输入前就须要分配好空间,这个參数看上去两个方法一样但事实上并不是如此。对于AudioFileReadPacketData来说仅仅要分配近似帧大小
* 帧数
的内存空间就可以,方法本身会针对给定的内存空间大小来决定最后输出多少个帧,假设空间不够会适当降低出的帧数;而对于AudioFileReadPackets来说则须要分配最大帧大小(或帧大小上界)
* 帧数
的内存空间才行(最大帧大小和帧大小上界的差别等下会说);这也就是为何第三个參数一个是输入输出双向使用的,而还有一个仅仅是输出时使用的原因。

就这点来说两个方法中前者在使用的过程中要比后者更省内存;

返回值。同AudioFileReadBytes

这两个方法读取后的数据为帧分离后的数据,能够直接用来播放或者解码。

以下给出两个方法的使用代码(以MP3为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
AudioFileID fileID; //Open方法返回的AudioFileID
UInt32 ioNumPackets = ...; //要读取多少个packet
SInt64 inStartingPacket = ...; //从第几个Packet開始读取 UInt32 bitRate = ...; //AudioFileGetProperty读取kAudioFilePropertyBitRate
UInt32 sampleRate = ...; //AudioFileGetProperty读取kAudioFilePropertyDataFormat或kAudioFilePropertyFormatList
UInt32 byteCountPerPacket = 144 * bitRate / sampleRate; //MP3数据每一个Packet的近似大小 UInt32 descSize = sizeof(AudioStreamPacketDescription) * ioNumPackets;
AudioStreamPacketDescription * outPacketDescriptions = (AudioStreamPacketDescription *)malloc(descSize); UInt32 ioNumBytes = byteCountPerPacket * ioNumPackets;
void * outBuffer = (void *)malloc(ioNumBytes); OSStatus status = AudioFileReadPacketData(fileID,
false,
&ioNumBytes,
outPacketDescriptions,
inStartingPacket,
&ioNumPackets,
outBuffer);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
AudioFileID fileID; //Open方法返回的AudioFileID
UInt32 ioNumPackets = ...; //要读取多少个packet
SInt64 inStartingPacket = ...; //从第几个Packet開始读取 UInt32 maxByteCountPerPacket = ...; //AudioFileGetProperty读取kAudioFilePropertyMaximumPacketSize,最大的packet大小
//也能够用:
//UInt32 byteCountUpperBoundPerPacket = ...; //AudioFileGetProperty读取kAudioFilePropertyPacketSizeUpperBound,当前packet大小上界(未扫描全文件的情况下) UInt32 descSize = sizeof(AudioStreamPacketDescription) * ioNumPackets;
AudioStreamPacketDescription * outPacketDescriptions = (AudioStreamPacketDescription *)malloc(descSize); UInt32 outNumBytes = 0;
UInt32 ioNumBytes = maxByteCountPerPacket * ioNumPackets;
void * outBuffer = (void *)malloc(ioNumBytes); OSStatus status = AudioFileReadPackets(fileID,
false,
&outNumBytes,
outPacketDescriptions,
inStartingPacket,
&ioNumPackets,
outBuffer);

Seek

seek的思路和之前讲AudioFileStream时讲到的是一样的,差别在于AudioFile没有方法来帮助修正seek的offset和seek的时间:

  • 使用AudioFileReadBytes时须要计算出approximateSeekOffset
  • 使用AudioFileReadPacketData或者AudioFileReadPackets时须要计算出seekToPacket

approximateSeekOffset和seekToPacket的计算方法參见第三篇


关闭AudioFile

AudioFile使用完成后须要调用AudioFileClose进行关闭,没啥特别须要注意的。

1
extern OSStatus AudioFileClose (AudioFileID inAudioFile);

小结

本篇针对AudioFile的音频读取功能做了介绍。小结一下:

  • AudioFile有两个Open方法,须要针对自身的使用场景选择不同的方法;

  • AudioFileOpenURL用来读取本地文件

  • AudioFileOpenWithCallbacks的使用场景比前者要广泛。使用时须要注意AudioFile_ReadProc,这个回调方法在Open方法本身和Read方法被调用时会被同步调用

  • 必须保证音频文件格式信息可读时才干使用AudioFile的Open方法,AudioFile并不能独立用于音频流的读取。须要配合AudioStreamFile使用才干读取流(须要用AudioStreamFile来推断文件格式信息可读之后再调用Open方法)。

  • 使用AudioFileGetProperty读取格式信息时须要推断所读取的信息是否须要先调用AudioFileGetPropertyInfo获得数据大小后再进行读取;

  • 读取音频数据应该依据使用的场景选择不同的音频读取方法,对于不同的读取方法seek时须要计算的变量也不同样;

  • AudioFile使用完成后须要调用AudioFileClose进行关闭;


演示样例代码

对于本地文件用AudioFile读取比較简单就不在这里提供demo了,对于流播放中的AudioFile使用推荐大家阅读豆瓣的开源播放器代码DOUAudioStreamer


下篇预告

下一篇将讲述怎样使用AudioQueue

iOS音频播放 (四):AudioFile 转的更多相关文章

  1. IOS 音频播放

    iOS音频播放 (一):概述 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖 ...

  2. iOS音频播放 (五):AudioQueue

    码农人生 ChengYin's coding life 主页 Blog 分类 Categories 归档 Archives 关于 About Weibo GitHub RSS Where there ...

  3. iOS音频播放(一):概述

    (本文转自码农人生) 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改,我也因此对于iOS下的音频播放实现有了一定的研究.写这个 系列的博客目的一方面希望能够抛砖引玉 ...

  4. iOS音频播放 (二):AudioSession 转

    原文出处 :http://msching.github.io/blog/2014/07/08/audio-in-ios-2/ 前言 本篇为<iOS音频播放>系列的第二篇. 在实施前一篇中所 ...

  5. iOS音频播放(二):AudioSession

    (本文转自码农人生) 前言 在实施前一篇中所述的7个步骤步之前还必须面对一个麻烦的问题,AudioSession.   AudioSession简介 AudioSession这个玩意的主要功能包括以下 ...

  6. iOS音频播放、录音、视频播放、拍照、视频录制

    随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...

  7. iOS音频播放概述

    在iOS系统中apple对音频播放需要的操作进行了封装并提供了不同层次的接口 下面对其中的中高层接口进行功能说明: Audio File Services:读写音频数据,可以完成播放流程中的第2步: ...

  8. iOS音频播放之AudioQueue(一):播放本地音乐

    AudioQueue简单介绍 AudioStreamer说明 AudioQueue具体解释 AudioQueue工作原理 AudioQueue主要接口 AudioQueueNewOutput Audi ...

  9. iOS音频播放 (三):AudioFileStream 转

    原文出处 :http://msching.github.io/blog/2014/07/09/audio-in-ios-3/ 前言 本来说好是要在第三篇中讲AudioFileStream和AudioQ ...

随机推荐

  1. node.js从入门到放弃(一)

    以下内容全是我个人理解写出,如有不对,请立刻练习本人进行更改.以免被刚入门的被我带入坑里. —node是什么?我想大家应该都知道. node是前端未来干掉后端的一种语言,是用JavaScript来编写 ...

  2. 访问修饰词--Java

    public(公共的) 权限: 完全公开 protected(受保护的) 权限: 对子类和同包中的其他类公开 default(默认的,可不写) 权限: 对同包中的其他类公开 private(私有的) ...

  3. Thinkphp框架图片上传实例

     https://www.cnblogs.com/wupeiky/p/5802191.html    [原文转载自:https://www.cnblogs.com/guoyachao/p/628286 ...

  4. 在single模式修改密码

    当前是在CentOS 6.5 的命令行模式下 开机后在这个界面按随意键 接着来到新界面中,在当前选项中按下e键: 接着来到新界面,选择第二个,按下e键: 在这里我们输入single接着回车: 接着回到 ...

  5. phpstorm使用Database关联数据库

    1.如图选择mysql 配置数据库连接及shh 点击Test Connection,如果成功,即可点击ok连接mysql数据库 2.如图选择所有的表 3.更新数据

  6. 深入Linux内核架构——简介与概述

    一.内核的任务 纯技术层面上,内核是硬件与软件的之间的一个中间层.作用是将应用程序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址. 从应用程序视角上看,内核可以被认为是一台增强 ...

  7. POJ 1905 Expanding Rods (求直杆弯曲拱起的高度)(二分法,相交弦定理)

    Description When a thin rod of length L is heated n degrees, it expands to a new length L' = (1+n*C) ...

  8. POJ 2267 From Dusk till Dawn or: Vladimir the Vampire(最短路变形)

    题意: 有一个吸血鬼要旅游, 他只能在晚上6点到第二天凌晨6点行动(18:00 ~ 6:00), 然后每天中午12点要喝1L的血(12:00), 现有m条火车的发车时间和行程时间, 问他从a到达b需要 ...

  9. Apache手册

    一.apache的安装 如果不指定安装位置,默认为/usr/local/apache2/

  10. AutoItLibrary之键盘操作(send)

    最近有人问到我键盘操作用什么库?用到库里面的哪个方法?我在这里总结一下,第一次写,有片面的地方还请指出,一块进步.1.首先,用到的库是AutoItLibrary,用到的方法是send:按F5可用看到 ...