iOS语音通话(语音对讲)
中間參考了別人的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);
}
然后就是socket接收數據了,是個代理方法:
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(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包頭的編碼類
總結
以上是生活随笔為你收集整理的iOS语音通话(语音对讲)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国气象站点数据获取的几种方式
- 下一篇: Chrome正常使用谷歌引擎流程