全部测试

This commit is contained in:
zedoCN 2024-08-31 02:04:45 +08:00
commit fa8a0777e6
16 changed files with 539 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/VoiceChat.iml" filepath="$PROJECT_DIR$/VoiceChat.iml" />
</modules>
</component>
</project>

11
VoiceChat.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

55
src/AudioConvert.java Normal file
View File

@ -0,0 +1,55 @@
import javax.sound.sampled.AudioFormat;
public class AudioConvert {
public static AudioFormat getAudioFormat(int sampleRate, int channels) {
return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, 16, channels, channels * 2, sampleRate, false);
}
/**
* float[] 转换为 short byte[]
*
* @param input 输入的 float[] 数据
* @param output 转换后的 byte[] 数据
*/
public static void convertFloatToShortByte(float[] input, byte[] output) {
if (input.length * 2 != output.length) {
throw new IllegalArgumentException("output byte array length must be twice the length of the input float array.");
}
for (int i = 0; i < input.length; i++) {
// float 值限制在 -1.0 1.0 之间
float sample = Math.min(Math.max(input[i], -1.0f), 1.0f);
// float 值转换为 short
short shortSample = (short) (sample * 32767);
// short 值转换为两个字节并存储在 lineBuffer
output[i * 2] = (byte) (shortSample & 0xFF); // 低位字节
output[i * 2 + 1] = (byte) ((shortSample >> 8) & 0xFF); // 高位字节
}
}
/**
* short byte[] 转换为 float[]
*
* @param input 输入的 byte[] 数据每两个字节表示一个 short
* @param output 转换后的 float[] 数据
*/
public static void convertShortByteToFloat(byte[] input, float[] output) {
if (input.length != output.length * 2) {
throw new IllegalArgumentException("Input byte array length must be twice the length of the output float array.");
}
for (int i = 0; i < output.length; i++) {
// 将两个字节转换为 short
int low = input[i * 2] & 0xFF;
int high = input[i * 2 + 1] & 0xFF;
short shortSample = (short) ((high << 8) | low);
// short 值转换为 float
output[i] += shortSample / 32768.0f;
}
}
}

33
src/AudioInputPlayer.java Normal file
View File

@ -0,0 +1,33 @@
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.util.Arrays;
public class AudioInputPlayer implements IAudioProcessor {
private TargetDataLine targetDataLine;
private byte[] buffer;
public AudioInputPlayer() {
}
@Override
public void process(float[] samples, int channels, int sampleRate) {
if (targetDataLine == null) {
buffer = new byte[samples.length * 2];
var format = AudioConvert.getAudioFormat(sampleRate, channels);
try {
targetDataLine = AudioSystem.getTargetDataLine(format);
targetDataLine.open(format, samples.length * 2);
} catch (LineUnavailableException e) {
throw new RuntimeException(e);
}
targetDataLine.start();
}
targetDataLine.read(buffer, 0, buffer.length);
//System.out.println(Arrays.toString(buffer));
AudioConvert.convertShortByteToFloat(buffer, samples);
//System.out.println(Arrays.toString(samples));
}
}

75
src/AudioOutputLine.java Normal file
View File

