我正在尝试使用 将出站 SIP 呼叫添加到 Android 应用程序中android.net.sip.SipManager
,onRingBack
但当对方接听电话时,我会收到onError
带有代码 -4 和消息的呼叫android.system.ErrnoException: sendto failed: ENETUNREACH (Network is unreachable)
。
我怀疑这是某种防火墙或网络问题,因为根据我使用的网络,我在过程中的不同点会收到 ENETUNREACH 错误。启用我的 VPN 后,我会在onRingBack
回调之前得到它。使用公共 WiFi 网络,我根本不会收到onError
回调,我只是在onRingBack
. 通过我的个人 WiFi 网络,我得到了onError
如上所述的结果。 然而不知何故,LinPhoneAndroid 应用程序没有这个问题,所以必须有一种方法来解决这个问题。
对于我的测试设置,我在 linphone.org 创建了两个帐户:
- sip:[myphonenumber]@sip.linphone.org // iPhone
- sip:[myusername]@sip.linphone.org // Android
我在我的 iPhone 11 上使用 LinPhone iOS 应用程序的第一个帐户。我在我的带有 Android 12 的 Google Pixel 4a 上使用 LinPhone Android 应用程序的第二个帐户。我验证我可以使用这些 LinPhone 应用程序成功地从Android拨打iOS电话。
然后我在 Android 上编写了自己的应用程序来调用 iPhone。它成功地向 SIP 服务器注册:
sipProfile = SipProfile.Builder(username, domain).setPassword(password).build()
val intent = Intent()
intent.action = "android.SipDemo.INCOMING_CALL"
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, Intent.FILL_IN_DATA)
sipManager.open(sipProfile, pendingIntent, null)
sipManager.setRegistrationListener(sipProfile.uriString, object: SipRegistrationListener {
override fun onRegistering(localProfileUri: String?) {
Log.d(TAG, "Registering")
}
override fun onRegistrationDone(localProfileUri: String?, expiryTime: Long) {
registrationExpiration = Date(expiryTime)
...
Log.d(TAG, "Registration done. LocalProfileUri: $localProfileUri Expiry Time: ${registrationExpiration}")
}
override fun onRegistrationFailed(
localProfileUri: String?,
errorCode: Int,
errorMessage: String?
) {
Log.d(TAG, "Registration failed. LocalProfileUri: $localProfileUri, Error code: $errorCode, Error MessagE: $errorMessage")
}
})
然后,我可以使用 向 iPhone 发起出站呼叫,sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30)
iPhone 实际上会响铃并且可以接听启动 LinPhone iOS 应用程序并显示呼叫进行中计时器的呼叫。**当 iPhone 开始响铃时,我的 Android 代码会回调到onRingingBack
. 但是当 iPhone 接听电话时,它会收到一个onError
带有代码 -4 和 d 消息的电话sendto failed: ENETUNREACH (Network is unreachable)
以下是启动调用并实现回调的完整代码:
audioCallListener = object: SipAudioCall.Listener() {
public override fun onError(
call: SipAudioCall?,
errorCode: Int,
errorMessage: String?
) {
super.onError(call, errorCode, errorMessage)
Log.d(TAG, "Error making call code: $errorCode, message: $errorMessage")
}
public override fun onReadyToCall(call: SipAudioCall?) {
Log.d(TAG, "ready to call")
super.onReadyToCall(call)
}
public override fun onRingingBack(call: SipAudioCall?) {
Log.d(TAG, "onRingingBack")
super.onRingingBack(call)
}
public override fun onCalling(call: SipAudioCall?) {
Log.d(TAG, "onCalling")
super.onCalling(call)
}
public override fun onCallHeld(call: SipAudioCall?) {
Log.d(TAG, "onCallHeld")
super.onCallHeld(call)
}
public override fun onCallBusy(call: SipAudioCall?) {
Log.d(TAG, "onCallBusy")
super.onCallBusy(call)
}
public override fun onChanged(call: SipAudioCall?) {
val state = call?.state ?: -1
Log.d(TAG, "onChanged state: ${state}")
super.onChanged(call)
}
public override fun onRinging(call: SipAudioCall?, caller: SipProfile?) {
Log.d(TAG, "Ringing...")
super.onRinging(call, caller)
}
public override fun onCallEstablished(call: SipAudioCall) {
mediaPlayer?.stop()
call.startAudio()
call.setSpeakerMode(true)
call.toggleMute()
Log.d(TAG, "Calls started")
super.onCallEstablished(call)
}
public override fun onCallEnded(call: SipAudioCall) {
Log.d(TAG, "Call ended")
super.onCallEnded(call)
}
}
GlobalScope.launch {
Log.d(TAG, "Making call from ${sipProfile.getUriString()} to ${sipAddress}")
try {
lastCall = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30);
}
catch (e: SipException) {
Log.e(TAG, "Call failed", e)
}
}