当前位置: 首页 > 工具软件 > LinPhone > 使用案例 >

Linphone的相关入门

叶元凯
2023-12-01

Linphone的使用

前些日子因为工作需要接触到SIP电话这块的东西,到各个网站上闲逛发现了Linphone这个东西,本来想先学习一下PJSIP的,但是我的电脑编译出现问题,所以就先研究linphone了。因为它的集成相对来说比较方便,通过gradle集成即可。linphone的相关发展历史,这里就不作叙述了,官网http://www.linphone.org/都有介绍的,去官网了解一下就可以了,下面是记录我学习集成的过程

LinPhone的集成

我使用linphone做了语音通话以及视频通话,通话质量受到网络环境影响。
1、集成:版本建议使用4.2以上的,因为4.2以下的版本跟4.2以上的版本会有些类不同或者已经去掉。

implementation 'org.linphone:linphone-sdk-android:4.2+'

2、封装一下相关方法(注册,拨打电话)

class PhoneVoiceUtils private constructor() {
    private var mLinphoneCore: Core? = null
  
    /**
     * 注册到服务器
     *
     * @param name     账号名
     * @param password 密码
     * @param host     IP地址:端口号
     */
    @JvmOverloads
    fun registerUserAuth(
        name: String?,
        password: String?,
        host: String?,
        type: TransportType? = TransportType.Udp
    ) {
        unRegisterUserAuth()
        //    String identify = "sip:" + name + "@" + host;
        val mAccountCreator = mLinphoneCore!!.createAccountCreator(null)
        mAccountCreator.username = name
        mAccountCreator.domain = host
        mAccountCreator.password = password
        mAccountCreator.transport = type
        val cfg = mAccountCreator.createProxyConfig()
        // Make sure the newly created one is the default
        mLinphoneCore!!.defaultProxyConfig = cfg
    }

    //取消注册
    fun unRegisterUserAuth() {
        mLinphoneCore!!.clearAllAuthInfo()
    }

    /**
     * 是否已经注册了
     *
     * @return
     */
    val isRegistered: Boolean
        get() {
            val serverBean: ServerBean? = mcontext?.let { ServerInfo.getInfo(it) }
            val authInfos = mLinphoneCore!!.authInfoList
            if (authInfos.size > 0) {
                for (authInfo in authInfos) {
                    if (authInfo.domain == serverBean?.domainPref) {
                        return true
                    }
                }
            }
            return false
        }

