AudioToolBox播放pcm音频数据

最近集成一种新设备,设备与手机相连时,通过socket连接将视频与音频数据分别以h264和pcm的形式传输到手机上。厂商建议我们将h264视频流和pcm音频流合成为本地直播流,使用IJKPlayer无缓冲播放。
但在实际测试中,无缓冲播放会存在比较严重的卡顿、延迟现象。于是,我们选择方案,将传递的音视频流缓存3s,使用AudioToolBox和VideoToolBox分别播放。
本篇文章为AudioToolBox播放pcm流的内容。

1. AudioQueuePlay.h
#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@protocol AudioQueuePlayDelegate 

- (NSData *_Nullable) getAudioData;
- (void) audioNeedPaused;

@end

@interface AudioQueuePlay : NSObject

@property(nonatomic, weak) id delegate;

- (void) start;
- (void)play;
- (void)pause;
- (void)stop;

@end

NS_ASSUME_NONNULL_END

2. AudioQueuePlay.m
#import "AudioQueuePlay.h"

#define MIN_SIZE_PER_FRAME 8192  // 根据一帧音频的实际大小确定
#define QUEUE_BUFFER_SIZE 3      // 音频缓冲个数

@interface AudioQueuePlay() {
    
    AudioQueueRef audioQueue;                                 //音频播放队列
    AudioStreamBasicDescription _audioDescription;            //音频播放上下文
    AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音频缓存
    BOOL bufferCanByPlay[QUEUE_BUFFER_SIZE];                  //判断缓存的音频是否可以播放
    int pauseCount;
}

@end

@implementation AudioQueuePlay

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 播放PCM使用
        if (_audioDescription.mSampleRate <= 0) {
            //设置音频参数
            _audioDescription.mSampleRate = 8000.0;//采样率
            _audioDescription.mFormatID = kAudioFormatLinearPCM;
            // 下面这个是保存音频数据的方式的说明,如可以根据大端字节序或小端字节序,浮点数或整数以及不同体位去保存数据
            _audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
            //1单声道 2双声道
            _audioDescription.mChannelsPerFrame = 1;
            //每一个packet一侦数据,每个数据包下的桢数,即每个数据包里面有多少桢
            _audioDescription.mFramesPerPacket = 1;
            //每个采样点16bit量化 语音每采样点占用位数
            _audioDescription.mBitsPerChannel = 16;
            _audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;
            //每个数据包的bytes总数,每桢的bytes数*每个数据包的桢数
            _audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;
        }
        
        // 使用player的内部线程播放 新建输出
        AudioQueueNewOutput(&_audioDescription, AudioQueueBufferDone, (__bridge void * _Nullable)(self), NULL, 0, 0, &audioQueue);
        // 设置音量
        AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
        
        // 初始化需要的缓冲区及使用标记
        OSStatus osState;
        for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
            // 创建buffer
            osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
            // 此时未填充数据,标记为不能播放
            bufferCanByPlay[i] = NO;
            printf("第 %d 个AudioQueueAllocateBuffer 初始化结果 %d (0表示成功)\n", i + 1, osState);
        }
    }
    return self;
}

// ************************** 回调 **********************************

// 回调回来把buffer状态设为未使用
static void AudioQueueBufferDone(void* audioQueuePlayData, AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {
    AudioQueuePlay* player = (__bridge AudioQueuePlay*)audioQueuePlayData;
    
    [player resetAudioQueueBuffer:audioQueueRef and:audioQueueBufferRef];
}
// 重置播放完毕的buffer
- (void)resetAudioQueueBuffer:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
    // 查找当前播放完毕的buffer位置
    int oldIndex = -1;
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
        if (audioQueueBuffers[i] == audioQueueBufferRef) {
            oldIndex = i;
            break;
        }
    }
    // 获取新数据
    NSData *data = [self.delegate getAudioData];
    if (data == nil) {
        // 新数据获取失败,将buffer标记为不能播放
        bufferCanByPlay[oldIndex] = NO;
        // 已暂停buffer数量+1
        pauseCount += 1;
        // 如已暂停buffer数量达到缓冲大小,此时通知用户暂停播放
        if (pauseCount > QUEUE_BUFFER_SIZE-1) {
            [self.delegate audioNeedPaused];
        }
        return;
    }
    // 获取新数据成功
    NSMutableData *tempData = [NSMutableData new];
    [tempData appendData: data];
    // 将新数据更新到播放完毕的buffer中
    NSUInteger len = tempData.length;
    Byte *bytes = (Byte*)malloc(len);
    [tempData getBytes:bytes length: len];
    bufferCanByPlay[oldIndex] = YES;
    audioQueueBuffers[oldIndex] -> mAudioDataByteSize =  (unsigned int)len;
    memcpy(audioQueueBuffers[oldIndex] -> mAudioData, bytes, len);
    free(bytes);
    AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[oldIndex], 0, NULL);
    NSLog(@"已更新音频数据:%d", oldIndex);
}
/*
 启动音频播放机
 注意:音频播放之前,请保持充足的音频缓存,在暂停之前不能重复调用start
 */
- (void)start{
    // 将暂停数量标记为0,暂停数量表示
    pauseCount = 0;
    // 填充已创建的buffer,并压入播放队列
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
        NSData *data = [self.delegate getAudioData];
        if (data == nil) {
            break;
        }
        NSMutableData *tempData = [NSMutableData new];
        [tempData appendData: data];
        // 得到数据
        NSUInteger len = tempData.length;
        Byte *bytes = (Byte*)malloc(len);
        [tempData getBytes:bytes length: len];
        bufferCanByPlay[i] = YES; // 标记为使用
        audioQueueBuffers[i] -> mAudioDataByteSize = (unsigned int)len;
        memcpy(audioQueueBuffers[i] -> mAudioData, bytes, len);
        free(bytes);
        AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);
    }
    // 启动播放队列
    OSStatus osState = AudioQueueStart(audioQueue, NULL);
    if (osState != noErr) {
        printf("AudioQueueStart Error");
    }
}
- (void) play {
    if (bufferCanByPlay[0] == NO && bufferCanByPlay[1] == NO && bufferCanByPlay[2] == NO) {
        [self start];
    }else {
        AudioQueueStart(audioQueue, NULL);
    }
}
- (void) pause {
    AudioQueuePause(audioQueue);
}
- (void)stop {
    AudioQueueReset(audioQueue);
    bufferCanByPlay[0] = NO;
    bufferCanByPlay[1] = NO;
    bufferCanByPlay[2] = NO;
    printf("音频队列已重置\n");
    // 若要将内存处理的更干净,可以继续处理 audioQueueBuffers中的data,我这里最大只有24k的数据量,必要性不强
}

@end

你可能感兴趣的:(AudioToolBox播放pcm音频数据)