代码格式整理
This commit is contained in:
parent
7d20b9c841
commit
25805cd556
@ -1,7 +1,9 @@
|
|||||||
import io.github.jaredmdobson.concentus.*
|
import io.github.jaredmdobson.concentus.OpusApplication
|
||||||
|
import io.github.jaredmdobson.concentus.OpusDecoder
|
||||||
|
import io.github.jaredmdobson.concentus.OpusEncoder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
@ -11,57 +13,48 @@ import java.net.Socket
|
|||||||
import javax.sound.sampled.AudioSystem
|
import javax.sound.sampled.AudioSystem
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
val SERVER_PORT = 7860
|
class Client {
|
||||||
val ECHO_CANCELLATION = true
|
companion object {
|
||||||
val PACKET_SAMPLES = 2880
|
|
||||||
val SAMPLE_RATE = 48000
|
|
||||||
val CHANNELS = 1
|
|
||||||
val BUFFER_SIZE = PACKET_SAMPLES * CHANNELS
|
|
||||||
val AUDIO_FORMAT = AudioConvert.getAudioFormat(SAMPLE_RATE, CHANNELS)
|
|
||||||
|
|
||||||
object Client {
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
mainClient()
|
Client().mainClient()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DataPack(
|
val bufferOut = ByteArray(BUFFER_SIZE)
|
||||||
val timestamp: Long,
|
val targetDataLine = AudioSystem.getTargetDataLine(AUDIO_FORMAT).also {
|
||||||
val opus: ByteArray,
|
it.open(AUDIO_FORMAT, bufferOut.size)
|
||||||
) : Serializable
|
it.start()
|
||||||
|
}
|
||||||
|
|
||||||
fun mainClient() = runBlocking {
|
val bufferIn = ByteArray(BUFFER_SIZE)
|
||||||
|
val sourceDataLine = AudioSystem.getSourceDataLine(AUDIO_FORMAT).also {
|
||||||
|
it.open(AUDIO_FORMAT, bufferIn.size)
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
val decoder = OpusDecoder(SAMPLE_RATE, CHANNELS)
|
||||||
|
val encoder = OpusEncoder(SAMPLE_RATE, CHANNELS, OpusApplication.OPUS_APPLICATION_AUDIO).also {
|
||||||
|
it.bitrate = OPUS_BITRATE
|
||||||
|
it.forceMode = OPUS_MODE
|
||||||
|
it.signalType = OPUS_SIGNAL_TYPE
|
||||||
|
it.complexity = OPUS_COMPLEXITY
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun mainClient() {
|
||||||
val socket = Socket()
|
val socket = Socket()
|
||||||
|
|
||||||
val t = measureTimeMillis {
|
val t = measureTimeMillis {
|
||||||
socket.connect(InetSocketAddress("zedo.top", SERVER_PORT))
|
socket.connect(InetSocketAddress(SERVER_HOST, SERVER_PORT))
|
||||||
// socket.connect(InetSocketAddress("127.0.0.1", SERVER_PORT))
|
|
||||||
}
|
}
|
||||||
println("connect time $t")
|
"connect time $t".loge()
|
||||||
|
|
||||||
val out = ObjectOutputStream(socket.getOutputStream())
|
val out = ObjectOutputStream(socket.getOutputStream())
|
||||||
val inp = ObjectInputStream(socket.getInputStream())
|
val inp = ObjectInputStream(socket.getInputStream())
|
||||||
|
|
||||||
val bufferOut = ByteArray(BUFFER_SIZE * 2)
|
scope.launch {
|
||||||
val targetDataLine = AudioSystem.getTargetDataLine(AUDIO_FORMAT)
|
|
||||||
targetDataLine.open(AUDIO_FORMAT, bufferOut.size)
|
|
||||||
targetDataLine.start()
|
|
||||||
|
|
||||||
val bufferIn = ByteArray(BUFFER_SIZE * 2)
|
|
||||||
val sourceDataLine = AudioSystem.getSourceDataLine(AUDIO_FORMAT)
|
|
||||||
sourceDataLine.open(AUDIO_FORMAT, bufferIn.size)
|
|
||||||
sourceDataLine.start()
|
|
||||||
|
|
||||||
val decoder = OpusDecoder(SAMPLE_RATE, CHANNELS)
|
|
||||||
val encoder = OpusEncoder(SAMPLE_RATE, CHANNELS, OpusApplication.OPUS_APPLICATION_AUDIO).also {
|
|
||||||
it.bitrate = 96000
|
|
||||||
it.forceMode = OpusMode.MODE_CELT_ONLY
|
|
||||||
it.signalType = OpusSignal.OPUS_SIGNAL_MUSIC
|
|
||||||
it.complexity = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
targetDataLine.read(bufferOut, 0, bufferOut.size)
|
targetDataLine.read(bufferOut, 0, bufferOut.size)
|
||||||
try {
|
try {
|
||||||
@ -74,7 +67,7 @@ fun mainClient() = runBlocking {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
// if (`in`!!.available() > bufferIn.size * 2) {
|
// if (`in`!!.available() > bufferIn.size * 2) {
|
||||||
@ -91,5 +84,5 @@ fun mainClient() = runBlocking {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
10
src/DataPack.kt
Normal file
10
src/DataPack.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by LoliBall on 2024/8/31 23:02.
|
||||||
|
* https://github.com/WhichWho
|
||||||
|
*/
|
||||||
|
data class DataPack(
|
||||||
|
val timestamp: Long,
|
||||||
|
val opus: ByteArray,
|
||||||
|
) : Serializable
|
42
src/Main.kt
42
src/Main.kt
@ -1,30 +1,50 @@
|
|||||||
import io.github.jaredmdobson.concentus.OpusDecoder
|
import io.github.jaredmdobson.concentus.*
|
||||||
import io.github.jaredmdobson.concentus.OpusEncoder
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by LoliBall on 2024/8/31 5:26.
|
* Created by LoliBall on 2024/8/31 5:26.
|
||||||
* https://github.com/WhichWho
|
* https://github.com/WhichWho
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//val SERVER_HOST = "zedo.top"
|
||||||
|
val SERVER_HOST = "127.0.0.1"
|
||||||
|
val SERVER_PORT = 7860
|
||||||
|
val ECHO_CANCELLATION = true // 回声消除
|
||||||
|
|
||||||
|
// 2.5, 5, 10, 20, 40, 60 ms
|
||||||
|
// 120, 240, 480, 960, 1920, 2880
|
||||||
|
val PACKET_SAMPLES = 2880 // 48000Hz * 60ms = 2880
|
||||||
|
val SAMPLE_RATE = 48000 // 8000、12000、16000、24000、48000
|
||||||
|
val CHANNELS = 1
|
||||||
|
val BUFFER_SIZE = PACKET_SAMPLES * CHANNELS * 2 // * 2是short | 48000Hz * 2(short) * 60ms = 5760B
|
||||||
|
val AUDIO_FORMAT = AudioConvert.getAudioFormat(SAMPLE_RATE, CHANNELS)
|
||||||
|
|
||||||
|
val OPUS_BITRATE = OpusConstants.OPUS_AUTO // 500 - 300000
|
||||||
|
val OPUS_COMPLEXITY = 10 // 0: 快,质量差 10: 慢,质量好
|
||||||
|
val OPUS_SIGNAL_TYPE = OpusSignal.OPUS_SIGNAL_VOICE
|
||||||
|
val OPUS_MODE = OpusMode.MODE_HYBRID
|
||||||
|
|
||||||
fun main() = runBlocking {
|
fun main() = runBlocking {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
Server.main(emptyArray())
|
Server.main(emptyArray())
|
||||||
}
|
}
|
||||||
delay(1000)
|
delay(1000)
|
||||||
Client.main(emptyArray())
|
Client.main(emptyArray())
|
||||||
|
delay(Long.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OpusEncoder.encode(
|
val encodeOpusBuffer = mutableMapOf<OpusEncoder, ByteArray>()
|
||||||
pcm: ByteArray,
|
|
||||||
opusBuffer: ByteArray = ByteArray(pcm.size)
|
// 1275是opus单帧包最大值
|
||||||
): DataPack {
|
fun OpusEncoder.encode(pcm: ByteArray): DataPack {
|
||||||
|
val opusBuffer = encodeOpusBuffer[this] ?: ByteArray(1275).also { encodeOpusBuffer[this] = it }
|
||||||
val bytesEncoded = encode(pcm, 0, PACKET_SAMPLES, opusBuffer, 0, opusBuffer.size)
|
val bytesEncoded = encode(pcm, 0, PACKET_SAMPLES, opusBuffer, 0, opusBuffer.size)
|
||||||
|
"compress rate: %.2f%%".format(bytesEncoded / pcm.size.toFloat() * 100).loge()
|
||||||
return DataPack(System.currentTimeMillis(), opusBuffer.sliceArray(0..<bytesEncoded))
|
return DataPack(System.currentTimeMillis(), opusBuffer.sliceArray(0..<bytesEncoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OpusDecoder.decode(
|
fun OpusDecoder.decode(pack: DataPack, pcm: ByteArray) {
|
||||||
pack: DataPack,
|
decode(pack.opus, 0, pack.opus.size, pcm, 0, PACKET_SAMPLES, false)
|
||||||
output: ByteArray,
|
|
||||||
) {
|
|
||||||
decode(pack.opus, 0, pack.opus.size, output, 0, PACKET_SAMPLES, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> T.loge() = also { println(this) }
|
118
src/Program.java
118
src/Program.java
@ -1,118 +0,0 @@
|
|||||||
import io.github.jaredmdobson.concentus.*;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lostromb
|
|
||||||
*/
|
|
||||||
public class Program {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param args the command line arguments
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void test() {
|
|
||||||
try {
|
|
||||||
FileInputStream fileIn = new FileInputStream("a.pcm");
|
|
||||||
OpusEncoder encoder = new OpusEncoder(48000, 1, OpusApplication.OPUS_APPLICATION_AUDIO);
|
|
||||||
encoder.setBitrate(96000);
|
|
||||||
encoder.setForceMode(OpusMode.MODE_CELT_ONLY);
|
|
||||||
encoder.setSignalType(OpusSignal.OPUS_SIGNAL_MUSIC);
|
|
||||||
encoder.setComplexity(0);
|
|
||||||
|
|
||||||
OpusDecoder decoder = new OpusDecoder(48000, 1);
|
|
||||||
|
|
||||||
FileOutputStream fileOut = new FileOutputStream("b.pcm");
|
|
||||||
int packetSamples = 960;
|
|
||||||
byte[] inBuf = new byte[packetSamples * 2 * 2];
|
|
||||||
byte[] data_packet = new byte[1275];
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
long size = 0;
|
|
||||||
while (fileIn.available() >= inBuf.length) {
|
|
||||||
int bytesRead = fileIn.read(inBuf, 0, inBuf.length);
|
|
||||||
short[] pcm = BytesToShorts(inBuf, 0, inBuf.length);
|
|
||||||
int bytesEncoded = encoder.encode(pcm, 0, packetSamples, data_packet, 0, 1275);
|
|
||||||
//System.out.println(bytesEncoded + " bytes encoded");
|
|
||||||
|
|
||||||
size += bytesEncoded;
|
|
||||||
|
|
||||||
int samplesDecoded = decoder.decode(data_packet, 0, bytesEncoded, pcm, 0, packetSamples, false);
|
|
||||||
//System.out.println(samplesDecoded + " samples decoded");
|
|
||||||
byte[] bytesOut = ShortsToBytes(pcm);
|
|
||||||
fileOut.write(bytesOut, 0, bytesOut.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
long end = System.currentTimeMillis();
|
|
||||||
System.out.println("Time was " + (end - start) + "ms");
|
|
||||||
fileIn.close();
|
|
||||||
fileOut.close();
|
|
||||||
System.out.println("Done!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.out.println(e.getMessage());
|
|
||||||
} catch (OpusException e) {
|
|
||||||
System.out.println(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts interleaved byte samples (such as what you get from a capture device)
|
|
||||||
/// into linear short samples (that are much easier to work with)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static short[] BytesToShorts(byte[] input) {
|
|
||||||
return BytesToShorts(input, 0, input.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts interleaved byte samples (such as what you get from a capture device)
|
|
||||||
/// into linear short samples (that are much easier to work with)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static short[] BytesToShorts(byte[] input, int offset, int length) {
|
|
||||||
short[] processedValues = new short[length / 2];
|
|
||||||
for (int c = 0; c < processedValues.length; c++) {
|
|
||||||
short a = (short) (((int) input[(c * 2) + offset]) & 0xFF);
|
|
||||||
short b = (short) (((int) input[(c * 2) + 1 + offset]) << 8);
|
|
||||||
processedValues[c] = (short) (a | b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts linear short samples into interleaved byte samples, for writing to a file, waveout device, etc.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static byte[] ShortsToBytes(short[] input) {
|
|
||||||
return ShortsToBytes(input, 0, input.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts linear short samples into interleaved byte samples, for writing to a file, waveout device, etc.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static byte[] ShortsToBytes(short[] input, int offset, int length) {
|
|
||||||
byte[] processedValues = new byte[length * 2];
|
|
||||||
for (int c = 0; c < length; c++) {
|
|
||||||
processedValues[c * 2] = (byte) (input[c + offset] & 0xFF);
|
|
||||||
processedValues[c * 2 + 1] = (byte) ((input[c + offset] >> 8) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedValues;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,9 @@
|
|||||||
import io.github.jaredmdobson.concentus.*
|
import io.github.jaredmdobson.concentus.OpusApplication
|
||||||
|
import io.github.jaredmdobson.concentus.OpusDecoder
|
||||||
|
import io.github.jaredmdobson.concentus.OpusEncoder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
@ -13,15 +15,15 @@ import java.util.concurrent.CopyOnWriteArrayList
|
|||||||
val clientAudios = CopyOnWriteArrayList<ClientAudio>()
|
val clientAudios = CopyOnWriteArrayList<ClientAudio>()
|
||||||
|
|
||||||
object Server {
|
object Server {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
mainServer()
|
mainServer()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun mainServer() = runBlocking {
|
fun mainServer() {
|
||||||
//混音
|
|
||||||
launch(Dispatchers.IO) {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
for (client in clientAudios) {
|
for (client in clientAudios) {
|
||||||
if (!client.isConnected) {
|
if (!client.isConnected) {
|
||||||
@ -32,7 +34,7 @@ fun mainServer() = runBlocking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (client in clientAudios) {
|
for (client in clientAudios) {
|
||||||
launch(Dispatchers.IO) {
|
launch {
|
||||||
for (audio in clientAudios) {
|
for (audio in clientAudios) {
|
||||||
if (audio != client || !ECHO_CANCELLATION) {
|
if (audio != client || !ECHO_CANCELLATION) {
|
||||||
client.mix(audio.sample)
|
client.mix(audio.sample)
|
||||||
@ -52,21 +54,23 @@ fun mainServer() = runBlocking {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class ClientAudio(val socket: Socket) {
|
class ClientAudio(val socket: Socket) {
|
||||||
|
|
||||||
val inp = ObjectInputStream(socket.getInputStream())
|
val inp = ObjectInputStream(socket.getInputStream())
|
||||||
val out = ObjectOutputStream(socket.getOutputStream())
|
val out = ObjectOutputStream(socket.getOutputStream())
|
||||||
val buffer = ByteArray(BUFFER_SIZE * 2)
|
val buffer = ByteArray(BUFFER_SIZE)
|
||||||
val sample = FloatArray(BUFFER_SIZE)
|
val sample = FloatArray(PACKET_SAMPLES)
|
||||||
val mixSample = FloatArray(BUFFER_SIZE)
|
val mixSample = FloatArray(PACKET_SAMPLES)
|
||||||
val mixBuffer = ByteArray(BUFFER_SIZE * 2)
|
val mixBuffer = ByteArray(BUFFER_SIZE)
|
||||||
|
|
||||||
val decoder = OpusDecoder(48000, 1)
|
val decoder = OpusDecoder(SAMPLE_RATE, CHANNELS)
|
||||||
val encoder = OpusEncoder(48000, 1, OpusApplication.OPUS_APPLICATION_AUDIO).also {
|
val encoder = OpusEncoder(SAMPLE_RATE, CHANNELS, OpusApplication.OPUS_APPLICATION_AUDIO).also {
|
||||||
it.bitrate = 96000
|
it.bitrate = OPUS_BITRATE
|
||||||
it.forceMode = OpusMode.MODE_CELT_ONLY
|
it.forceMode = OPUS_MODE
|
||||||
it.signalType = OpusSignal.OPUS_SIGNAL_MUSIC
|
it.signalType = OPUS_SIGNAL_TYPE
|
||||||
it.complexity = 0
|
it.complexity = OPUS_COMPLEXITY
|
||||||
}
|
}
|
||||||
|
|
||||||
var isConnected = true
|
var isConnected = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user