代码格式整理
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.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.IOException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
@ -11,57 +13,48 @@ import java.net.Socket
|
||||
import javax.sound.sampled.AudioSystem
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
val SERVER_PORT = 7860
|
||||
val ECHO_CANCELLATION = true
|
||||
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 {
|
||||
class Client {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
mainClient()
|
||||
Client().mainClient()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class DataPack(
|
||||
val timestamp: Long,
|
||||
val opus: ByteArray,
|
||||
) : Serializable
|
||||
val bufferOut = ByteArray(BUFFER_SIZE)
|
||||
val targetDataLine = AudioSystem.getTargetDataLine(AUDIO_FORMAT).also {
|
||||
it.open(AUDIO_FORMAT, bufferOut.size)
|
||||
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 t = measureTimeMillis {
|
||||
socket.connect(InetSocketAddress("zedo.top", SERVER_PORT))
|
||||
// socket.connect(InetSocketAddress("127.0.0.1", SERVER_PORT))
|
||||
socket.connect(InetSocketAddress(SERVER_HOST, SERVER_PORT))
|
||||
}
|
||||
println("connect time $t")
|
||||
"connect time $t".loge()
|
||||
|
||||
val out = ObjectOutputStream(socket.getOutputStream())
|
||||
val inp = ObjectInputStream(socket.getInputStream())
|
||||
|
||||
val bufferOut = ByteArray(BUFFER_SIZE * 2)
|
||||
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) {
|
||||
scope.launch {
|
||||
while (true) {
|
||||
targetDataLine.read(bufferOut, 0, bufferOut.size)
|
||||
try {
|
||||
@ -74,7 +67,7 @@ fun mainClient() = runBlocking {
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
scope.launch {
|
||||
while (true) {
|
||||
try {
|
||||
// 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.OpusEncoder
|
||||
import io.github.jaredmdobson.concentus.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
/**
|
||||
* Created by LoliBall on 2024/8/31 5:26.
|
||||
* 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 {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Server.main(emptyArray())
|
||||
}
|
||||
delay(1000)
|
||||
Client.main(emptyArray())
|
||||
delay(Long.MAX_VALUE)
|
||||
}
|
||||
|
||||
fun OpusEncoder.encode(
|
||||
pcm: ByteArray,
|
||||
opusBuffer: ByteArray = ByteArray(pcm.size)
|
||||
): DataPack {
|
||||
val encodeOpusBuffer = mutableMapOf<OpusEncoder, ByteArray>()
|
||||
|
||||
// 1275是opus单帧包最大值
|
||||
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)
|
||||
"compress rate: %.2f%%".format(bytesEncoded / pcm.size.toFloat() * 100).loge()
|
||||
return DataPack(System.currentTimeMillis(), opusBuffer.sliceArray(0..<bytesEncoded))
|
||||
}
|
||||
|
||||
fun OpusDecoder.decode(
|
||||
pack: DataPack,
|
||||
output: ByteArray,
|
||||
) {
|
||||
decode(pack.opus, 0, pack.opus.size, output, 0, PACKET_SAMPLES, false)
|
||||
fun OpusDecoder.decode(pack: DataPack, pcm: ByteArray) {
|
||||
decode(pack.opus, 0, pack.opus.size, pcm, 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.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.IOException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
@ -13,15 +15,15 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||
val clientAudios = CopyOnWriteArrayList<ClientAudio>()
|
||||
|
||||
object Server {
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
mainServer()
|
||||
}
|
||||
}
|
||||
|
||||
fun mainServer() = runBlocking {
|
||||
//混音
|
||||
launch(Dispatchers.IO) {
|
||||
fun mainServer() {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (true) {
|
||||
for (client in clientAudios) {
|
||||
if (!client.isConnected) {
|
||||
@ -32,7 +34,7 @@ fun mainServer() = runBlocking {
|
||||
}
|
||||
|
||||
for (client in clientAudios) {
|
||||
launch(Dispatchers.IO) {
|
||||
launch {
|
||||
for (audio in clientAudios) {
|
||||
if (audio != client || !ECHO_CANCELLATION) {
|
||||
client.mix(audio.sample)
|
||||
@ -50,23 +52,25 @@ fun mainServer() = runBlocking {
|
||||
println("Accepted connection from " + socket.remoteSocketAddress)
|
||||
clientAudios += ClientAudio(socket)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ClientAudio(val socket: Socket) {
|
||||
|
||||
val inp = ObjectInputStream(socket.getInputStream())
|
||||
val out = ObjectOutputStream(socket.getOutputStream())
|
||||
val buffer = ByteArray(BUFFER_SIZE * 2)
|
||||
val sample = FloatArray(BUFFER_SIZE)
|
||||
val mixSample = FloatArray(BUFFER_SIZE)
|
||||
val mixBuffer = ByteArray(BUFFER_SIZE * 2)
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
val sample = FloatArray(PACKET_SAMPLES)
|
||||
val mixSample = FloatArray(PACKET_SAMPLES)
|
||||
val mixBuffer = ByteArray(BUFFER_SIZE)
|
||||
|
||||
val decoder = OpusDecoder(48000, 1)
|
||||
val encoder = OpusEncoder(48000, 1, OpusApplication.OPUS_APPLICATION_AUDIO).also {
|
||||
it.bitrate = 96000
|
||||
it.forceMode = OpusMode.MODE_CELT_ONLY
|
||||
it.signalType = OpusSignal.OPUS_SIGNAL_MUSIC
|
||||
it.complexity = 0
|
||||
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
|
||||
}
|
||||
|
||||
var isConnected = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user