移动端(iOS)实现sip通话-去电(Linphone接入)

Linphone简介

1. 核心功能与架构

  • 功能‌:提供基于 SIP 协议的语音/视频通话、即时消息等 VoIP 功能,支持高级 API 调用‌。
  • 依赖库 ‌:
    • Liblinphone‌:核心功能实现(通话控制、信令处理)‌。
    • Mediastreamer2‌:多媒体流处理(编解码、回声消除)‌。
    • RTP‌:基于 RTP 的实时数据传输‌。
    • Belle-sip‌:SIP 协议栈实现‌。

2. 相关文档链接

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];
    }];
}

小记

未完待续,如有侵权请联系本人,随时删除。

相关推荐
iOS阿玮9 小时前
AppStore提审混合开发技术选型,独立开发者和公司都适用。
uni-app·app·apple
招风的黑耳10 小时前
商城类电商购物APP网购原型——实战项目原型
app·axure·电商·移动端
_大学牲2 天前
Flutter 集成 Google ML Kit 体态识别模型 (二) 如何用姿态数据实现运动动作检测
前端·app
小小章鱼哥xxx4 天前
Xcode26-iOS26适配
app·apple
前行的小黑炭10 天前
Android :Compose如何监听生命周期?NavHostController和我们传统的Activity的任务栈有什么不同?
android·kotlin·app
前行的小黑炭10 天前
Android 关于状态栏的内容:开启沉浸式页面内容被状态栏遮盖;状态栏暗亮色设置;
android·kotlin·app
namehu10 天前
搞定 iOS App 测试包分发,也就这么简单!😎
前端·ios·app
真夜11 天前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
前行的小黑炭11 天前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app