    /**
     * 拨打电话
     *
     * @param phone 手机号
     * @return
     */
    fun startSingleCallingTo(
        phone: String?,
        isVideoCall: Boolean
    ): Call? {
        var call: Call? = null
        try {
            val addressToCall = mLinphoneCore!!.interpretUrl(phone)
            val params = mLinphoneCore!!.createCallParams(null)            
                params.enableVideo(false)         
            if (addressToCall != null) {
                call = mLinphoneCore!!.inviteAddressWithParams(addressToCall, params)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return call
    }

    /**
     * 挂断电话
     */
    fun hangUp() {
        if (mLinphoneCore == null) {
            mLinphoneCore = LinphoneManager.core
        }
        val currentCall = mLinphoneCore!!.currentCall
        if (currentCall != null) {
            mLinphoneCore!!.terminateCall(currentCall)
        } else if (mLinphoneCore!!.isInConference) {
            mLinphoneCore!!.terminateConference()
        } else {
            mLinphoneCore!!.terminateAllCalls()
        }
    }

    /**
     * 是否静音
     *
     * @param isMicMuted
     */
    fun toggleMicro(isMicMuted: Boolean) {
        if (mLinphoneCore == null) {
            mLinphoneCore = LinphoneManager.core
        }
        mLinphoneCore!!.enableMic(isMicMuted)
    }

    /**
     * 接听来电
     *
     * @param
     */
    fun receiveCall() {
        if (mLinphoneCore == null) {
            mLinphoneCore = LinphoneManager.core
        }
        val call = mLinphoneCore!!.currentCall
        val isLowBandwidthConnection = !LinPhoneService.instance()?.getApplicationContext()?.let {
            isHighBandwidthConnection(
                it
            )
        }!!
        val params = mLinphoneCore!!.createCallParams(call)
        if (params != null) {
            if (call!!.remoteParams.videoEnabled()) {
                params.enableVideo(true)
                mLinphoneCore!!.enableVideoCapture(true)
                mLinphoneCore!!.enableVideoDisplay(true)
            }
            params.enableLowBandwidth(isLowBandwidthConnection)
        }
        android.util.Log.i(
            "infos",
            "来电是否视频:" + params!!.videoEnabled() + call!!.remoteParams.videoEnabled()
        )
        call?.acceptWithParams(params)
        val remoteParams = call.remoteParams
        if (remoteParams != null && remoteParams.videoEnabled()) {
            switchVideo(true)
        } else {
            switchVideo(false)
        }
    }

    companion object {
        @Volatile
        private var sPhoneVoiceUtils: PhoneVoiceUtils? = null
        private var mcontext: Context? = null
        fun getInstance(context: Context?): PhoneVoiceUtils {
            mcontext = context
         if (sPhoneVoiceUtils == null) {
                synchronized(PhoneVoiceUtils::class.java) {
                    if (sPhoneVoiceUtils == null) {
                        sPhoneVoiceUtils = PhoneVoiceUtils()
                    }
                }
            }
            return sPhoneVoiceUtils!!
        }
  

        /**
         * 获取 LinphoneCore
         * @return LinphoneCore
         */
        val lC: Core?
            get() = LinphoneManager.core
    }

    init {
        mLinphoneCore = LinphoneManager.core
    }
}

初始化linphone

/**
 * 初始化 linphone
 */
class LinphoneManager(private val mServiceContext: Context) : SensorEventListener {
    private var mLinphoneFactoryConfigFile: String? = null
    var mLinphoneConfigFile: String? = null
    private var mAudioManager: AndroidAudioManager? = null
    private val mCallManager: CallManager?
    private val mPowerManager: PowerManager
    private var mLPConfigXsd: String? = null
    private var mLinphoneRootCaFile: String? = null
    private var mRingSoundFile: String? = null
    private var mRingBackSoundFile: String? = null
    private var mPauseSoundFile: String? = null
    private var mChatDatabaseFile: String? = null
    private var mUserCerts: String? = null
    private val mResources: Resources
    private var mProximitySensingEnabled = false
    private val mHasLastCallSasBeenRejected = false
    private val mIterateRunnable: Runnable? = null
    private var mProximityWakelock: WakeLock? = null
    private var mSensorManager: SensorManager ? = null
    private var mProximity: Sensor? = null
    private var mCore: Core? = null
    private var mCoreListener: CoreListener? = null
    private var mTimer: Timer? = null
    private val mHandler: Handler

/**
*开始加载LinPhone
**/
    @Synchronized
    private fun startLibLinphone(
        context: Context,
        coreListener: CoreListener
    ) {
        try {
            mCoreListener = coreListener
            copyAssetsFromPackage()

            // Create the Core and add our listener
            mCore = Factory.instance()
                .createCore(mLinphoneConfigFile, mLinphoneFactoryConfigFile, context)
            mCore?.addListener(coreListener)
            mAudioManager = AndroidAudioManager(mServiceContext)
            initLibLinphone()		//加载LinPhone

            // Core must be started after being created and configured
            mCore?.start()
            // We also MUST call the iterate() method of the Core on a regular basis
            val lTask: TimerTask = object : TimerTask() {
                override fun run() {
                    mHandler.post {
                        if (mCore != null) {
                            mCore!!.iterate()
                        }
                    }
                }
            }
            mTimer = Timer("Linphone scheduler")
            mTimer!!.schedule(lTask, 0, 20)
            mProximityWakelock = mPowerManager.newWakeLock(
                PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
                mServiceContext.packageName + ";manager_proximity_sensor"
            )
            resetCameraFromPreferences()
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    @Throws(IOException::class)
    private fun copyAssetsFromPackage() {
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.oldphone_mono, mRingSoundFile)
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.ringback, mRingBackSoundFile)
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.toy_mono, mPauseSoundFile)
        // LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_default, mLinphoneConfigFile);
        //LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_factory, new File(mLinphoneFactoryConfigFile).getName());
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.lpconfig, mLPConfigXsd)
        LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.rootca, mLinphoneRootCaFile)
    }

    private fun initLibLinphone() {
        val f = File(mUserCerts)
        if (!f.exists()) {
            if (!f.mkdir()) {
                Log.e("$mUserCerts can't be created.")
            }
        }
        mCore!!.userCertificatesPath = mUserCerts
    }

    private fun doDestroy() {
        try {
            mCore!!.removeListener(mCoreListener)
            mCallManager?.destroy()
            if (mAudioManager != null) mAudioManager!!.destroy()
            mTimer!!.cancel()
            mCore!!.stop()
        } catch (e: RuntimeException) {
            e.printStackTrace()
        } finally {
            mCore = null
            instance = null
        }
    }

    fun enableProximitySensing(enable: Boolean) {
        if (enable) {
            if (!mProximitySensingEnabled) {
                mSensorManager?.registerListener(
                    this, mProximity, SensorManager.SENSOR_DELAY_NORMAL
                )
                mProximitySensingEnabled = true
            }
        } else {
            if (mProximitySensingEnabled) {
                mSensorManager?.unregisterListener(this)
                mProximitySensingEnabled = false
                // Don't forgeting to release wakelock if held
                if (mProximityWakelock!!.isHeld) {
                    mProximityWakelock!!.release()
                }
            }
        }
    }

    private fun isProximitySensorNearby(event: SensorEvent): Boolean {
        var threshold = 4.001f // <= 4 cm is near
        val distanceInCm = event.values[0]
        val maxDistance = event.sensor.maximumRange
        Log.d(
            "[Manager] Proximity sensor report ["
                    + distanceInCm
                    + "] , for max range ["
                    + maxDistance
                    + "]"
        )
        if (maxDistance <= threshold) {
            // Case binary 0/1 and short sensors
            threshold = maxDistance
        }
        return distanceInCm < threshold
    }

     override fun onSensorChanged(event: SensorEvent) {
        if (event.timestamp == 0L) return
        if (isProximitySensorNearby(event)) {
            if (!mProximityWakelock!!.isHeld) {
                mProximityWakelock!!.acquire()
            }
        } else {
            if (mProximityWakelock!!.isHeld) {
                mProximityWakelock!!.release()
            }
        }
    }

    override fun onAccuracyChanged(
        sensor: Sensor,
        accuracy: Int
    ) {
    }

    companion object {
        private var instance: LinphoneManager? = null
        private var sExited = false

        @Synchronized
        fun createAndStart(
            context: Context,
            coreListener: CoreListener
        ): LinphoneManager? {
            if (instance != null) {
                throw RuntimeException("Linphone Manager is already initialized")
            }
            instance = LinphoneManager(context)
            instance!!.startLibLinphone(context, coreListener)
            return instance
        }

        @get:Synchronized
        val callManager: CallManager?
            get() = getInstance()!!.mCallManager

        @get:Synchronized
        val coreIfManagerNotDestroyOrNull: Core?
            get() {
                if (sExited || instance == null) {
                    Log.e("Trying to get linphone core while LinphoneManager already destroyed or not created")
                    return null
                }
                return core
            }

        @JvmStatic
        @get:Synchronized
        val core: Core?
            get() = getInstance()!!.mCore

        val isInstanceiated: Boolean
            get() = instance != null

        @Synchronized
        fun getInstance(): LinphoneManager? {
            if (instance != null) {
                return instance
            }
            if (sExited) {
                throw RuntimeException("Linphone Manager was already destroyed. " + "Better use getLcIfManagerNotDestroyed and check returned value")
            }
            throw RuntimeException("Linphone Manager should be created before accessed")
        }

        @Synchronized
        fun destroy() {
            if (instance == null) {
                return
            }
            sExited = true
            instance!!.doDestroy()
        }

        @get:Synchronized
        val audioManager: AndroidAudioManager?
            get() = getInstance()!!.mAudioManager
    }

    init {
        val basePath = mServiceContext.filesDir.absolutePath
        mLPConfigXsd = "$basePath/lpconfig.xsd"
        mLinphoneFactoryConfigFile = "$basePath/linphonerc"
        mLinphoneConfigFile = "$basePath/.linphonerc"
        mLinphoneRootCaFile = "$basePath/rootca.pem"
        mRingSoundFile =
            "$basePath/dont_wait_too_long.mkv" //dont_wait_too_long.mkv   oldphone_mono.wav
        mRingBackSoundFile = "$basePath/ringback.wav"
        mPauseSoundFile = "$basePath/toy_mono.wav"
        mChatDatabaseFile = "$basePath/linphone-history.db"
        mUserCerts = "$basePath/user-certs"
        mCallManager = CallManager(mServiceContext)
        mPowerManager =
            mServiceContext.getSystemService(Context.POWER_SERVICE) as PowerManager
        mSensorManager =
            mServiceContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        mProximity = mSensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY)
        //      mErrorToneFile = basePath + "/error.wav";
        mResources = mServiceContext.resources
        Factory.instance().setLogCollectionPath(basePath)
        Factory.instance().enableLogCollection(LogCollectionState.Enabled) //日志开关
        Factory.instance().setDebugMode(true, "Linphone")
        mHandler = Handler()
    }
}

