都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音。
所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现。
要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用AVAudioRecorder和AVAudioPlayer。度娘大多数也是如此。但是这种方法有很大的局限性。单说说这种做法:录音,首先得设置录音文件路径,然后录音数据直接写入了文件。播放也是首先给出文件路径,等到音频整个加载完成了,才能开始播放。这相当不灵活。
我的做法是利用音频队列AudioQueue,将声音暂存至缓冲区,然后从缓冲区取出音频数据,进行播放。
声音采集:
使用AudioQueue框架以队列的形式处理音频数据。因此使用时需要给队列分配缓存空间,由回调(Callback)函数完成向队列缓存读写音频数据的功能。
一个Recording Audio Queue,包括Buffer(缓冲器)组成的Buffer Queue(缓冲队列),以及一个Callback(回调)。实现主要步骤为:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | Record.h#import <Foundation/Foundation.h>#import <AudioToolbox/AudioToolbox.h>#import <CoreAudio/CoreAudioTypes.h>#import "AudioConstant.h"// use Audio QueuetypedefstructAQCallbackStruct{    AudioStreamBasicDescription mDataFormat;    AudioQueueRef               queue;    AudioQueueBufferRef         mBuffers[kNumberBuffers];    AudioFileID                 outputFile;        unsigned longframeSize;    longlongrecPtr;    intrun;    } AQCallbackStruct;@interfaceRecord : NSObject{    AQCallbackStruct aqc;    AudioFileTypeID fileFormat;    longaudioDataLength;    Byte audioByte[999999];    longaudioDataIndex;}- (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) longaudioDataLength;@end | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | Record.mm#import "Record.h"@implementationRecord@synthesizeaqc;@synthesizeaudioDataLength;staticvoidAQInputCallback (void* inUserData,                             AudioQueueRef          inAudioQueue,                             AudioQueueBufferRef    inBuffer,                             constAudioTimeStamp   * inStartTime,                             unsigned longinNumPackets,                             constAudioStreamPacketDescription * 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= [superinit];    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(inti=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;    returnself;}- (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{    returnaudioByte;}- (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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Play.h#import <Foundation/Foundation.h>#import <AudioToolbox/AudioToolbox.h>#import "AudioConstant.h"@interfacePlay : NSObject{    //音频参数    AudioStreamBasicDescription audioDescription;    // 音频播放队列    AudioQueueRef audioQueue;    // 音频缓存    AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE];}-(void)Play:(Byte *)audioByte Length:(long)len;@end | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | Play.mm#import "Play.h"@interfacePlay(){    Byte *audioByte;    longaudioDataIndex;    longaudioDataLength;}@end@implementationPlay//回调函数(Callback)的实现staticvoidBufferCallback(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;        [selfCreateAudioQueue];}-(void)CreateAudioQueue{    [selfCleanup];    //使用player的内部线程播    AudioQueueNewOutput(&audioDescription, BufferCallback, (__bridge void*)(self), nil, nil, 0, &audioQueue);    if(audioQueue)    {        ////添加buffer区        for(inti=0;i<QUEUE_BUFFER_SIZE;i++)        {            intresult =  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");                [selfStop];        for(inti=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{    [selfStop];    audioByte = byte;    audioDataLength = len;        NSLog(@"Audio Play Start >>>>>");        [selfSetAudioFormat];        AudioQueueReset(audioQueue);    audioDataIndex = 0;    for(inti=0; i<QUEUE_BUFFER_SIZE; i++)    {        [selfFillBuffer:audioQueue queueBuffer:audioQueueBuffers[i]];    }    AudioQueueStart(audioQueue, NULL);}@end | 
以上,实现了通过内存缓存,声音的采集和播放,包括了声音采集,暂停,结束,播放等主要流程。
PS:由于本人水品有限加之这方面资料较少,只跑通了正常流程,暂时没做异常处理。采集的声音Buffer限定大小每次只有十来秒钟的样子,这个留给需要的人自己去优化了。
iOS中声音采集与播放的实现(使用AudioQueue),布布扣,bubuko.com
原文地址:http://www.cnblogs.com/yulang314/p/3719843.html