iOS语音通话(语音对讲)

因为项目需要做一个语音对讲功能,其实说白了就是类似QQ的语音通话,但是资料少之又少,研究了好久,才跟同事弄出一个粗略的版本。我记性不好,所以来记录一下,也希望能够帮助其他人。

本来以为是要做语音对讲,类似微信的发送语音,我觉得这个还挺简单的,就是发送一个语音的文件,所以一开始用的是AVAudioPlayer,因为这个东西只能播放本地音频,而且非常简单。可是都快做好了,头头才说明白要的是语音通话。(小公司,别说文档了,连接口文档都没有)

后来找到AudioQueue,找了好多demo和资料,都没有直接播放从服务器端接收到的数据的例子,后来没办法,只能自己想办法咯。不过大致过程是一致的。

首先肯定是设置创建录音的音频队列,以及缓冲区,还有播放的队列和播放缓冲区,因为我们是要一起打开,所以一起创建,开始录音,并播放声音。

后面会上传demo,开始对讲的方法如下:

[objc]  view plain  copy
  1. //开始对讲  
  2. - (IBAction)startIntercom:(id)sender {  
  3.     //让udpSocket 开始接收数据  
  4.     [self.udpSocket beginReceiving:nil];  
  5.     //先把接收数组清空  
  6.     if (receiveData) {  
  7.         receiveData = nil;  
  8.     }  
  9.     receiveData = [[NSMutableArray alloc] init];  
  10.       
  11.     if (_recordAmrCode == nil) {  
  12.         _recordAmrCode = [[RecordAmrCode alloc] init];  
  13.     }  
  14.     //设置录音的参数  
  15.     [self setupAudioFormat:kAudioFormatLinearPCM SampleRate:kDefaultSampleRate];  
  16.     _audioFormat.mSampleRate = kDefaultSampleRate;  
  17.     //创建一个录制音频队列  
  18.     AudioQueueNewInput (&(_audioFormat),GenericInputCallback,(__bridge voidvoid *)self,NULL,NULL,0,&_inputQueue);  
  19.     //创建一个输出队列  
  20.     AudioQueueNewOutput(&_audioFormat, GenericOutputCallback, (__bridge voidvoid *) self, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0,&_outputQueue);  
  21.     //设置话筒属性等  
  22.     [self initSession];  
  23.       
  24.     NSError *error = nil;  
  25.     //设置audioSession格式 录音播放模式  
  26.     [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];  
  27.       
  28.     UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;  //设置成话筒模式  
  29.     AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,  
  30.                              sizeof (audioRouteOverride),  
  31.                              &audioRouteOverride);  
  32.       
  33.     //创建录制音频队列缓冲区  
  34.     for (int i = 0; i < kNumberAudioQueueBuffers; i++) {  
  35.         AudioQueueAllocateBuffer (_inputQueue,kDefaultInputBufferSize,&_inputBuffers[i]);  
  36.           
  37.         AudioQueueEnqueueBuffer (_inputQueue,(_inputBuffers[i]),0,NULL);  
  38.     }  
  39.       
  40.     //创建并分配缓冲区空间 4个缓冲区  
  41.     for (int i = 0; i
  42.     {  
  43.         AudioQueueAllocateBuffer(_outputQueue, kDefaultOutputBufferSize, &_outputBuffers[i]);  
  44.     }  
  45.     for (int i=0; i < kNumberAudioQueueBuffers; ++i) {  
  46.         makeSilent(_outputBuffers[i]);  //改变数据  
  47.         // 给输出队列完成配置  
  48.         AudioQueueEnqueueBuffer(_outputQueue,_outputBuffers[i],0,NULL);  
  49.     }  
  50.       
  51.     Float32 gain = 1.0;                                       // 1  
  52.     // Optionally, allow user to override gain setting here 设置音量  
  53.     AudioQueueSetParameter (_outputQueue,kAudioQueueParam_Volume,gain);  
  54.       
  55.     //开启录制队列  
  56.     AudioQueueStart(self.inputQueueNULL);  
  57.     //开启播放队列  
  58.     AudioQueueStart(_outputQueue,NULL);  
  59.       
  60.     [_startButton setEnabled:NO];  
  61.     [_stopButton setEnabled:YES];  
  62.       
  63. }  

然后就是实现录音和播放的回调,录音回调中对PCM数据编码,打包。代码如下:

