#基于AudioQueue实现音频的录制和播放

文章目录

  • 背景
  • 总览
  • Audio Queue 架构
    • AudioQueueBuffer数据结构
    • 创建 AudioQueueBuffer
    • 释放 AudioQueueBuffer
    • Buffer Queue 和Enqueuing
    • Audio Queue Callback
  • 音频录制
    • 创建一个录音 AudioQueue 的示例
  • 音频播放
    • 播放流程说明
    • 通过AudioQueue来控制音频的播放
  • Audio Queue 的控制和状态
  • Audio Queue 运行状态的监控
  • demo地址
  • 参考文献

背景

在iOS中常使用AVPlayerAVAudioPlayer来播放在线音乐或者本地音乐,但是支持的格式都是封装好的,比如Mp3,Wav 格式的音频,如果需要播放流式的PCM音频数据该怎么办呢? 答案是使用Audio Queue,它也是苹果官方封装的音频处理框架,可以用来播放或录制音频,并且支持平台级音频格式的编码和解码。

AudioQueue 有以下作用

  1. 连接设备的音频硬件
  2. 管理音频播放的内存数据
  3. 协作codec 进行音频的的编解码
  4. 实现音频的录制和播放

本篇文章主要以PCM 数据为例子进行讲解,讲解音频的录制和实现,文末会附带基于AudioQueue的录音器和播放起的源代码文件;

总览

本篇主要介绍来音频的录制和播放过程,共包含三个部分,Audio Queue 架构、音频的录制、和音频的播放,其中Audio Queue 架构是实现录制和播放的核心,理解了AudioQueue的实现原理,再来看录制和播放将会更加高效率;

Audio Queue 架构

Audio Queue 架包含三个部分: audio queue buffers, Buffer queueaudio queue callback; audio queue buffers 在结构上是一个数组结构,存储的AudioBuffer数据,下面会专门分析AudioQueueBuffer 的数据结构; Buffer queue 可以理解为管理类,用来管理和组织这些audio queue buffers 按照一定的顺序进行排列和运行, 并且协调audio queue callback 的调用;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thal9tUz-1645166143259)(https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Art/recording_architecture_2x.png “音频录制”)]

AudioQueueBuffer数据结构

下面重点来说明AudioQueueBuffer的数据结构的数据结构如下,主要包含四个部分,其中最核心的是aAduioData 部分。

