davidliu
Committed by GitHub

Prewarm audio to speed up mic publishing (#623)

* Prewarm audio to speed up mic publishing

* update cache action

* spotless and changeset
---
"client-sdk-android": minor
---
Prewarm audio to speed up mic publishing
... ...
... ... @@ -35,7 +35,7 @@ jobs:
java-version: '17'
distribution: 'adopt'
- uses: actions/cache@v3.3.2
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
... ...
[versions]
webrtc = "125.6422.06.1"
webrtc = "125.6422.07"
androidJainSipRi = "1.3.0-91"
androidx-activity = "1.9.0"
... ...
/*
* Copyright 2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.livekit.android.audio
import livekit.org.webrtc.audio.JavaAudioDeviceModule
/**
* @suppress
*/
interface AudioRecordPrewarmer {
fun prewarm()
fun stop()
}
/**
* @suppress
*/
class NoAudioRecordPrewarmer : AudioRecordPrewarmer {
override fun prewarm() {
// nothing to do.
}
override fun stop() {
// nothing to do.
}
}
/**
* @suppress
*/
class JavaAudioRecordPrewarmer(private val audioDeviceModule: JavaAudioDeviceModule) : AudioRecordPrewarmer {
override fun prewarm() {
audioDeviceModule.prewarmRecording()
}
override fun stop() {
audioDeviceModule.requestStopRecording()
}
}
... ...
... ... @@ -27,8 +27,11 @@ import io.livekit.android.LiveKit
import io.livekit.android.audio.AudioBufferCallbackDispatcher
import io.livekit.android.audio.AudioProcessingController
import io.livekit.android.audio.AudioProcessorOptions
import io.livekit.android.audio.AudioRecordPrewarmer
import io.livekit.android.audio.AudioRecordSamplesDispatcher
import io.livekit.android.audio.CommunicationWorkaround
import io.livekit.android.audio.JavaAudioRecordPrewarmer
import io.livekit.android.audio.NoAudioRecordPrewarmer
import io.livekit.android.memory.CloseableManager
import io.livekit.android.util.LKLog
import io.livekit.android.util.LoggingLevel
... ... @@ -244,6 +247,15 @@ internal object RTCModule {
}
@Provides
fun audioPrewarmer(audioDeviceModule: AudioDeviceModule): AudioRecordPrewarmer {
return if (audioDeviceModule is JavaAudioDeviceModule) {
JavaAudioRecordPrewarmer(audioDeviceModule)
} else {
NoAudioRecordPrewarmer()
}
}
@Provides
@Singleton
fun eglBase(
@Named(InjectionNames.OVERRIDE_EGL_BASE)
... ...
... ... @@ -32,6 +32,7 @@ import io.livekit.android.RoomOptions
import io.livekit.android.Version
import io.livekit.android.audio.AudioHandler
import io.livekit.android.audio.AudioProcessingController
import io.livekit.android.audio.AudioRecordPrewarmer
import io.livekit.android.audio.AudioSwitchHandler
import io.livekit.android.audio.AuthedAudioProcessingController
import io.livekit.android.audio.CommunicationWorkaround
... ... @@ -104,6 +105,7 @@ constructor(
private val audioDeviceModule: AudioDeviceModule,
private val regionUrlProviderFactory: RegionUrlProvider.Factory,
private val connectionWarmer: ConnectionWarmer,
private val audioRecordPrewarmer: AudioRecordPrewarmer,
) : RTCEngine.Listener, ParticipantListener {
private lateinit var coroutineScope: CoroutineScope
... ... @@ -177,6 +179,7 @@ constructor(
State.DISCONNECTED -> {
audioHandler.stop()
communicationWorkaround.stop()
audioRecordPrewarmer.stop()
}
else -> {}
... ... @@ -475,6 +478,7 @@ constructor(
networkCallbackManager.registerCallback()
if (options.audio) {
val audioTrack = localParticipant.createAudioTrack()
audioTrack.prewarm()
localParticipant.publishAudioTrack(audioTrack)
}
ensureActive()
... ...
... ... @@ -317,6 +317,7 @@ internal constructor(
Track.Source.MICROPHONE -> {
val track = createAudioTrack()
track.prewarm()
publishAudioTrack(track)
}
... ...
/*
* Copyright 2023-2024 LiveKit, Inc.
* Copyright 2023-2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -26,6 +26,7 @@ import dagger.assisted.AssistedInject
import io.livekit.android.audio.AudioBufferCallback
import io.livekit.android.audio.AudioBufferCallbackDispatcher
import io.livekit.android.audio.AudioProcessingController
import io.livekit.android.audio.AudioRecordPrewarmer
import io.livekit.android.audio.AudioRecordSamplesDispatcher
import io.livekit.android.audio.MixerAudioBufferCallback
import io.livekit.android.dagger.InjectionNames
... ... @@ -70,6 +71,7 @@ constructor(
private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher,
@Named(InjectionNames.LOCAL_AUDIO_BUFFER_CALLBACK_DISPATCHER)
private val audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher,
private val audioRecordPrewarmer: AudioRecordPrewarmer,
) : AudioTrack(name, mediaTrack) {
/**
* To only be used for flow delegate scoping, and should not be cancelled.
... ... @@ -83,6 +85,17 @@ constructor(
private val trackSinks = mutableSetOf<AudioTrackSink>()
/**
* Prewarms the audio stack if needed by starting the recording regardless of whether it's being published.
*/
fun prewarm() {
audioRecordPrewarmer.prewarm()
}
fun stopPrewarm() {
audioRecordPrewarmer.stop()
}
/**
* Note: This function relies on us setting
* [JavaAudioDeviceModule.Builder.setSamplesReadyCallback].
* If you provide your own [AudioDeviceModule], or set your own
... ...
/*
* Copyright 2023-2024 LiveKit, Inc.
* Copyright 2023-2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,7 +22,9 @@ import dagger.Module
import dagger.Provides
import io.livekit.android.audio.AudioBufferCallbackDispatcher
import io.livekit.android.audio.AudioProcessingController
import io.livekit.android.audio.AudioRecordPrewarmer
import io.livekit.android.audio.AudioRecordSamplesDispatcher
import io.livekit.android.audio.NoAudioRecordPrewarmer
import io.livekit.android.dagger.CapabilitiesGetter
import io.livekit.android.dagger.InjectionNames
import io.livekit.android.test.mock.MockAudioDeviceModule
... ... @@ -75,6 +77,11 @@ object TestRTCModule {
}
@Provides
fun audioPrewarmer(): AudioRecordPrewarmer {
return NoAudioRecordPrewarmer()
}
@Provides
@Singleton
fun peerConnectionFactory(
appContext: Context,
... ...
/*
* Copyright 2024 LiveKit, Inc.
* Copyright 2024-2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,7 +18,9 @@ package io.livekit.android.test.mock.room.track
import io.livekit.android.audio.AudioBufferCallbackDispatcher
import io.livekit.android.audio.AudioProcessingController
import io.livekit.android.audio.AudioRecordPrewarmer
import io.livekit.android.audio.AudioRecordSamplesDispatcher
import io.livekit.android.audio.NoAudioRecordPrewarmer
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.room.track.LocalAudioTrackOptions
import io.livekit.android.test.MockE2ETest
... ... @@ -38,6 +40,7 @@ fun MockE2ETest.createMockLocalAudioTrack(
dispatcher: CoroutineDispatcher = coroutineRule.dispatcher,
audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher = AudioRecordSamplesDispatcher(),
audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher = AudioBufferCallbackDispatcher(),
audioRecordPrewarmer: AudioRecordPrewarmer = NoAudioRecordPrewarmer(),
): LocalAudioTrack {
return LocalAudioTrack(
name = name,
... ... @@ -47,5 +50,6 @@ fun MockE2ETest.createMockLocalAudioTrack(
dispatcher = dispatcher,
audioRecordSamplesDispatcher = audioRecordSamplesDispatcher,
audioBufferCallbackDispatcher = audioBufferCallbackDispatcher,
audioRecordPrewarmer = audioRecordPrewarmer,
)
}
... ...
/*
* Copyright 2023-2024 LiveKit, Inc.
* Copyright 2023-2025 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,6 +21,7 @@ import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import androidx.test.core.app.ApplicationProvider
import io.livekit.android.audio.NoAudioHandler
import io.livekit.android.audio.NoAudioRecordPrewarmer
import io.livekit.android.audio.NoopCommunicationWorkaround
import io.livekit.android.e2ee.E2EEManager
import io.livekit.android.events.DisconnectReason
... ... @@ -131,6 +132,7 @@ class RoomTest {
audioDeviceModule = MockAudioDeviceModule(),
regionUrlProviderFactory = regionUrlProviderFactory,
connectionWarmer = MockConnectionWarmer(),
audioRecordPrewarmer = NoAudioRecordPrewarmer(),
)
}
... ...