全部测试
This commit is contained in:
commit
fa8a0777e6
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal 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
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/modules.xml
generated
Normal 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
11
VoiceChat.iml
Normal 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
55
src/AudioConvert.java
Normal 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
33
src/AudioInputPlayer.java
Normal 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
75
src/AudioOutputLine.java
Normal 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();
|
||||
}
|
||||
}
|
33
src/AudioSampleInputStream.java
Normal file
33
src/AudioSampleInputStream.java
Normal 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();
|
||||
}
|
61
src/BufferedAudioPlayer.java
Normal file
61
src/BufferedAudioPlayer.java
Normal 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
72
src/Client.java
Normal 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
15
src/IAudioProcessor.java
Normal 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
6
src/IPlayController.java
Normal file
@ -0,0 +1,6 @@
|
||||
public interface IPlayController {
|
||||
|
||||
void pause();
|
||||
|
||||
void resume();
|
||||
}
|
11
src/IPlayEvent.java
Normal file
11
src/IPlayEvent.java
Normal file
@ -0,0 +1,11 @@
|
||||
public interface IPlayEvent {
|
||||
void play();
|
||||
|
||||
void pause();
|
||||
|
||||
void resume();
|
||||
|
||||
void reset();
|
||||
|
||||
void stop();
|
||||
}
|
22
src/IPlayTime.java
Normal file
22
src/IPlayTime.java
Normal 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
14
src/Main.java
Normal 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
86
src/Server.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user