最近在做UDP
实时语音通信,采用了GCDAsyncUdpSocket
进行UDP
传输,音频使用的是AudioUnit
,录音回调和播放回调那里使用的TPCircularBuffer进行音频环形缓冲。由于录音和从服务器收到的语音数据都是PCM
编码格式的,8K
的采样率,每20ms
一帧,数据比较大,也耗流量,因此采用了opus
进行编码解码。编码之后发现数据明显变小,以前是512长度的,现在是20左右的长度,因为opus
编码后是不定长的。使用APP跟手机通话时,由于APP发过去的语音数据,不仅录下了说话者的声音还把对方从扬声器播放的声音也一起录下来传给对方,所以产生了回音。而对方手机发过来的语音,是经过手机系统进行了回音消除的,所以我们听不到自己的声音。项目里采用了webRtc
的AEC
模块进行了回音消除。
在UDP
创建那里修改了一下,之前socket使用的代理队列是dispatch_get_main_queue()
,现在改成了自己创建的串行队列DISPATCH_QUEUE_SERIAL
。
// self.udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
self.udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.socketQueue];
- (dispatch_queue_t)socketQueue {
if (_socketQueue == nil) {
_socketQueue = dispatch_queue_create("com.sendSocket", DISPATCH_QUEUE_SERIAL);
}
return _socketQueue;
}
使用串行队列
作为代理队列之后,发数据那里也要使用代理队列
,不可用使用其他线程来设置本socket
//发数据到UDP
-(int) vopc_send_dataA:(const void *)data length:(int)len
{
DLog(@"=====发数据到UDP时加协议头");
dispatch_async(self.socketQueue, ^{
NSData *data1=[NSData dataWithBytes:data length:320];
unsigned char outBuf[320];
short *readBuf = NULL;
readBuf = (short *)[data1 bytes];
//回音消除Process
int aceProcess = WebRtcAecm_Process(self.waveIO.AecmInst, (short *)refBuf, readBuf, (short *)outBuf, 160, 60);
DLog(@"===aceProcess:%d",aceProcess);
NSData *outData = [NSData dataWithBytes:(Byte *)outBuf length:320];
//刘文静添加 opus 编码数据
outData = [self.waveIO.opusCode encodePCMData:outData];
// DLog(@"===outData:%@",outData);
outData = [self getSIMUDPData:outData];
// DLog(@"===加头发数据DATA:%@",outData);
[self.udpSocket sendData:outData toHost:self.host port:self.hostPort withTimeout:0.0 tag:_udpTag];
++_udpTag;
});
return true;
}
这样可以把原先在主线程跑的数据处理拿到串行队列
里来。
在收数据那里使用了串行队列receiveQueue
.
-(dispatch_queue_t )receiveQueue
{
if (_receiveQueue == nil)
{
_receiveQueue = dispatch_queue_create("com.udp.receiveQueue", DISPATCH_QUEUE_SERIAL);
}
return _receiveQueue;
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext
{
// 防止 didReceiveData 被阻塞,用个其他队列里的线程去回调 block
dispatch_async(self.receiveQueue, ^{
@autoreleasepool {
NSData* myBlob=[[NSData alloc]init];
DLog(@"-===UDP收到数据====didReceiveData");
if (data.length>4)
{
myBlob=[data subdataWithRange:NSMakeRange(4, data.length-4)];
}
//刘文静添加 收到数据 opus解码
myBlob = [self.waveIO.opusCode decodeOpusData:myBlob];
// DLog(@"===UDP收到语音解压数据data:%@",myBlob);
NSInteger len=myBlob.length;
if (len>0)
{
if (len==320)
{
if(len%320==0)
{
// DLog(@"===解码后数据长度len/320==0");
NSUInteger length = [myBlob length];
NSUInteger offset = 0;
do {
NSUInteger thisChunkSize=320;
NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[myBlob bytes] + offset
length:thisChunkSize
freeWhenDone:NO];
// DLog(@"--==解码后=-chunk:%@",chunk);
memcpy(refBuf,[chunk bytes], 320);
int aecBuffer = WebRtcAecm_BufferFarend(self.waveIO.AecmInst, (short *)refBuf, 160);
DLog(@"===aecBuffer:%d",aecBuffer);
[self procAUdpPack:chunk];
offset += thisChunkSize;
} while (offset < length);
}else
{
DLog(@"--====I can not handle half package,%ld",(long)len);
}
return;
}
else
{
[self procAUdpPack:myBlob];
}
}
}
});
}
在使用opus编码解码后,CPU
的使用率上去了。看到有很多人使用ProtocolBuffer
。那么为什么要用ProtocolBuffer
呢?它是一种二进制格式,免去了文本格式转换的各种困扰,并且转换效率非常快,由于它的跨平台、跨编程语言的特点,让它越来越普及,尤其是网络数据交换方面日趋成为一种主流.这个序列化成的二进制的包比其他传输格式小很多,可以节约网络流量。后面优化时,或许可以尝试一下。
相关文章
ProtocolBuffer for Objective-C 运行环境配置及使用
iOS 下Opus 压缩PCM音频数据方法
iOS下Opus 编译教程
视频通话之音频(介绍的很详细)
iOS音频播放 (二):AudioSession