主要部分:
- 播放pcm文件流
- 實時錄音和播放
AudioQueue播放pcm文件流
導(dǎo)入頭文件定義所需變量
#import <AudioToolbox/AudioToolbox.h>
#define QUEUE_BUFFER_SIZE 5 //隊列緩沖個數(shù)
#define EVERY_READ_LENGTH 1000 //每次從文件讀取的長度
#define MIN_SIZE_PER_FRAME 2000 //每偵最小數(shù)據(jù)長度
@interface ViewController ()
{
AudioStreamBasicDescription audioDescription;///音頻參數(shù)
AudioQueueRef audioQueue;//音頻播放隊列
AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE];//音頻緩存
NSLock *synlock ;//同步控制
Byte *pcmDataBuffer;//pcm的讀文件數(shù)據(jù)區(qū)
NSInputStream *inputSteam;//用于讀pcm文件
}
@end
讀pcm文件
- (void)initFile
{
NSString *filepath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"abc.pcm"];
inputSteam = [[NSInputStream alloc] initWithFileAtPath:filepath];
[inputSteam open];
pcmDataBuffer = malloc(EVERY_READ_LENGTH);
synlock = [[NSLock alloc] init];
}
設(shè)置音頻參數(shù)
-(void)initAudio
{
///設(shè)置音頻參數(shù)
audioDescription.mSampleRate = 44100;//采樣率
audioDescription.mFormatID = kAudioFormatLinearPCM;
audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioDescription.mChannelsPerFrame = 1;///單聲道
audioDescription.mFramesPerPacket = 1;//每一個packet一偵數(shù)據(jù)
audioDescription.mBitsPerChannel = 16;//每個采樣點16bit量化
audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel/8) * audioDescription.mChannelsPerFrame;
audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame ;
///創(chuàng)建一個新的從audioqueue到硬件層的通道
// AudioQueueNewOutput(&audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &audioQueue);///使用當(dāng)前線程播
AudioQueueNewOutput(&audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, nil, 0, &audioQueue);//使用player的內(nèi)部線程播
////添加buffer區(qū)
for(int i=0;i<QUEUE_BUFFER_SIZE;i++)
{
int result = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);///創(chuàng)建buffer區(qū),MIN_SIZE_PER_FRAME為每一偵所需要的最小的大小,該大小應(yīng)該比每次往buffer里寫的最大的一次還大
NSLog(@"AudioQueueAllocateBuffer i = %d,result = %d",i,result);
}
}
注意:此時設(shè)置的音頻參數(shù)需要和pcm文件的數(shù)據(jù)相匹配,本文用到的
abc.pcm采樣率是44100
讀入數(shù)據(jù)到緩沖區(qū)等待播放
-(void)readPCMAndPlay:(AudioQueueRef)outQ buffer:(AudioQueueBufferRef)outQB
{
[synlock lock];
size_t readLength = [inputSteam read:pcmDataBuffer maxLength:EVERY_READ_LENGTH];
NSLog(@"read raw data size = %zi",readLength);
if (readLength == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"文件讀取完成");
});
return ;
}
outQB->mAudioDataByteSize = (UInt32)readLength;
memcpy((Byte *)outQB->mAudioData, pcmDataBuffer, readLength);
/*
將創(chuàng)建的buffer區(qū)添加到audioqueue里播放
AudioQueueBufferRef用來緩存待播放的數(shù)據(jù)區(qū),AudioQueueBufferRef有兩個比較重要的參數(shù),AudioQueueBufferRef->mAudioDataByteSize用來指示數(shù)據(jù)區(qū)大小,AudioQueueBufferRef->mAudioData用來保存數(shù)據(jù)區(qū)
*/
AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL);
[synlock unlock];
}
開始播放
-(void)startPlay
{
[self initFile];
[self initAudio];
AudioQueueStart(audioQueue, NULL);
for(int i=0;i<QUEUE_BUFFER_SIZE;i++)
{
[self readPCMAndPlay:audioQueue buffer:audioQueueBuffers[i]];
}
/*
audioQueue使用的是驅(qū)動回調(diào)方式,即通過AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL);傳入一個buff去播放,播放完buffer區(qū)后通過回調(diào)通知用戶,
用戶得到通知后再重新初始化buff去播放,周而復(fù)始,當(dāng)然,可以使用多個buff提高效率(測試發(fā)現(xiàn)使用單個buff會小卡)
*/
}
回調(diào)函數(shù)
void AudioPlayerAQInputCallback(void *input, AudioQueueRef outQ, AudioQueueBufferRef outQB)
{
NSLog(@"AudioPlayerAQInputCallback");
ViewController *mainviewcontroller = (__bridge ViewController *)input;
[mainviewcontroller checkUsedQueueBuffer:outQB];
[mainviewcontroller readPCMAndPlay:outQ buffer:outQB];
}
檢測當(dāng)前回調(diào)的是哪個緩沖區(qū)
-(void)checkUsedQueueBuffer:(AudioQueueBufferRef) qbuf
{
if(qbuf == audioQueueBuffers[0])
{
NSLog(@"AudioPlayerAQInputCallback,bufferindex = 0");
}
if(qbuf == audioQueueBuffers[1])
{
NSLog(@"AudioPlayerAQInputCallback,bufferindex = 1");
}
if(qbuf == audioQueueBuffers[2])
{
NSLog(@"AudioPlayerAQInputCallback,bufferindex = 2");
}
if(qbuf == audioQueueBuffers[3])
{
NSLog(@"AudioPlayerAQInputCallback,bufferindex = 3");
}
if(qbuf == audioQueueBuffers[4])
{
NSLog(@"AudioPlayerAQInputCallback,bufferindex = 4");
}
}
注意:播放pcm文件中用到
NSInputStream的部分知識,對這塊有疑問的可以看這里
AudioQueue實時錄音
AudioQueueRecorder頭文件定義
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
@class AudioQueueRecorder;
@protocol AudioQueueRecorderDelegate < NSObject>
@optional
//實時錄音pcm數(shù)據(jù)流
-(void)AudioQueueRecorder:(AudioQueueRecorder *)recorder pcmData:(NSData *)pcmData;
@end
@interface AudioQueueRecorder : NSObject
@property (nonatomic, weak) id<AudioQueueRecorderDelegate> deledate;
-(void)startRecording;
-(void)stopRecording;
@end
定義變量
#define QUEUE_BUFFER_SIZE 3 // 輸出音頻隊列緩沖個數(shù)
#define kDefaultBufferDurationSeconds 0.03//調(diào)整這個值使得錄音的緩沖區(qū)大小為960,實際會小于或等于960,需要處理小于960的情況
#define kDefaultSampleRate 16000 //定義采樣率為16000
static BOOL isRecording = NO;
@interface AudioQueueRecorder (){
AudioQueueRef _audioQueue;//輸出音頻播放隊列
AudioStreamBasicDescription _recordFormat;//音頻參數(shù)
AudioQueueBufferRef _audioBuffers[QUEUE_BUFFER_SIZE];//輸出音頻緩存
UInt32 bufferByteSize;//緩存區(qū)大小
}
@end
設(shè)置參數(shù)以及初始化緩沖器
- (instancetype)init
{
self = [super init];
if (self) {
//重置下
memset(&_recordFormat, 0, sizeof(_recordFormat));
_recordFormat.mSampleRate = kDefaultSampleRate;
_recordFormat.mChannelsPerFrame = 1;
_recordFormat.mFormatID = kAudioFormatLinearPCM;
_recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_recordFormat.mBitsPerChannel = 16;
_recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
_recordFormat.mFramesPerPacket = 1;
//初始化音頻輸入隊列
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);
//計算估算的緩存區(qū)大小
DeriveBufferSize(_audioQueue, _recordFormat, kDefaultBufferDurationSeconds, &bufferByteSize);
NSLog(@"緩存區(qū)大小%d",bufferByteSize);
//創(chuàng)建緩沖器
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){
AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]);
AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);
}
}
return self;
}
開始錄音
-(void)startRecording
{
// 開始錄音
AudioQueueStart(_audioQueue, NULL);
isRecording = YES;
}
停止錄音
-(void)stopRecording
{
if (isRecording)
{
isRecording = NO;
//停止錄音隊列和移除緩沖區(qū),以及關(guān)閉session,這里無需考慮成功與否
AudioQueueStop(_audioQueue, true);
//移除緩沖區(qū),true代表立即結(jié)束錄制,false代表將緩沖區(qū)處理完再結(jié)束
AudioQueueDispose(_audioQueue, true);
}
NSLog(@"停止錄音");
}
錄音回調(diào)函數(shù)
void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
if (inNumPackets > 0) {
AudioQueueRecorder *recorder = (__bridge AudioQueueRecorder*)inUserData;
[recorder processAudioBuffer:inBuffer withQueue:inAQ];
}
if (isRecording) {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}
處理數(shù)據(jù),通過代理返回pcm實時數(shù)據(jù)流
- (void)processAudioBuffer:(AudioQueueBufferRef )audioQueueBufferRef withQueue:(AudioQueueRef )audioQueueRef
{
NSMutableData * dataM = [NSMutableData dataWithBytes:audioQueueBufferRef->mAudioData length:audioQueueBufferRef->mAudioDataByteSize];
if (dataM.length < bufferByteSize) { //處理長度小于bufferByteSize的情況,此處是補00
Byte byte[] = {0x00};
NSData * zeroData = [[NSData alloc] initWithBytes:byte length:1];
for (NSUInteger i = dataM.length; i < bufferByteSize; i++) {
[dataM appendData:zeroData];
}
}
if(self.deledate&&[self.deledate respondsToSelector:@selector(AudioQueueRecorder:pcmData:)]){
[self.deledate AudioQueueRecorder:self pcmData:dataM];
}
}
計算估算的緩存區(qū)的大小
void DeriveBufferSize (AudioQueueRef audioQueue,
AudioStreamBasicDescription ASBDescription,
Float64 seconds,
UInt32 *outBufferSize)
{
static const int maxBufferSize = 0x50000; // 5
int maxPacketSize = ASBDescription.mBytesPerPacket; // 6
if (maxPacketSize == 0) { // 7
UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
AudioQueueGetProperty (
audioQueue,
kAudioQueueProperty_MaximumOutputPacketSize,
// in Mac OS X v10.5, instead use
// kAudioConverterPropertyMaximumOutputPacketSize
&maxPacketSize,
&maxVBRPacketSize
);
}
Float64 numBytesForTime = ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
*outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ?
numBytesForTime : maxBufferSize); // 9
}
AudioQueue實時播放
AudioQueuePlay頭文件定義
#import <Foundation/Foundation.h>
@interface AudioQueuePlay : NSObject
// 播放的數(shù)據(jù)流數(shù)據(jù)
- (void)playWithData:(NSData *)data;
// 聲音播放出現(xiàn)問題的時候可以重置一下
- (void)resetPlay;
// 停止播放
- (void)stop;
@end
定義變量
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#define MIN_SIZE_PER_FRAME 1920 //每個包的大小,室內(nèi)機要求為960,具體看下面的配置信息
#define QUEUE_BUFFER_SIZE 3 //緩沖器個數(shù)
#define SAMPLE_RATE 16000 //采樣頻率
@interface AudioQueuePlay(){
AudioQueueRef audioQueue; //音頻播放隊列
AudioStreamBasicDescription _audioDescription;
AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音頻緩存
BOOL audioQueueBufferUsed[QUEUE_BUFFER_SIZE]; //判斷音頻緩存是否在使用
NSLock *sysnLock;
NSMutableData *tempData;
OSStatus osState;
Byte *pcmDataBuffer;//pcm的讀文件數(shù)據(jù)區(qū)
}
@end
initialize方法
#pragma mark - 提前設(shè)置AVAudioSessionCategoryMultiRoute 播放和錄音
+ (void)initialize
{
NSError *error = nil;
//只想要播放:AVAudioSessionCategoryPlayback
//只想要錄音:AVAudioSessionCategoryRecord
//想要"播放和錄音"同時進行 必須設(shè)置為:AVAudioSessionCategoryMultiRoute 而不是AVAudioSessionCategoryPlayAndRecord(設(shè)置這個不好使)
BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryMultiRoute error:&error];
if (!ret) {
NSLog(@"設(shè)置聲音環(huán)境失敗");
return;
}
//啟用audio session
ret = [[AVAudioSession sharedInstance] setActive:YES error:&error];
if (!ret)
{
NSLog(@"啟動失敗");
return;
}
}
設(shè)置參數(shù)以及初始化緩沖器
- (instancetype)init
{
self = [super init];
if (self) {
sysnLock = [[NSLock alloc]init];
pcmDataBuffer = malloc(MIN_SIZE_PER_FRAME);
//設(shè)置音頻參數(shù) 具體的信息需要問后臺
_audioDescription.mSampleRate = SAMPLE_RATE;
_audioDescription.mFormatID = kAudioFormatLinearPCM;
_audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
//1單聲道
_audioDescription.mChannelsPerFrame = 1;
//每一個packet一偵數(shù)據(jù),每個數(shù)據(jù)包下的楨數(shù),即每個數(shù)據(jù)包里面有多少楨
_audioDescription.mFramesPerPacket = 1;
//每個采樣點16bit量化 語音每采樣點占用位數(shù)
_audioDescription.mBitsPerChannel = 16;
_audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;
//每個數(shù)據(jù)包的bytes總數(shù),每楨的bytes數(shù)*每個數(shù)據(jù)包的楨數(shù)
_audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;
// 使用player的內(nèi)部線程播放 新建輸出
AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, 0, 0, &audioQueue);
// 設(shè)置音量
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
// 初始化需要的緩沖區(qū)
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
audioQueueBufferUsed[i] = false;
osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
}
osState = AudioQueueStart(audioQueue, NULL);
if (osState != noErr) {
NSLog(@"AudioQueueStart Error");
}
}
return self;
}
得到空閑的緩沖區(qū)
- (AudioQueueBufferRef)getNotUsedBuffer
{
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
if (NO == audioQueueBufferUsed[i]) {
audioQueueBufferUsed[i] = YES;
return audioQueueBuffers[i];
}
}
return NULL;
}
拿到pcm數(shù)據(jù)播放
// 播放數(shù)據(jù)
-(void)playWithData:(NSData *)data
{
[sysnLock lock];
tempData = [NSMutableData new];
[tempData appendData: data];
NSUInteger len = tempData.length;
[tempData getBytes:pcmDataBuffer length: len];
AudioQueueBufferRef audioQueueBuffer = NULL;
//獲取可用buffer
while (true) {
[NSThread sleepForTimeInterval:0.0005];
audioQueueBuffer = [self getNotUsedBuffer];
if (audioQueueBuffer != NULL) {
break;
}
}
audioQueueBuffer -> mAudioDataByteSize = (unsigned int)len;
// 把bytes的頭地址開始的len字節(jié)給mAudioData,向第i個緩沖器
memcpy(audioQueueBuffer -> mAudioData, pcmDataBuffer, len);
//將第i個緩沖器放到隊列中,剩下的都交給系統(tǒng)了
AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffer, 0, NULL);
[sysnLock unlock];
}
回調(diào)函數(shù)重置緩沖區(qū)狀態(tài)
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {
AudioQueuePlay* audio = (__bridge AudioQueuePlay*)inUserData;
[audio resetBufferState:audioQueueRef and:audioQueueBufferRef];
}
- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
// 防止空數(shù)據(jù)讓audioqueue后續(xù)都不播放,為了安全防護一下
if (tempData.length == 0) {
audioQueueBufferRef->mAudioDataByteSize = 1;
Byte* byte = audioQueueBufferRef->mAudioData;
byte = 0;
AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL);
}
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
// 將這個buffer設(shè)為未使用
if (audioQueueBufferRef == audioQueueBuffers[i]) {
audioQueueBufferUsed[i] = false;
}
}
}
使用方法
self.audioQueuePlay = [[AudioQueuePlay alloc]init];
self.audioQueueRecorder = [[AudioQueueRecorder alloc]init];
self.audioQueueRecorder.deledate = self;
代理方法
-(void)AudioQueueRecorder:(AudioQueueRecorder *)recorder pcmData:(NSData *)pcmData
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.audioQueuePlay playWithData:pcmData];
});
}
總結(jié):本文基本上都是代碼的實現(xiàn),并沒有太多原理上的介紹,不久便會補上原理性的文章,多多關(guān)注我喲!