@ -0,0 +1,75 @@
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import java.util.Arrays;
public class AudioOutputLine {
IAudioProcessor audioProcessor;
SourceDataLine line;
float[] buffer;
byte[] lineBuffer;
int channels;
int sampleRate;
private final Thread AudioOutputThread = new Thread("AudioOutputLineThread") {
@Override
public void run() {
while (!isInterrupted()) {
//清除缓冲区
Arrays.fill(buffer, 0f);
//处理
if (audioProcessor != null)
audioProcessor.process(buffer, channels, sampleRate);
AudioConvert.convertFloatToShortByte(buffer, lineBuffer);
//写入音频线
line.write(lineBuffer, 0, lineBuffer.length);
}
}
};
public void setAudioProcessor(IAudioProcessor audioProcessor) {
this.audioProcessor = audioProcessor;
}
public void open(int sampleRate, int channels, int bufferSize) throws LineUnavailableException {
this.sampleRate = sampleRate;
this.channels = channels;
bufferSize = bufferSize * channels;//所有通道的缓冲区大小
buffer = new float[bufferSize];
lineBuffer = new byte[buffer.length * 2];//音频线的字节缓冲区 short格式
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate, 16, channels, channels * 2, sampleRate, false);
//释放原音频线
if (line != null) {
line.stop();
line.close();
}
//使用系统默认源数据线路
line = AudioSystem.getSourceDataLine(format);
//启动线路
line.open(format, lineBuffer.length);
line.start();
//启动线程
if (!AudioOutputThread.isAlive())
AudioOutputThread.start();
}
/**
* 关闭
*/
public void close() {
if (AudioOutputThread.isAlive())
AudioOutputThread.interrupt();
}
}

View File

@ -0,0 +1,33 @@
public interface AudioSampleInputStream {
/**
* 打开音频流
*
* @param sampleRate 采样率
* @param channels 通道数
* @throws Exception 如果音频流无法打开
*/
void open(int sampleRate, int channels) throws Exception;
/**
* 从音频流中读取采样数据
*
* @param samples 采样数据的缓冲区
* @param offset 缓冲区中的起始偏移量
* @param length 需要读取的采样数据的数量
* @return 实际读取的采样数
* @throws Exception 如果读取时发生错误
*/
int read(float[] samples, int offset, int length) ;
/**
* 关闭音频流
*/
void close();
/**
* 检查音频流是否已到达结尾
*
* @return 如果音频流已到结尾则返回true
*/
boolean isEndOfStream();
}

View File

@ -0,0 +1,61 @@
public class BufferedAudioPlayer implements IAudioProcessor, IPlayTime {
// 音频缓冲区
private final float[] buffer;
// 当前帧位置
private int position = 0;
// 采样率
private int sampleRate;
// 通道数
private int channels;
public BufferedAudioPlayer(float[] buffer) {
this.buffer = buffer;
}
@Override
public void process(float[] samples, int channels, int sampleRate) {
// 缓存采样率和通道数避免在循环内重复设置
this.channels = channels;
this.sampleRate = sampleRate;
int bufferLength = buffer.length; // 缓存缓冲区长度
int positionInBuffer = position * channels;
for (int i = 0; i < samples.length; i++) {
// 循环处理样本
samples[i] += buffer[positionInBuffer + i];
// 如果到达缓冲区末尾重置位置并退出处理
if (positionInBuffer + i >= bufferLength) {
position = 0;
return;
}
}
// 更新当前位置
position += samples.length / channels;
}
@Override
public long getTime() {
return position * 1000L / sampleRate;
}
@Override
public void setTime(long time) {
position = (int) (time * sampleRate / 1000);
// 限制 position 在有效范围内
if (position < 0) {
position = 0;
} else if (position >= buffer.length / channels) {
position = buffer.length / channels - 1;
}
}
@Override
public long getDuration() {
return buffer.length * 1000L / ((long) channels * sampleRate);
}
}

72
src/Client.java Normal file
View File

