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

都说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

来源:http://www.cnblogs.com/anjohnlv/p/3383908.html

时间: 2024-08-29 18:11:01

AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放的相关文章

iOS中声音采集与播放的实现(使用AudioQueue)

都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用AVAudioRecorder和AVAudioPlayer.度娘大多数也是如此.但是这种方法有很大的局限性.单说说这种做法:录音,首先得设置录音文件路径,然后录音数据直接写入了文件.播放也是首先给出文件路径,等到音频整个加载完成了,才能开始播放.这相当不灵活. 我的做法是利用音频队列AudioQue

如何利用Nginx的缓冲、缓存优化提升性能

使用缓冲释放后端服务器 反向代理的一个问题是代理大量用户时会增加服务器进程的性能冲击影响.在大多数情况下,可以很大程度上能通过利用Nginx的缓冲和缓存功能减轻. 当代理到另一台服务器,两个不同的连接速度会影响客户的体验: 从客户机到Nginx代理的连接. 从Nginx代理到后端服务器的连接. Nginx具有优化这些连接调整其行为的能力. 如果没有缓冲,数据从代理的服务器发送并立即开始被发送到客户.如果假定客户端很快,缓冲可以关闭而尽快使数据到客户端,有了缓冲,Nginx 代理将暂时存储后端的响

利用java的代理建立缓存

背景: 为了实现组件的复用,几乎所有的项目都会调用一个通用的用户组件(org).各系统和org之间是使用webservice技术进行通,主要是org提供了webservice业务接口.经过了一段时间的使用发现组件相当稳定,正常情况下几乎可以满足所有系统的要求.只是有一个问题比较突出就是当一个方法包含过多的webservice请求时还是会有性能问题,这个问题应该说是webservice的通病.所以这里提供一种解决方法,建立缓存机制. 分析: 首先建立缓存位置其实有两个选择,一是建立在org服务器端

Audio Queue Services Programming Guide(音频队列服务编程指南)

Audio Queue Services 的苹果官方文档: https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005343-CH1-SW1 网友对上面的苹果官方文档的部分翻译: 音频队列服务编程指南(Audio Queue Servi

利用栈实现队列(C语言实现)

在上一篇优化后队列的实现(C语言实现)  中,虽然我们对队列的时间复杂度进行了优化,但是却让代码的可读性变差了,代码显得略微臃肿(当然,这些话你看看就好,主要是为了奉承这篇博文的). 这里主要实现的是:利用栈来实现队列 基本思路: 1,创建两个栈 2,两个栈合并起来组装成一个队列,分别取名为instack,outstack,用于进队列,出队列 3,比如有1,2,3,4,5 需要进入队列,先将这一串数压入instack栈中,假设压入顺序为1,2,3,4,5(1为栈底),再将instack中的数据移

利用JS实现图片的缓存

web页面使用HTML的<img>元素来嵌入图片,和所有HTML元素一样,<img>元素也是可以通过脚本来操控的(设置元素的src属性,将其指向一个新的URL会导致浏览器载入并展示一张新的图片).为了让图片缓存起来,客户端JS定义了一个API,首先利用Image()构造函数来创建一个屏幕外图片对象,之后将该对象的src属性设置 期望的URL,由于图片元素并没有添加到文档中,因此它是不可见的,但是浏览器还是会加载图片并将其缓存起来. //需要预加载的图片路径存放在数组里 var im

利用双缓冲队列来减少锁的竞争

在日常的开发中,日志的记录是必不可少的.但是我们也清楚对同一个文本进行写日志只能单线程的去写,那么我们也经常会使用简单lock锁来保证只有一个线程来写入日志信息.但是在多线程的去写日志信息的时候,由于记录日志信息是需要进行I/O交互的,导致我们占用锁的时间会加长,从而导致大量线程的阻塞与等待. 这种场景下我们就会去思考,我们该怎么做才能保证当有多个线程来写日志的时候我们能够在不利用锁的情况下让他们依次排队去写呢?这个时候我们就可以考虑下使用双缓冲队列来完成. 所谓双缓冲队列就是有两个队列,一个是

利用redis List队列简单实现秒杀 PHP代码实现

利用redis List队列简单实现秒杀 PHP代码实现 2018年05月28日 11:37:46 m_nanle_xiaobudiu 阅读数 35674更多 分类专栏: Redis 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m_nanle_xiaobudiu/article/details/80479666 一 生产者producer部分 ---------------------

Android音频开发(2):如何采集一帧音频

本文重点关注如何在Android平台上采集一帧音频数据.阅读本文之前,建议先读一下我的上一篇文章<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的概念后,开发过程中的很多参数和流程就会更加容易理解. Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR.MP3等)并存成文件,而后者则更接