我正在尝试将音频字节流式传输到不和谐机器人,但没有音乐,而是有很多噪音。此外,机器人连接成功,编解码器工作。我首先想到的是我要么将数据放在错误的位置,要么错误地将其传输到机器人。此外,机器人本身工作正常,它正确连接到语音通道,甚至可以识别声音,但不是正确的声音。我使用 Exoplayer 在设备本身和 Discord 中播放本地 mp3 文件。在手机上播放没有问题
为了使用不和谐机器人,我使用 Kord。文档中描述了其对传输数据的要求:
一帧 20ms Opus 编码的 48k 立体声音频数据。
基于此,我根据要求设置了以下编解码器设置:
@OptIn(UnstableApi::class)
class BotAudioProcessor(
private val audioDataListener: AudioDataListener
): BaseAudioProcessor(){
val codec = Opus()
val SAMPLE_RATE = Constants.SampleRate._48000()
val CHANNELS = Constants.Channels.stereo()
val APPLICATION = Constants.Application.audio()
val FRAME_SIZE = Constants.FrameSize._960()
val COMPLEXITY = Constants.Complexity.instance(10)
val BITRATE = Constants.Bitrate.max()
init {
codec.encoderInit(SAMPLE_RATE, CHANNELS, APPLICATION)
codec.encoderSetComplexity(COMPLEXITY)
codec.encoderSetBitrate(BITRATE)
}
override fun onConfigure(inputFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat {
val outputFormat = AudioProcessor.AudioFormat(
SAMPLE_RATE.v,
CHANNELS.v,
C.ENCODING_PCM_16BIT
)
return outputFormat
}
//... queueInput()
}
这里我按照文档中的要求设置了所有参数。所有这些值都是通过编解码器设置的,唯一的是我花了一个小时计算帧大小 帧大小 = SAMPLE_RATE * 0.02 (20ms) = 960
如果我理解正确的话,可以使用 AudioProcessor 获得未压缩的 PCM 流,为此,我继承自 BaseAudioProcessor 并重写 queueInput 方法,在该方法中我以 Opus 格式对缓冲区进行编码,然后将其传递给侦听器,这将将流发送到不和谐机器人。
override fun queueInput(inputBuffer: ByteBuffer) {
if (inputBuffer.remaining() < FRAME_SIZE.v / 2) {
Log.e("BotAudioProcessor", "Input buffer too small")
return
}
val frame = ShortArray( FRAME_SIZE.v / 2)
inputBuffer.asShortBuffer().get(frame)
val encoded = codec.encode(frame, FRAME_SIZE)
audioDataListener.onAudioData(encoded!!.toByteArray())
}
接下来,接收到的音频流保存在一个变量中,然后由机器人广播。
@UnstableApi
class MusicBot(...) : AudioDataListener {
private var audioData: ByteArray? = null
//...
@OptIn(KordVoice::class)
suspend fun start() {
kord!!.on<ReadyEvent> {
//...
voiceChannel.connect {
audioProvider {
println(audioBuffer?.joinToString(" "))
println(audioBuffer?.size)
audioBuffer?.let { AudioFrame(it) }
}
}
}
}
override fun onAudioData(data: ByteArray) {
audioBuffer = data
}
}
这就是我将其连接到播放器的方式
@OptIn(UnstableApi::class)
class DiscordRendersFactory
(
context: Context,
private val audioDataListener: AudioDataListener
) : DefaultRenderersFactory(context) {
override fun buildAudioSink(
context: Context,
enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean
): AudioSink {
val defaultAudioSink = DefaultAudioSink.Builder()
.setAudioProcessors(arrayOf(BotAudioProcessor(audioDataListener)))
.setEnableFloatOutput(true)
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
.build()
return defaultAudioSink
}
}
在
queueInput
方法中,您会收到 inputBuffer
充满音频样本。您对其进行编码并发送前 FRAME_SIZE
字节。其余的样品都被扔掉了。
AFAIK Opus 默认使用 20 毫秒长帧。所以你可能根本不需要对缓冲区进行切片。
但是如果这样做,您应该确保处理每个缓冲区中的所有样本。您还需要处理剩菜(长度<
FRAME_SIZE
的切片)。