最近集成一种新设备,设备与手机相连时,通过socket连接将视频与音频数据分别以h264和pcm的形式传输到手机上。厂商建议我们将h264视频流和pcm音频流合成为本地直播流,使用IJKPlayer无缓冲播放。
但在实际测试中,无缓冲播放会存在比较严重的卡顿、延迟现象。于是,我们选择方案,将传递的音视频流缓存3s,使用AudioToolBox和VideoToolBox分别播放。
本篇文章为AudioToolBox播放pcm流的内容。
1. AudioQueuePlay.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
NS_ASSUME_NONNULL_BEGIN
@protocol AudioQueuePlayDelegate <NSObject>
- (NSData *_Nullable) getAudioData;
- (void) audioNeedPaused;
@end
@interface AudioQueuePlay : NSObject
@property(nonatomic, weak) id<AudioQueuePlayDelegate> 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
网友评论