iOS中声音采集与播放的实现(使用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 Queue typedef 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限定大小每次只有十来秒钟的样子,这个留给需要的人自己去优化了。
iOS中声音采集与播放的实现(使用AudioQueue)的更多相关文章
- iOS 实时音频采集与播放Audio Unit使用
前言 在iOS中有很多方法可以进行音视频采集.如 AVCaptureDevice, AudioQueue以及Audio Unit.其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; ...
- iOS中 MPMoviePlayer 实现视频音频播放 作者:韩俊强
ios播放视频文件一般使用 MPMoviePlayerViewController 和 MPMoviePlayerController.前者是一个view,后者是个Controller.区别就是 MP ...
- 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- ios设置音乐audio自动播放
因为audio标签的自动播放:autoplay.在ios系统中不能自动播放,此时需要设置,在进入页面自动播放音乐. 第一步,先引入js微信 <script src="js/jweixi ...
- ios audio不能够正常播放
ios中audio不能直接通过audio.play()播放,需要用户在click事件或者touch事件中执行audio.play()才能播放. ajax回调中audio.play()音乐不能正常播放. ...
- IOS中微信摇一摇声音无法播放解决办法
在IOS中第一次调用play方法播放音频会被阻止,必须得等用户有交互动作,比如touchstart,click后才能正常调用,所以可以在摇一摇之前提醒用户点击一下开始游戏的按钮或者给用户一个弹窗,用户 ...
- 直播软件开发关于Android、iOS中的视频采集步骤
很多人对直播软件开发还是抱有想法的,但是在这个资本冷静的市场下,直播平台该怎么玩,在直播软件开发过程中哪些功能是必须具备的,这都是值得关注的话题.今天我们给大家分享一份详细的直播软件开发关于Andro ...
- 【VS开发】【智能语音处理】VS中声音的采集实现
vc中声音的采集是用api函数来实现的. 一.数字音频基础知识 Fourier级数: 任何周期的波形可以分解成多个正弦波,这些正弦波的频率都是整数倍.级数中其他正线波的频率是基础频率的整数倍.基础 ...
随机推荐
- Java-Runoob-高级教程-实例-字符串:07. Java 实例 - 字符串分割
ylbtech-Java-Runoob-高级教程-实例-字符串:07. Java 实例 - 字符串分割 1.返回顶部 1. Java 实例 - 字符串分割 Java 实例 以下实例使用了 split ...
- mybatis基础学习5---懒加载和缓存
1:懒加载 1)在主配置文件设置(要放在配置文件最前面) 1 <!-- 延迟加载配置,两个都必须同时有 --> <settings> 3 <!-- lazyLoading ...
- python 面向对象九 定制类
一.定制类,实质就是我们自己重写特殊函数 看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的. __slots__我们已经知道怎么用了,__l ...
- codeforces 880E. Maximum Subsequence(折半搜索+双指针)
E. Maximum Subsequence time limit per test 1 second memory limit per test 256 megabytes input standa ...
- 【插件开发】—— 5 SWT控件以及布局使用
前文回顾: 1 插件学习篇 2 简单的建立插件工程以及模型文件分析 3 利用扩展点,开发透视图 4 SWT编程须知 经过前几篇的介绍,多少对SWT又有了一些认识,那么这篇继续来看一下一些控件的组合使用 ...
- Servlet,jsp,jsp的9大内置对象
以servlet作为控制器 1:servlet的生命周期:以下方法都是servlet容器进行调用 1)构造函数:只被调用一次,当项目启动时或者该servlet被容器第一次调用时,会创建servlet实 ...
- Pycharm安装及第一次使用导航
Pycharm:Pycharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试.语法高亮.Project管理.代码跳转.智能提示.自动完成.单元 ...
- sublime 解决中文乱码
步骤: 在Sublime Text里,按ctrl+`,打开Console,一次性输入如下代码: import urllib.request,os; pf = 'Package Control.subl ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- CF449D Jzzhu and Numbers
题解 刚刚学习了高维前缀和 这道题就肥肠简单了 高维前缀和其实原理肥肠简单 就是每次只考虑一维,然后只做这一维的前缀和 最后求出的就是总前缀和了 那么对于这道题 也就很简单了 发现选择的所有数每一位都 ...