开启一个service监听sip服务器返回的信息从而实现拨打接听电话

class LinPhoneService : Service() {
    private var mActivityCallbacks: ActivityLifecycleCallbacks? = null
    private var mOverlay: LinphoneOverlay? = null
    private var mWindowManager: WindowManager? = null
    var isIncome : Boolean = false
    //监听
    private val mCoreListnerStub: CoreListenerStub = object : CoreListenerStub() {
        /**
         * 通话状态
         * @param lc
         * @param call
         * @param cstate
         * @param message
         */
        override fun onCallStateChanged(
            lc: Core,
            call: Call,
            cstate: Call.State,
            message: String
        ) {
            Log.i("zss", "---- 通话状态ing  [ 状态:$cstate  ;消息:  $message ]")
            if (cstate == Call.State.IncomingReceived) { //来电
             
                println("当前是否支持视频通话:" + call.remoteParams.videoEnabled())
                isIncome = true
                var record : Record = Record()
                val formats = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") //日期格式化
                val times = formats.format(Date())
                record.num = call.remoteAddress.username
                record.time = times
                record.isFriendMsg = true
                MainActivity.addRecord(this@LinPhoneService,record)
                if (call.remoteParams.videoEnabled()) {
                    val intent = Intent(this@LinPhoneService, VideoActivity::class.java)
                    val receiveDataModel = ReceiveDataModel()
                    receiveDataModel.isActiveCall =(false)
                    receiveDataModel.num = (call.remoteAddress.username)
                    intent.putExtra("ReceiveDataModel", receiveDataModel)
                    intent.putExtra("IncomingReceived", 1)
                    intent.putExtra("num", receiveDataModel.num)
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    startActivity(intent)
                } else {
                    val intent =
                        Intent(this@LinPhoneService, CustomReceiveActivity::class.java)
                    CustomReceiveActivity.getReceivedCallFromService(call)
                    val receiveDataModel = ReceiveDataModel()
                    receiveDataModel.isActiveCall=(false)
                    receiveDataModel.num=(call.remoteAddress.username)
                    intent.putExtra("ReceiveDataModel", receiveDataModel)
                    intent.putExtra("IncomingReceived", 1)
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    startActivity(intent)
                }
            } else if (cstate == Call.State.OutgoingProgress) { //正在呼叫
            } else if (cstate == Call.State.Connected) { //接通或者拒绝

                if (null != sPhoneServiceCallback) {

                    sPhoneServiceCallback?.callConnected()
                }
            } else if (cstate == Call.State.StreamsRunning){
                   
            }
            else if (cstate == Call.State.Paused){

            else if (cstate == Call.State.End || cstate == Call.State.Released) { //挂断,未接
                if (null != sPhoneServiceCallback) {
                    sPhoneServiceCallback?.callReleased()
                }
                val intent = Intent(this@LinPhoneService, MainActivity::class.java)
                val receiveDataModel = ReceiveDataModel()
                receiveDataModel.isActiveCall =(false)
                receiveDataModel.num =(call.remoteAddress.username)
                intent.putExtra("ReceiveDataModel", receiveDataModel)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
        }

        /**
         * 注册状态
         * @param lc
         * @param cfg
         * @param cstate
         * @param message
         */
        override fun onRegistrationStateChanged(
            lc: Core,
            cfg: ProxyConfig,
            cstate: RegistrationState,
            message: String
        ) {
            if (null != sPhoneServiceCallback) {
                sPhoneServiceCallback?.onRegistrationStateChanged(
                    lc,
                    cfg,
                    cstate,
                    message
                )
            }
        }
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        Log.i("zss", "---- Service_onCreate ")
        setupActivityMonitor()
        LinphoneManager.createAndStart(this@LinPhoneService, mCoreListnerStub)
        mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        Log.i("zss", "---- Service_onStartCommand ")
        // If our Service is already running, no need to continue
        if (sInstance != null) {
            return START_STICKY
        }
        sInstance = this
        return START_STICKY
    }

    override fun onDestroy() {
        Log.i("zss", "---- Service_onDestroy ")
        sInstance = null
        destroyOverlay()
        LinphoneManager.destroy()
        mPreviewView = null
        mRenderingView = null
        if (mAndroidVideoWindow != null) {
            mAndroidVideoWindow!!.release()
            mAndroidVideoWindow = null
        }
        super.onDestroy()
    }

    override fun onTaskRemoved(rootIntent: Intent) {
        Log.i("zss", "---- Service_onTaskRemoved ")
        sInstance = null
        LinphoneManager.destroy()
        // For this sample we will kill the Service at the same time we kill the app
        stopSelf()
        super.onTaskRemoved(rootIntent)
    }


    companion object {
        private var sInstance: LinPhoneService? = null
        private var sPhoneServiceCallback: PhoneServiceCallback? = null
        private var context: Context? = null
        private var mAndroidVideoWindow: AndroidVideoWindowImpl? = null
        private var mRenderingView: SurfaceView? = null
        private var mPreviewView: SurfaceView? = null
        private val renderingView: TextureView? = null
        private val previewView: TextureView? = null
        private var mCore: Core? = null
        private var mAudioManager: AndroidAudioManager? = null
        fun addCallback(phoneServiceCallback: PhoneServiceCallback?) {
            sPhoneServiceCallback = phoneServiceCallback
        }

        fun startService(mContext: Context) {
            context = mContext
            mContext.startService(Intent(mContext, LinPhoneService::class.java))

        }

        val isReady: Boolean
            get() = sInstance != null

        fun instance(): LinPhoneService? {
            if (sInstance == null) {
                throw RuntimeException("[Context] LinphoneService not available!")
            }
            return sInstance
        }

        /**
         * 切换静音
         * @param isMicMuted 是否静音
         */
        fun toggleMicro(isMicMuted: Boolean) {
            LinphoneManager.getInstance()?.toggleMicro(isMicMuted)
        }

        /**
         * 切换免提
         * 是否免提
         */
        fun toggleSpeaker() {
            mAudioManager = LinphoneManager.audioManager
            if (mAudioManager?.isAudioRoutedToSpeaker!!) {
                mAudioManager?.routeAudioToEarPiece()
            } else {
                mAudioManager?.routeAudioToSpeaker()
            }
        }

    }
}

至此LinPhone的相关封装基本完成,接下来的就是使用了,使用部分就不再详细写了。基本上都是对封装的方法进行调用,界面展示这些就看使用者的设计了。视频通话部分会相对复杂一些,这里未能展示,有兴趣的小伙伴能一起探讨。最后附上linphone官网地址:http://www.linphone.org/,在上面可以免费注册SIP账号进行测试

 类似资料: