iOS语音通话(语音对讲)
来源:互联网 发布:ae cs4软件下载 编辑:程序博客网 时间:2024/04/29 17:06
中间参考了别人的Demo,下载地址不记得了。
因为项目需要做一个语音对讲功能,其实说白了就是类似QQ的语音通话,但是资料少之又少,研究了好久,才跟同事弄出一个粗略的版本。我记性不好,所以来记录一下,也希望能够帮助其他人。
本来以为是要做语音对讲,类似微信的发送语音,我觉得这个还挺简单的,就是发送一个语音的文件,所以一开始用的是AVAudioPlayer,因为这个东西只能播放本地音频,而且非常简单。可是都快做好了,头头才说明白要的是语音通话。(小公司,别说文档了,连接口文档都没有)
后来找到AudioQueue,找了好多demo和资料,都没有直接播放从服务器端接收到的数据的例子,后来没办法,只能自己想办法咯。不过大致过程是一致的。
首先肯定是设置创建录音的音频队列,以及缓冲区,还有播放的队列和播放缓冲区,因为我们是要一起打开,所以一起创建,开始录音,并播放声音。
后面会上传demo,开始对讲的方法如下:
//开始对讲- (IBAction)startIntercom:(id)sender { //让udpSocket 开始接收数据 [self.udpSocket beginReceiving:nil]; //先把接收数组清空 if (receiveData) { receiveData = nil; } receiveData = [[NSMutableArray alloc] init]; if (_recordAmrCode == nil) { _recordAmrCode = [[RecordAmrCode alloc] init]; } //设置录音的参数 [self setupAudioFormat:kAudioFormatLinearPCM SampleRate:kDefaultSampleRate]; _audioFormat.mSampleRate = kDefaultSampleRate; //创建一个录制音频队列 AudioQueueNewInput (&(_audioFormat),GenericInputCallback,(__bridge void *)self,NULL,NULL,0,&_inputQueue); //创建一个输出队列 AudioQueueNewOutput(&_audioFormat, GenericOutputCallback, (__bridge void *) self, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0,&_outputQueue); //设置话筒属性等 [self initSession]; NSError *error = nil; //设置audioSession格式 录音播放模式 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker; //设置成话筒模式 AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof (audioRouteOverride), &audioRouteOverride); //创建录制音频队列缓冲区 for (int i = 0; i < kNumberAudioQueueBuffers; i++) { AudioQueueAllocateBuffer (_inputQueue,kDefaultInputBufferSize,&_inputBuffers[i]); AudioQueueEnqueueBuffer (_inputQueue,(_inputBuffers[i]),0,NULL); } //创建并分配缓冲区空间 4个缓冲区 for (int i = 0; i<kNumberAudioQueueBuffers; ++i) { AudioQueueAllocateBuffer(_outputQueue, kDefaultOutputBufferSize, &_outputBuffers[i]); } for (int i=0; i < kNumberAudioQueueBuffers; ++i) { makeSilent(_outputBuffers[i]); //改变数据 // 给输出队列完成配置 AudioQueueEnqueueBuffer(_outputQueue,_outputBuffers[i],0,NULL); } Float32 gain = 1.0; // 1 // Optionally, allow user to override gain setting here 设置音量 AudioQueueSetParameter (_outputQueue,kAudioQueueParam_Volume,gain); //开启录制队列 AudioQueueStart(self.inputQueue, NULL); //开启播放队列 AudioQueueStart(_outputQueue,NULL); [_startButton setEnabled:NO]; [_stopButton setEnabled:YES]; }
然后就是实现录音和播放的回调,录音回调中对PCM数据编码,打包。代码如下:
//录音回调void GenericInputCallback ( void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPackets, const AudioStreamPacketDescription *inPacketDescs ){ NSLog(@"录音回调方法"); RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData); if (inNumberPackets > 0) { NSData *pcmData = [[NSData alloc] initWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize]; //pcm数据不为空时,编码为amr格式 if (pcmData && pcmData.length > 0) { NSData *amrData = [rootCtrl.recordAmrCode encodePCMDataToAMRData:pcmData]; //这里是对编码后的数据,通过socket发送到另一个客户端 [rootCtrl.udpSocket sendData:amrData toHost:kDefaultIP port:kDefaultPort withTimeout:-1 tag:0]; } } AudioQueueEnqueueBuffer (inAQ,inBuffer,0,NULL); }
// 输出回调void GenericOutputCallback ( void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer ){ NSLog(@"播放回调"); RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData); NSData *pcmData = nil; if([receiveData count] >0) { NSData *amrData = [receiveData objectAtIndex:0]; pcmData = [rootCtrl.recordAmrCode decodeAMRDataToPCMData:amrData]; if (pcmData) { if(pcmData.length < 10000){ memcpy(inBuffer->mAudioData, pcmData.bytes, pcmData.length); inBuffer->mAudioDataByteSize = (UInt32)pcmData.length; inBuffer->mPacketDescriptionCount = 0; } } [receiveData removeObjectAtIndex:0]; } else { makeSilent(inBuffer); } AudioQueueEnqueueBuffer(rootCtrl.outputQueue,inBuffer,0,NULL);}
#pragma mark - GCDAsyncUdpSocketDelegate- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)addresswithFilterContext:(id)filterContext{ //这里因为对录制的PCM数据编码为amr格式并添加RTP包头之后的大小,大家可以根据自己的协议,在包头中封装上数据长度再来解析。 //PS:因为socket在发送过程中会粘包,如发送数据AAA,然后再发送BBB,可能会一次收到AAABBB,也可能会一次收到AAA,另一次收到BBB,所以针对这种情况要判断接收数据大小,来拆包 if(data.length >667) { int num = (data.length)/667; int sum = 0; for (int i=0; i<num; i++) { NSData *receviceData = [data subdataWithRange:NSMakeRange(i*667,667)]; [receiveData addObject:receviceData]; sum = sum+667; } if(sum < data.length) { NSData *otherData = [data subdataWithRange:NSMakeRange(sum, (data.length-sum))]; [receiveData addObject:otherData]; } } else { [receiveData addObject:data]; } }待会上传demo。但是demo是点对点发送的。
PS:附送一点思路,服务器端做一个对讲的服务器,然后所有人都用SOCKET 的TCP方式连接对讲服务器的IP和端口号,然后我们把编码后的数据发送给服务器,通过服务器转发给其他人。
上传的代码里除了有amr编码,还加了RTP包头,我等会上传一个不含RTP包头的,只是把PCM数据编码为AMR格式,把AMR格式数据解码为PCM数据的类文件。至于怎么把文件转码为AMR格式,网上的demo太多咯。
Demo和一个不含RTP包头的编码类
3 0
- iOS语音通话(语音对讲)
- iOS语音通话(语音对讲)
- 多方语音对讲系统
- 语音通话-sip电话(一)
- 语音通话技术浅析
- 安卓语音通话
- SIP语音对讲从零到整之(一)sip介绍
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- 基于Android/IOS视频语音通话商业产品源码转让
- iOS WebRTC语音视频通话实现与demo
- Android开发--仿微信语音对讲录音
- 语音对讲---基于图灵机器人+科大讯飞
- Android开发--仿微信语音对讲录音
- 【cocos2dx-3.2】Tile Map 的六边形实现
- GIT 教程
- 【SSH之旅】一步步学习Struts1框架(一):封装什么
- win7下在当前目录下打开cmd命令窗口
- Selector android
- iOS语音通话(语音对讲)
- 以正确的方式开始一个 Django 1.6 项目
- oracle impdp的table_exists_action详解
- STL系列之九 探索hash_set
- Linux & Unix 中 df 和 du 命令统计磁盘空间数值不一致
- Linux启动为什么没有最先调用main函数?
- Windows Recovery Environment (WinRE) --- create recovery
- bzoj3389 [Usaco2004 Dec]Cleaning Shifts安排值班
- 红黑树从头至尾插入和删除结点的全程演示图