日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

iOS语音通话(语音对讲)

發布時間:2024/6/21 综合教程 36 生活家
生活随笔 收集整理的這篇文章主要介紹了 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语音通话(语音对讲)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。