Skip to content

Commit 38bdaad

Browse files
Added support for High fidelity (hi-fi) audio capture (#1564)
* Update to latest proto * Send stereo = true in Audio track's TrackInfo if hifi is enabled in dashboard * fix: microphone not getting enabled if we join with USAGE_MEDIA mode * Provide a way to set the audio usage from app to call.speaker - it can be either VOICE_COMMUNICATION or MEDIA * Provide a way on the debug option menu to switch between usage_media and usage_voice_communication * Expose the audioDeviceModule from peerConnectionFactory * Apply spotless * Upgraded to webrtc version that supports switching audio usage mid call * api dump * Dont send stereo = true in trackInfo while publishing. This will be done when adding stereo capture to recording feature * Added unit tests to test the audioUsage stateFlow of SpeakerManager * refactor(demo-app): simplify onToggleAudioUsage by removing suspend modifier * Fix: `setAudioUsage` of speakerManager does not work if called before any remote track has come in. Updated to webrtc which has the fix for this. * Added : Enable Hifi audio recording * Make setters for audio,video and screenshare tracks so that they dont come under breaking change Make eglbase optional param while creating StreamPeerConnectionFctory so that it doesnt come under breaking change * Fix: have a setter to peerConnectionFactory so that it can be accessed from uit tests * Fix: unit test setup for StreamPeerConnectionFactoryTest * Changed StreamPeerConnectionFactory to use a provider function for EglBase instead of a direct parameter. So that unit test can initiaze StreamPeerConnectionFactory without invoking EglBase.create() in the constructor * Make adm private to StreamPeerConnectionFactory * api dump * Fix tests not running locally * Fix: Publish unit tests need to mock the value of mediaManager.microphone.audioBitrateProfile * 1. make setAudioBitrateProfile a suspend method so that it can fetch the call settings before setting the profile in the call 2. Lobby does not show the hifi toggle button if it is not enables in the call settings * Making the methods internal which need not be public * Making the methods internal which need not be public --------- Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
1 parent d37f7e3 commit 38bdaad

File tree

18 files changed

+655
-98
lines changed

18 files changed

+655
-98
lines changed

demo-app/src/main/kotlin/io/getstream/video/android/ui/lobby/CallLobbyScreen.kt

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import io.getstream.video.android.compose.ui.components.video.config.videoRender
8080
import io.getstream.video.android.core.Call
8181
import io.getstream.video.android.core.call.state.CallAction
8282
import io.getstream.video.android.core.call.state.ToggleCamera
83+
import io.getstream.video.android.core.call.state.ToggleHifiAudio
8384
import io.getstream.video.android.core.call.state.ToggleMicrophone
8485
import io.getstream.video.android.core.events.ParticipantCount
8586
import io.getstream.video.android.mock.StreamPreviewDataUtils
@@ -97,10 +98,14 @@ fun CallLobbyScreen(
9798
val isLoading by callLobbyViewModel.isLoading.collectAsStateWithLifecycle()
9899
val isMicrophoneEnabled by callLobbyViewModel.microphoneEnabled.collectAsStateWithLifecycle()
99100
val isCameraEnabled by callLobbyViewModel.cameraEnabled.collectAsStateWithLifecycle()
101+
val hifiAudioEnabled by callLobbyViewModel.hifiAudioEnabled.collectAsStateWithLifecycle()
102+
val settingsLoaded by callLobbyViewModel.settingsLoaded.collectAsStateWithLifecycle()
100103
val call by remember {
101104
mutableStateOf(callLobbyViewModel.call)
102105
}
103106

107+
val showHifiAudioToggle = settingsLoaded && hifiAudioEnabled
108+
104109
Box(modifier = Modifier.fillMaxSize()) {
105110
Column(
106111
modifier = Modifier
@@ -124,12 +129,16 @@ fun CallLobbyScreen(
124129
.weight(1f),
125130
isMicrophoneEnabled = isMicrophoneEnabled,
126131
isCameraEnabled = isCameraEnabled,
132+
showHifiAudioToggle = showHifiAudioToggle,
127133
onToggleCamera = {
128134
callLobbyViewModel.enableCamera(it)
129135
},
130136
onToggleMicrophone = {
131137
callLobbyViewModel.enableMicrophone(it)
132138
},
139+
onToggleHifiAudio = {
140+
callLobbyViewModel.setAudioBitrateProfile(it)
141+
},
133142
call = call,
134143
) {
135144
LobbyDescription(callLobbyViewModel = callLobbyViewModel)
@@ -226,8 +235,10 @@ private fun CallLobbyBodyResponsive(
226235
call: Call,
227236
isCameraEnabled: Boolean,
228237
isMicrophoneEnabled: Boolean,
238+
showHifiAudioToggle: Boolean = false,
229239
onToggleCamera: (Boolean) -> Unit,
230240
onToggleMicrophone: (Boolean) -> Unit,
241+
onToggleHifiAudio: (Boolean) -> Unit,
231242
description: @Composable () -> Unit,
232243
) {
233244
val configuration = LocalConfiguration.current
@@ -238,8 +249,10 @@ private fun CallLobbyBodyResponsive(
238249
call,
239250
isCameraEnabled,
240251
isMicrophoneEnabled,
252+
showHifiAudioToggle,
241253
onToggleCamera,
242254
onToggleMicrophone,
255+
onToggleHifiAudio,
243256
description,
244257
)
245258
} else {
@@ -248,8 +261,10 @@ private fun CallLobbyBodyResponsive(
248261
call,
249262
isCameraEnabled,
250263
isMicrophoneEnabled,
264+
showHifiAudioToggle,
251265
onToggleCamera,
252266
onToggleMicrophone,
267+
onToggleHifiAudio,
253268
description,
254269
)
255270
}
@@ -262,8 +277,10 @@ private fun CallLobbyBodyPortrait(
262277
call: Call,
263278
isCameraEnabled: Boolean,
264279
isMicrophoneEnabled: Boolean,
280+
showHifiAudioToggle: Boolean = false,
265281
onToggleCamera: (Boolean) -> Unit,
266282
onToggleMicrophone: (Boolean) -> Unit,
283+
onToggleHifiAudio: (Boolean) -> Unit,
267284
description: @Composable () -> Unit,
268285
) {
269286
Column(
@@ -290,6 +307,14 @@ private fun CallLobbyBodyPortrait(
290307
text = "Set up your test call",
291308
style = VideoTheme.typography.titleS,
292309
)
310+
val onCallAction: (CallAction) -> Unit = { action ->
311+
when (action) {
312+
is ToggleCamera -> onToggleCamera(action.isEnabled)
313+
is ToggleMicrophone -> onToggleMicrophone(action.isEnabled)
314+
is ToggleHifiAudio -> onToggleHifiAudio(action.isHifiAudioEnabled)
315+
else -> Unit
316+
}
317+
}
293318
CallLobby(
294319
call = call,
295320
modifier = Modifier
@@ -317,12 +342,19 @@ private fun CallLobbyBodyPortrait(
317342
videoRendererConfig = videoRendererConfig,
318343
)
319344
},
320-
onCallAction = { action ->
321-
when (action) {
322-
is ToggleCamera -> onToggleCamera(action.isEnabled)
323-
is ToggleMicrophone -> onToggleMicrophone(action.isEnabled)
324-
else -> Unit
325-
}
345+
onCallAction = onCallAction,
346+
lobbyControlsContent = { modifier, _ ->
347+
ControlActions(
348+
modifier = modifier,
349+
call = call,
350+
actions = buildDefaultLobbyControlActions(
351+
call = call,
352+
onCallAction = onCallAction,
353+
isCameraEnabled = isCameraEnabled,
354+
isMicrophoneEnabled = isMicrophoneEnabled,
355+
showHifiAudioToggle = showHifiAudioToggle,
356+
),
357+
)
326358
},
327359
)
328360
if (BuildConfig.BUILD_TYPE == "benchmark") {
@@ -343,8 +375,10 @@ private fun CallLobbyBodyLandscape(
343375
call: Call,
344376
isCameraEnabled: Boolean,
345377
isMicrophoneEnabled: Boolean,
378+
showHifiAudioToggle: Boolean = false,
346379
onToggleCamera: (Boolean) -> Unit,
347380
onToggleMicrophone: (Boolean) -> Unit,
381+
onToggleHifiAudio: (Boolean) -> Unit,
348382
description: @Composable () -> Unit,
349383
) {
350384
Box(modifier = Modifier.background(VideoTheme.colors.baseSheetPrimary)) {
@@ -360,6 +394,7 @@ private fun CallLobbyBodyLandscape(
360394
when (action) {
361395
is ToggleCamera -> onToggleCamera(action.isEnabled)
362396
is ToggleMicrophone -> onToggleMicrophone(action.isEnabled)
397+
is ToggleHifiAudio -> onToggleHifiAudio(action.isHifiAudioEnabled)
363398
else -> Unit
364399
}
365400
}
@@ -409,6 +444,7 @@ private fun CallLobbyBodyLandscape(
409444
onCallAction = onCallAction,
410445
isCameraEnabled = isCameraEnabled,
411446
isMicrophoneEnabled = isMicrophoneEnabled,
447+
showHifiAudioToggle = showHifiAudioToggle,
412448
),
413449
)
414450
},
@@ -576,6 +612,7 @@ private fun CallLobbyBodyPortraitPreview() {
576612
call = previewCall,
577613
onToggleMicrophone = {},
578614
onToggleCamera = {},
615+
onToggleHifiAudio = {},
579616
) {
580617
LobbyDescriptionContent(participantCounts = ParticipantCount(1, 1)) {}
581618
}
@@ -598,6 +635,7 @@ private fun CallLobbyBodyLandscapePreview() {
598635
call = previewCall,
599636
onToggleMicrophone = {},
600637
onToggleCamera = {},
638+
onToggleHifiAudio = {},
601639
) {
602640
LobbyDescriptionContent(participantCounts = ParticipantCount(1, 1)) {}
603641
}

demo-app/src/main/kotlin/io/getstream/video/android/ui/lobby/CallLobbyViewModel.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ import kotlinx.coroutines.flow.flowOf
4343
import kotlinx.coroutines.flow.map
4444
import kotlinx.coroutines.flow.onCompletion
4545
import kotlinx.coroutines.flow.shareIn
46+
import kotlinx.coroutines.flow.stateIn
4647
import kotlinx.coroutines.launch
48+
import stream.video.sfu.models.AudioBitrateProfile
4749
import javax.inject.Inject
4850

4951
@HiltViewModel
@@ -83,6 +85,13 @@ class CallLobbyViewModel @Inject constructor(
8385
val isLoggedOut = dataStore.user.map { it == null }
8486
val cameraEnabled: StateFlow<Boolean> = call.camera.isEnabled
8587
val microphoneEnabled: StateFlow<Boolean> = call.microphone.isEnabled
88+
val hifiAudioEnabled: StateFlow<Boolean> = call.state.settings
89+
.map { it?.audio?.hifiAudioEnabled ?: false }
90+
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
91+
92+
val settingsLoaded: StateFlow<Boolean> = call.state.settings
93+
.map { it != null }
94+
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
8695

8796
init {
8897
// for demo we set the default state for mic and camera to be on
@@ -170,6 +179,23 @@ class CallLobbyViewModel @Inject constructor(
170179
call.microphone.setEnabled(enabled)
171180
}
172181

182+
fun setAudioBitrateProfile(isHifiAudioEnabled: Boolean) {
183+
viewModelScope.launch {
184+
val newProfile = if (isHifiAudioEnabled) {
185+
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
186+
} else {
187+
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED
188+
}
189+
val result = call.microphone.setAudioBitrateProfile(newProfile)
190+
if (result.isFailure) {
191+
Log.e(
192+
"CallLobbyViewModel",
193+
"Failed to set audio bitrate profile: ${result.exceptionOrNull()?.message}",
194+
)
195+
}
196+
}
197+
}
198+
173199
fun signOut() {
174200
viewModelScope.launch {
175201
googleSignInClient.signOut()

stream-video-android-core/api/stream-video-android-core.api

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7702,6 +7702,7 @@ public final class io/getstream/video/android/core/MicrophoneManager {
77027702
public final fun cleanup ()V
77037703
public final fun disable (Z)V
77047704
public static synthetic fun disable$default (Lio/getstream/video/android/core/MicrophoneManager;ZILjava/lang/Object;)V
7705+
public final fun getAudioBitrateProfile ()Lkotlinx/coroutines/flow/StateFlow;
77057706
public final fun getAudioUsage ()I
77067707
public final fun getAudioUsageProvider ()Lkotlin/jvm/functions/Function0;
77077708
public final fun getDevices ()Lkotlinx/coroutines/flow/StateFlow;
@@ -7715,6 +7716,7 @@ public final class io/getstream/video/android/core/MicrophoneManager {
77157716
public final fun resume (Z)V
77167717
public static synthetic fun resume$default (Lio/getstream/video/android/core/MicrophoneManager;ZILjava/lang/Object;)V
77177718
public final fun select (Lio/getstream/video/android/core/audio/StreamAudioDevice;)V
7719+
public final fun setAudioBitrateProfile-gIAlu-s (Lstream/video/sfu/models/AudioBitrateProfile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
77187720
public final fun setEnabled (ZZ)V
77197721
public static synthetic fun setEnabled$default (Lio/getstream/video/android/core/MicrophoneManager;ZZILjava/lang/Object;)V
77207722
}
@@ -8300,9 +8302,8 @@ public class io/getstream/video/android/core/call/connection/StreamPeerConnectio
83008302
}
83018303

83028304
public final class io/getstream/video/android/core/call/connection/StreamPeerConnectionFactory {
8303-
public fun <init> (Landroid/content/Context;ILkotlin/jvm/functions/Function0;Lorg/webrtc/ManagedAudioProcessingFactory;)V
8304-
public synthetic fun <init> (Landroid/content/Context;ILkotlin/jvm/functions/Function0;Lorg/webrtc/ManagedAudioProcessingFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
8305-
public final fun getAdm ()Lorg/webrtc/audio/JavaAudioDeviceModule;
8305+
public fun <init> (Landroid/content/Context;ILkotlin/jvm/functions/Function0;Lorg/webrtc/ManagedAudioProcessingFactory;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
8306+
public synthetic fun <init> (Landroid/content/Context;ILkotlin/jvm/functions/Function0;Lorg/webrtc/ManagedAudioProcessingFactory;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
83068307
public final fun getEglBase ()Lorg/webrtc/EglBase;
83078308
public final fun getSenderCapabilities (Lorg/webrtc/MediaStreamTrack$MediaType;)Lorg/webrtc/RtpCapabilities;
83088309
public final fun isAudioProcessingEnabled ()Z
@@ -8312,7 +8313,6 @@ public final class io/getstream/video/android/core/call/connection/StreamPeerCon
83128313
public final fun makePeerConnection (Lorg/webrtc/PeerConnection$RTCConfiguration;Lio/getstream/video/android/core/model/StreamPeerType;Lorg/webrtc/MediaConstraints;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/String;)Lio/getstream/video/android/core/call/connection/StreamPeerConnection;
83138314
public static synthetic fun makePeerConnection$default (Lio/getstream/video/android/core/call/connection/StreamPeerConnectionFactory;Lorg/webrtc/PeerConnection$RTCConfiguration;Lio/getstream/video/android/core/model/StreamPeerType;Lorg/webrtc/MediaConstraints;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/call/connection/StreamPeerConnection;
83148315
public final fun makeVideoTrack (Lorg/webrtc/VideoSource;Ljava/lang/String;)Lorg/webrtc/VideoTrack;
8315-
public final fun setAdm (Lorg/webrtc/audio/JavaAudioDeviceModule;)V
83168316
public final fun setAudioProcessingEnabled (Z)V
83178317
public final fun setAudioRecordDataCallback (Lkotlin/jvm/functions/Function4;)V
83188318
public final fun setAudioSampleCallback (Lkotlin/jvm/functions/Function1;)V
@@ -8452,6 +8452,17 @@ public final class io/getstream/video/android/core/call/state/ToggleCamera : io/
84528452
public fun toString ()Ljava/lang/String;
84538453
}
84548454

8455+
public final class io/getstream/video/android/core/call/state/ToggleHifiAudio : io/getstream/video/android/core/call/state/CallAction {
8456+
public fun <init> (Z)V
8457+
public final fun component1 ()Z
8458+
public final fun copy (Z)Lio/getstream/video/android/core/call/state/ToggleHifiAudio;
8459+
public static synthetic fun copy$default (Lio/getstream/video/android/core/call/state/ToggleHifiAudio;ZILjava/lang/Object;)Lio/getstream/video/android/core/call/state/ToggleHifiAudio;
8460+
public fun equals (Ljava/lang/Object;)Z
8461+
public fun hashCode ()I
8462+
public final fun isHifiAudioEnabled ()Z
8463+
public fun toString ()Ljava/lang/String;
8464+
}
8465+
84558466
public final class io/getstream/video/android/core/call/state/ToggleMicrophone : io/getstream/video/android/core/call/state/CallAction {
84568467
public fun <init> (Z)V
84578468
public final fun component1 ()Z

stream-video-android-core/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ android {
114114
resourcePrefix = "stream_video_"
115115

116116
sourceSets.configureEach {
117-
kotlin.srcDir("build/generated/source/services")
117+
kotlin.srcDir("${project.buildDir}/generated/source/services")
118118
}
119119

120120
packaging {

0 commit comments

Comments
 (0)