iOS音频播放之AudioQueue(一):播放本地音乐
iOS实现播放本地音乐,有非常多种方法。比如AVAudioPlayer,这些都能非常好的胜任。有人就奇怪了。为什么要退而求其次。使用更复杂的AudioQueue来播放本地音乐呢?请继续往下看
AudioQueue简单介绍
AudioQueue,在苹果的开发人员文档上是这么说的
"Audio Queue Services provides a straightforward, low overhead way to record and play audio in iOS and Mac OS X."
AudioQueue服务提供一种直接的,低开销的方式以用于在iOS及Mac OS X上录音和播放音乐。
使用AudioQueue播放音乐的长处就是开销非常小并且支持流式播放(边下边播)。可是缺点就是开发难度大。所以有网络音频库AudioStreamer,网上有非常多讲AudioQueue的。可是有实例代码说明的,实在是少之又少。正好公司项目有音频需求,尽管项目中使用的并不是我自己写的音频播放功能,但事后还是想自己来研究一下,这个在我看来比較奇妙也比較有趣的AudioQueue。
AudioStreamer说明
iOS上一个比較有名的流媒体音频播放库是AudioStreamer,该库即使用了AudioQueue。只是该音频库并不支持本地音乐播放,我感觉非常奇怪,为什么作者不支持。并且在使用过程中,我发现该库还是有点问题,尽管我对音频方面的知识并不怎么了解,也并不能与大师媲美并论。但我也希望通过自己的学习,终于完成一个相似AudioStreamer的网络音乐库,眼下或许仅仅是一个设想。无论最后自己有没有那能力,起码我以前也尝试过。只是工作近期比較忙,加上自己知识的欠缺。不知何时才干实现。本次就先来补上AudioStreamer没有支持的,使用AudioQueue播放本地音乐。
AudioQueue具体解释
AudioQueue工作原理
我从Apple的官方文档上截下下面该图:
该图非常好的说明了AudioQueue的工作原理,例如以下说明:
1. 用户调用对应的方法,将音频数据从硬盘中读入到AudioQueue的缓冲区中,并将缓冲区送入音频队列。
2. 用户App通过AudioQueue提供的接口。告诉外放设备,缓冲区中已经有数据。能够拿去播放。
3. 当一个缓冲区中的音频数据播放完成之后,AudioQueue告诉用户,当前有一个空的缓冲区能够用来给你填充数据。
4. 反复以上步骤。直至数据播放完成。
到这里,肯定有不少同学发现了,AudioQueue事实上就是生产者-消费者模型的典型应用。
AudioQueue主要接口
AudioQueueNewOutput
OSStatus AudioQueueNewOutput(const AudioStreamBasicDescription *inFormat, AudioQueueOutputCallback inCallbackProc, void *inUserData, CFRunLoopRef inCallbackRunLoop, CFStringRef inCallbackRunLoopMode, UInt32 inFlags, AudioQueueRef _Nullable *outAQ);
该方法用于创建一个用于输出音频的AudioQueue
參数及返回说明例如以下:
1. inFormat:该參数指明了即将播放的音频的数据格式
2. inCallbackProc:该回调用于当AudioQueue已使用完一个缓冲区时通知用户,用户能够继续填充音频数据
3. inUserData:由用户传入的数据指针,用于传递给回调函数
4. inCallbackRunLoop:指明回调事件发生在哪个RunLoop之中,假设传递NULL,表示在AudioQueue所在的线程上运行该回调事件,普通情况下,传递NULL就可以。
5. inCallbackRunLoopMode:指明回调事件发生的RunLoop的模式,传递NULL相当于kCFRunLoopCommonModes,通常情况下传递NULL就可以
6. outAQ:该AudioQueue的引用实例,
返回OSStatus,假设值为noErr。则表示没有错误。AudioQueue创建成功。
AudioQueueAllocateBuffer
OSStatus AudioQueueAllocateBuffer(AudioQueueRef inAQ, UInt32 inBufferByteSize, AudioQueueBufferRef _Nullable *outBuffer);
该方法的作用是为存放音频数据的缓冲区开辟空间
參数及返回说明例如以下:
1. inAQ:AudioQueue的引用实例
2. inBufferByteSize:须要开辟的缓冲区的大小
3. outBuffer:开辟的缓冲区的引用实例
返回OSStatus,假设值为noErr,则表示缓冲区开辟成功。
AudioQueueEnqueueBuffer
OSStatus AudioQueueEnqueueBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, UInt32 inNumPacketDescs, const AudioStreamPacketDescription *inPacketDescs);
该方法用于将已经填充数据的AudioQueueBuffer入队到AudioQueue
參数及返回说明例如以下:
1. inAQ:AudioQueue的引用实例
2. inBuffer:须要入队的缓冲区实例
3. inNumPacketDescs:缓冲区中共存在有多少帧音频数据
4. inPacketDescs:缓冲区中每一帧的相关信息。用户须要指明当中每一帧在缓冲区中数据的偏移值,通过字段mStartOffset来指定
返回OSStatus。假设值为noErr,则表示缓冲区已经成功入队。等待播放
AudioQueueStart Pause Stop Flush Reset Dispose
OSStatus AudioQueueStart(AudioQueueRef inAQ, const AudioTimeStamp *inStartTime);
OSStatus AudioQueuePause(AudioQueueRef inAQ);
OSStatus AudioQueueStop(AudioQueueRef inAQ, Boolean inImmediate);
OSStatus AudioQueueFlush(AudioQueueRef inAQ);
OSStatus AudioQueueReset(AudioQueueRef inAQ);
OSStatus AudioQueueDispose(AudioQueueRef inAQ, Boolean inImmediate);
顾名思义,前三个方法用于音频的播放,暂停及停止。
后两个方法用于在最后清洗及重置音频队列,清洗确保队列中的数据全然输出。AudioQueuDispose用于清理AudioQueue所占资源。
參数及返回说明例如以下:
1. inAQ:AudioQueue的引用实例
2. inStartTime:指明要開始播放音频的时间,假设要马上開始。传递NULL
3. inImmediate:指明是否要马上停止音频播放,如是。传递true
返回OSStatus表示相关的操作是否成功运行。
AudioQueueFreeBuffer
OSStatus AudioQueueFreeBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
该方法用于在播放结束时。释放清理缓冲区时使用
AudioQueueGetProperty AudioQueueSetProperty
OSStatus AudioQueueGetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, void *outData, UInt32 *ioDataSize);
OSStatus AudioQueueSetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, const void *inData, UInt32 inDataSize);
此GET/SET方法,用于设置获取AudioQueue的相关属性,请參看AudioQueue.h头文件里的相关说明。
音频播放(LocalAudioPlayer)
使用AudioQueue播放音乐,一般须要配合AudioFileStream一起,AudioFileStream负责解析音频数据。AudioQueue负责播放解析到的音频数据。
此次仅实现最主要的本地音频播放功能。旨在为以后打下基础,不处理不论什么相关的状态(如暂停、停止、SEEK),错误等。
播放方式相似流式播放,仅仅是音频数据来源于本地文件而非网络。需经过下面几个步骤:
1. 持续不断的从文件里读取部分数据,直到数据所有读取结束
2. 将文件里读出的数据,交给AudioFileStream进行数据解析
3. 创建AudioQueue,当数据放入到AudioQueueBuffer中
4. 将缓冲区放到到AudioQueue中,開始播放音频
5. 播放音频结束,清理相关资源
播放器的初始化
播放器的init主要用于指定要播放的音频文件,例如以下所看到的:
读取文件操作,使用NSFileHandle类。audioInUseLock,是一个NSLock*类型,用于在AudioQueue通知我们有空的缓冲区能够使用时做标记。
我们在用户点击playbutton的时候,初始化该播放器,并调用play方法进行播放
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="初始化播放器实例" title="">
播放音频
播放音频将分为下面步骤:
1. 读取并開始解析音频
2. 解析音频信息
3. 解析音频帧
4. 播放音频数据
5. 清理相关资源
我们先定义几个宏,用于指定一些缓冲区的大小
#define kNumberOfBuffers 3 //AudioQueueBuffer数量。一般指明为3
#define kAQBufSize 128 * 1024 //每一个AudioQueueBuffer的大小
#define kAudioFileBufferSize 2048 //文件读取数据的缓冲区大小
#define kMaxPacketDesc 512 //最大的AudioStreamPacketDescription个数
LocalAudioPlayer相关属性
LocalAudioPlayer中定义的属性例如以下所看到的:
读取并開始解析音频
我们使用AudioFileStream来解析音频信息。在用户调用play方法之后。首先调用AudioFileStreamOpen,打开AudioFileStream,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="打开AudioFileStream" title="">
extern OSStatus
AudioFileStreamOpen (void *inClientData, AudioFileStream_PropertyListenerProc inPropertyListenerProc, AudioFileStream_PacketsProc inPacketsProc, AudioFileTypeID inFileTypeHint, AudioFileStreamID * outAudioFileStream);
AudioFileStreamOpen的參数说明例如以下:
1. inClientData:用户指定的数据,用于传递给回调函数。这里我们指定(__bridge LocalAudioPlayer*)self
2. inPropertyListenerProc:当解析到一个音频信息时。将回调该方法
3. inPacketsProc:当解析到一个音频帧时,将回调该方法
4. inFileTypeHint:指明音频数据的格式。假设你不知道音频数据的格式,能够传0
5. outAudioFileStream:AudioFileStreamID实例,需保存供兴许使用
读取到数据之后。调用AudioFileStreamParseBytes解析数据,其原型例如以下:
extern OSStatus
AudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream, UInt32 inDataByteSize, const void * inData, AudioFileStreamParseFlags inFlags);
參数的说明例如以下:
1. inAudioFileStream:AudioFileStreamID实例,由AudioFileStreamOpen打开
2. inDataByteSize:此次解析的数据字节大小
3. inData:此次解析的数据大小
4. inFlags:数据解析标志。当中仅仅有一个值kAudioFileStreamParseFlag_Discontinuity = 1,表示解析的数据是否是不连续的,眼下我们能够传0。
当文件数据合部读取结束的时候,此时便能够关闭文件。
解析音频信息
假设解析到音频信息,那么将会调用之前指定的回调函数,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="解析音频信息-1" title="">
每一个相关的属性都能够调用AudioFileStreamGetProperty来获取到对应的值,原型例如以下:
extern OSStatus
AudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioPropertyDataSize, void * outPropertyData);
參数说明:
1. inAudioFileStream:AudioFileStreamID实例,由AudioFileStreamOpen打开
2. inPropertyID:要获取的属性名称。參见AudioFileStream.h
3. ioPropertyDataSize:指明该属性的大小
4. outPropertyData:用于存放该属性值的空间
kAudioFileStreamProperty_DataFormat
该属性指明了音频数据的格式信息,返回的数据是一个AudioStreamBasicDescription结构。需保存用于AudioQueue的使用
kAudioFileStreamProperty_FileFormat
该属性指明了音频数据的编码格式,如MPEG等。
kAudioFileStreamProperty_AudioDataByteCount
该属性可获取到音频数据的长度,可用于计算音频时长,计算公式为:
时长 = (音频数据字节大小 * 8) / 採样率
kAudioFileStreamProperty_BitRate
该属性可获取到音频的採样率。可用于计算音频时长
kAudioFileStreamProperty_DataOffset
该属性指明了音频数据在整个音频文件里的偏移量:
音频文件总大小 = 偏移量 + 音频数据字节大小
kAudioFileStreamProperty_AudioDataPacketCount
该属性指明了音频文件里共同拥有多少帧
kAudioFileStreamProperty_ReadyToProducePackets
该属性告诉我们,已经解析到完整的音频帧数据,准备产生音频帧。之后会调用到另外一个回调函数。我们在这里创建音频队列AudioQueue,假设音频数据中有Magic Cookie Data,则先调用AudioFileStreamGetPropertyInfo,获取该数据是否可写,假设可写再取出该属性值,并写入到AudioQueue。之后便是音频数据帧的解析。
解析音频帧
音频信息解析完成之后,就应该解析音频数据帧了,代码例如以下所看到的:
在这里。我们利用之前设置的inClientData,将该回调函数,由C语言形式改为Objc的形式,解析到音频数据之后的处理代码例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="解析音频数据帧-2" title="">
解析到音频数据之后,我们要将数据写入到AudioQueueBuffer中,首先,该回调函数的原型例如以下所看到的:
typedef void (*AudioFileStream_PacketsProc)(void * inClientData, UInt32 inNumberBytes,UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);
參数说明:
1. inClientData:由AudioFileStreamOpen设置的用户数据
2. inNumberBytes:音频数据的字节数
3. inNumberPackets:解析到的音频帧数
4. inInputData:包括这些音频数据帧的数据
5. inPacketDescriptions:AudioStreamPacketDescription数组。当中包括mStartOffset,指明了该帧相关数据的起始位置。mDataByteSize指明了该帧数据的大小。
此时我们首先创建一个音频队列。用以播放音频。并为每一个缓冲区分配空间,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="创建音频队列" title="">
之后我们遍历每一帧。获取每一帧的偏移位置及数据大小,假设当前帧的数据大小,已经无法存放到当前的缓冲区当中,此时,我们应该将当前的缓冲区入队。改动当前使用的缓冲区为下一个。并重置相关的数据填充信息及已包括的数据帧信息。
self.audioQueueCurrentBufferIndex = (++self.audioQueueCurrentBufferIndex) % kNumberOfBuffers;
self.audioPacketsFilled = 0;
self.audioDataBytesFilled = 0;
假设此时还没有開始播放音乐,则能够開始播放音乐
if(self.isPlaying == NO) {
err = AudioQueueStart(audioQueueRef, NULL);
self.isPlaying = YES;
}
若下一个指定的缓冲区已经在使用。即所有缓冲区已满且入队。则应该进行等待
while(inuse[self.audioQueueCurrentBufferIndex]);
最后,假设缓冲区空间还能存放一帧数据,我们就使用memcpy将数据拷贝到缓冲区中对应的位置上去,保存每一帧的相关信息,设置每一帧在缓冲区中的偏移量(mStartOffset),设置当前缓冲区已存方的数据大小。及已包括的帧量。
当一个缓冲区使用结束之后,AudioQueue将会调用之前由AudioQueueNewOutput设置的回调函数,例如以下所看到的:
在该回调中,我们遍历每一个缓冲区,找到空缓冲区。将该缓冲区的标记改动为未使用。
播放音频数据
在处理数据帧的时候。假设缓冲区已经满(指缓冲区的空间不足以存放下一帧数据),则此时能够開始播放音频
if(self.isPlaying == NO) {
err = AudioQueueStart(audioQueueRef, NULL);
self.isPlaying = YES;
}
我们还能够调用AudioQueuePause等相关方法来暂停和终止音频播放。例如以下所看到的:
清理相关资源
终于。我们清理相关资源,在前面,我们利用了AudioQueueAddPropertyListener为kAudioQueueProperty_IsRunning属性设置了一个监听器,当AudioQueue启动或者是终止的时候,会调用该函数:
该回调函数,例如以下所看到的:
在该方法中,我们使用AudioQueueReset重置播放队列,调用AudioQueueFreeBuffer释放缓冲区空间,释放AudioQueue所有资源,关闭AudioFileStream。
只是在实际使用过程中,我发现数据为空的时候AudioQueue并不会主动终止,即不会主动调用该回调。所以我想,应该是要我们自己获取到当前播放的进度,当播放完成的时候调用AudioQueueStop终止播放吧,这个问题留着待以后再继续研究。
结束
终于完成了所有的代码,用户仅仅要点击play,即能够听到《遥远的她》这首美妙的音乐了。因为界面上仅仅有一个playbutton。不放演示图了。戴上耳机。静静的享受自己的成果与这美妙的音乐。
。
iOS音频播放之AudioQueue(一):播放本地音乐的更多相关文章
- iOS音频篇:使用AVPlayer播放网络音乐
http://www.cocoachina.com/ios/20160324/15767.html 引言 假如你现在打算做一个类似百度音乐.豆瓣电台的在线音乐类APP,你会怎样做? 首先了解一下音频播 ...
- iOS音频播放 (五):AudioQueue
码农人生 ChengYin's coding life 主页 Blog 分类 Categories 归档 Archives 关于 About Weibo GitHub RSS Where there ...
- IOS 音频播放
iOS音频播放 (一):概述 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖 ...
- iOS音频播放(一):概述
(本文转自码农人生) 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改,我也因此对于iOS下的音频播放实现有了一定的研究.写这个 系列的博客目的一方面希望能够抛砖引玉 ...
- IOS音频1:之采用四种方式播放音频文件(一)AudioToolbox AVFoundation OpenAL AUDIO QUEUE
本文转载至 http://blog.csdn.net/u014011807/article/details/40187737 在本卷你可以学到什么? 采用四种方法设计应用于各种场合的音频播放器: 基于 ...
- iOS音频播放、录音、视频播放、拍照、视频录制
随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...
- iOS音频播放概述
在iOS系统中apple对音频播放需要的操作进行了封装并提供了不同层次的接口 下面对其中的中高层接口进行功能说明: Audio File Services:读写音频数据,可以完成播放流程中的第2步: ...
- iOS音频播放 (二):AudioSession 转
原文出处 :http://msching.github.io/blog/2014/07/08/audio-in-ios-2/ 前言 本篇为<iOS音频播放>系列的第二篇. 在实施前一篇中所 ...
- iOS音频与视频的开发(一)-使用AVAudioPlayer播放音乐、使用AVPlayerViewController播放视频
iOS的多媒体支持非常强大,它提供了多套支持多媒体的API,无论是音频.视频的播放,还是录制,iOS都提供了多种API支持.借助于这些API的支持,iOS应用既可以查看.播放手机相册中的照片.视频,也 ...
随机推荐
- python 9:list.reverse()(倒置原列表,可恢复改变)
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles) bicycles.reverse() #倒置原列 ...
- B - Double Cola
Problem description Sheldon, Leonard, Penny, Rajesh and Howard are in the queue for a "Double C ...
- 自己整理的HTML基本标签参考知识
基 本 标 签 创建一个HTML文档 <html></html> 设置文档标题以及其他不在WEB网页上显示的信息 <head></head> ...
- android黑科技系列——Wireshark和Fiddler分析Android中的TLS协议包数据(附带案例样本)
一.前言 在之前一篇文章已经介绍了一款网络访问软件的破解教程,当时采用的突破口是应用程序本身的一个漏洞,就是没有关闭日志信息,我们通过抓取日志获取到关键信息来找到突破口进行破解的.那篇文章也说到了,如 ...
- Eclipse的菜单简介
在Eclipse工作台的上方提供了菜单栏,该菜单栏包含了实现Eclipse各项功能的命令,并且与编辑器相关,即菜单栏中的菜单项与当前编辑器内打开的文件是关联的.例如,编辑器内没有打开任何文件,那么,将 ...
- Unity引擎GUI之Slider和Scrollbar
Slider(滑动条):是一个主要用于形象的拖动以改变目标值的控件,他的最恰当应用是用来改变一个数值,最大值和最小值自定义,拖动滑块可在此之间改变,例如改变声音大小. Scrollbar(滚动条):是 ...
- VS2012快捷操作功能
VS2003用了6年,感情深厚,最近换工作刚刚接触VS2010,使用一个月感觉VS2010在人性化方面的功能实在是太强悍了,大大提高了写代码的效率,就如同魔兽世界里的快捷键操作一样,左手抚键右手摸鼠, ...
- tomcat配置SSH加密
[root@tomcat2 ~]# keytool -genkeypair -alias tomcat -keyalg RSA -keystore /usr/local/tomcat7/keystor ...
- PAT_A1113#Integer Set Partition
Source: PAT A1113 Integer Set Partition (25 分) Description: Given a set of N (>) positive integer ...
- scrapy-redis让redis不止使用db0
废话不多说,直接在 custom_settings 设置即可 代码: class MySpider(RedisSpider): """Spider that reads ...