都说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. hdu4507(数位DP)

    题目意思: 给定一个区间,求这段区间中,不含7,对7取余为0,各个位数相加之和对7取余为0的数的平方和. 设d[i][j][k][m]代表长度为i的,对7取余为j的,各个位数相加之和对7取余为k的数的 ...

  2. Swift4 基本数据类型(范围型, Stride型, 数组, 字符串, 哈希表)

    创建: 2018/02/28 完成: 2018/03/04 更新: 2018/05/03 给主要标题加上英语, 方便页内搜索 [任务表]TODO 范围型(Range)与Stride型  与范围运算符相 ...

  3. 我的周记5——"侵略如火,不动如山"

    这周发生的事儿 最近同事晋升的参加答辩,还有的同事要转岗走了.难受... 有时候自己常常想,我是不是应该也要走了. 这儿的人好优秀呀,又舍不得离开.但是这里太安逸了,接触不到技术,靠自学呢 又感觉力不 ...

  4. mysql 状态查询

    select  COUNT(case when info.State = '0' then State end  ) as daichuliCount, COUNT(case when info.St ...

  5. 字体使用sp、dp的区别

    Android设置字体大小, 该用sp还是dp? 大部分人肯定脱口而出, 用sp啊, 傻瓜都知道要用sp而不是dp!!! 那么为什么呢? 可能有人会说, 是google官方专门定义了sp这个单位来描述 ...

  6. python3 写CSV文件多一个空行的解决办法

    Python文档中有提到: open('eggs.csv', newline='') 也就是说,打开文件的时候多指定一个参数.Python文档中也有这样的示例: import csvwith open ...

  7. stack(数组模拟) POJ 2559 Largest Rectangle in a Histogram

    题目传送门 /* 题意:宽度为1,高度不等,求最大矩形面积 stack(数组模拟):对于每个a[i]有L[i],R[i]坐标位置 表示a[L[i]] < a[i] < a[R[i]] 的极 ...

  8. WIN7中Beyond Compare报错误“应用程序发生错误” 无法启动

    BCompare在WIN7中打开提示"应用程序发生错误"的解决办法: WIN7下寻找:把C:\用户\[用户名]\AppData\Roaming\Scooter Software\B ...

  9. Proactive Patching Overview

    1.Proactive Patching Overview Between Patch Set releases, Oracle's proactive patching program provid ...

  10. jmeter(十六)Jmeter之Bean shell使用(二)

    上一篇Jmeter之Bean shell使用(一)简单介绍了下Jmeter中的Bean shell,本文是对上文的一个补充,主要总结下常用的几种场景和方法,相信这些基本可以涵盖大部分的需求.本节内容如 ...