iOS 基于LinPhone的语音通话

语音通话涉及到的概念介绍:

VoIP(Voice over Internet Protocol)即首先数字化语音信号并压缩成帧,转换为IP数据包在网络上传输,以此完成语音通话的业务,是一种利用IP协议传输语音数据的通信技术,VoIP通话中媒体流是走的UDP,一旦网络质量不好,语音的质量就会有延时或者断续,但是速度快。

Linphone是一款基于标准SIP的开源VoIP电话工具,是一款遵循GPL的开源的网络电话系统。它能够让你通过internet来查询朋友的IP,并通过IP给他打电话的软件,功能非常强大,既支持桌面系统,也支持移动终端,还能支持WEB浏览器。使用linphone,我们可以在互联网上随意的通信,通过语音、视频、即时文本消息。

Linphone基于ilbc的编解码;ilbc的编解码压缩比率还是比较大的,大概在1/10至1/9之间。也就是说假如每秒20kb的语音数据,编码后就2kb/s,非常小,非常利于网络传输。它使用了一个C库speex, 来实现回声消除模块。Linphone的最大优势在于全平台支持,android,ios,winphone,windows,linux,mac osx,web 全都支持


语音通话开发流程:

1)Cocoapods集成

Cocoapods 需要引入的开源三方库及版本:'linphone-sdk', ‘4.2’   'CocoaAsyncSocket', '7.6.5'

linphone-sdk:用于实际的语音通话功能

CocoaAsyncSocket:用于与后台建立链接,分配网络座席和通话状态改变监听(开通多少个座席要注意,这会涉及到不菲的价格…)

2)后台的简单介绍

因为不是我做的后台,我只能做简要的介绍,我们后台使用的语言是C++,如果不想付出太高的成本,又有比较高的效率, C++无疑是很好的选择。在语音通话过程中,客户端与后台会进行三大类型的交互:

1.向主管账号信息的服务器发送请求交互,获取与第二个服务器进行第二类和第三类交互要使用的数据模型

2.根据第一类与服务器交互获取的数据模型,LinPhoneSDK与服务器进行第二类交互,建立UDP链接,用于语音通话

3.根据第一类与后台交互获取的数据模型,使用客户端基于TCP/IP协议的socket网络库GCDAsyncSocket与后台进行第三类交互,建立链接,保持长链接,用于获取网络坐席和更改通话状态

3)客户端语音通话的各状态搭建分析

1.登录

I. 向主管账号信息的服务器发送请求, 传入对应的URL,账号和密码,使用AES加密(key和偏移量与后台协商一致),获取接下来要使用的数据模型



//向主管账号信息的服务器发送请求,建立链接,获取接下来要用到的数据模型
YGCallManager *manager = [YGCallManager instance];
[manager initSdk:model success:^(NSDictionary * _Nullable responseObject) {
      NSLog(@"initSdk:%@", responseObject);
      //与socket服务器和LinPhone服务器建立链接
     [[YGCallManager instance] login];
   } failure:^(NSError * _Nullable error) {
      NSLog(@"initSdk:%@", error);
}];

II. LinPhoneSDK与服务器建立第二类交互,建立UDP连接,为语音通话服务



ESSipManager *sipManager = [ESSipManager instance];
[sipManager login:@"你的LoginNum" password:@"你的pwd" displayName:@"" domain:@"你的sipIP:sipPort" port:@"你的sipPort" withTransport:@"UDP"];

III. 当Linphone登录成功,会调用成功的回调,这时socket与服务器建立第三类链接,为开通网络坐席和切换通话状态服务



sipManager.linphoneBlock = ^(NSInteger registrationState) {
   if (self.linphoneRegistrationState != registrationState) {
         self.linphoneRegistrationState = registrationState;
           if (registrationState == 2) {
               //2.socket连接
               self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
               NSError *socketError = nil;
               if (![self.clientSocket connectToHost:self.tcpModel.TransferIP onPort:self.tcpModel.TransferPort withTimeout:-1 error:&socketError]) {
                   if (socketError) {
                       NSLog(@"连接服务器失败:%@", socketError.localizedDescription);
                       return;
                }
      }

这里说的切换通话状态服务,也就是说接通和挂断等需要让客户端知道,以做到相应的处理,客户端接受状态的变化主要是通过socket的代理来实现

主要功能代码:



- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    //接收登录服务消息
    if ([lastString containsString:@"LOGIN_SUCCEED"]) {
        //向app端推送EventLogin成功消息
        if (self.eventBlock) {
            NSDictionary *dic = @{@"msg":@"登录成功"};
            self.eventBlock(EVENTLogin, YES, dic);
        }
   //通知服务器当前账号的通道已经被占用
        NSData *data4 = [self sendMsgWithName:@"" type:@"" targetType:@"" msg:[NSString stringWithFormat:@"与后台协商好的信令服务"]];
        [self.clientSocket writeData:data4 withTimeout:-1 tag:4];
    }
}

IV. 做登录功能遇到的问题: 在一开始实现登录的时候发现多次登录会经常报用户正忙,无法登录。后来与后台联调,发现单点登录功能(也就是这次登录会把之前的登录踢下去)还不完善,针对socket接口进行了优化升级.流程修改为: 发送准备状态给语音通话的服务器 => 发送初始化消息给分配座席服务器 => 发送清理通道消息给服务器 =》 发送登录消息给服务器

实现功能的部分代码(主要是发送与后台协商好的socket):



//登录
- (void)ygTcpLogin {
    //发送准备状态给服务器
    NSData *data = [self sendMsgWithName:@"" type:@"" targetType:@"" msg:@"与后台约定好的信令服务"];
    [self.clientSocket writeData:data withTimeout:-1 tag:0];
    //发送初始化消息给转发服务器
    NSData *data1 = [self sendMsgWithName:@"" type:@"" targetType:@"" msg:@"与后台约定好的信令服务"];
    [self.clientSocket writeData:data1 withTimeout:-1 tag:1];
    //发送清理通道消息给服务器
    NSData *data2 = [self sendMsgWithName:@"" type:@"" targetType:@"" msg:@"与后台约定好的信令服务"];
    [self.clientSocket writeData:data2 withTimeout:-1 tag:2];
    //发送登录消息给服务器
    NSData *data3 = [self sendMsgWithName:@"" type:@"" targetType:@"" msg:@"与后台约定好的信令服务"];
    [self.clientSocket writeData:data3 withTimeout:-1 tag:3];
}

V.  做登陆功能遇到的问题2: iOS16以上的版本Linphone与语音通话后台无法建立链接,原因是端口存在被占用的可能。解决方式是每次链接时,给LinPhone分配未被占用的随机端口

实现的功能的部分代码:



    [LinphoneManager.instance resetLinphoneCore];
 
    LinphoneProxyConfig *config = linphone_core_create_proxy_config(LC);
    LinphoneAddress *addr = linphone_address_new([NSString stringWithFormat:@"sip:%@@%@",username, domain].UTF8String);
    Linp
© 版权声明

相关文章

暂无评论

none
暂无评论...