[objc]  view plain  copy
  1. //录音回调  
  2. void GenericInputCallback (  
  3.                            void                                *inUserData,  
  4.                            AudioQueueRef                       inAQ,  
  5.                            AudioQueueBufferRef                 inBuffer,  
  6.                            const AudioTimeStamp                *inStartTime,  
  7.                            UInt32                              inNumberPackets,  
  8.                            const AudioStreamPacketDescription  *inPacketDescs  
  9.                            )  
  10. {  
  11.     NSLog(@"录音回调方法");  
  12.     RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData);  
  13.       
  14.     if (inNumberPackets > 0) {  
  15.         NSData *pcmData = [[NSData alloc] initWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize];  
  16.         //pcm数据不为空时,编码为amr格式  
  17.         if (pcmData && pcmData.length > 0) {  
  18.             NSData *amrData = [rootCtrl.recordAmrCode encodePCMDataToAMRData:pcmData];  
  19.             //这里是对编码后的数据,通过socket发送到另一个客户端  
  20.             [rootCtrl.udpSocket sendData:amrData toHost:kDefaultIP port:kDefaultPort withTimeout:-1 tag:0];  
  21.               
  22.               
  23.         }  
  24.           
  25.     }  
  26.     AudioQueueEnqueueBuffer (inAQ,inBuffer,0,NULL);  
  27.       
  28. }  

[objc]  view plain  copy
  1. // 输出回调  
  2. void GenericOutputCallback (  
  3.                             void                 *inUserData,  
  4.                             AudioQueueRef        inAQ,  
  5.                             AudioQueueBufferRef  inBuffer  
  6.                             )  
  7. {  
  8.     NSLog(@"播放回调");  
  9.     RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData);  
  10.     NSData *pcmData = nil;  
  11.     if([receiveData count] >0)  
  12.     {  
  13.         NSData *amrData = [receiveData objectAtIndex:0];  
  14.           
  15.         pcmData = [rootCtrl.recordAmrCode decodeAMRDataToPCMData:amrData];  
  16.           
  17.         if (pcmData) {  
  18.             if(pcmData.length < 10000){  
  19.                 memcpy(inBuffer->mAudioData, pcmData.bytes, pcmData.length);  
  20.                 inBuffer->mAudioDataByteSize = (UInt32)pcmData.length;  
  21.                 inBuffer->mPacketDescriptionCount = 0;  
  22.             }  
  23.         }  
  24.         [receiveData removeObjectAtIndex:0];  
  25.     }  
  26.     else  
  27.     {  
  28.         makeSilent(inBuffer);  
  29.     }  
  30.     AudioQueueEnqueueBuffer(rootCtrl.outputQueue,inBuffer,0,NULL);  
  31. }  


然后就是socket接收数据了,是个代理方法:

[objc]  view plain  copy
  1. #pragma mark - GCDAsyncUdpSocketDelegate  
  2. - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data  
  3.       fromAddress:(NSData *)address  
  4. withFilterContext:(id)filterContext  
  5. {  
  6.     //这里因为对录制的PCM数据编码为amr格式并添加RTP包头之后的大小,大家可以根据自己的协议,在包头中封装上数据长度再来解析。  
  7.     //PS:因为socket在发送过程中会粘包,如发送数据AAA,然后再发送BBB,可能会一次收到AAABBB,也可能会一次收到AAA,另一次收到BBB,所以针对这种情况要判断接收数据大小,来拆包  
  8.     if(data.length >667)  
  9.     {  
  10.         int num = (data.length)/667;  
  11.         int sum = 0;  
  12.         for (int i=0; i
  13.         {  
  14.             NSData *receviceData = [data subdataWithRange:NSMakeRange(i*667,667)];  
  15.             [receiveData addObject:receviceData];  
  16.             sum = sum+667;  
  17.         }  
  18.         if(sum < data.length)  
  19.         {  
  20.             NSData *otherData = [data subdataWithRange:NSMakeRange(sum, (data.length-sum))];  
  21.             [receiveData addObject:otherData];  
  22.         }  
  23.     }  
  24.     else  
  25.     {  
  26.         [receiveData addObject:data];  
  27.     }  
  28.       
  29. }  
待会上传demo。但是demo是点对点发送的。

PS:附送一点思路,服务器端做一个对讲的服务器,然后所有人都用SOCKET 的TCP方式连接对讲服务器的IP和端口号,然后我们把编码后的数据发送给服务器,通过服务器转发给其他人。

上传的代码里除了有amr编码,还加了RTP包头,我等会上传一个不含RTP包头的,只是把PCM数据编码为AMR格式,把AMR格式数据解码为PCM数据的类文件。至于怎么把文件转码为AMR格式,网上的demo太多咯。

Demo和一个不含RTP包头的编码类



原文博客:http://blog.csdn.net/u011619283/article/details/39613335



你可能感兴趣的:(音频视频)