Linphone简介
1. 核心功能与架构
- 功能:提供基于 SIP 协议的语音/视频通话、即时消息等 VoIP 功能,支持高级 API 调用。
- 依赖库 :
- Liblinphone:核心功能实现(通话控制、信令处理)。
- Mediastreamer2:多媒体流处理(编解码、回声消除)。
- RTP:基于 RTP 的实时数据传输。
- Belle-sip:SIP 协议栈实现。
2. 相关文档链接
- Linphone官网:www.linphone.org/technical-c...
- 官网文档:wiki.linphone.org/xwiki/wiki/...
- 官方Demo:github.com/BelledonneC...
3. SDK集成(OC)
bash
platform :ios, '12.0'
source 'https://github.com/CocoaPods/Specs.git'
source 'https://gitlab.linphone.org/BC/public/podspec.git'
target 'xxx' do
use_frameworks!
inhibit_all_warnings!
# linphone
pod 'linphone-sdk'
end
核心功能代码
AXFSUtility.h文件实现
objectivec
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#include "linphone/linphonecore.h"
#include "bctoolbox/list.h"
NS_ASSUME_NONNULL_BEGIN
#define LC ([AXFSUtility getLc])
extern NSString *const kLinphoneCallUpdate;
extern NSString *const kLinphoneGlobalStateUpdate;
@interface LinphoneCallAppData :NSObject {
@public
bool_t videoRequested;
};
@end
@interface AXFSUtility : NSObject
@property (nonatomic, assign) BOOL speakerEnabled;
@property (nonatomic, assign) BOOL bluetoothAvailable;
@property (nonatomic, assign) BOOL bluetoothEnabled;
@property (readonly) LpConfig *configDb;
/// 单例
+ (AXFSUtility *)instance;
/// 获取LinphoneCore
+ (LinphoneCore *)getLc;
/// 初始化LinphoneCore
- (void)launchLinphoneCore;
/// 启动LinphoneCore
- (void)startLinphoneCore;
/// 停止LinphoneCore
- (void)stopLinphoneCore;
/// 重置LinphoneCore
- (void)resetLinphoneCore;
/// 销毁LinphoneCore
- (void)destroyLinphoneCore;
/// 呼叫挂断
- (void)hangupLinphoneCore;
/// 状态激活
- (void)becomeActive;
/// 是否允许免提
- (BOOL)allowSpeaker;
/// 注册
/// - Parameters:
/// - userName: 用户名
/// - passWord: 密码
/// - displayName: 昵称
/// - domain: 域名
/// - port: 端口号
/// - transport: 传输协议 - UDP/TCP/TLS
- (void)registerByUserName:(NSString *)userName passWord:(NSString *)passWord displayName:(NSString *)displayName domain:(NSString *)domain port:(NSString *)port transport:(NSString *)transport;
/// 拨打电话
/// - Parameters:
/// - callData: 呼叫目标
/// - calleeNum: 被叫
/// - callUUID: uuid
- (void)callPhoneWithCalleeNumber:(NSDictionary *)callData calleeNum:(NSString *)calleeNum callUUID:(NSString *)callUUID;
/// 清除配置账户
- (void)clearAccountsConfig;
@end
NS_ASSUME_NONNULL_END
AXFSUtility.m文件实现
ini
#import "AXFSUtility.h"
#import <AVFoundation/AVFoundation.h>
#include "linphone/linphonecore_utils.h"
static LinphoneCore *theLinphoneCore = nil;
static AXFSUtility *theLinphoneManager = nil;
NSString *const kLinphoneCallUpdate = @"LinphoneCallUpdate";
NSString *const kLinphoneGlobalStateUpdate = @"LinphoneGlobalStateUpdate";
extern void libmsamr_init(MSFactory *factory);
extern void libmsopenh264_init(MSFactory *factory);
extern void libmssilk_init(MSFactory *factory);
extern void libmswebrtc_init(MSFactory *factory);
extern void libmscodec2_init(MSFactory *factory);
@implementation LinphoneCallAppData
- (id)init {
self = [super init];
if (self) {
videoRequested = FALSE;
}
return self;
}
@end
@implementation AXFSUtility
// MARK: - 单例
+ (AXFSUtility *)instance {
@synchronized(self) {
if (theLinphoneManager == nil) {
theLinphoneManager = [[AXFSUtility alloc] init];
}
}
return theLinphoneManager;
}
// MARK: - 初始化
- (id)init {
self = [super init];
if (self) {
_speakerEnabled = FALSE;
_bluetoothEnabled = FALSE;
_configDb = linphone_config_new(@"axlinphone".UTF8String);
}
return self;
}
// MARK: - 获取lc
+ (LinphoneCore *)getLc {
if (theLinphoneCore == nil) {
@throw([NSException exceptionWithName:@"LinphoneCoreException"
reason:@"Linphone core not initialized yet"
userInfo:nil]);
}
return theLinphoneCore;
}
static BOOL libStarted = FALSE;
// MARK: - 初始化LinphoneCore
- (void)launchLinphoneCore {
if (libStarted) {
NSLog(@"Liblinphone已初始化");
return;
}
libStarted = TRUE;
signal(SIGPIPE, SIG_IGN);
// 创建 linphone core
[self createLinphoneCore];
}
// MARK: - 创建 linphone core
- (void)createLinphoneCore {
if (theLinphoneCore != nil) {
NSLog(@"linphonecore已创建");
return;
}
// 日志输出相关
[self enableLogs:LinphoneLogLevelDebug];
// 添加监听回调
LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get());
linphone_core_cbs_set_call_state_changed(cbs, linphone_iphone_call_state); linphone_core_cbs_set_account_registration_state_changed(cbs,linphone_iphone_registration_state);
linphone_core_cbs_set_global_state_changed(cbs, linphone_iphone_global_state_changed);
linphone_core_cbs_set_user_data(cbs, (__bridge void *)(self));
theLinphoneCore = linphone_factory_create_core_with_config_3(linphone_factory_get(), _configDb, NULL);
linphone_core_add_callbacks(theLinphoneCore, cbs);
NSLog(@"创建linphonecore %p", theLinphoneCore);
[AXFSUtility.instance startLinphoneCore];
linphone_core_cbs_unref(cbs);
// 加载linphone SDK中可用的插件
MSFactory *factory = linphone_core_get_ms_factory(theLinphoneCore);
libmssilk_init(factory);
libmsamr_init(factory);
libmsopenh264_init(factory);
libmswebrtc_init(factory);
libmscodec2_init(factory);
linphone_core_reload_ms_plugins(theLinphoneCore, NULL);
// 设置回声、噪音消除
linphone_core_enable_agc(LC, false);
linphone_core_enable_echo_cancellation(LC, true);
linphone_core_enable_echo_limiter(LC, false);
linphone_core_enable_generic_comfort_noise(LC, true);
linphone_core_enable_audio_adaptive_jittcomp(LC, true);
// 设置最大呼叫数
linphone_core_set_max_calls(LC, 1);
// 设置CA证书
NSString *rootcaPath = [[NSBundle mainBundle] pathForResource:[@"rootca.pem" stringByDeletingPathExtension] ofType:[@"rootca.pem" pathExtension]];
linphone_core_set_root_ca(theLinphoneCore, rootcaPath.UTF8String);
// 设置用户证书路径
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
BOOL isDir = NO;
NSError *error;
// 不存在就创建
if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDir] && isDir == NO) {
[[NSFileManager defaultManager] createDirectoryAtPath:cachePath
withIntermediateDirectories:NO
attributes:nil
error:&error];
}
linphone_core_set_user_certificates_path(theLinphoneCore, cachePath.UTF8String);
// 自定义DNS服务器,则配置DNS
NSString *dns_server_ip = @"";
if ([dns_server_ip isEqualToString:@""]) {dns_server_ip = NULL;}
bctbx_list_t *dns_server_list = dns_server_ip?bctbx_list_new((void *)[dns_server_ip UTF8String]):NULL;
linphone_core_set_dns_servers_app(LC, dns_server_list);
bctbx_list_free(dns_server_list);
// 立即调用iterate一次,以便启动与sip服务器的后台连接或远程配置抓取
linphone_core_iterate(theLinphoneCore);
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(globalStateChangedNotificationHandler:)
name:kLinphoneGlobalStateUpdate
object:nil];
}
// MARK: - 日志相关配置
- (void)enableLogs:(LinphoneLogLevel)level {
static BOOL stderrInUse = NO;
if (!stderrInUse) {
stderrInUse = YES;
}
if (level == 5) {
linphone_logging_service_set_log_level(linphone_logging_service_get(), LinphoneLogLevelFatal);
} else {
linphone_logging_service_set_log_level(linphone_logging_service_get(), level);
}
}
// MARK: - 启动LinphoneCore
- (void)startLinphoneCore {
linphone_core_start(LC);
}
// MARK: - 停止LinphoneCore
- (void)stopLinphoneCore {
if (theLinphoneCore != nil) {
int number = linphone_core_get_calls_nb(theLinphoneCore);
if (number==0) {
linphone_core_stop_async(theLinphoneCore);
}
}
}
// MARK: - 重置LinphoneCore
- (void)resetLinphoneCore {
[self destroyLinphoneCore];
[self createLinphoneCore];
}
// MARK: - 销毁LinphoneCore
- (void)destroyLinphoneCore {
// 以防在linphone初始化前终止
if (theLinphoneCore != nil) {
linphone_core_unref(theLinphoneCore);
theLinphoneCore = nil;
}
libStarted = FALSE;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// MARK: - 呼叫挂断
- (void)hangupLinphoneCore {
if (theLinphoneCore != nil) {
LinphoneCall *currentCall = linphone_core_get_current_call(theLinphoneCore);
if (currentCall != NULL) {
linphone_call_terminate(currentCall);
} else {
const MSList *calls = linphone_core_get_calls(LC);
if (bctbx_list_size(calls) == 1) {
linphone_core_terminate_all_calls(LC);
}
}
}
}
// MARK: - 状态激活
- (void)becomeActive {
// 检测语音是否开启
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
completionHandler:^(BOOL granted){
}];
linphone_core_start_dtmf_stream(theLinphoneCore);
}
// MARK: - 是否允许免提
- (BOOL)allowSpeaker {
BOOL allow = true;
AVAudioSessionRouteDescription *newRoute = [AVAudioSession sharedInstance].currentRoute;
if (newRoute) {
NSString *route = [newRoute.outputs firstObject].portType;
allow = !([route isEqualToString:AVAudioSessionPortLineOut] ||
[route isEqualToString:AVAudioSessionPortHeadphones] ||
[route isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
[route isEqualToString:AVAudioSessionPortBluetoothLE] ||
[route isEqualToString:AVAudioSessionPortBluetoothHFP]);
}
return allow;
}
// MARK: - 设置免提
- (void)setSpeakerEnabled:(BOOL)enable{
_speakerEnabled = enable;
NSError *err = nil;
if (enable && [self allowSpeaker]) {
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&err];
[[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE];
_bluetoothEnabled = FALSE;
} else {
AVAudioSessionPortDescription *builtinPort = [self audioDeviceFromTypes:@[ AVAudioSessionPortBuiltInMic ]];
[[AVAudioSession sharedInstance] setPreferredInput:builtinPort error:&err];
[[UIDevice currentDevice] setProximityMonitoringEnabled:(linphone_core_get_calls_nb(LC) > 0)];
}
if (err) {
NSLog(@"无法更改音频路由 - 错误: %@", err.localizedDescription);
err = nil;
}
}
// MARK: - 音频路由
- (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types {
NSArray *routes = [[AVAudioSession sharedInstance] availableInputs];
for (AVAudioSessionPortDescription *route in routes) {
if ([types containsObject:route.portType]) {
return route;
}
}
return nil;
}
// MARK: - 设置蓝牙
- (void)setBluetoothEnabled:(BOOL)enable {
if (_bluetoothAvailable) {
_bluetoothEnabled = enable;
if (_bluetoothEnabled) {
NSError *err = nil;
AVAudioSessionPortDescription *_bluetoothPort = [self audioDeviceFromTypes:@[ AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE, AVAudioSessionPortBluetoothHFP ]];
[[AVAudioSession sharedInstance] setPreferredInput:_bluetoothPort error:&err];
if (err) {
_bluetoothEnabled = FALSE;
NSLog(@"无法启用蓝牙 - 错误: %@", err.localizedDescription);
err = nil;
} else {
_speakerEnabled = FALSE;
return;
}
}
}
[self setSpeakerEnabled:_speakerEnabled];
}
// MARK: - 注册
- (void)registerByUserName:(NSString *)userName passWord:(NSString *)passWord displayName:(NSString *)displayName domain:(NSString *)domain port:(NSString *)port transport:(NSString *)transport {
LinphoneAccount *deAccount = linphone_core_get_default_account(LC);
if (deAccount) {
// 获取注册状态
LinphoneRegistrationState state = linphone_account_get_state(deAccount);
const LinphoneAccountParams *aParams = linphone_account_get_params(deAccount);
const LinphoneAddress *aaddr = linphone_account_params_get_identity_address(aParams);
int aPort = linphone_address_get_port(aaddr);
NSString *aDomain = [[NSString alloc] initWithUTF8String:linphone_address_get_domain(aaddr)];
NSString *aName = [[NSString alloc] initWithUTF8String:linphone_address_get_username(aaddr)];
NSString *aPwd = [[NSString alloc] initWithUTF8String:linphone_address_get_password(aaddr)];
if (state==LinphoneRegistrationOk) {
if ([aName isEqualToString:userName]&&[aPwd isEqualToString:passWord]&&[aDomain isEqualToString:domain]&&aPort==[port intValue]) {
return;
}
}
}
// 域名 + 端口号
NSString *domainName = domain;
if (!isBlankString(port)) {
domainName = [NSString stringWithFormat:@"%@:%@",domain,port];
}
// 设置配置参数
LinphoneAccountParams *accountParams = linphone_core_create_account_params(LC);
LinphoneAddress *addr = linphone_address_new(NULL);
LinphoneAddress *tmpAddr = linphone_address_new([NSString stringWithFormat:@"sip:%@",domainName].UTF8String);
if (tmpAddr == nil) {
[ToastUtility showMessage:@"请检查呼叫线路配置"];
return;
}
// 设置传输协议
linphone_address_set_transport(addr, LinphoneTransportUdp);
// 设置域名
linphone_address_set_domain(addr, linphone_address_get_domain(tmpAddr));
// 设置端口号
linphone_address_set_port(addr, linphone_address_get_port(tmpAddr));
// 设置用户名
linphone_address_set_username(addr, userName.UTF8String);
// 设置密码
linphone_address_set_password(addr, passWord.UTF8String);
// 设置别名
if (displayName && !isBlankString(displayName)) {
linphone_address_set_display_name(addr, displayName.UTF8String);
}
// 将用户标识设置为SIP地址
linphone_account_params_set_identity_address(accountParams, addr);
// 设置注册过期时间(秒)- 15分钟
linphone_account_params_set_expires(accountParams, 900);
if (transport) {
LinphoneAddress *transportAddr = linphone_address_new([NSString stringWithFormat:@"sip:%s;transport=%s", domainName.UTF8String, transport.lowercaseString.UTF8String].UTF8String);
// 设置SIP路由
linphone_account_params_set_routes_addresses(accountParams, bctbx_list_new(transportAddr));
// 设置代理地址
linphone_account_params_set_server_address(accountParams, transportAddr);
linphone_address_unref(transportAddr);
}
// 注册
linphone_account_params_enable_register(accountParams, TRUE);
// 身份验证
LinphoneAuthInfo *info =
linphone_auth_info_new(linphone_address_get_username(addr),
NULL,
passWord.UTF8String,
NULL,
linphone_address_get_domain(addr),
linphone_address_get_domain(addr)
);
// 将身份验证信息添加到#LinphoneCore
linphone_core_add_auth_info(LC, info);
// 释放无用地址
linphone_address_unref(addr);
linphone_address_unref(tmpAddr);
// 帐户
LinphoneAccount *account = linphone_core_create_account(LC, accountParams);
linphone_account_params_unref(accountParams);
if (account) {
if (linphone_core_add_account(LC, account) != -1) {
linphone_core_set_default_account(LC, account);
} else {
[ToastUtility showMessage:@"无法配置您的帐户,请检查参数或稍后重试"];
}
} else {
[ToastUtility showMessage:@"无法配置您的帐户,请检查参数或稍后重试"];
}
}
// MARK: - 拨打电话
callData:呼叫参数信息
calleeNum:被叫
- (void)callPhoneWithCalleeNumber:(NSDictionary *)callData calleeNum:(NSString *)calleeNum callUUID:(NSString *)callUUID {
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (!account) {
return;
}
NSString *callingNumber = 主叫;
NSString *prefixNumber = 号码前缀(+86);
NSString *calleeNumber = calleeNum;
NSString *calleeNbr = 被叫;
if (!isBlankString(calleeNbr)) {
calleeNumber = calleeNbr;
}
NSString *passWord = 密码;
// 判断被叫/主叫/前缀/密码是否包含大写字母
BOOL hasUppercase = ([callingNumber ax_includeUppercase] || [prefixNumber ax_includeUppercase] || [calleeNumber ax_includeUppercase] || [passWord ax_includeUppercase]);
LinphoneAddress *addr = [self normalizeSipOrPhoneAddress:callData calleeNum:calleeNum callUUID:callUUID hasUppercase:hasUppercase];
linphone_core_enable_video_display(LC,NO);
[self startCall:addr];
if (addr)
linphone_address_unref(addr);
}
// MARK: - 获取address
- (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSDictionary *)callData calleeNum:(NSString *)calleeNum callUUID:(NSString *)callUUID hasUppercase:(BOOL)hasUppercase {
NSString *calleeNumber = calleeNum;
NSString *prefixNumber = 前缀;
NSString *wsPortNumber = 端口号;
NSString *callingNumber = 主叫;
NSString *wsIp = 后台呼叫配置的ip;
if (!isBlankString(wsIp)) {
wsIp = [wsIp stringByReplacingOccurrencesOfString:@"." withString:@"_"];
}
// 呼叫目标 - 和后端协商好的格式,根据自己的业务调整
NSString *callTarget = [NSString stringWithFormat:@"callout_%@_%@_%@_%@_%@",prefixNumber,calleeNumber,wsIp,wsPortNumber,callingNumber];
NSString *callUUidName = @"X-XXX_XXX"; // 根据自己业务设置字段名
NSString *callUUid = callUUID;
if (!calleeNumber || [calleeNumber isEqualToString:@""] || !wsIp || [wsIp isEqualToString:@""] || !callingNumber || [callingNumber isEqualToString:@""])
return NULL;
LinphoneAccount *account = linphone_core_get_default_account(LC);
const char *normvalue;
normvalue = linphone_account_is_phone_number(account, callTarget.UTF8String)
? linphone_account_normalize_phone_number(account, callTarget.UTF8String)
: callTarget.UTF8String;
LinphoneAddress *addr = linphone_account_normalize_sip_uri(account, normvalue);
if (hasUppercase) { // 包含大写字母
// 获取配置信息
NSString *aDomain = [[NSString alloc] initWithUTF8String:linphone_address_get_domain(addr)];
NSString *aPwd = [[NSString alloc] initWithUTF8String:linphone_address_get_password(addr)];
int port = linphone_address_get_port(addr);
NSString *addressStr = [NSString stringWithFormat:@"sip:%@:%@@%@:%d",callTarget,aPwd,aDomain,port];
LinphoneAddress *newAddr = linphone_address_new(addressStr.UTF8String);
// 添加额外头信息
linphone_address_set_header(newAddr, callUUidName.UTF8String, callUUid.UTF8String);
if (newAddr && account) {
const char *username = linphone_account_params_dial_escape_plus_enabled(linphone_account_get_params(account)) ? normvalue : callTarget.UTF8String;
if (linphone_account_is_phone_number(account, username))
linphone_address_set_username(newAddr, linphone_account_normalize_phone_number(account, username));
}
linphone_address_unref(addr);
return newAddr;
} else {
// 添加额外头信息
linphone_address_set_header(addr, callUUidName.UTF8String, callUUid.UTF8String);
if (addr && account) {
const char *username = linphone_account_params_dial_escape_plus_enabled(linphone_account_get_params(account)) ? normvalue : callTarget.UTF8String;
if (linphone_account_is_phone_number(account, username))
linphone_address_set_username(addr, linphone_account_normalize_phone_number(account, username));
}
return addr;
}
}
// MARK: - 呼叫
- (void)startCall:(const LinphoneAddress *)iaddr {
// 网络是否可用
if (!linphone_core_is_network_reachable(theLinphoneCore)) {
[ToastUtility showMessage:@"网络不可用,请检查网络"];
return;
}
// 检查SIP地址
if (!iaddr) {
[ToastUtility showMessage:@"无效的SIP地址"];
return;
}
LinphoneAddress *addr = linphone_address_clone(iaddr);
// 呼叫参数设置
LinphoneCallParams *lcallParams = linphone_core_create_call_params(theLinphoneCore, NULL);
// 启用低宽带模式
// linphone_call_params_enable_low_bandwidth(lcallParams, YES);
LinphoneCall *call = linphone_core_invite_address_with_params(theLinphoneCore, addr, lcallParams);
if (call) {
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (data == nil) {
NSLog(@"已发出新呼叫,但未设置应用程序数据。预计它会崩溃");
} else {
data->videoRequested = linphone_call_params_video_enabled(lcallParams);
linphone_call_set_user_data(call, (void *)CFBridgingRetain(data));
linphone_call_enable_echo_cancellation(call, true);
linphone_call_enable_echo_limiter(call, false);
linphone_call_set_speaker_volume_gain(call, 0.5);
linphone_call_set_microphone_volume_gain(call, 0.5);
}
}
linphone_address_unref(addr);
linphone_call_params_unref(lcallParams);
}
// MARK: - 注册状态监听
static void linphone_iphone_registration_state(LinphoneCore *lc, LinphoneAccount *account, LinphoneRegistrationState state, const char *message) {
[(__bridge AXFSUtility *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onRegister:lc account:account state:state message:message];
}
// MARK: - 注册状态更新
- (void)onRegister:(LinphoneCore *)lc account:(LinphoneAccount *)account state:(LinphoneRegistrationState)state message:(const char *)cmessage {
NSString *registerState = nil;
switch (state) {
case LinphoneRegistrationNone:
registerState = @"注册的初始状态";
break;
case LinphoneRegistrationProgress:
registerState = @"正在进行注册";
break;
case LinphoneRegistrationOk:
registerState = @"注册成功";
break;
case LinphoneRegistrationCleared:
registerState = @"取消注册成功";
break;
case LinphoneRegistrationFailed:
registerState = @"注册失败";
break;
case LinphoneRegistrationRefreshing:
registerState = @"注册刷新";
break;
}
LinphoneReason reason = linphone_account_get_error(account);
NSString *message = nil;
switch (reason) {
case LinphoneReasonNone:
message = @"核心未设置任何原因";
break;
case LinphoneReasonNoResponse:
message = @"未从远程收到响应";
break;
case LinphoneReasonForbidden:
message = @"由于凭据错误或资源被禁止,身份验证失败";
break;
case LinphoneReasonDeclined:
message = @"呼叫已被拒绝";
break;
case LinphoneReasonNotFound:
message = @"找不到呼叫的目标";
break;
case LinphoneReasonNotAnswered:
message = @"呼叫未及时应答(请求超时)";
break;
case LinphoneReasonBusy:
message = @"电话线占线";
break;
case LinphoneReasonUnsupportedContent:
message = @"不支持的内容";
break;
case LinphoneReasonBadEvent:
message = @"错误事件";
break;
case LinphoneReasonIOError:
message = @"传输错误:连接失败、断开连接等";
break;
case LinphoneReasonDoNotDisturb:
message = @"请勿打扰原因";
break;
case LinphoneReasonUnauthorized:
message = @"操作未经授权,因为缺少凭据";
break;
case LinphoneReasonNotAcceptable:
message = @"由于不兼容或不支持的媒体参数,操作被拒绝";
break;
case LinphoneReasonNoMatch:
message = @"服务器或远程客户端无法执行操作,因为它没有任何上下文";
break;
case LinphoneReasonMovedPermanently:
message = @"资源已永久移动";
break;
case LinphoneReasonGone:
message = @"资源不再存在";
break;
case LinphoneReasonTemporarilyUnavailable:
message = @"暂时不可用";
break;
case LinphoneReasonAddressIncomplete:
message = @"地址不完整";
break;
case LinphoneReasonNotImplemented:
message = @"未实现";
break;
case LinphoneReasonBadGateway:
message = @"坏网关";
break;
case LinphoneReasonSessionIntervalTooSmall:
message = @"收到的请求包含持续时间低于最小计时器的Session Expires头字段";
break;
case LinphoneReasonServerTimeout:
message = @"服务器超时";
break;
case LinphoneReasonUnknown:
message = @"未知原因";
break;
case LinphoneReasonTransferred:
message = @"呼叫已转移";
break;
case LinphoneReasonConditionalRequestFailed:
message = @"请求失败";
break;
}
NSLog(@"注册状态-----%@ 信息----%@",registerState,message);
}
// MARK: - 呼叫状态监听
static void linphone_iphone_call_state(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state, const char *message) {
[(__bridge AXFSUtility *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onCall:call StateChanged:state withMessage:message];
}
// MARK: - 呼叫状态更新
- (void)onCall:(LinphoneCall *)call StateChanged:(LinphoneCallState)state withMessage:(const char *)message {
// Handling wrapper
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (!data) {
data = [[LinphoneCallAppData alloc] init];
linphone_call_set_user_data(call, (void *)CFBridgingRetain(data));
}
// Post event
NSDictionary *dict = @{
@"call" : [NSValue valueWithPointer:call],
@"state" : [NSNumber numberWithInt:state],
@"message" : [NSString stringWithUTF8String:message]
};
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self userInfo:dict];
}
// MARK: - 全局状态监听
static void linphone_iphone_global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message) {
[(__bridge AXFSUtility *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onGlobalStateChanged:gstate withMessage:message];
}
// MARK: - 全局状态更新
- (void)onGlobalStateChanged:(LinphoneGlobalState)state withMessage:(const char *)message {
NSLog(@"onGlobalStateChanged: %d (message: %s)", state, message);
NSDictionary *dict = [NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:state], @"state",
[NSString stringWithUTF8String:message ? message : ""], @"message", nil];
// dispatch the notification asynchronously
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (theLinphoneCore && linphone_core_get_global_state(theLinphoneCore) != LinphoneGlobalOff)
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict];
});
}
// MARK: - 全局状态通知
- (void)globalStateChangedNotificationHandler:(NSNotification *)notif {
if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) {
[self finishCoreConfiguration];
}
}
// MARK: - 配置信息
- (void)finishCoreConfiguration{
// 启用信令保持活动,定期发送小udp数据包以保持udp-NAT关联
linphone_core_enable_keep_alive(LC, true);
// 设置SIP消息中使用的用户代理字符串
NSString *device = [[NSMutableString alloc] initWithString:[NSString stringWithFormat:@"项目:%@-版本号:%@-设备:%@-linphone版本",
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleName"], kAppVersion,
[[UIDevice currentDevice] name]]];
const char *version = linphone_core_get_version();
linphone_core_set_user_agent(theLinphoneCore, device.UTF8String, version);
}
// MARK: - 清除配置账户
- (void)clearAccountsConfig {
linphone_core_clear_accounts(LC);
linphone_core_clear_all_auth_info(LC);
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
@end
具体使用
初始化 - 根据自己的需求可以选择合适的地方初始化
objectivec
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// fs
[[AXFSUtility instance] launchLinphoneCore];
return YES;
}
// MARK: - 即将进入前台
- (void)applicationWillEnterForeground:(UIApplication *)application
// fs
[[AXFSUtility instance] startLinphoneCore];
}
// MARK: - 程序复原
- (void)applicationDidBecomeActive:(UIApplication *)application
// fs
[AXFSUtility.instance becomeActive];
}
// MARK: - 程序被杀死
- (void)applicationWillTerminate:(UIApplication *)application {
// 程序被杀死 - fs
linphone_core_terminate_all_calls(LC);
[AXFSUtility.instance destroyLinphoneCore];
}
拨打电话之前进行注册
调用方法:
css
[[AXFSUtility instance] registerByUserName:userName passWord:passWord displayName:userName domain:fsIp port:[lineObj getString:@"udpPort"] transport:@"UDP"];
通话界面实现
相关监听
ini
- (void)viewDidLoad {
[super viewDidLoad];
// 监听耳机插入与拔出
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
// 呼叫状态监听
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(callUpdate:)
name:kLinphoneCallUpdate
object:nil];
}
// MARK: - 耳机插入/拔出监听
- (void)audioRouteChangeListenerCallback:(NSNotification *)notification{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger changeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
/// 旧设备不可用 - 拔出耳机
[AXFSUtility instance].bluetoothAvailable = FALSE;
} else if (changeReason==AVAudioSessionRouteChangeReasonNewDeviceAvailable) {/// 新设备 - 插入耳机
AVAudioSessionRouteDescription *currentRount = [AVAudioSession sharedInstance].currentRoute;
if (currentRount) {
AVAudioSessionPortDescription *outputPortDesc = [currentRount.outputs firstObject];
AVAudioSessionPort portType = [outputPortDesc portType];
if ([portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {// 内置扬声器
[AXFSUtility instance].speakerEnabled = TRUE;
[AXFSUtility instance].bluetoothAvailable = FALSE;
} else if ([portType isEqualToString:AVAudioSessionPortBluetoothA2DP] || [portType isEqualToString:AVAudioSessionPortBluetoothLE] || [portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {// 蓝牙耳机
[AXFSUtility instance].bluetoothAvailable = TRUE;
[AXFSUtility instance].bluetoothEnabled = TRUE;
} else if ([portType isEqualToString:AVAudioSessionPortHeadsetMic]) {
[AXFSUtility instance].speakerEnabled = FALSE;
[AXFSUtility instance].bluetoothAvailable = FALSE;
}
}
}
}
// MARK: - 呼叫状态更新
- (void)callUpdate:(NSNotification *)notif
LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
NSString *message = [notif.userInfo objectForKey:@"message"];
LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneReason reason = linphone_call_get_reason(call);
int errorCode = linphone_reason_to_error_code(reason);
self.errorMsg = [self.errorInfo getString:@(errorCode).stringValue];
switch (state) {
case LinphoneCallIdle:
NSLog(@"初始状态=====%@",message);
break;
case LinphoneCallIncomingReceived:
NSLog(@"来电话啦=====%@",message);
break;
case LinphoneCallPushIncomingReceived:
NSLog(@"推送来电已接收=====%@",message);
break;
case LinphoneCallOutgoingInit:
NSLog(@"呼叫已初始化=====%@",message);
break;
case LinphoneCallOutgoingProgress: {
NSLog(@"呼叫进行中=====%@",message);
}
break;
case LinphoneCallOutgoingRinging: {
NSLog(@"呼叫响铃中=====%@",message);
break;
}
case LinphoneCallOutgoingEarlyMedia:
NSLog(@"呼叫媒体=====%@",message);
break;
case LinphoneCallConnected: {
NSLog(@"已连接=====%@",message);
@weakifyObj(self)
/// 开始计时
dispatch_async(dispatch_get_main_queue(), ^{
@strongifyObj(self)
[self.connectLabel timeMeterStart];
});
break;
}
case LinphoneCallStreamsRunning:
NSLog(@"推流进行中=====%@",message);
break;
case LinphoneCallPausing:
NSLog(@"呼叫暂停中=====%@",message);
break;
case LinphoneCallPaused:
NSLog(@"呼叫已暂停=====%@",message);
break;
case LinphoneCallResuming:
NSLog(@"呼叫恢复中=====%@",message);
break;
case LinphoneCallRefered:
NSLog(@"呼叫参考=====%@",message);
break;
case LinphoneCallError: {
[ReminderView showFsReminder:self.errorMsg];
[_connectLabel timeMeterEnd];
_connectLabel.text = @"通话即将结束";
@weakifyObj(self)
// 关闭界面
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongifyObj(self)
[self dismissViewControllerAnimated:YES completion:^{
}];
});
}
break;
case LinphoneCallEnd: {
[_connectLabel timeMeterEnd];
_connectLabel.text = @"通话即将结束";
@weakifyObj(self)
// 关闭界面
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongifyObj(self)
[self dismissViewControllerAnimated:YES completion:^{
}];
});
}
break;
case LinphoneCallPausedByRemote:
NSLog(@"呼叫远程暂停=====%@",message);
break;
case LinphoneCallUpdatedByRemote:
NSLog(@"呼叫"quot;s参数会被更新,例如当远程请求视频时=====%@",message);
break;
case LinphoneCallIncomingEarlyMedia: {
if (linphone_core_get_calls_nb(LC) > 1 ||
(floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
NSLog(@"来电使用媒体=====%@",message);
}
break;
}
case LinphoneCallUpdating:
NSLog(@"已启动呼叫更新=====%@",message);
break;
case LinphoneCallReleased: {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
dispatch_async(dispatch_get_main_queue(), ^{
[AXFSUtility.instance stopLinphoneCore];
});
}
NSLog(@"呼叫释放=====%@",message);
}
break;
case LinphoneCallEarlyUpdatedByRemote:
NSLog(@"呼叫在尚未应答时由远程更新(收到早期对话中的SIP更新)=====%@",message);
break;
case LinphoneCallEarlyUpdating:
NSLog(@"我们正在更新呼叫,但尚未应答(早期对话中的SIP UPDATE已发送)=====%@",message);
break;
}
}
通话计时、免提/静音、接听/挂断
ini
// MARK: - 静音
- (void)muteClick:(UIButton *)button {
button.selected = !button.selected;
/// 设置静音 ● true:取消静音 ● false:静音
linphone_core_enable_mic([AXFSUtility getLc], !button.selected);
}
// MARK: - 免提
- (void)loudspeakerClick:(UIButton *)button
button.selected = !button.selected;
/// 设置免提 ● true:免提 ● false:取消免提
[[AXFSUtility instance] setSpeakerEnabled:button.selected];
}
// MARK: - 挂断
- (void)hangupClick:(UIButton *)button {
self.hangupBtn.enabled = NO;
[_connectLabel timeMeterEnd];
_connectLabel.text = @"通话即将结束";
[self dismissViewControllerAnimated:YES completion:^{
[AXFSUtility.instance hangupLinphoneCore];
}];
}
小记
未完待续,如有侵权请联系本人,随时删除。