iOS 通过Audio Queue播放音频数据

这是一篇干货,是直接应用的关于audio queue 播放

先看下面一张运行时的流程原理图:

playback_callback_function_2x.png

上图中的步骤:
1、准备播放的音频队列,为每个音频队列缓冲区(Buffer)进行数据填充;
2、当启用AudioQueueStart时,即刻进行播放数据;
3、将队列里第一个缓冲的buffer发送到音频输出区
4、播放队列进入循环模式,音频队列可以进行下一个的音频缓冲区播放
5、回调告诉上层缓冲的buffer已被使用了,然后可以进行下一次的缓冲
6、待上一个已被播放了的音频buffer释放后再次填充buffer

1、设置宏定义

#define MIN_SIZE_PER_FRAME 2000
#define QUEUE_BUFFER_NUM 3

2、定义相关的属性

    AudioQueueRef audioQueue;
    AudioStreamBasicDescription _audioDescription;
    AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_NUM];
    BOOL audioqueueBufferUsed[QUEUE_BUFFER_NUM];
    NSLock *sysLock;
    NSMutableData *tempData;
    OSStatus osState;

3、初始化音频播放的参数

  _audioDescription.mSampleRate = 8000;//采样率
            _audioDescription.mFormatID = kAudioFormatLinearPCM;//音频格式
            _audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;//保存音频数据的方式的说明
            _audioDescription.mChannelsPerFrame = 1; //1单声道 2双声道
            _audioDescription.mFramesPerPacket = 1;//每一个packet指定只有一帧数据,每个数据包的帧数
            _audioDescription.mBitsPerChannel = 16;//采样点,语音每个采样点的占用位数
            _audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel/8) * _audioDescription.mChannelsPerFrame;
            _audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;
 AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void*)self, NULL, kCFRunLoopCommonModes, 0, &audioQueue);
        //设置AudioSessionInitialize


        // 设置音量
        AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
        
        //初始化缓冲区
        for (int i = 0; i < QUEUE_BUFFER_NUM; i++) {
            audioqueueBufferUsed[i] = false;
            osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
            printf("第 %d 个AudioQueueAllocateBuffer 初始化结果 %d (0表示成功)\n", i + 1, osState);
        }
        
        osState = AudioQueueStart(audioQueue, NULL);
        if (osState != noErr) {
            NSLog(@"Audio Queue Start Failed");
        }

4、设置相关的回调函数,用于设置数据的回调

// 回调回来把buffer状态设为未使用
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {
    
    ECAudioQueuePlayer* player = (__bridge ECAudioQueuePlayer*)inUserData;
    
    [player resetBufferState:audioQueueRef and:audioQueueBufferRef];
}

- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
    
    for (int i = 0; i < QUEUE_BUFFER_NUM; i++) {
        // 将这个buffer设为未使用
        if (audioQueueBufferRef == audioQueueBuffers[i]) {
            audioqueueBufferUsed[i] = false;
        }
    }
}

6、输入播放数据

-(void)playWithData:(NSData *)data{
    [sysLock lock];
    tempData = nil;
    tempData = [NSMutableData new];
    [tempData appendData:data];
    NSUInteger len = tempData.length;
    Byte *bytes = (Byte *)malloc(len);
    [tempData getBytes:bytes length:len];
    int i = 0;
    while (true) {
        if (!audioqueueBufferUsed[i]) {
            audioqueueBufferUsed[i] = true;
            break;
        }else{
            i++;
            if (i>=QUEUE_BUFFER_NUM) {
                i = 0;
            }
        }
    }
    
    audioQueueBuffers[i]->mAudioDataByteSize = (unsigned int)len;
    memcpy(audioQueueBuffers[i]->mAudioData, bytes, len);
    
    free(bytes);
    AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);
    [sysLock unlock];

}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 步骤 cmd —— Python >>>100+200 回车 300 >>>print('hello...
    行走的大丸子阅读 169评论 0 0
  • 巫山一段云 文∕冯素萍 多日春风劲, 晨行蓦色新。 向阳寒柳干枝青, 苍松滴翠殷。 今去北山何似? 能否心情如是?...
    冯素萍阅读 1,678评论 14 74
  • 1 他们都说那是最后一次聚餐,于是都把它叫做散伙饭。 直到现在我都还觉得那次聚餐实际是个很错误的决定。可是不吃饭钱...
    夜叶心阅读 486评论 0 0
  • 《阴天有时下肉丸》这本书我选来分享时就单单觉得这是一本很有趣的书,只有在绘本的世界里,我们才能看到“天上掉馅饼”的...
    Ice的零度空间阅读 33,248评论 0 6