typedef struct AudioQueueBuffer {const UInt32   mAudioDataBytesCapacity;void *const    mAudioData;UInt32         mAudioDataByteSize;void           *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;

mAudioData, 它代表要录制和播放的音频数据;
mAudioDataByteSize: 用来表示audioDatalength;
mAudioDataBytesCapacity: 表示一个mAudioData 需要分配的空间,单位是字节(Byte),它的值必须大于mAudioDataByteSize,否则音频的数据放不下会出现丢失;
mUserData: 一个万能指针,用来传递调用者的值,一般结合bridge(void *)传递self对象,实现C函数和OC 语言的交互

创建 AudioQueueBuffer

调用函数AudioQueueAllocateBuffer 来分配,以下为示例代码;

int result =  AudioQueueAllocateBuffer(_audioQueue, kAudioBufferSize, &_audioQueueBuffers[i]);NSLog(@"Mic AudioQueueAllocateBuffer i = %d,result = %d", i, result);AudioQueueEnqueueBuffer(_audioQueue, _audioQueueBuffers[i], 0, NULL);

释放 AudioQueueBuffer

销毁则是通过AudioQueueDispose来实现;

- (void)freeAudioBuffers {for(int i=0; i<kAudioQueueBufferCount; i++) {int result = AudioQueueFreeBuffer(_audioQueue, _audioQueueBuffers[i]);NSLog(@"AudioQueueFreeBuffer i = %d,result = %d", i, result);}AudioQueueDispose(_audioQueue, YES);
}

Buffer Queue 和Enqueuing

Buffer queue 的数据结构是一个队列,队列里可以存放许多Auido Queue Buffer, 存放的Buffer数量不受限制,推荐使用三个,例如录制的过程,一个buffer 负责收集麦克风的数据,一个buffer负责将数据传递给磁盘,还有一个Buffe用来做备用,防止磁盘I/O 时间过长出现卡顿。Enqueuing的含义是加入队列, 可以通过下图来了解Enqueue的过程。

音频录制Buffer Queuing的Enqueuing说明:

  1. 读取麦克风的数据到内存中,然后将数据写入到buffer中;
  2. 当第一个buffer数据写满后,会将数据存放到磁盘中,并且触发回调函数,这个时候回调函数需要处理数据,将新的音频数据覆写到该buffer中;
  3. 在回调函数中将采集的音频数据写入到磁盘中;
  4. 当三个buffer 都填满后之后会继续复用第一个buffer;
  5. 重复第2部进行填充数据和进行回调;
  6. 重复第3步将数据写入磁盘中

Audio Queue Callback

Audio Queue 的Callback 是开发的重要内容,它是通过AudioQueueEnqueueBuffer函数来驱动数据,它是在音频录制或播放中进行重复调用的,调用的间隔取决于buffer的大小,设置的buffer数据容量越大,回调触发的间隔也越大,一般为0.5秒到几秒不等; AudioQueueCallback分为两个部分,录制AuidoQueueInputCallback 和播放 AudioQueuOutputCallBack

音频录制

音频录制的本质是调用手机上的录音设置(如麦克风,耳机)来采集声音,采集声音的声音进行数字编码调制,形成音频数据,然后读取到内存中,最后写入到手机设备的硬盘中进行保存。iOS 的录音实现是通过Audio Queue 进行实现,下面主要分析AudioQueu的的结构和使用原理。

###录制 AuidoQueueInputCallback 回调函数

AudioQueueInputCallback (void                               *inUserData,AudioQueueRef                      inAQ,AudioQueueBufferRef                inBuffer,const AudioTimeStamp               *inStartTime,UInt32                             inNumberPacketDescriptions,const AudioStreamPacketDescription *inPacketDescs
);

inUserData 用户数据指针,本身是一个无类型的指针,常被用来指向调用实例;
inAQ 调用该 CallBack的AudioQueue;
inBuffer 初始化AudioBuffer的时候我们封装好的音频数据;
inStartTime 每个buffer 对应的时间,这里我们用不上;
inNumberPacketDescriptions结合inPacketDescs的参数使用,一般涉及到编码的地方会用到;
inPacketDescs 对应buffer的音频包描述,一般涉及到编码的地方会用到;

音频录制的调用实例

// 音频录制的回调函数
void AudioAQInputCallback(void * __nullable               inUserData,AudioQueueRef                   inAQ,AudioQueueBufferRef             inBuffer,const AudioTimeStamp *          inStartTime,UInt32                          inNumberPacket,const AudioStreamPacketDescription * __nullable inPacketDescs) {DBAudioMicrophone * SELF = (__bridge DBAudioMicrophone *)inUserData;NSLog(@"Mic Audio Callback");if (inNumberPacket > 0){[SELF processAudioBuffer:inBuffer withQueue:inAQ];}
}// 读取录制到的音频数据 - (void)processAudioBuffer:(AudioQueueBufferRef)inBuffer withQueue:(AudioQueueRef)inAudioQueue {NSData *data = [NSData dataWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize];[self.sendData appendData:data];if (_isOn) {AudioQueueEnqueueBuffer(inAudioQueue, inBuffer, 0, NULL);}
}

创建一个录音 AudioQueue 的示例