@ -0,0 +1,72 @@
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;
public class Client {
public static InputStream in;
public static OutputStream out;
public static SourceDataLine sourceDataLine;
public static TargetDataLine targetDataLine;
public static byte[] bufferOut;
public static byte[] bufferIn;
public static float[] sample;
public static int SAMPLE_RATE = 48000;
public static int CHANNELS = 1;
public static AudioFormat format = AudioConvert.getAudioFormat(SAMPLE_RATE, CHANNELS);
public static Socket socket;
public static void main(String[] args) throws Exception {
socket = new Socket("127.0.0.1", 11451);
in = socket.getInputStream();
out = socket.getOutputStream();
sample = new float[Server.BUFFER_SIZE];
bufferIn = new byte[Server.BUFFER_SIZE * 2];
bufferOut = new byte[Server.BUFFER_SIZE * 2];
targetDataLine = AudioSystem.getTargetDataLine(format);
targetDataLine.open(format, bufferIn.length);
targetDataLine.start();
sourceDataLine = AudioSystem.getSourceDataLine(format);
sourceDataLine.open(format, bufferIn.length);
sourceDataLine.start();
new Thread(() -> {
while (true) {
//Arrays.fill(bufferOut, (byte) 0);
targetDataLine.read(bufferOut, 0, bufferOut.length);
try {
out.write(bufferOut, 0, bufferOut.length);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true) {
try {
in.read(bufferIn, 0, bufferIn.length);
} catch (IOException _) {
}
sourceDataLine.write(bufferIn, 0, bufferIn.length);
}
}).start();
/*
audioOutputLine.setAudioProcessor();
audioOutputLine.open(48000, 1, 20480);*/
}
}

15
src/IAudioProcessor.java Normal file
View File

@ -0,0 +1,15 @@
/**
* 音频处理器接口
*/
public interface IAudioProcessor {
/**
* 处理
*
* @param samples 采样缓冲区
* @param channels 通道数
* @param sampleRate 采样率
*/
void process(float[] samples, int channels, int sampleRate);
}

6
src/IPlayController.java Normal file
View File

@ -0,0 +1,6 @@
public interface IPlayController {
void pause();
void resume();
}

11
src/IPlayEvent.java Normal file
View File

@ -0,0 +1,11 @@
public interface IPlayEvent {
void play();
void pause();
void resume();
void reset();
void stop();
}

22
src/IPlayTime.java Normal file
View File

@ -0,0 +1,22 @@
public interface IPlayTime {
/**
* 获取时间
*
* @return 当前播放位置
*/
long getTime();
/**
* 设置时间
*
* @param time 当前播放位置
*/
void setTime(long time);
/**
* 获取时长
*
* @return 总时长
*/
long getDuration();
}

14
src/Main.java Normal file
View File

@ -0,0 +1,14 @@
import java.net.ServerSocket;
import java.net.Socket;
public class Main {
public static void main(String[] args) throws Exception {
AudioOutputLine audioOutputLine = new AudioOutputLine();
audioOutputLine.setAudioProcessor(new AudioInputPlayer());
audioOutputLine.open(48000, 1, 20480);
}
}

86
src/Server.java Normal file
View File

@ -0,0 +1,86 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Server {
public static List<ClientAudio> clientAudios = new CopyOnWriteArrayList<>();
public static int BUFFER_SIZE = 2048;
public static float[] mixerSample = new float[BUFFER_SIZE];
public static byte[] buffer = new byte[BUFFER_SIZE * 2];
public static void main(String[] args) throws Exception {
//混音
new Thread(() -> {
while (true) {
Arrays.fill(mixerSample, 0);
for (var client : clientAudios) {
if (!client.isConnected()) {
clientAudios.remove(client);
continue;
}
client.mixerSample(mixerSample);
}
AudioConvert.convertFloatToShortByte(mixerSample, buffer);
for (var client : clientAudios) {
client.send(buffer);
}
//System.out.println("混音");
}
}).start();
ServerSocket sever = new ServerSocket(11451);
while (true) {
Socket socket = sever.accept();
System.out.println("Accepted connection from " + socket.getRemoteSocketAddress());
clientAudios.add(new ClientAudio(socket));
}
}
public static class ClientAudio {
Socket socket;
InputStream in;
OutputStream out;
byte[] buffer = new byte[BUFFER_SIZE * 2];
public ClientAudio(Socket socket) {
this.socket = socket;
try {
in = socket.getInputStream();
out = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public boolean isConnected() {
return socket.isConnected();
}
public void send(byte[] buffer) {
try {
out.write(buffer);
} catch (IOException e) {
}
}
public void mixerSample(float[] sample) {
try {
in.read(buffer);
AudioConvert.convertShortByteToFloat(buffer, sample);
} catch (IOException e) {
//throw new RuntimeException(e);
}
}
}
}