Committed by
GitHub
Prewarm audio to speed up mic publishing (#623)
* Prewarm audio to speed up mic publishing * update cache action * spotless and changeset
正在显示
11 个修改的文件
包含
107 行增加
和
6 行删除
.changeset/silent-planes-count.md
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.audio | ||
| 18 | + | ||
| 19 | +import livekit.org.webrtc.audio.JavaAudioDeviceModule | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * @suppress | ||
| 23 | + */ | ||
| 24 | +interface AudioRecordPrewarmer { | ||
| 25 | + fun prewarm() | ||
| 26 | + fun stop() | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * @suppress | ||
| 31 | + */ | ||
| 32 | +class NoAudioRecordPrewarmer : AudioRecordPrewarmer { | ||
| 33 | + override fun prewarm() { | ||
| 34 | + // nothing to do. | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + override fun stop() { | ||
| 38 | + // nothing to do. | ||
| 39 | + } | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +/** | ||
| 43 | + * @suppress | ||
| 44 | + */ | ||
| 45 | +class JavaAudioRecordPrewarmer(private val audioDeviceModule: JavaAudioDeviceModule) : AudioRecordPrewarmer { | ||
| 46 | + override fun prewarm() { | ||
| 47 | + audioDeviceModule.prewarmRecording() | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + override fun stop() { | ||
| 51 | + audioDeviceModule.requestStopRecording() | ||
| 52 | + } | ||
| 53 | +} |
| @@ -27,8 +27,11 @@ import io.livekit.android.LiveKit | @@ -27,8 +27,11 @@ import io.livekit.android.LiveKit | ||
| 27 | import io.livekit.android.audio.AudioBufferCallbackDispatcher | 27 | import io.livekit.android.audio.AudioBufferCallbackDispatcher |
| 28 | import io.livekit.android.audio.AudioProcessingController | 28 | import io.livekit.android.audio.AudioProcessingController |
| 29 | import io.livekit.android.audio.AudioProcessorOptions | 29 | import io.livekit.android.audio.AudioProcessorOptions |
| 30 | +import io.livekit.android.audio.AudioRecordPrewarmer | ||
| 30 | import io.livekit.android.audio.AudioRecordSamplesDispatcher | 31 | import io.livekit.android.audio.AudioRecordSamplesDispatcher |
| 31 | import io.livekit.android.audio.CommunicationWorkaround | 32 | import io.livekit.android.audio.CommunicationWorkaround |
| 33 | +import io.livekit.android.audio.JavaAudioRecordPrewarmer | ||
| 34 | +import io.livekit.android.audio.NoAudioRecordPrewarmer | ||
| 32 | import io.livekit.android.memory.CloseableManager | 35 | import io.livekit.android.memory.CloseableManager |
| 33 | import io.livekit.android.util.LKLog | 36 | import io.livekit.android.util.LKLog |
| 34 | import io.livekit.android.util.LoggingLevel | 37 | import io.livekit.android.util.LoggingLevel |
| @@ -244,6 +247,15 @@ internal object RTCModule { | @@ -244,6 +247,15 @@ internal object RTCModule { | ||
| 244 | } | 247 | } |
| 245 | 248 | ||
| 246 | @Provides | 249 | @Provides |
| 250 | + fun audioPrewarmer(audioDeviceModule: AudioDeviceModule): AudioRecordPrewarmer { | ||
| 251 | + return if (audioDeviceModule is JavaAudioDeviceModule) { | ||
| 252 | + JavaAudioRecordPrewarmer(audioDeviceModule) | ||
| 253 | + } else { | ||
| 254 | + NoAudioRecordPrewarmer() | ||
| 255 | + } | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + @Provides | ||
| 247 | @Singleton | 259 | @Singleton |
| 248 | fun eglBase( | 260 | fun eglBase( |
| 249 | @Named(InjectionNames.OVERRIDE_EGL_BASE) | 261 | @Named(InjectionNames.OVERRIDE_EGL_BASE) |
| @@ -32,6 +32,7 @@ import io.livekit.android.RoomOptions | @@ -32,6 +32,7 @@ import io.livekit.android.RoomOptions | ||
| 32 | import io.livekit.android.Version | 32 | import io.livekit.android.Version |
| 33 | import io.livekit.android.audio.AudioHandler | 33 | import io.livekit.android.audio.AudioHandler |
| 34 | import io.livekit.android.audio.AudioProcessingController | 34 | import io.livekit.android.audio.AudioProcessingController |
| 35 | +import io.livekit.android.audio.AudioRecordPrewarmer | ||
| 35 | import io.livekit.android.audio.AudioSwitchHandler | 36 | import io.livekit.android.audio.AudioSwitchHandler |
| 36 | import io.livekit.android.audio.AuthedAudioProcessingController | 37 | import io.livekit.android.audio.AuthedAudioProcessingController |
| 37 | import io.livekit.android.audio.CommunicationWorkaround | 38 | import io.livekit.android.audio.CommunicationWorkaround |
| @@ -104,6 +105,7 @@ constructor( | @@ -104,6 +105,7 @@ constructor( | ||
| 104 | private val audioDeviceModule: AudioDeviceModule, | 105 | private val audioDeviceModule: AudioDeviceModule, |
| 105 | private val regionUrlProviderFactory: RegionUrlProvider.Factory, | 106 | private val regionUrlProviderFactory: RegionUrlProvider.Factory, |
| 106 | private val connectionWarmer: ConnectionWarmer, | 107 | private val connectionWarmer: ConnectionWarmer, |
| 108 | + private val audioRecordPrewarmer: AudioRecordPrewarmer, | ||
| 107 | ) : RTCEngine.Listener, ParticipantListener { | 109 | ) : RTCEngine.Listener, ParticipantListener { |
| 108 | 110 | ||
| 109 | private lateinit var coroutineScope: CoroutineScope | 111 | private lateinit var coroutineScope: CoroutineScope |
| @@ -177,6 +179,7 @@ constructor( | @@ -177,6 +179,7 @@ constructor( | ||
| 177 | State.DISCONNECTED -> { | 179 | State.DISCONNECTED -> { |
| 178 | audioHandler.stop() | 180 | audioHandler.stop() |
| 179 | communicationWorkaround.stop() | 181 | communicationWorkaround.stop() |
| 182 | + audioRecordPrewarmer.stop() | ||
| 180 | } | 183 | } |
| 181 | 184 | ||
| 182 | else -> {} | 185 | else -> {} |
| @@ -475,6 +478,7 @@ constructor( | @@ -475,6 +478,7 @@ constructor( | ||
| 475 | networkCallbackManager.registerCallback() | 478 | networkCallbackManager.registerCallback() |
| 476 | if (options.audio) { | 479 | if (options.audio) { |
| 477 | val audioTrack = localParticipant.createAudioTrack() | 480 | val audioTrack = localParticipant.createAudioTrack() |
| 481 | + audioTrack.prewarm() | ||
| 478 | localParticipant.publishAudioTrack(audioTrack) | 482 | localParticipant.publishAudioTrack(audioTrack) |
| 479 | } | 483 | } |
| 480 | ensureActive() | 484 | ensureActive() |
| @@ -317,6 +317,7 @@ internal constructor( | @@ -317,6 +317,7 @@ internal constructor( | ||
| 317 | 317 | ||
| 318 | Track.Source.MICROPHONE -> { | 318 | Track.Source.MICROPHONE -> { |
| 319 | val track = createAudioTrack() | 319 | val track = createAudioTrack() |
| 320 | + track.prewarm() | ||
| 320 | publishAudioTrack(track) | 321 | publishAudioTrack(track) |
| 321 | } | 322 | } |
| 322 | 323 |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -26,6 +26,7 @@ import dagger.assisted.AssistedInject | @@ -26,6 +26,7 @@ import dagger.assisted.AssistedInject | ||
| 26 | import io.livekit.android.audio.AudioBufferCallback | 26 | import io.livekit.android.audio.AudioBufferCallback |
| 27 | import io.livekit.android.audio.AudioBufferCallbackDispatcher | 27 | import io.livekit.android.audio.AudioBufferCallbackDispatcher |
| 28 | import io.livekit.android.audio.AudioProcessingController | 28 | import io.livekit.android.audio.AudioProcessingController |
| 29 | +import io.livekit.android.audio.AudioRecordPrewarmer | ||
| 29 | import io.livekit.android.audio.AudioRecordSamplesDispatcher | 30 | import io.livekit.android.audio.AudioRecordSamplesDispatcher |
| 30 | import io.livekit.android.audio.MixerAudioBufferCallback | 31 | import io.livekit.android.audio.MixerAudioBufferCallback |
| 31 | import io.livekit.android.dagger.InjectionNames | 32 | import io.livekit.android.dagger.InjectionNames |
| @@ -70,6 +71,7 @@ constructor( | @@ -70,6 +71,7 @@ constructor( | ||
| 70 | private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher, | 71 | private val audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher, |
| 71 | @Named(InjectionNames.LOCAL_AUDIO_BUFFER_CALLBACK_DISPATCHER) | 72 | @Named(InjectionNames.LOCAL_AUDIO_BUFFER_CALLBACK_DISPATCHER) |
| 72 | private val audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher, | 73 | private val audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher, |
| 74 | + private val audioRecordPrewarmer: AudioRecordPrewarmer, | ||
| 73 | ) : AudioTrack(name, mediaTrack) { | 75 | ) : AudioTrack(name, mediaTrack) { |
| 74 | /** | 76 | /** |
| 75 | * To only be used for flow delegate scoping, and should not be cancelled. | 77 | * To only be used for flow delegate scoping, and should not be cancelled. |
| @@ -83,6 +85,17 @@ constructor( | @@ -83,6 +85,17 @@ constructor( | ||
| 83 | private val trackSinks = mutableSetOf<AudioTrackSink>() | 85 | private val trackSinks = mutableSetOf<AudioTrackSink>() |
| 84 | 86 | ||
| 85 | /** | 87 | /** |
| 88 | + * Prewarms the audio stack if needed by starting the recording regardless of whether it's being published. | ||
| 89 | + */ | ||
| 90 | + fun prewarm() { | ||
| 91 | + audioRecordPrewarmer.prewarm() | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + fun stopPrewarm() { | ||
| 95 | + audioRecordPrewarmer.stop() | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 86 | * Note: This function relies on us setting | 99 | * Note: This function relies on us setting |
| 87 | * [JavaAudioDeviceModule.Builder.setSamplesReadyCallback]. | 100 | * [JavaAudioDeviceModule.Builder.setSamplesReadyCallback]. |
| 88 | * If you provide your own [AudioDeviceModule], or set your own | 101 | * If you provide your own [AudioDeviceModule], or set your own |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -22,7 +22,9 @@ import dagger.Module | @@ -22,7 +22,9 @@ import dagger.Module | ||
| 22 | import dagger.Provides | 22 | import dagger.Provides |
| 23 | import io.livekit.android.audio.AudioBufferCallbackDispatcher | 23 | import io.livekit.android.audio.AudioBufferCallbackDispatcher |
| 24 | import io.livekit.android.audio.AudioProcessingController | 24 | import io.livekit.android.audio.AudioProcessingController |
| 25 | +import io.livekit.android.audio.AudioRecordPrewarmer | ||
| 25 | import io.livekit.android.audio.AudioRecordSamplesDispatcher | 26 | import io.livekit.android.audio.AudioRecordSamplesDispatcher |
| 27 | +import io.livekit.android.audio.NoAudioRecordPrewarmer | ||
| 26 | import io.livekit.android.dagger.CapabilitiesGetter | 28 | import io.livekit.android.dagger.CapabilitiesGetter |
| 27 | import io.livekit.android.dagger.InjectionNames | 29 | import io.livekit.android.dagger.InjectionNames |
| 28 | import io.livekit.android.test.mock.MockAudioDeviceModule | 30 | import io.livekit.android.test.mock.MockAudioDeviceModule |
| @@ -75,6 +77,11 @@ object TestRTCModule { | @@ -75,6 +77,11 @@ object TestRTCModule { | ||
| 75 | } | 77 | } |
| 76 | 78 | ||
| 77 | @Provides | 79 | @Provides |
| 80 | + fun audioPrewarmer(): AudioRecordPrewarmer { | ||
| 81 | + return NoAudioRecordPrewarmer() | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @Provides | ||
| 78 | @Singleton | 85 | @Singleton |
| 79 | fun peerConnectionFactory( | 86 | fun peerConnectionFactory( |
| 80 | appContext: Context, | 87 | appContext: Context, |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -18,7 +18,9 @@ package io.livekit.android.test.mock.room.track | @@ -18,7 +18,9 @@ package io.livekit.android.test.mock.room.track | ||
| 18 | 18 | ||
| 19 | import io.livekit.android.audio.AudioBufferCallbackDispatcher | 19 | import io.livekit.android.audio.AudioBufferCallbackDispatcher |
| 20 | import io.livekit.android.audio.AudioProcessingController | 20 | import io.livekit.android.audio.AudioProcessingController |
| 21 | +import io.livekit.android.audio.AudioRecordPrewarmer | ||
| 21 | import io.livekit.android.audio.AudioRecordSamplesDispatcher | 22 | import io.livekit.android.audio.AudioRecordSamplesDispatcher |
| 23 | +import io.livekit.android.audio.NoAudioRecordPrewarmer | ||
| 22 | import io.livekit.android.room.track.LocalAudioTrack | 24 | import io.livekit.android.room.track.LocalAudioTrack |
| 23 | import io.livekit.android.room.track.LocalAudioTrackOptions | 25 | import io.livekit.android.room.track.LocalAudioTrackOptions |
| 24 | import io.livekit.android.test.MockE2ETest | 26 | import io.livekit.android.test.MockE2ETest |
| @@ -38,6 +40,7 @@ fun MockE2ETest.createMockLocalAudioTrack( | @@ -38,6 +40,7 @@ fun MockE2ETest.createMockLocalAudioTrack( | ||
| 38 | dispatcher: CoroutineDispatcher = coroutineRule.dispatcher, | 40 | dispatcher: CoroutineDispatcher = coroutineRule.dispatcher, |
| 39 | audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher = AudioRecordSamplesDispatcher(), | 41 | audioRecordSamplesDispatcher: AudioRecordSamplesDispatcher = AudioRecordSamplesDispatcher(), |
| 40 | audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher = AudioBufferCallbackDispatcher(), | 42 | audioBufferCallbackDispatcher: AudioBufferCallbackDispatcher = AudioBufferCallbackDispatcher(), |
| 43 | + audioRecordPrewarmer: AudioRecordPrewarmer = NoAudioRecordPrewarmer(), | ||
| 41 | ): LocalAudioTrack { | 44 | ): LocalAudioTrack { |
| 42 | return LocalAudioTrack( | 45 | return LocalAudioTrack( |
| 43 | name = name, | 46 | name = name, |
| @@ -47,5 +50,6 @@ fun MockE2ETest.createMockLocalAudioTrack( | @@ -47,5 +50,6 @@ fun MockE2ETest.createMockLocalAudioTrack( | ||
| 47 | dispatcher = dispatcher, | 50 | dispatcher = dispatcher, |
| 48 | audioRecordSamplesDispatcher = audioRecordSamplesDispatcher, | 51 | audioRecordSamplesDispatcher = audioRecordSamplesDispatcher, |
| 49 | audioBufferCallbackDispatcher = audioBufferCallbackDispatcher, | 52 | audioBufferCallbackDispatcher = audioBufferCallbackDispatcher, |
| 53 | + audioRecordPrewarmer = audioRecordPrewarmer, | ||
| 50 | ) | 54 | ) |
| 51 | } | 55 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -21,6 +21,7 @@ import android.net.ConnectivityManager.NetworkCallback | @@ -21,6 +21,7 @@ import android.net.ConnectivityManager.NetworkCallback | ||
| 21 | import android.net.Network | 21 | import android.net.Network |
| 22 | import androidx.test.core.app.ApplicationProvider | 22 | import androidx.test.core.app.ApplicationProvider |
| 23 | import io.livekit.android.audio.NoAudioHandler | 23 | import io.livekit.android.audio.NoAudioHandler |
| 24 | +import io.livekit.android.audio.NoAudioRecordPrewarmer | ||
| 24 | import io.livekit.android.audio.NoopCommunicationWorkaround | 25 | import io.livekit.android.audio.NoopCommunicationWorkaround |
| 25 | import io.livekit.android.e2ee.E2EEManager | 26 | import io.livekit.android.e2ee.E2EEManager |
| 26 | import io.livekit.android.events.DisconnectReason | 27 | import io.livekit.android.events.DisconnectReason |
| @@ -131,6 +132,7 @@ class RoomTest { | @@ -131,6 +132,7 @@ class RoomTest { | ||
| 131 | audioDeviceModule = MockAudioDeviceModule(), | 132 | audioDeviceModule = MockAudioDeviceModule(), |
| 132 | regionUrlProviderFactory = regionUrlProviderFactory, | 133 | regionUrlProviderFactory = regionUrlProviderFactory, |
| 133 | connectionWarmer = MockConnectionWarmer(), | 134 | connectionWarmer = MockConnectionWarmer(), |
| 135 | + audioRecordPrewarmer = NoAudioRecordPrewarmer(), | ||
| 134 | ) | 136 | ) |
| 135 | } | 137 | } |
| 136 | 138 |
-
请 注册 或 登录 后发表评论