- (void)audioNewInput {/// 创建一个新的从audioqueue到硬件层的通道AudioQueueNewInput(&_audioDescription, AudioAQInputCallback, (__bridge void * _Nullable)(self), NULL, kCFRunLoopCommonModes, 0, &_audioQueue);}

说明:
1._audioDescription 是 AudioStreamBasicDescription 类型的数据结构,主要用来描述数据的特点,包含采样率,位深,声道数量,音频格式,音频包,音频帧等数据;
2.AudioAQInputCallback 是音频的回调函数;
3.kCFRunLoopCommonModes 当前AudioQueue所运行的Runloop;
4._audioQueue 是AudioQueuueRef 类型的指针,此处用它的指针来进行实例化;

下面看一下AudioStreamBasicDescription的初始化赋值;

+ (AudioStreamBasicDescription)defaultAudioDescriptionWithSampleRate:(Float64)sampleRate numOfChannel:(NSInteger)numOfChannel {AudioStreamBasicDescription asbd;memset(&asbd, 0, sizeof(asbd));asbd.mSampleRate = sampleRate;asbd.mFormatID = kAudioFormatLinearPCM;asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;asbd.mChannelsPerFrame = (UInt32)numOfChannel;asbd.mFramesPerPacket = 1;//每一个packet一侦数据asbd.mBitsPerChannel = 16;//每个采样点16bit量化asbd.mBytesPerFrame = (asbd.mBitsPerChannel/8) * asbd.mChannelsPerFrame;asbd.mBytesPerPacket = asbd.mBytesPerFrame * asbd.mFramesPerPacket;return asbd;
}

说明:传入的参数是采样率和声道的数据,这里位深一般用的是16位;

音频播放

播放流程说明

音频的播放也包含三个部分,1.磁盘的的音频输入流;2.音频队列; 3. 扬声器;

说明:
首先读取磁盘中的音频数据,第二,填充音频数据到Audio Queue 中; 第三, 驱动数据,使用扬声器播放数据;

通过AudioQueue来控制音频的播放

1.声明AudioStreamBasicDescription 来描述音频特征,如采样率,位深,声道数量等;

   audioDescription.mSampleRate =16000;//采样率audioDescription.mFormatID =kAudioFormatLinearPCM;audioDescription.mFormatFlags =kLinearPCMFormatFlagIsSignedInteger |kAudioFormatFlagIsPacked;audioDescription.mChannelsPerFrame =1;///单声道audioDescription.mFramesPerPacket =1;//每一个packet一侦数据audioDescription.mBitsPerChannel =16;//每个采样点16bit量化audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel / 8) * audioDescription.mChannelsPerFrame;

2.AudioQueueOutputCallback 设置回调函数

AudioQueueOutputCallback (void                  *inUserData,AudioQueueRef         inAQ,AudioQueueBufferRef   inBuffer
);

说明:
inUserData: 用户指针,用来处理用户数据;
inAQ: auidoQueue的引用对象
inBuffer: AudioQueueBufferRef 对象,用来描述音频数据;

3.处理音频回调函数收到的数据

static void AudioPlayerAQInputCallbackV2(void* inUserData,AudioQueueRef outQ, AudioQueueBufferRef outQB){DSAQPool* pool = (__bridge DSAQPool*)inUserData;[pool playCallBack:outQB];
}-(BOOL)enqueueBuffer:(AudioQueueBufferRefWrapper*)buf{if(AudioQueueEnqueueBuffer(audioQueue, buf.ref,0,NULL) == noErr){buf.inUse = YES;@synchronized (_buffers) {[_buffers addObject:buf];}return YES;}else{//DDLogError(@"AudioQueueEnqueueBuffer error.");return NO;}
}

说明:
1.AudioPlayerAQInputCallbackV2是回调函数,数据在播放的过程中会不停的触发;
2.-(BOOL)enqueueBuffer:(AudioQueueBufferRefWrapper*)buf; 在回调的过程中需要不停的对buffer进行赋值,驱动扬声器进行播放;

Audio Queue 的控制和状态

常用的控制有开始,暂停和停止;

开始-AudioQueueStart 控制开始录制或者开始播放;
暂停AudioQueuePause 控制暂停,可以调用开始的方法继续播放;
停止 AudioQueueStop结束,调用这个方法后不能再调用start方法进行播放了,表示音频已经播放完成了。

Audio Queue 运行状态的监控

1.检测声音的分贝值

-(float)getCurrentPower {UInt32 dataSize = sizeof(AudioQueueLevelMeterState) * _audioDescription.mChannelsPerFrame;AudioQueueLevelMeterState *levels = (AudioQueueLevelMeterState*)malloc(dataSize);OSStatus rc = AudioQueueGetProperty(_audioQueue, kAudioQueueProperty_CurrentLevelMeterDB, levels, &dataSize);if (rc) {NSLog(@"NoiseLeveMeter>>takeSample - AudioQueueGetProperty(CurrentLevelMeter) returned %d", rc);}float channelAvg = 0;for (int i = 0; i < _audioDescription.mChannelsPerFrame; i++) {channelAvg += levels[i].mAveragePower;}free(levels);return channelAvg ;
}

说明: 这个方法主要用来监控声音的分贝数,一般在录音的过程中需要对音量的大小进行反馈会用到;也可以利用AudioQueueGetProperty 来监控音频的运行状态,具体可以参照: AudioQueuePropertyID 中的说明;

demo地址

音频录制: https://github.com/data-baker/BakerIosSdks/tree/main/DBAudioSDK/Classes/DBToolKit/DBMicrophone
音频播放:https://github.com/data-baker/BakerIosSdks/tree/main/DBAudioSDK/Classes/DBToolKit/DBPlayer

参考文献

苹果开发者文档:https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html

iOS AudioQueue实现音频的录制和播放(附Demo)相关推荐

  1. 基于AudioQueue实现音频的录制和播放(标贝科技)

    标贝科技 https://ai.data-baker.com/#/?source=qwer12 填写邀请码fwwqgs,每日免费调用量还可以翻倍 基于AudioQueue实现音频的录制和播放 文章目录 ...

  2. iOS设计模式四部曲(二) 结构型模式 内附Demo

    本篇是四部曲的第二篇,第一篇请点这里iOS设计模式四部曲(一):创建型模式 内附Demo,关于设计模式强烈推荐图书<Head First设计模式>以及<研磨设计模式>.由于个人 ...

  3. iOS 音频的录制、播放及音频文件管理

    文章目录 音频会话 音效播放 音乐播放 音频录制 音频管理 补充:音频队列服务 参考地址 音频会话 在使用Apple设备时,我们注意到有些应用打开音频播放时,其他音频就会终止,而有些应用却可以同时使用 ...

  4. 微信小程序音频(录制、播放、重录)功能

    1.图片展示及相关图片            2.wxml代码: <view class="" ><view class="recordingPlace ...

  5. android aac格式,android aac文件格式音频的录制和播放

    封装的工具类: package com.example.voaactest; import java.io.File; import java.io.FileNotFoundException; im ...

  6. iOS设计模式四部曲(三):行为型模式 内附Demo

    本篇是四部曲的第三篇,第一篇请点这里iOS设计模式四部曲(一):创建型模式,第二篇请点击这里iOS设计模式四部曲(二):结构型模式.由于个人能力有限,文中难免有一些遗漏或者错误,请各位看官不吝赐教!谢 ...

  7. iOS 蓝牙4.0开发使用(内附Demo)

    一: 介绍 近几年,智能设备越来越火,这些智能设备中,有很大一部分是通过手机来控制硬件设备,来达到预期的效果,这中间少不了要使用到蓝牙功能,通过蓝牙来通信来控制设备. 蓝牙分为蓝牙2.0和蓝牙4.0. ...

  8. iOS 音频录制、播放(本地、网络)

    文章目录一.录音机(AVAudioRecorder)1.简介2.如何使用3.具体实现(开始.暂停.停止.播放 四个功能)4.附件实现demo二.播放音频1.播放本地音频文件(AVAudioPlayer ...

  9. iOS 音频的实时录制和播放

    需求:最近公司需要做一个楼宇对讲的功能:门口机(连接WIFI)拨号对室内机(对应的WIFI)的设备进行呼叫,室内机收到呼叫之后将对收到的数据进行UDP广播的转发,手机(连接对应的WIFI)收到视频流之 ...

最新文章

  1. MySQL学习笔记_9_MySQL高级操作(上)
  2. IdentityServer4(7)- 使用客户端认证控制API访问(客户端授权模式)
  3. 后端码农谈前端(HTML篇)第三课:常见属性
  4. Vmware 连接局域网通过桥接方式
  5. python 的 virtualenv 环境搭建及 sublime 手动创建运行环境
  6. 如何在Outlook 2003和OWA中允许接受EXE附件
  7. linux系统编程 -- 僵尸进程 孤儿进程
  8. python中测试字符串类型的函数_Python新手学习基础之数据类型——字符串类型
  9. 基于html房屋管理系统,一种Web界面的互联网房屋销售管理系统设计的制作方法...
  10. vue——菜鸟教程学习
  11. linuxcan之kvaser使用笔记
  12. python中re模块的span_python3正则模块re的使用方法详解
  13. A股的日内交易如何进行?
  14. 爱上调试:div初探,参照物的重要性!
  15. Gym - 100886I 2015-2016 Petrozavodsk Winter Training Camp, Saratov SU Contest I - Archaeological Res
  16. win10计算机控制面板在哪里,教您win10控制面板在哪
  17. Image 图像转化为 PDF 文件
  18. HTML+CSS好看的小黄人网页制作(首页部分)
  19. 开设生物医学工程的高校(按区域划分)
  20. ssm智能社区服务的设计与实现毕业设计-附源码221512

热门文章

  1. python 为女神编朵玫瑰花的代码,python绘制玫瑰的代码
  2. 推荐几个前端 模板 框架,收藏
  3. Mounty for NTFS-让你的Mac OS M1可以正确读写NTFS文件系统
  4. 阿里云服务器被恶意ddos攻击了怎么办?
  5. 6.2.2 分区与副本
  6. 给腾讯云主机上配置SFTP
  7. 乱得那么认真|阿里小二办公桌上的秘密~内部流出
  8. 微信公众号html怎么做的,微信公众平台页面模板怎么用?分类目录页面是如何制作的?...
  9. 学车笔记(科目二——总结)
  10. Hive中sort by、distribute by、cluster by的区别及用法