由于项目需要 自己用socket 加udp做一个小区物业和业主的视频通话功能。我们需要采集麦克风声音传输出去和播放接收到的udp传过来的pcm原始音频文件。因为我们这个功能很简单,只需要通话几秒钟 最多20秒。就没有做音频编码和解码的操作,直接传输的pcm原始数据。
但是我在网上找了很多播放pcm文件的代码,很少找到即时播放传过来的udp包的,一般都是播放本地的pcm或者解码过来的稳定的数据流。不能满足我udp经常丢包 导致播放停止的问题。
结合了很多别人的播放器和数据包的处理 自己改了一下 这个播放器能满足我的需要了 希望能帮到有同样需求的人。
AudioQueuePlay.h
//
// AudioQueuePlay.h
// udp
//
// Created by 唐超 on 3/19/18.
// Copyright © 2018 唐超. All rights reserved.
//
#import
#import
@interfaceAudioQueuePlay :NSObject
/**
播放音频pcm数据 udp包传过来的
@paramdata pcm data
*/
- (void)playWithData: (NSData*)data;
/**
当播放不出声音的时候 重启一下播放器
*/
- (void)resetPlay;
/**
开始播放
*/
- (void)startPlay;
/**
停止播放
*/
- (void)stopPlay;
@end
我们的需求是通过socket 传输udp语音包(pcm)和图片实现视频通话 但由于udp的不可靠性 网速慢的时候 播放声音有问题 在网上找了好久的资料 终于找到能解决这个问题的代码 下面是我在网上看到的有意义的资料
码农人生博客 比较详细了 网易云音乐的大神
http://msching.github.io/blog/categories/audio/
从这里我找到了怎么填充空数据包的方法 赞
https://my.oschina.net/xikan/blog/483929
这个就是我现在的播放器来源 感谢 结合第二个加空数据包的 就是我完整的例子啦
https://segmentfault.com/a/1190000010177336
#import "AudioQueuePlay.h"
/*我们的需求是通过socket 传输udp语音包(pcm)和图片实现视频通话 但由于udp的不可靠性 网速慢的时候 播放声音有问题 在网上找了好久的资料 终于找到能解决这个问题的代码 下面是我在网上看到的有意义的资料
码农人生博客 比较详细了 网易云音乐的大神
http://msching.github.io/blog/categories/audio/
从这里我找到了怎么填充空数据包的方法 赞
https://my.oschina.net/xikan/blog/483929
这个就是我现在的播放器来源 感谢 结合第二个加空数据包的 就是我完整的例子啦
https://segmentfault.com/a/1190000010177336
*/
#define MIN_SIZE_PER_FRAME5000//缓冲区大小 如果一次性数据超出 会内存溢出崩溃
#define QUEUE_BUFFER_SIZE3 //队列缓冲个数
@interface AudioQueuePlay() {
AudioQueueRefaudioQueue; //音频播放队列
AudioStreamBasicDescription _audioDescription;
AudioQueueBufferRefaudioQueueBuffers[QUEUE_BUFFER_SIZE];//音频缓存
BOOLaudioQueueBufferUsed[QUEUE_BUFFER_SIZE]; //判断音频缓存是否在使用
NSLock*sysnLock;
NSMutableData*tempData;
OSStatusosState;
}
@property (nonatomic, assign) BOOL isRuning;
@end
@implementation AudioQueuePlay
- (instancetype)init
{
self= [superinit];
if(self) {
sysnLock= [[NSLockalloc]init];
// 播放PCM使用
//设置音频参数 这些参数根据传过来的音频参数自己设置 如果有偏差 可能会出现杂音或者卡顿的情况
_audioDescription.mSampleRate = 22050;//采样率
_audioDescription.mFormatID = kAudioFormatLinearPCM;
// 下面这个是保存音频数据的方式的说明,如可以根据大端字节序或小端字节序,浮点数或整数以及不同体位去保存数据
_audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | 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,AudioPlayerAQInputCallback, (__bridgevoid*_Nullable)(self),nil,0,0, &audioQueue);
// 设置音量
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
// 初始化需要的缓冲区
for(inti =0; i
audioQueueBufferUsed[i] = false;
osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
}
}
return self;
}
//添加静音包 当来源音频数据不足的时候 往里面添加静音包
- (void)fillNullData{
if(_isRuning) {
BOOLisNull =YES;
for(inti =0; i<3; i++) {
BOOLused =audioQueueBufferUsed[i];
if(used) {
isNull =NO;
}
}
if(isNull) {
//填空数据包
// NSLog(@"填空数据包");
NSMutableData*tmpData = [[NSMutableDataalloc]init];
for(inti=0; i<600; i++) {
[tmpDataappendBytes:"\x00"length:1];
}
[selfplayWithData:tmpData];
}
}
}
- (void)startPlay{
[self resetPlay];
}
- (void)stopPlay{
_isRuning = NO;
if (audioQueue) {
AudioQueueStop(audioQueue,true);
}
}
- (void)resetPlay {
_isRuning = NO;
if(audioQueue!=nil) {
AudioQueueReset(audioQueue);
//延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
osState=AudioQueueStart(audioQueue,NULL);
if(osState!=noErr) {
NSLog(@"AudioQueueStart Error");
}
_isRuning=YES;
});
}
}
// 播放相关
-(void)playWithData:(NSData*)data {
if(!_isRuning) {
return;
}
[sysnLock lock];
tempData = [NSMutableData new];
[tempDataappendData:data];
// 得到数据
NSUInteger len = tempData.length;
Byte*bytes = (Byte*)malloc(len);
[tempDatagetBytes:byteslength: len];
inti =0;
while (true) {
if (!audioQueueBufferUsed[i]) {
audioQueueBufferUsed[i] = true;
break;
}else{
i++;
if(i >=QUEUE_BUFFER_SIZE) {
i =0;
}
}
}
AudioQueueBufferRef buffer = audioQueueBuffers[i];
buffer ->mAudioDataByteSize= (unsignedint)len;
// 把bytes的头地址开始的len字节给mAudioData
memcpy(audioQueueBuffers[i] ->mAudioData, bytes, len);
free(bytes);
AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);
printf("本次播放数据大小: %lu \n", len);
[sysnLock unlock];
}
// 回调回来把buffer状态设为未使用
staticvoidAudioPlayerAQInputCallback(void* inUserData,AudioQueueRefaudioQueueRef,AudioQueueBufferRefaudioQueueBufferRef) {
AudioQueuePlay* player = (__bridgeAudioQueuePlay*)inUserData;
[playerresetBufferState:audioQueueRefand:audioQueueBufferRef];
// NSLog(@"AudioPlayerAQInputCallback");
}
- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
for(inti =0; i
// 将这个buffer设为未使用
if(audioQueueBufferRef ==audioQueueBuffers[i]) {
audioQueueBufferUsed[i] = false;
}
}
[self fillNullData];
}
// ************************** 内存回收 **********************************
- (void)dealloc {
if(audioQueue!=nil) {
AudioQueueStop(audioQueue,true);
}
audioQueue = nil;
sysnLock = nil;
}
@end
网友评论