AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音。
所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现。
要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用AVAudioRecorder和AVAudioPlayer。度娘大多数 也是如此。但是这种方法有很大的局限性。单说说这种做法:录音,首先得设置录音文件路径,然后录音数据直接写入了文件。播放也是首先给出文件路径,等到音 频整个加载完成了,才能开始播放。这相当不灵活。
我的做法是利用音频队列AudioQueue,将声音暂存至缓冲区,然后从缓冲区取出音频数据,进行播放。
声音采集:
使用AudioQueue框架以队列的形式处理音频数据。因此使用时需要给队列分配缓存空间,由回调(Callback)函数完成向队列缓存读写音频数据的功能。
一个Recording Audio Queue,包括Buffer(缓冲器)组成的Buffer Queue(缓冲队列),以及一个Callback(回调)。实现主要步骤为:
- 设置音频的参数
- 准备并启动声音采集的音频队列
- 在回调函数中处理采集到的音频Buffer,在这里是暂存在了一个Byte数组里,提供给播放端使用
|
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
29
30
31
32
33
34
35
36
37
38
39
40
|
Record.h#import <Foundation/Foundation.h>#import <AudioToolbox/AudioToolbox.h>#import <CoreAudio/CoreAudioTypes.h>#import "AudioConstant.h"// use Audio Queuetypedef struct AQCallbackStruct{ AudioStreamBasicDescription mDataFormat; AudioQueueRef queue; AudioQueueBufferRef mBuffers[kNumberBuffers]; AudioFileID outputFile; unsigned long frameSize; long long recPtr; int run; } AQCallbackStruct;@interface Record : NSObject{ AQCallbackStruct aqc; AudioFileTypeID fileFormat; long audioDataLength; Byte audioByte[999999]; long audioDataIndex;}- (id) init;- (void) start;- (void) stop;- (void) pause;- (Byte *) getBytes;- (void) processAudioBuffer:(AudioQueueBufferRef) buffer withQueue:(AudioQueueRef) queue;@property (nonatomic, assign) AQCallbackStruct aqc;@property (nonatomic, assign) long audioDataLength;@end |
|
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
Record.mm#import "Record.h"@implementation Record@synthesize aqc;@synthesize audioDataLength;static void AQInputCallback (void * inUserData, AudioQueueRef inAudioQueue, AudioQueueBufferRef inBuffer, const AudioTimeStamp * inStartTime, unsigned long inNumPackets, const AudioStreamPacketDescription * inPacketDesc){ Record * engine = (__bridge Record *) inUserData; if (inNumPackets > 0) { [engine processAudioBuffer:inBuffer withQueue:inAudioQueue]; } if (engine.aqc.run) { AudioQueueEnqueueBuffer(engine.aqc.queue, inBuffer, 0, NULL); }}- (id) init{ self = [super init]; if (self) { aqc.mDataFormat.mSampleRate = kSamplingRate; aqc.mDataFormat.mFormatID = kAudioFormatLinearPCM; aqc.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |kLinearPCMFormatFlagIsPacked; aqc.mDataFormat.mFramesPerPacket = 1; aqc.mDataFormat.mChannelsPerFrame = kNumberChannels; aqc.mDataFormat.mBitsPerChannel = kBitsPerChannels; aqc.mDataFormat.mBytesPerPacket = kBytesPerFrame; aqc.mDataFormat.mBytesPerFrame = kBytesPerFrame; aqc.frameSize = kFrameSize; AudioQueueNewInput(&aqc.mDataFormat, AQInputCallback, (__bridge void *)(self), NULL, kCFRunLoopCommonModes,0, &aqc.queue); for (int i=0;i<kNumberBuffers;i++) { AudioQueueAllocateBuffer(aqc.queue, aqc.frameSize, &aqc.mBuffers[i]); AudioQueueEnqueueBuffer(aqc.queue, aqc.mBuffers[i], 0, NULL); } aqc.recPtr = 0; aqc.run = 1; } audioDataIndex = 0; return self;}- (void) dealloc{ AudioQueueStop(aqc.queue, true); aqc.run = 0; AudioQueueDispose(aqc.queue, true);}- (void) start{ AudioQueueStart(aqc.queue, NULL);}- (void) stop{ AudioQueueStop(aqc.queue, true);}- (void) pause{ AudioQueuePause(aqc.queue);}- (Byte *)getBytes{ return audioByte;}- (void) processAudioBuffer:(AudioQueueBufferRef) buffer withQueue:(AudioQueueRef) queue{ NSLog(@"processAudioData :%ld", buffer->mAudioDataByteSize); //处理data:忘记oc怎么copy内存了,于是采用的C++代码,记得把类后缀改为.mm。同Play memcpy(audioByte+audioDataIndex, buffer->mAudioData, buffer->mAudioDataByteSize); audioDataIndex +=buffer->mAudioDataByteSize; audioDataLength = audioDataIndex;}@end |
声音播放:
同采集一样,播放主要步骤如下:
- 设置音频参数(需和采集时设置参数一样)
- 取得缓存的音频Buffer
- 准备并启动声音播放的音频队列
- 在回调函数中处理Buffer
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Play.h#import <Foundation/Foundation.h>#import <AudioToolbox/AudioToolbox.h>#import "AudioConstant.h"@interface Play : NSObject{ //音频参数 AudioStreamBasicDescription audioDescription; // 音频播放队列 AudioQueueRef audioQueue; // 音频缓存 AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE];}-(void)Play:(Byte *)audioByte Length:(long)len;@end |
|
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
Play.mm#import "Play.h"@interface Play(){ Byte *audioByte; long audioDataIndex; long audioDataLength;}@end@implementation Play//回调函数(Callback)的实现static void BufferCallback(void *inUserData,AudioQueueRef inAQ,AudioQueueBufferRef buffer){ NSLog(@"processAudioData :%u", (unsigned int)buffer->mAudioDataByteSize); Play* player=(__bridge Play*)inUserData; [player FillBuffer:inAQ queueBuffer:buffer];}//缓存数据读取方法的实现-(void)FillBuffer:(AudioQueueRef)queue queueBuffer:(AudioQueueBufferRef)buffer{ if(audioDataIndex + EVERY_READ_LENGTH < audioDataLength) { memcpy(buffer->mAudioData, audioByte+audioDataIndex, EVERY_READ_LENGTH); audioDataIndex += EVERY_READ_LENGTH; buffer->mAudioDataByteSize =EVERY_READ_LENGTH; AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); } }-(void)SetAudioFormat{ ///设置音频参数 audioDescription.mSampleRate = kSamplingRate;//采样率 audioDescription.mFormatID = kAudioFormatLinearPCM; audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger;//|kAudioFormatFlagIsNonInterleaved; audioDescription.mChannelsPerFrame = kNumberChannels; audioDescription.mFramesPerPacket = 1;//每一个packet一侦数据 audioDescription.mBitsPerChannel = kBitsPerChannels;//av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*8;//每个采样点16bit量化 audioDescription.mBytesPerFrame = kBytesPerFrame; audioDescription.mBytesPerPacket = kBytesPerFrame; [self CreateAudioQueue];}-(void)CreateAudioQueue{ [self Cleanup]; //使用player的内部线程播 AudioQueueNewOutput(&audioDescription, BufferCallback, (__bridge void *)(self), nil, nil, 0, &audioQueue); if(audioQueue) { ////添加buffer区 for(int i=0;i<QUEUE_BUFFER_SIZE;i++) { int result = AudioQueueAllocateBuffer(audioQueue, EVERY_READ_LENGTH, &audioQueueBuffers[i]); ///创建buffer区,MIN_SIZE_PER_FRAME为每一侦所需要的最小的大小,该大小应该比每次往buffer里写的最大的一次还大 NSLog(@"AudioQueueAllocateBuffer i = %d,result = %d",i,result); } }}-(void)Cleanup{ if(audioQueue) { NSLog(@"Release AudioQueueNewOutput"); [self Stop]; for(int i=0; i < QUEUE_BUFFER_SIZE; i++) { AudioQueueFreeBuffer(audioQueue, audioQueueBuffers[i]); audioQueueBuffers[i] = nil; } audioQueue = nil; }}-(void)Stop{ NSLog(@"Audio Player Stop"); AudioQueueFlush(audioQueue); AudioQueueReset(audioQueue); AudioQueueStop(audioQueue,TRUE);}-(void)Play:(Byte *)byte Length:(long)len{ [self Stop]; audioByte = byte; audioDataLength = len; NSLog(@"Audio Play Start >>>>>"); [self SetAudioFormat]; AudioQueueReset(audioQueue); audioDataIndex = 0; for(int i=0; i<QUEUE_BUFFER_SIZE; i++) { [self FillBuffer:audioQueue queueBuffer:audioQueueBuffers[i]]; } AudioQueueStart(audioQueue, NULL);}@end |
以上,实现了通过内存缓存,声音的采集和播放,包括了声音采集,暂停,结束,播放等主要流程。
PS:由于本人水品有限加之这方面资料较少,只跑通了正常流程,暂时没做异常处理。采集的声音Buffer限定大小每次只有十来秒钟的样子,这个留给需要的人自己去优化了。
demo的下载地址:demo
AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放的更多相关文章
- 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- iOS中声音采集与播放的实现(使用AudioQueue)
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- 如何利用Nginx的缓冲、缓存优化提升性能
使用缓冲释放后端服务器 反向代理的一个问题是代理大量用户时会增加服务器进程的性能冲击影响.在大多数情况下,可以很大程度上能通过利用Nginx的缓冲和缓存功能减轻. 当代理到另一台服务器,两个不同的连接 ...
- 利用LinkedHashMap实现简单的缓存
update1:第二个实现,读操作不必要采用独占锁,缓存显然是读多于写,读的时候一开始用独占锁是考虑到要递增计数和更新时间戳要加锁,不过这两个变量都是采用原子变量,因此也不必采用独占锁,修改为读写锁. ...
- Audio Queue Services Programming Guide(音频队列服务编程指南)
Audio Queue Services 的苹果官方文档: https://developer.apple.com/library/ios/documentation/MusicAudio/Conce ...
- iOS 实时音频采集与播放Audio Unit使用
前言 在iOS中有很多方法可以进行音视频采集.如 AVCaptureDevice, AudioQueue以及Audio Unit.其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; ...
- 如何让音频跟视频在ios跟android上自动播放
如何让音频跟视频在ios跟android上自动播放 <audio autoplay ><source src="audio/alarm1.mp3" type=&q ...
- RabbitMQ、Memcache、Redis(队列、缓存)
RabbitMQ 一.解释 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消 ...
- python 自动化之路 day 10 协程、异步IO、队列、缓存
本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 RabbitMQ队列 Redis\Memcached缓存 Paramiko SSH Twsited网络框架 引子 到目 ...
随机推荐
- git rebase命令
使用git rebase合并多次commit. 当年提交代码后,管理员发现,你的代码不能提交到服务器上,注意原因在于,你的commit中的commit和服务器中的有些commit不在同一时间轴上,即: ...
- 爬虫中采集动态HTML介绍
JavaScript JavaScript 是网络上最常用也是支持者最多的客户端脚本语言.它可以收集 用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页游戏. 我们可以在网 ...
- 【GMT43智能液晶模块】例程二十二:USB_CDC实验——高速数据传输
源代码下载链接: 链接:https://pan.baidu.com/s/10KOWONWbNYlonyuX0W0Mcg 提取码:ggpo 复制这段内容后打开百度网盘手机App,操作更方便哦 GMT43 ...
- package.json详解以及package-lock.json的作用
一.创建 package.json输入如下命令之后,会要求填写基本的配置信息,这里,我们选择一路回车即可,待生成 package.json 文件之后,再来配置. npm init 二.配置 packa ...
- 内存自动清理.sql
--清除存储过程缓存 DBCC FREEPROCCACHE --注:方便记住关键字 FREEPROCCACHE可以拆解成 FREE(割舍,清除) PROC(存储过程关键字简写),CACHE(缓存) - ...
- iOS - 在xib中UILabel文字如何换行
在换行的位置按住Option + Enter键即可换行
- 量化编程技术—pandas与数据分析
# -*- coding: utf-8 -*- # @Date: 2017-08-26 # @Original: import numpy as np stock_cnt = 200 view_day ...
- FinalShell—一体化服务器管理软件(SSH客户端)
下面附上一些截图和官方连接: 官网:http://www.hostbuf.com/ FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发 ...
- SQL SERVER 日志写入原理浅析
昨天看到网上有一个关于SQL SERVER 课件,便随手下载了下来看看主要讲了些什么内容,于是看到了下面两个PPT页面 由于第一张PPT上的内容不太准确(日志文件中没有“日志页”的概念,只有VLF的概 ...
- 6. 运行Spark SQL CLI
Spark SQL CLI可以很方便的在本地运行Hive元数据服务以及从命令行执行任务查询.需要注意的是,Spark SQL CLI不能与Thrift JDBC服务交互.在Spark目录下执行如下命令 ...