diff --git a/LavalinkServer/application.yml.example b/LavalinkServer/application.yml.example index 617b1bfde..3badfaf47 100644 --- a/LavalinkServer/application.yml.example +++ b/LavalinkServer/application.yml.example @@ -38,6 +38,7 @@ lavalink: rotation: true channelMix: true lowPass: true + nonAllocatingFrameBuffer: false # Setting to true reduces the number of allocations made by each player at the expense of frame rebuilding (e.g. non-instantaneous volume changes) bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses. Duration <= 0 to disable JDA-NAS. Minimum of 40ms, lower values may introduce pauses. frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU. diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt index ca6fd99c1..96571e689 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt @@ -14,6 +14,7 @@ import com.sedmelluq.discord.lavaplayer.source.soundcloud.* import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager +import com.sedmelluq.discord.lavaplayer.track.playback.NonAllocatingAudioFrameBuffer import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup import com.sedmelluq.lava.extensions.youtuberotator.planner.* import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block @@ -55,6 +56,11 @@ class AudioPlayerConfiguration { audioPlayerManager.enableGcMonitoring() } + if (serverConfig.isNonAllocatingFrameBuffer) { + log.info("Using a non-allocating frame buffer") + audioPlayerManager.configuration.setFrameBufferFactory(::NonAllocatingAudioFrameBuffer) + } + val defaultFrameBufferDuration = audioPlayerManager.frameBufferDuration serverConfig.frameBufferDurationMs?.let { if (it < 200) { // At the time of writing, LP enforces a minimum of 200ms. diff --git a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt index 4cd3a0b97..da31f3251 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt @@ -30,6 +30,7 @@ import org.springframework.stereotype.Component @Component class ServerConfig { var password: String? = null + var isNonAllocatingFrameBuffer = false var bufferDurationMs: Int? = null var frameBufferDurationMs: Int? = null var opusEncodingQuality: Int? = null diff --git a/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt b/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt index aeaec5866..039a360c5 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt @@ -21,12 +21,14 @@ */ package lavalink.server.player +import com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats import com.sedmelluq.discord.lavaplayer.player.AudioPlayer import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame +import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame import dev.arbjerg.lavalink.api.AudioPluginInfoModifier import dev.arbjerg.lavalink.api.IPlayer import io.netty.buffer.ByteBuf @@ -36,6 +38,7 @@ import lavalink.server.io.SocketServer.Companion.sendPlayerUpdate import lavalink.server.player.filters.FilterChain import moe.kyokobot.koe.MediaConnection import moe.kyokobot.koe.media.OpusAudioFrameProvider +import java.nio.ByteBuffer import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @@ -46,6 +49,9 @@ class LavalinkPlayer( audioPlayerManager: AudioPlayerManager, pluginInfoModifiers: List ) : AudioEventAdapter(), IPlayer { + private val buffer = ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize()) + private val mutableFrame = MutableAudioFrame().apply { setBuffer(buffer) } + val audioLossCounter = AudioLossCounter() var endMarkerHit = false var filters: FilterChain = FilterChain() @@ -117,21 +123,15 @@ class LavalinkPlayer( } private inner class Provider(connection: MediaConnection?) : OpusAudioFrameProvider(connection) { - private var lastFrame: AudioFrame? = null - - override fun canProvide(): Boolean { - lastFrame = audioPlayer.provide() - return if (lastFrame == null) { + override fun canProvide() = audioPlayer.provide(mutableFrame).also { provided -> + if (!provided) { audioLossCounter.onLoss() - false - } else { - true } } override fun retrieveOpusFrame(buf: ByteBuf) { audioLossCounter.onSuccess() - buf.writeBytes(lastFrame!!.data) + buf.writeBytes(buffer.flip()) } } }