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