Started audio routes

This commit is contained in:
Sylvain Berfini 2023-08-24 16:31:23 +02:00
parent a02896333a
commit c79f2896da
3 changed files with 218 additions and 17 deletions

View file

@ -30,12 +30,14 @@ import androidx.lifecycle.ViewModel
import java.util.Locale
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
import org.linphone.core.MediaDirection
import org.linphone.core.MediaEncryption
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.utils.AudioRouteUtils
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
@ -105,10 +107,18 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@WorkerThread
override fun onStateChanged(call: Call, state: Call.State?, message: String) {
if (CurrentCallViewModel@call != call) {
return
}
if (LinphoneUtils.isCallOutgoing(call.state)) {
isVideoEnabled.postValue(call.params.isVideoEnabled)
} else {
val videoEnabled = call.currentParams.isVideoEnabled
if (videoEnabled && isVideoEnabled.value == false) {
Log.i("$TAG Video enabled, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call)
}
isVideoEnabled.postValue(videoEnabled)
// Toggle full screen OFF when remote disables video
@ -117,6 +127,12 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
}
}
@WorkerThread
override fun onAudioDeviceChanged(call: Call, audioDevice: AudioDevice) {
Log.i("$TAG Audio device changed [$audioDevice]")
isSpeakerEnabled.postValue(AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed(call))
}
}
init {
@ -133,8 +149,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
call = currentCall
Log.i("$TAG Found call [$call]")
configureCall(call)
isSpeakerEnabled.postValue(AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed(call))
} else {
Log.e("$TAG Failed to find outgoing call!")
Log.e("$TAG Failed to find call!")
}
showSwitchCamera.postValue(coreContext.showSwitchCameraButton())
@ -155,16 +172,20 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@UiThread
fun answer() {
coreContext.postOnCoreThread {
Log.i("$TAG Answering call [$call]")
call.accept()
if (::call.isInitialized) {
Log.i("$TAG Answering call [$call]")
call.accept()
}
}
}
@UiThread
fun hangUp() {
coreContext.postOnCoreThread {
Log.i("$TAG Terminating call [$call]")
call.terminate()
if (::call.isInitialized) {
Log.i("$TAG Terminating call [$call]")
call.terminate()
}
}
}
@ -187,15 +208,30 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
// TODO: request record audio permission
return
}
coreContext.postOnCoreThread {
call.microphoneMuted = !call.microphoneMuted
isMicrophoneMuted.postValue(call.microphoneMuted)
if (::call.isInitialized) {
call.microphoneMuted = !call.microphoneMuted
isMicrophoneMuted.postValue(call.microphoneMuted)
}
}
}
@UiThread
fun changeAudioOutputDevice() {
// TODO: display list of all output devices
// TODO: display list of all output devices if more then earpiece &
val routeAudioToSpeaker = isSpeakerEnabled.value != true
coreContext.postOnCoreThread {
if (::call.isInitialized) {
if (routeAudioToSpeaker) {
AudioRouteUtils.routeAudioToSpeaker(call)
} else {
AudioRouteUtils.routeAudioToEarpiece(call)
}
}
}
}
@UiThread
@ -258,14 +294,6 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
}
@WorkerThread
fun forceShowZrtpSasDialog() {
val authToken = call.authenticationToken
if (authToken.orEmpty().isNotEmpty()) {
showZrtpSasDialog(authToken!!.uppercase(Locale.getDefault()))
}
}
@WorkerThread
private fun showZrtpSasDialog(authToken: String) {
val toRead: String

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.utils
import androidx.annotation.WorkerThread
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.tools.Log
class AudioRouteUtils {
companion object {
private const val TAG = "[Audio Route Utils]"
@WorkerThread
fun isSpeakerAudioRouteCurrentlyUsed(call: Call? = null): Boolean {
val currentCall = if (coreContext.core.callsNb > 0) {
call ?: coreContext.core.currentCall ?: coreContext.core.calls[0]
} else {
Log.w("$TAG No call found, checking audio route on Core")
null
}
val conference = coreContext.core.conference
val audioDevice = if (conference != null && conference.isIn) {
conference.outputAudioDevice
} else if (currentCall != null) {
currentCall.outputAudioDevice
} else {
coreContext.core.outputAudioDevice
}
if (audioDevice == null) return false
Log.i(
"$TAG Playback audio device currently in use is [${audioDevice.deviceName} (${audioDevice.driverName}) ${audioDevice.type}]"
)
return audioDevice.type == AudioDevice.Type.Speaker
}
@WorkerThread
fun routeAudioToEarpiece(call: Call? = null) {
routeAudioTo(call, arrayListOf(AudioDevice.Type.Earpiece))
}
@WorkerThread
fun routeAudioToSpeaker(call: Call? = null) {
routeAudioTo(call, arrayListOf(AudioDevice.Type.Speaker))
}
@WorkerThread
private fun routeAudioTo(
call: Call?,
types: List<AudioDevice.Type>
) {
val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull()
if (currentCall != null) {
applyAudioRouteChange(currentCall, types)
changeCaptureDeviceToMatchAudioRoute(currentCall, types)
} else {
applyAudioRouteChange(call, types)
changeCaptureDeviceToMatchAudioRoute(call, types)
}
}
@WorkerThread
private fun applyAudioRouteChange(
call: Call?,
types: List<AudioDevice.Type>,
output: Boolean = true
) {
val currentCall = if (coreContext.core.callsNb > 0) {
call ?: coreContext.core.currentCall ?: coreContext.core.calls[0]
} else {
Log.w("$TAG No call found, setting audio route on Core")
null
}
val capability = if (output) {
AudioDevice.Capabilities.CapabilityPlay
} else {
AudioDevice.Capabilities.CapabilityRecord
}
val preferredDriver = if (output) {
coreContext.core.defaultOutputAudioDevice?.driverName
} else {
coreContext.core.defaultInputAudioDevice?.driverName
}
val extendedAudioDevices = coreContext.core.extendedAudioDevices
Log.i(
"$TAG Looking for an ${if (output) "output" else "input"} audio device with capability [$capability], driver name [$preferredDriver] and type [$types] in extended audio devices list (size ${extendedAudioDevices.size})"
)
val foundAudioDevice = extendedAudioDevices.find {
it.driverName == preferredDriver && types.contains(it.type) && it.hasCapability(
capability
)
}
val audioDevice = if (foundAudioDevice == null) {
Log.w(
"$TAG Failed to find an audio device with capability [$capability], driver name [$preferredDriver] and type [$types]"
)
extendedAudioDevices.find {
types.contains(it.type) && it.hasCapability(capability)
}
} else {
foundAudioDevice
}
if (audioDevice == null) {
Log.e(
"$TAG Couldn't find audio device with capability [$capability] and type [$types]"
)
for (device in extendedAudioDevices) {
Log.i(
"$TAG Extended audio device: [${device.deviceName} (${device.driverName}) ${device.type} / ${device.capabilities}]"
)
}
return
}
if (currentCall != null) {
Log.i(
"$TAG Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing call audio to it"
)
if (output) {
currentCall.outputAudioDevice = audioDevice
} else {
currentCall.inputAudioDevice = audioDevice
}
} else {
Log.i(
"$TAG Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], changing core default audio device"
)
if (output) {
coreContext.core.outputAudioDevice = audioDevice
} else {
coreContext.core.inputAudioDevice = audioDevice
}
}
}
@WorkerThread
private fun changeCaptureDeviceToMatchAudioRoute(call: Call?, types: List<AudioDevice.Type>) {
when (types.first()) {
AudioDevice.Type.Earpiece, AudioDevice.Type.Speaker -> {
Log.i(
"$TAG Audio route requested to Earpiece or Speaker, setting input to Microphone"
)
applyAudioRouteChange(call, (arrayListOf(AudioDevice.Type.Microphone)), false)
}
else -> {
Log.w("$TAG Unexpected audio device type: ${types.first()}")
}
}
}
}
}

View file

@ -81,7 +81,7 @@
android:layout_height="@dimen/voip_button_size"
android:layout_marginEnd="30dp"
android:padding="@dimen/voip_button_icon_padding"
android:src="@{viewModel.isSpeakerEnabled ? @drawable/speaker_slash : @drawable/speaker_high, default=@drawable/speaker_slash}"
android:src="@{viewModel.isSpeakerEnabled ? @drawable/speaker_high : @drawable/speaker_slash, default=@drawable/speaker_slash}"
android:background="@drawable/in_call_button_background"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"