diff --git a/src/AudioConvert.kt b/src/AudioConvert.kt index 1fbb1f9..afb5941 100644 --- a/src/AudioConvert.kt +++ b/src/AudioConvert.kt @@ -56,4 +56,20 @@ object AudioConvert { output[i] = shortSample / 32768.0f } } + + fun bytes2shorts(input: ByteArray, output: ShortArray) { + for (c in output.indices) { + val low = input[(c * 2)].toInt() and 0xFF + val high = input[(c * 2) + 1].toInt() shl 8 + output[c] = (low or high).toShort() + } + } + + fun shorts2bytes(input: ShortArray, output: ByteArray) { + for (c in input.indices) { + output[c * 2] = (input[c].toInt() and 0xFF).toByte() + output[c * 2 + 1] = ((input[c].toInt() shr 8) and 0xFF).toByte() + } + } + } diff --git a/src/Client.kt b/src/Client.kt index 20e06c2..7d40eae 100644 --- a/src/Client.kt +++ b/src/Client.kt @@ -4,6 +4,7 @@ import io.github.jaredmdobson.concentus.OpusEncoder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream @@ -41,10 +42,9 @@ class Client { it.complexity = OPUS_COMPLEXITY } - val scope = CoroutineScope(Dispatchers.IO) - fun mainClient() { val socket = Socket() + val scope = CoroutineScope(Dispatchers.IO) val t = measureTimeMillis { socket.connect(InetSocketAddress(SERVER_HOST, SERVER_PORT)) @@ -54,7 +54,7 @@ class Client { val out = ObjectOutputStream(socket.getOutputStream()) val inp = ObjectInputStream(socket.getInputStream()) - scope.launch { + val recordJob = scope.launch { while (true) { targetDataLine.read(bufferOut, 0, bufferOut.size) try { @@ -67,7 +67,7 @@ class Client { } } - scope.launch { + val playJob = scope.launch { while (true) { try { // if (`in`!!.available() > bufferIn.size * 2) { @@ -84,5 +84,10 @@ class Client { } } } + + runBlocking { + recordJob.join() + playJob.join() + } } } diff --git a/src/Main.kt b/src/Main.kt index 081616f..efb8f51 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -1,5 +1,8 @@ import io.github.jaredmdobson.concentus.* import kotlinx.coroutines.* +import java.util.concurrent.atomic.AtomicInteger +import javax.print.Doc +import kotlin.concurrent.fixedRateTimer /** * Created by LoliBall on 2024/8/31 5:26. @@ -9,7 +12,8 @@ import kotlinx.coroutines.* //val SERVER_HOST = "zedo.top" val SERVER_HOST = "127.0.0.1" val SERVER_PORT = 7860 -val ECHO_CANCELLATION = true // 回声消除 +val ECHO_CANCELLATION = false // 回声消除 +val RAW_PCM_PASS = true // 不进行opus压缩pcm直传 // 2.5, 5, 10, 20, 40, 60 ms // 120, 240, 480, 960, 1920, 2880 @@ -24,27 +28,53 @@ val OPUS_COMPLEXITY = 10 // 0: 快,质量差 10: 慢,质量好 val OPUS_SIGNAL_TYPE = OpusSignal.OPUS_SIGNAL_VOICE val OPUS_MODE = OpusMode.MODE_HYBRID -fun main() = runBlocking { - CoroutineScope(Dispatchers.IO).launch { + +// 下面是统计用的 +val compressRateOpus = AtomicInteger() +val compressRatePcm = AtomicInteger() + +fun main(args: Array) { + val scope = CoroutineScope(Dispatchers.IO) + scope.launch { Server.main(emptyArray()) } - delay(1000) - Client.main(emptyArray()) - delay(Long.MAX_VALUE) + scope.launch { + delay(1000) // 等服务器起来 + Client.main(emptyArray()) + } + fixedRateTimer(period = 1000) { + "compress rate: %.2f%%".format(compressRateOpus.get() * 100f / compressRatePcm.get()).loge() + compressRateOpus.set(0) + compressRatePcm.set(0) + } } val encodeOpusBuffer = mutableMapOf() +val encodePcmShortBuffer = mutableMapOf() // 1275是opus单帧包最大值 fun OpusEncoder.encode(pcm: ByteArray): DataPack { + if (RAW_PCM_PASS) { + return DataPack(System.currentTimeMillis(), pcm.copyOf()) + } val opusBuffer = encodeOpusBuffer[this] ?: ByteArray(1275).also { encodeOpusBuffer[this] = it } - val bytesEncoded = encode(pcm, 0, PACKET_SAMPLES, opusBuffer, 0, opusBuffer.size) - "compress rate: %.2f%%".format(bytesEncoded / pcm.size.toFloat() * 100).loge() + val shortBuffer = encodePcmShortBuffer[this] ?: ShortArray(pcm.size / 2).also { encodePcmShortBuffer[this] = it } + AudioConvert.bytes2shorts(pcm, shortBuffer) + val bytesEncoded = encode(shortBuffer, 0, PACKET_SAMPLES, opusBuffer, 0, opusBuffer.size) + compressRatePcm.addAndGet(pcm.size) + compressRateOpus.addAndGet(bytesEncoded) return DataPack(System.currentTimeMillis(), opusBuffer.sliceArray(0..() fun OpusDecoder.decode(pack: DataPack, pcm: ByteArray) { - decode(pack.opus, 0, pack.opus.size, pcm, 0, PACKET_SAMPLES, false) + if (RAW_PCM_PASS) { + pack.opus.copyInto(pcm) + return + } + val shortBuffer = decodePcmShortBuffer[this] ?: ShortArray(pcm.size / 2).also { decodePcmShortBuffer[this] = it } + decode(pack.opus, 0, pack.opus.size, shortBuffer, 0, PACKET_SAMPLES, false) + AudioConvert.shorts2bytes(shortBuffer, pcm) } fun T.loge() = also { println(this) } \ No newline at end of file diff --git a/src/Server.kt b/src/Server.kt index abae919..a3f3710 100644 --- a/src/Server.kt +++ b/src/Server.kt @@ -23,7 +23,8 @@ object Server { fun mainServer() { - CoroutineScope(Dispatchers.IO).launch { + val scope = CoroutineScope(Dispatchers.IO) + scope.launch { while (true) { for (client in clientAudios) { if (!client.isConnected) {