移动端(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];
    }];
}

小记

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

相关推荐
_瑞12 小时前
深入理解灵动岛
前端·ios·app
私人珍藏库1 天前
[Android] BBLL 开源第三方B哩电视TV端
android·app·生活·工具·多功能
私人珍藏库2 天前
[Android] zip解压缩管理-全格式压缩包一键解压+打包
android·app·生活·工具·多功能
Fansi5 天前
iOS 实时活动(Live Activity)开发指南
app
duanze6 天前
从零开始Android商业项目Vibe coding完全指南(八)
app·vibecoding
Bigger11 天前
Tauri (26)——托盘图标总对不上系统主题?一行 Template Image 搞定
前端·rust·app
duanze15 天前
从零开始Android商业项目Vibe coding完全指南(七)
app·vibecoding
方白羽21 天前
一份 AGENTS.md,让 Android AI 代码规范率飙升
android·app·客户端
私人珍藏库21 天前
[Android] OldRoll复古胶片相机高级版-徕卡-哈苏-宝丽来等等
数码相机·智能手机·app·工具·软件·多功能
私人珍藏库21 天前
[Android] 红妆相机-拍照美颜图片美化工具
android·数码相机·app·软件·多功能