【iOS錄音與播放】實現利用音頻隊列,通過緩存進行對聲音的采集與播放


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM