iOS Audio Queues获取音频数据并写入文件

AudioQueue的工作模式

在使用AudioQueue之前首先必须理解其工作模式,它之所以这么命名是因为在其内部有一套缓冲队列(Buffer Queue)的机制。在AudioQueue启动之后需要通过AudioQueueAllocateBuffer生成若干个AudioQueueBufferRef结构,这些Buffer将用来存储即将要播放的音频数据,并且这些Buffer是受生成他们的AudioQueue实例管理的,内存空间也已经被分配(按照Allocate方法的参数),当AudioQueue被Dispose时这些Buffer也会随之被销毁。

当有音频数据需要被播放时首先需要被memcpy到AudioQueueBufferRef的mAudioData中(mAudioData所指向的内存已经被分配,之前AudioQueueAllocateBuffer所做的工作),并给mAudioDataByteSize字段赋值传入的数据大小。完成之后需要调用AudioQueueEnqueueBuffer把存有音频数据的Buffer插入到AudioQueue内置的Buffer队列中。在Buffer队列中有buffer存在的情况下调用AudioQueueStart,此时AudioQueue就回按照Enqueue顺序逐个使用Buffer队列中的buffer进行播放,每当一个Buffer使用完毕之后就会从Buffer队列中被移除并且在使用者指定的RunLoop上触发一个回调来告诉使用者,某个AudioQueueBufferRef对象已经使用完成,你可以继续重用这个对象来存储后面的音频数据。如此循环往复音频数据就会被逐个播放直到结束。

这里封装了一个c++文件类,对外提供两个接口

    bool startCollect();  //开启录制

    void stopCollect();  //停止录制

对内的方法和成员

    AudioQueueRef mQueue;

    AudioQueueBufferRef mBuffers;  //装载采集到的音频数据

    AudioStreamBasicDescription mRecordFormat;   //音频数据输入格式结构体(下面有对应的介绍)

    bool m_bAudioPlayFlag;                

    PGThread m_SendThread; 

    NSMutableArray *m_inputArray;

    void SetupAudioFormat(UInt32 inFormatID);  //配置音频数据输出格式参数

    static void MyInputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,                                                    const AudioTimeStamp *inStartTime, UInt32 inNumPackets,  const AudioStreamPacketDescription* inPacketDesc);  //采集到音频数据的回到方法

structAudioStreamBasicDescription{

    Float64 mSampleRate;// 采样率 :Hz

    AudioFormatID mFormatID;// 采样数据的类型,PCM,AAC等

    AudioFormatFlags mFormatFlags;// 每种格式特定的标志,无损编码 ,0表示没有

    UInt32 mBytesPerPacket;// 一个数据包中的字节数

    UInt32 mFramesPerPacket;// 一个数据包中的帧数,每个packet的帧数。如果是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,比如说AAC的1024。如果是动态大小帧数(比如Ogg格式)设置为0。

    UInt32 mBytesPerFrame;// 每一帧中的字节数

    UInt32 mChannelsPerFrame;// 每一帧数据中的通道数,单声道为1,立体声为2

    UInt32 mBitsPerChannel;// 每个通道中的位数,1byte = 8bit

    UInt32 mReserved;// 8字节对齐,填0

};typedefstructAudioStreamBasicDescription AudioStreamBasicDescription;

对外构造方法中初始化相关数据

AudioInput::AudioInput()

{

    m_bAudioPlayFlag = false;  //是否开启录制

    m_inputArray= [[NSMutableArray alloc] init];  //存储采集到的音频数据NSData

//创建线程来发送采集到的数据到设备端(相当于服务端),写入到文件就不需要这里

    GThread::ThreadFunc runSendFunction(bind(&AudioInput::send,this));

    m_SendThread=new GThread(runSendFunction,"sendThread");

    m_SendThread->setPalpitationTime(50);

}

开始录制

bool AudioInput::startCollect()

{

    m_SendThread->start();  //启动发送数据线程

    SetupAudioFormat(kAudioFormatLinearPCM);   //配置音频数据输出格式参数

    OSStatusstate =AudioQueueNewInput(&mRecordFormat,AudioInput::MyInputBufferHandler,this,NULL,NULL,0, &mQueue);  //开始采集,传入音频参数、回调方法以及音频采集队列

    if(state !=noErr){

        return false;

    }

    intbufferByteSize =5*1024;

    state =AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers);

    if(state !=noErr){

        return false;

    }

    state =AudioQueueEnqueueBuffer(mQueue, mBuffers, 0, NULL);

    if(state !=noErr){

        return false;

    }

    state =AudioQueueStart(mQueue,NULL);

    if(state !=noErr){

        return false;

    }

    m_bAudioPlayFlag = true;

    return true;

}

配置音频输出参数

void AudioInput::SetupAudioFormat(UInt32inFormatID)

{

    memset(&mRecordFormat, 0, sizeof(mRecordFormat));


UInt32 size = sizeof(mRecordFormat.mSampleRate);

AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, &mRecordFormat.mSampleRate);

    mRecordFormat.mSampleRate=8000;

size =sizeof(mRecordFormat.mChannelsPerFrame);

AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels,  &size, &mRecordFormat.mChannelsPerFrame);

mRecordFormat.mFormatID= inFormatID;

if (inFormatID == kAudioFormatLinearPCM){

// if we want pcm, default to signed 16-bit little-endian

mRecordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;

mRecordFormat.mBitsPerChannel = 16;

mRecordFormat.mBytesPerPacket = mRecordFormat.mBytesPerFrame = (mRecordFormat.mBitsPerChannel / 8) * mRecordFormat.mChannelsPerFrame;

mRecordFormat.mFramesPerPacket = 1;

}

}

采集数据后的回调方法

void AudioInput::MyInputBufferHandler(void*inUserData,AudioQueueRefinAQ,AudioQueueBufferRefinBuffer,

                                      constAudioTimeStamp*inStartTime,UInt32inNumPackets,

                                      constAudioStreamPacketDescription*inPacketDesc)

{

    AudioInput*aqr = (AudioInput*)inUserData;   //当前this

    if(inNumPackets >0){

       NSData*data = [NSData dataWithBytes:(char*)inBuffer->mAudioDatalength:inBuffer->mAudioDataByteSize];

        [handle seekToEndOfFile];

         [handlewriteData:data];  //将data数据写入到文件中

         [handlecloseFile];

        [m_inputArray addObject:data];   // 放到数组中,以便发送数据线程从数组中获取

    }

    AudioQueueEnqueueBuffer(inAQ, inBuffer,0,NULL);

}

创建文件

       staticNSString*aPath =nil;

      aPath = [NSString stringWithFormat:@"%@/Documents/%@",NSHomeDirectory(),@"test.pcm"];

            if(![fileMfileExistsAtPath:aPath]) {

                [fileM createFileAtPath:aPath contents:nil attributes:nil];

            }

            handle = [NSFileHandle fileHandleForWritingAtPath:aPath];

发送数据线程

int AudioInput::send()

{

    if (m_inputArray.count != 0 && m_bAudioPlayFlag)

    {

        NSData*data = [m_inputArray objectAtIndex:0];

        [m_inputArray removeObject:data];

//        发送数据到设备端

//        。。。。。。。。

    }

    return0;

}

停止录制

void AudioInput::stopCollect()

{

    if(!m_bAudioPlayFlag){

        return;

    }

    m_SendThread->stop();

    m_bAudioPlayFlag = false;

    AudioQueueStop(mQueue, false);

    AudioQueueDispose(mQueue, false);

}

析构方法

AudioInput::~AudioInput()

{

        stopCollect();    

}

写入到文件的pcm数据可以通过Cool Edit Pro工具来播放

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容