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

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

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

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

声音采集:

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

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

  1. 设置音频的参数
  2. 准备并启动声音采集的音频队列
  3. 在回调函数中处理采集到的音频Buffer,在这里是暂存在了一个Byte数组里,提供给播放端使用
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
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
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
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录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放的更多相关文章

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

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

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

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

  3. Swift实现iOS录音与播放音频功能

    作用AVPLayer:可以用来播放在线及本地音视频AVAudioSession:音频会话,主要用来管理音频设置与硬件交互使用时需要导入 #import <AVFoundation/AVFound ...

  4. iOS Dev (20) 用 AVAudioPlayer 播放一个本地音频文件

    iOS Dev (20) 用 AVAudioPlayer 播放一个本地音频文件 作者:CSDN 大锐哥 博客:http://blog.csdn.net/prevention 步骤 第一步:在 Proj ...

  5. iOS Dev (21) 用 AVPlayer 播放一个本地音频文件

    iOS Dev (21) 用 AVPlayer 播放一个本地音频文件 作者:CSDN 大锐哥 博客:http://blog.csdn.net/prevention 前言 这篇文章与上一篇极其相似,要注 ...

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

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

  7. iOS音频与视频的开发(一)-使用AVAudioPlayer播放音乐、使用AVPlayerViewController播放视频

    iOS的多媒体支持非常强大,它提供了多套支持多媒体的API,无论是音频.视频的播放,还是录制,iOS都提供了多种API支持.借助于这些API的支持,iOS应用既可以查看.播放手机相册中的照片.视频,也 ...

  8. egret 篇——关于ios环境下微信浏览器的音频自动播放问题

    前段时间公司突然想用egret(白鹭引擎)做一个金币游戏,大半个月边看文档边写吭哧吭哧也总算是弄完了.期间遇到一个问题,那就是ios环境下微信浏览器的音频自动播放问题. 个人感觉吧,egret自己封装 ...

  9. iOS录音加播放.

    现在发现的事实有: 如果没有蓝牙设备, 那么可以用下面的方法边录音, 边放音乐: 在录音按钮按下的时候: _avSession = [AVAudioSession sharedInstance];   ...

随机推荐

  1. 软件定义网络基础---REST API的设计规范

    一:REST API的设计 REST API是基于HTTP协议进行设计的,由HTTP动词+URI组成 (一)HTTP动词 (二)资源的原型 文档(Document): 文档是资源的单一表现形式: 集合 ...

  2. 413(Request Entity Too Large)

    场景,在现金速达后台上传图片的时候,fetch请求报错,图片虽然不大,只有几百kb但是需要转成bese64传给后台, 413(Request Entity Too Large) 一开始以为是fetch ...

  3. 导入一个eclipse的java项目到IDEA报Cannot find JDK '1.7' for module 'TEST'

    解决办法: 右键你的项目--------------->OPen module settings 将dependencies里面的module sdk改成你安装的java版本就可以了(比如我电脑 ...

  4. [LeetCode] 323. Number of Connected Components in an Undirected Graph 无向图中的连通区域的个数

    Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), ...

  5. redis密码配置

    配置密码 重启密码会失效 配置在redis.conf中 requirepass test123,则重启不会失效

  6. [C语言] 关于计算多边形面积的一点问题

    [一道练习题] 面基 时间限制:1000ms   内存限制:65536kb 通过率:107/134 (79.85%)    正确率:107/319 (33.54%) 题目描述 按顺时针或逆时针顺序输入 ...

  7. TCP协议有几大计时器?

    1)超时重传计时器 目的:避免无限等待确认报文 创建时间:在发送TCP报文段时,会为该报文段设置一个超时重传计时器 可能发生的情况:在超时时间到达之前,收到了该报文段的确认则撤销计时器,否则重传该报文 ...

  8. markdown ——flow流程图

    一个纯文本的语法怎么画图? 将流程图代码包含在```folw和`````之间即可 例子 st=>start: Start op=>operation: Your Operation sub ...

  9. #redis 安装命令

    redis 安装命令 cd /usr/local sudo wget http://download.redis.io/releases/redis-4.0.11.tar.gz sudo tar xf ...

  10. Vue框架初识01

    摘要 vue简介 vue使用 一.Vue简介: 简介: Vue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的渐进式框架.Vue.js 的目标是通过尽可能简单的 A ...