Java 语音聊天延迟
Java Voice Chat Lag
我正在制作一个语音 chat/messenger 程序,我有语音可以与聊天中的一个人一起工作,但是当我添加一秒钟时,语音会变得滞后和中断。我认为问题出在客户端音频接收 class。如果你不认为是,我会 link 把剩下的放在 pastebin 里。
package client;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class ClientAudioRec implements Runnable {
private ObjectInputStream i2;
private Socket s;
private AudioFormat af;
public ClientAudioRec(Socket s2, AudioFormat audioformat) {
s = s2;
af = audioformat;
}
public void run() {
try {
i2 = new ObjectInputStream(s.getInputStream());
} catch (IOException e2) {
e2.printStackTrace();
}
SourceDataLine inSpeaker = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
try {
inSpeaker = (SourceDataLine)AudioSystem.getLine(info);
inSpeaker.open(af);
} catch (LineUnavailableException e1) {
e1.printStackTrace();
}
int bytesRead = 0;
byte[] inSound = new byte[100];
inSpeaker.start();
while(true)
{
try{
bytesRead = i2.read(inSound, 0, inSound.length);
} catch (Exception e){
e.printStackTrace();
}
if(bytesRead >= 0)
{
inSpeaker.write(inSound, 0, bytesRead);
}
}
}
}
我怀疑你的服务器端语音代码:byte[] soundData = new byte[1];
。一个字节缓冲区?你能让 CPU 更努力地工作吗?哦,你也在你的客户端音频输入代码中这样做。
您发送语音的数据速率是多少? Cell phones 使用 20ms 帧。这些被完全采样(20 毫秒),然后传输到基站(20 毫秒),并可能发送到另一个小区 phone(20 毫秒),然后最后通过扬声器播放至少延迟 60 毫秒。没有听到不自然的延迟。单元 phone 数据速率为 8kbps,因此每帧为 160 位或 20 字节。我会将您的缓冲区大小增加到至少 20 个字节(可能高达 50 个字节),看看您是否有任何改进。
套接字的 "Quality of Service" 设置会影响性能。对于 VoIP,您需要低延迟连接。我不确定如何为 Java 套接字设置它;我将不得不做一些阅读。 TCP_NODELAY
是另一个要设置的选项(如果可能),以防止延迟确认减慢后续数据包的速度。发送许多小数据包时会发生这种情况。发送更大的数据包将减轻这种情况,这是将缓冲区增加到超过 1 字节的另一个原因!
编辑
与其发送许多微小的缓冲区,不如将数据累积成更大的固定大小的帧(例如 20 毫秒的数据),并且只发送完整的帧。要将数据累积到帧缓冲区中,您可以使用 #read(byte[] buffer, int offset, int length)
方法。例如:
byte[] buffer = new byte[100];
int offset = 0;
while(true) {
// Read as many bytes as possible, up to remaining space in buffer
int bytes_read = source.read(buffer, offset, buffer.length - offset);
if (bytes_read >= 0) {
// Accumulate number of bytes that has been read.
offset += bytes_read;
if (offset == buffer.length) {
// Buffer is full, send it.
sink.write(buffer, 0, buffer.length);
// Clear buffer for next frame
offset = 0;
}
} else {
break; // End of stream
}
}
如果读取了 30 个字节,则将它们读入 offset=0
处的缓冲区,并且 offset
递增到 30。如果在下一次读取中再读取 60 个字节,则将它们读入缓冲区从 offset=30
开始,offset
递增到 90。如果之后有 50 个字节可用,则只会读取 (buffer.length-offset
) 的 10 个字节来填充缓冲区。然后发送缓冲区,并将 offset
重置为零。剩余的 40 个字节(或者可能更多,因为数据不断到达)将在下一次调用时读取。
注意:您应该围绕 sink.write()
使用类似的循环,以防无法在一次调用中将整个缓冲区写入套接字。
我正在制作一个语音 chat/messenger 程序,我有语音可以与聊天中的一个人一起工作,但是当我添加一秒钟时,语音会变得滞后和中断。我认为问题出在客户端音频接收 class。如果你不认为是,我会 link 把剩下的放在 pastebin 里。
package client;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class ClientAudioRec implements Runnable {
private ObjectInputStream i2;
private Socket s;
private AudioFormat af;
public ClientAudioRec(Socket s2, AudioFormat audioformat) {
s = s2;
af = audioformat;
}
public void run() {
try {
i2 = new ObjectInputStream(s.getInputStream());
} catch (IOException e2) {
e2.printStackTrace();
}
SourceDataLine inSpeaker = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
try {
inSpeaker = (SourceDataLine)AudioSystem.getLine(info);
inSpeaker.open(af);
} catch (LineUnavailableException e1) {
e1.printStackTrace();
}
int bytesRead = 0;
byte[] inSound = new byte[100];
inSpeaker.start();
while(true)
{
try{
bytesRead = i2.read(inSound, 0, inSound.length);
} catch (Exception e){
e.printStackTrace();
}
if(bytesRead >= 0)
{
inSpeaker.write(inSound, 0, bytesRead);
}
}
}
}
我怀疑你的服务器端语音代码:byte[] soundData = new byte[1];
。一个字节缓冲区?你能让 CPU 更努力地工作吗?哦,你也在你的客户端音频输入代码中这样做。
您发送语音的数据速率是多少? Cell phones 使用 20ms 帧。这些被完全采样(20 毫秒),然后传输到基站(20 毫秒),并可能发送到另一个小区 phone(20 毫秒),然后最后通过扬声器播放至少延迟 60 毫秒。没有听到不自然的延迟。单元 phone 数据速率为 8kbps,因此每帧为 160 位或 20 字节。我会将您的缓冲区大小增加到至少 20 个字节(可能高达 50 个字节),看看您是否有任何改进。
套接字的 "Quality of Service" 设置会影响性能。对于 VoIP,您需要低延迟连接。我不确定如何为 Java 套接字设置它;我将不得不做一些阅读。 TCP_NODELAY
是另一个要设置的选项(如果可能),以防止延迟确认减慢后续数据包的速度。发送许多小数据包时会发生这种情况。发送更大的数据包将减轻这种情况,这是将缓冲区增加到超过 1 字节的另一个原因!
编辑
与其发送许多微小的缓冲区,不如将数据累积成更大的固定大小的帧(例如 20 毫秒的数据),并且只发送完整的帧。要将数据累积到帧缓冲区中,您可以使用 #read(byte[] buffer, int offset, int length)
方法。例如:
byte[] buffer = new byte[100];
int offset = 0;
while(true) {
// Read as many bytes as possible, up to remaining space in buffer
int bytes_read = source.read(buffer, offset, buffer.length - offset);
if (bytes_read >= 0) {
// Accumulate number of bytes that has been read.
offset += bytes_read;
if (offset == buffer.length) {
// Buffer is full, send it.
sink.write(buffer, 0, buffer.length);
// Clear buffer for next frame
offset = 0;
}
} else {
break; // End of stream
}
}
如果读取了 30 个字节,则将它们读入 offset=0
处的缓冲区,并且 offset
递增到 30。如果在下一次读取中再读取 60 个字节,则将它们读入缓冲区从 offset=30
开始,offset
递增到 90。如果之后有 50 个字节可用,则只会读取 (buffer.length-offset
) 的 10 个字节来填充缓冲区。然后发送缓冲区,并将 offset
重置为零。剩余的 40 个字节(或者可能更多,因为数据不断到达)将在下一次调用时读取。
注意:您应该围绕 sink.write()
使用类似的循环,以防无法在一次调用中将整个缓冲区写入套接字。