都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音。

所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现。

要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用AVAudioRecorder和AVAudioPlayer。度娘大多数也是如此。但是这种方法有很大的局限性。单说说这种做法:录音,首先得设置录音文件路径,然后录音数据直接写入了文件。播放也是首先给出文件路径,等到音频整个加载完成了,才能开始播放。这相当不灵活。

我的做法是利用音频队列AudioQueue,将声音暂存至缓冲区,然后从缓冲区取出音频数据,进行播放。

声音采集:

使用AudioQueue框架以队列的形式处理音频数据。因此使用时需要给队列分配缓存空间,由回调(Callback)函数完成向队列缓存读写音频数据的功能。

一个Recording Audio Queue,包括Buffer(缓冲器)组成的Buffer Queue(缓冲队列),以及一个Callback(回调)。实现主要步骤为:

  1. 设置音频的参数
  2. 准备并启动声音采集的音频队列
  3. 在回调函数中处理采集到的音频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

声音播放:

同采集一样,播放主要步骤如下:

  1. 设置音频参数(需和采集时设置参数一样)
  2. 取得缓存的音频Buffer
  3. 准备并启动声音播放的音频队列
  4. 在回调函数中处理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

 
 
 

iOS中声音采集与播放的实现(使用AudioQueue)的更多相关文章

  1. iOS 实时音频采集与播放Audio Unit使用

    前言 在iOS中有很多方法可以进行音视频采集.如 AVCaptureDevice, AudioQueue以及Audio Unit.其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; ...

  2. iOS中 MPMoviePlayer 实现视频音频播放 作者:韩俊强

    ios播放视频文件一般使用 MPMoviePlayerViewController 和 MPMoviePlayerController.前者是一个view,后者是个Controller.区别就是 MP ...

  3. 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放

    都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...

  4. AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放

    都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...

  5. ios设置音乐audio自动播放

    因为audio标签的自动播放:autoplay.在ios系统中不能自动播放,此时需要设置,在进入页面自动播放音乐. 第一步,先引入js微信 <script src="js/jweixi ...

  6. ios audio不能够正常播放

    ios中audio不能直接通过audio.play()播放,需要用户在click事件或者touch事件中执行audio.play()才能播放. ajax回调中audio.play()音乐不能正常播放. ...

  7. IOS中微信摇一摇声音无法播放解决办法

    在IOS中第一次调用play方法播放音频会被阻止,必须得等用户有交互动作,比如touchstart,click后才能正常调用,所以可以在摇一摇之前提醒用户点击一下开始游戏的按钮或者给用户一个弹窗,用户 ...

  8. 直播软件开发关于Android、iOS中的视频采集步骤

    很多人对直播软件开发还是抱有想法的,但是在这个资本冷静的市场下,直播平台该怎么玩,在直播软件开发过程中哪些功能是必须具备的,这都是值得关注的话题.今天我们给大家分享一份详细的直播软件开发关于Andro ...

  9. 【VS开发】【智能语音处理】VS中声音的采集实现

    vc中声音的采集是用api函数来实现的. 一.数字音频基础知识  Fourier级数:  任何周期的波形可以分解成多个正弦波,这些正弦波的频率都是整数倍.级数中其他正线波的频率是基础频率的整数倍.基础 ...

随机推荐

  1. HDU-ACM“菜鸟先飞”冬训系列赛——第10场

    Problem A 题意 给出l(房子宽度),d(pole距离房子的垂直距离),s(绳子长度),求可覆盖的面积 分析 一共四种情况 \[1.s<=d\] \[2.s<=sqrt(d*d+l ...

  2. 解决Bad owner or permissions on .ssh/config 的问题

    在使用gei fetch 或者 sftp的时候,出现 Bad owner or permissions on .ssh/config的问题的解决办法 修改.ssh/config的权限: sudo ch ...

  3. mac下安装ngnix以及开启关闭重启

    一.安装 执行如下命令 brew search nginx brew install nginx 安装完以后,可以在终端输出的信息里看到一些配置路径: /usr/local/etc/nginx/ngi ...

  4. ObjectARX2012错误1 fatal error C1083: 无法打开包括文件:“arxHeaders.h”: No such file or directory; fatal error C1083: 无法打开包括文件:“map”: No such file or directory

    问题1:fatal error C1083: 无法打开包括文件:“arxHeaders.h”: No such file or directory: 解决办法:这个问题很明显,是因为没有在工程属性里包 ...

  5. [BZOJ1453]Dface双面棋盘

    Description Input Output Sample Input Sample Output HINT 线段树+并查集,暴力记录和更新一些信息,详情见代码注解. #include<cm ...

  6. LightOj 1236 Pairs Forming LCM (素数筛选&&唯一分解定理)

    题目大意: 有一个数n,满足lcm(i,j)==n并且i<=j时,(i,j)有多少种情况? 解题思路: n可以表示为:n=p1^x1*p2^x1.....pk^xk. 假设lcm(a,b) == ...

  7. hdu 1863 畅通工程(Kruskal+并查集)

    畅通工程 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  8. clock()函数的返回值精度问题

    clock()函数返回值为1毫秒,就是0.001秒.clock函数功 能: 返回处理器调用某个进程或函数所花费的时间.用 法: clock_t clock(void);说明:clock_t其实就是lo ...

  9. Service官方教程(10)Bound Service的生命周期函数

    Managing the Lifecycle of a Bound Service When a service is unbound from all clients, the Android sy ...

  10. LinkedList,SortedList 基本用法

    LinkedList类是双向列表,列表中的每个节点都包含了对前一个和后一个元素的引用. LinkedList<int> ma = new LinkedList<int>(); ...