AudioQueue 錄音與播放

主要部分:

  • 播放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)注我喲!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容