为什么我的声音有延迟?
我正在开发一个用于处理声音数据的应用程序系统。第一个应用程序只是从麦克风插孔读取数据并将数据发送到下一个应用程序。主循环重复执行这段代码:
0 : Globals.mySleep(waitTime); // tells the thread to sleep for the proper amount of time for a given data format
1 : inputLine.read(buffer, 0, bufferSize); // reads sound data from the microphone jack into buffer
2 : if(connections.get(REGISTER) != null) { // if the next application is connected
3 : DataSlice slice = new DataSlice(buffer, serialIDCounter++, getDeviceName()); // create a slice of data to send, containing the sound data
4 : try{
5 : connections.get(REGISTER).sendDataSlice(slice); // send the data to the next application. supposed to block until next application receives the data
6 : connections.get(REGISTER).flush(); // make sure data gets sent
7 : } catch (IOException e) {
8 : // Stream has been broken. Shut Down
9 : close();
10: }
11: }
当我启动系统时,它总是落后几秒钟。如果我暂停系统(GUI 应用程序告诉输入应用程序后面的应用程序停止从输入应用程序接收数据,因此输入应用程序在暂停时应在第 5 行阻塞),等待,然后再次播放,系统会额外滞后多长时间我刚刚停下来。例如,如果一开始有 10 秒的延迟,然后暂停 5 秒,然后再次播放,则会延迟 15 秒。
当我将程序作为可运行的 jar 文件运行时,会发生这种情况。当我从 Eclipse 运行它时,它不会发生。
我在两台计算机上对此进行了测试,两台计算机都运行 Ubuntu Linux 10.04 LTS。它发生在一个人身上,而不是另一个人身上。另一方面,当我尝试从 Eclipse 运行它时,我确实遇到了一个完全不同的问题。不知道该怎么做。如果您想了解计算机的一些规格,我很乐意提供给您。只需告诉我您想要什么规格以及如何获得它们即可。
谁能告诉我什么可能导致延迟?谢谢。
--编辑--
根据安德鲁的建议,我创建了我认为是 SSCCE 的内容:
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.sound.sampled.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main implements MouseListener{
// Class that reads a signal from Line-in source and sends that signal
// to either a recorder module or the signal-viewing pipeline
public class PlayThread extends Thread {
byte[] buffer = new byte[bufferSize];
boolean playing = false;
boolean connected = false;
PlayThread() {}
public void run() {
while(true) {
try {
sleep(waitTime);
inputLine.read(buffer, 0, bufferSize);
if(connected) {
while(!playing)
sleep(100);
int max = 0;
for(int i = 0; i < buffer.length; i++) {
if(Math.abs(buffer[i]) > max)
max = Math.abs(buffer[i]);
}
System.out.println("Max: " + max);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setPlaying(boolean playing) {
this.playing = playing;
}
public void setConnected(boolean connected) {
this.connected = connected;
}
}
TargetDataLine inputLine;
AudioFormat format;
float sampleRate;
int sampleSizeBits;
int channels;
int waitTime;
int bufferSize;
int slicesPerSecond;
int windowSize = 512;
PlayThread pThread;
JFrame gui = new JFrame("Sound Lag");
JPanel panel = new JPanel();
JButton play = new JButton("Play"), pause = new JButton("Pause"),
connect = new JButton("Connect"), disconnect = new JButton("Disconnect");
Main() {
sampleRate = 44100;
sampleSizeBits = 16;
channels = 2;
bufferSize = (sampleSizeBits/8)*channels*windowSize;
slicesPerSecond = (int) ((sampleRate/(float)channels)/(float)windowSize);
waitTime = (int)((((1000f/sampleRate)/(float)sampleSizeBits)/2f)*8f*(float)bufferSize);
play.addMouseListener(this);
pause.addMouseListener(this);
connect.addMouseListener(this);
disconnect.addMouseListener(this);
panel.add(play);
panel.add(pause);
panel.add(connect);
panel.add(disconnect);
gui.add(panel);
gui.setVisible(true);
gui.pack();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void read() {
// Open line from line-in
format = new AudioFormat(sampleRate, sampleSizeBits, channels, true, true);
// Obtain and open the lines.
inputLine = getTargetDataLine();
pThread = new PlayThread();
pThread.start();
}
private TargetDataLine getTargetDataLine() {
try {
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
for (Mixer.Info mi : AudioSystem.getMixerInfo()) {
TargetDataLine dataline = null;
try {
Mixer mixer = AudioSystem.getMixer(mi);
dataline = (TargetDataLine)mixer.getLine(info);
dataline.open(format);
dataline.start();
return dataline;
}
catch (Exception e) {}
if (dataline != null)
try {
dataline.close();
}
catch (Exception e) {}
}
}
catch (Exception e) {}
return null;
}
public static void main(String[] args) {
Main main = new Main();
main.read();
}
@Override
public void mouseClicked(MouseEvent arg0) {
if(arg0.getSource() == play) {
System.out.println("Playing");
pThread.setPlaying(true);
}
else if(arg0.getSource() == pause) {
System.out.println("Paused");
pThread.setPlaying(false);
}
else if(arg0.getSource() == connect) {
System.out.println("Connected");
pThread.setConnected(true);
}
else if(arg0.getSource() == disconnect) {
System.out.println("Disconnected");
pThread.setConnected(false);
}
}
@Override public void mouseEntered(MouseEvent arg0) {}
@Override public void mouseExited(MouseEvent arg0) {}
@Override public void mousePressed(MouseEvent arg0) {}
@Override public void mouseReleased(MouseEvent arg0) {}
}
此代码生成窗口中有四个按钮:播放、暂停、连接和断开连接。如果按播放,程序就好像处于“播放”模式。如果单击“连接”,就好像声音输入应用程序已连接到下一个模块。
要进行测试,请执行以下操作:
将声音设备连接到麦克风插孔(但不播放任何内容)。
从此代码创建可运行的 jar 文件。
从终端运行文件。
点击“播放”。
单击“连接”。
此时,您应该会在终端中看到一堆较小的数字。
在您的声音设备上,开始播放声音。
您应该立即开始在终端中看到更大的数字。
停止在声音设备上播放声音(应返回到终端中较小的数字)。
点击“暂停”。
等待 5 秒钟。
单击“播放”。
开始使用音频设备播放声音。
这就是 bug 出现的地方。如果我在 Eclipse 中运行这段代码,我会立即再次得到更大的数字。如果我只是运行 jar 文件,会有 5 秒的延迟,然后我会得到更大的数字。
有什么新想法吗?
I'm working on a system of applications for processing sound data. The first application simply reads from a microphone jack and sends the data to the next application. The main loop repeatedly performs this code:
0 : Globals.mySleep(waitTime); // tells the thread to sleep for the proper amount of time for a given data format
1 : inputLine.read(buffer, 0, bufferSize); // reads sound data from the microphone jack into buffer
2 : if(connections.get(REGISTER) != null) { // if the next application is connected
3 : DataSlice slice = new DataSlice(buffer, serialIDCounter++, getDeviceName()); // create a slice of data to send, containing the sound data
4 : try{
5 : connections.get(REGISTER).sendDataSlice(slice); // send the data to the next application. supposed to block until next application receives the data
6 : connections.get(REGISTER).flush(); // make sure data gets sent
7 : } catch (IOException e) {
8 : // Stream has been broken. Shut Down
9 : close();
10: }
11: }
When I start the system, it is always several seconds behind. If I pause the system (GUI application tells the application following the input application to stop receiving data from the input application, so the input application should block at line 5 when paused), wait, then play again, the system lags additionally by however long I had just paused for. For example, if it started out with a 10-second lag, then paused for 5 seconds, and played again, it would then be lagging by 15 seconds.
This occurs when I run the program as a runnable jar file. It does not occur when I run it from Eclipse.
I've tested this on two computers, both running Ubuntu Linux 10.04 LTS. It occurs on one, not the other. Although on the other, I do get a whole different problem when I try to run it from Eclipse. Not sure what to make of this. If you would like some specs on the computers, I'd be happy to give them to you. Just tell me what specs you want and how to get them.
Could anyone tell me what might be causing the lag? Thanks.
--EDIT--
Per Andrew's suggestion, I created what I believe to be a SSCCE:
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.sound.sampled.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main implements MouseListener{
// Class that reads a signal from Line-in source and sends that signal
// to either a recorder module or the signal-viewing pipeline
public class PlayThread extends Thread {
byte[] buffer = new byte[bufferSize];
boolean playing = false;
boolean connected = false;
PlayThread() {}
public void run() {
while(true) {
try {
sleep(waitTime);
inputLine.read(buffer, 0, bufferSize);
if(connected) {
while(!playing)
sleep(100);
int max = 0;
for(int i = 0; i < buffer.length; i++) {
if(Math.abs(buffer[i]) > max)
max = Math.abs(buffer[i]);
}
System.out.println("Max: " + max);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setPlaying(boolean playing) {
this.playing = playing;
}
public void setConnected(boolean connected) {
this.connected = connected;
}
}
TargetDataLine inputLine;
AudioFormat format;
float sampleRate;
int sampleSizeBits;
int channels;
int waitTime;
int bufferSize;
int slicesPerSecond;
int windowSize = 512;
PlayThread pThread;
JFrame gui = new JFrame("Sound Lag");
JPanel panel = new JPanel();
JButton play = new JButton("Play"), pause = new JButton("Pause"),
connect = new JButton("Connect"), disconnect = new JButton("Disconnect");
Main() {
sampleRate = 44100;
sampleSizeBits = 16;
channels = 2;
bufferSize = (sampleSizeBits/8)*channels*windowSize;
slicesPerSecond = (int) ((sampleRate/(float)channels)/(float)windowSize);
waitTime = (int)((((1000f/sampleRate)/(float)sampleSizeBits)/2f)*8f*(float)bufferSize);
play.addMouseListener(this);
pause.addMouseListener(this);
connect.addMouseListener(this);
disconnect.addMouseListener(this);
panel.add(play);
panel.add(pause);
panel.add(connect);
panel.add(disconnect);
gui.add(panel);
gui.setVisible(true);
gui.pack();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void read() {
// Open line from line-in
format = new AudioFormat(sampleRate, sampleSizeBits, channels, true, true);
// Obtain and open the lines.
inputLine = getTargetDataLine();
pThread = new PlayThread();
pThread.start();
}
private TargetDataLine getTargetDataLine() {
try {
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
for (Mixer.Info mi : AudioSystem.getMixerInfo()) {
TargetDataLine dataline = null;
try {
Mixer mixer = AudioSystem.getMixer(mi);
dataline = (TargetDataLine)mixer.getLine(info);
dataline.open(format);
dataline.start();
return dataline;
}
catch (Exception e) {}
if (dataline != null)
try {
dataline.close();
}
catch (Exception e) {}
}
}
catch (Exception e) {}
return null;
}
public static void main(String[] args) {
Main main = new Main();
main.read();
}
@Override
public void mouseClicked(MouseEvent arg0) {
if(arg0.getSource() == play) {
System.out.println("Playing");
pThread.setPlaying(true);
}
else if(arg0.getSource() == pause) {
System.out.println("Paused");
pThread.setPlaying(false);
}
else if(arg0.getSource() == connect) {
System.out.println("Connected");
pThread.setConnected(true);
}
else if(arg0.getSource() == disconnect) {
System.out.println("Disconnected");
pThread.setConnected(false);
}
}
@Override public void mouseEntered(MouseEvent arg0) {}
@Override public void mouseExited(MouseEvent arg0) {}
@Override public void mousePressed(MouseEvent arg0) {}
@Override public void mouseReleased(MouseEvent arg0) {}
}
This code produces a window with four buttons in it: play, pause, connect, and disconnect. If you press play, it is as if the program were in "play" mode. If you click connect, it is as if the sound input application were connected to the next module.
To test, do the following:
Connect a sound device to your microphone jack (but don't play anything).
Create the runnable jar file from this code.
Run the file from a terminal.
Click "play".
Click "connect".
At this point, you should see a bunch of smaller numbers going down the terminal.
On your sound device, start playing sounds.
You should instantly start seeing bigger numbers in the terminal.
Stop playing sounds on sound device (should go back to smaller numbers in terminal).
Click "pause".
Wait 5 seconds.
Click "Play".
Start playing sound with audio device.
This is where the bug comes in. If I am running this code in Eclipse, I instantly get bigger numbers again. If I am just running the jar file, there is a 5-second delay, then I get the bigger numbers.
Any new thoughts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
它是固定的。每当我想让声音流继续运行时(每当我按下播放键时),我都会关闭当前的流并打开一个新的流。
我没有意识到 TargetDataLine 实际上保存了一个声音数据缓冲区,每当调用 read 方法时就会从中选取该缓冲区。
看起来当我从 Eclipse 运行应用程序时,它使用的 TargetDataLine 类型与我将其作为可运行的 jar 文件运行时不同。缓冲区之间的大小差异证明了这一点。尽管大小差异仅约为 2 倍,所以我认为问题不在于缓冲区的大小,而在于与获取的 TargetDataLines 有关的其他内容。
奇怪的是,删除 Globals.mySleep(waitTime) 可以修复 SSCCE,但不能修复它应该代表的真正程序。
我尝试了排水和冲洗管线,而不是更换它,但这两种方法似乎都不起作用,尽管我可能使用了错误的方法。
所以问题是:数据线的缓冲区被填满,当程序没有播放时,缓冲区没有被清空,所以当它开始播放时,它继续以通常的播放速率从缓冲区获取数据,导致它落后。
解决办法是:当节目开始播放时,更换DataLine。
--编辑--
进一步观察表明,当我从 Eclipse 运行时,它似乎使用了与我作为 jar 文件运行时不同的 JRE。我将默认的 java 程序设置为 java-6-sun 而不是 java-6-openjdk,并且它在 jar 文件中运行良好。
另外,我尝试在另一台计算机上运行更换DataLine的方法。在这台电脑上,我的信号出现了严重的中断。拉出新的数据线似乎需要更长的时间,所以我认为这行不通。现在,我只是随时从 DataLine 中读取数据。如果系统暂停,我只是不会向任何地方发送信号。
It's fixed. Whenever I want to get the sound stream going (whenever I press play), I close the current stream and open a new one.
I didn't realize the TargetDataLine actually held a buffer of sound data that just gets picked from whenever the read method is called.
It looks like when I ran the application from Eclipse, it was using a different type of TargetDataLine than when I ran it as a runnable jar file. This was evidenced by a difference in size between the buffers. Although the size difference was only about a factor of 2, so I think the problem wasn't in the size of the buffer, but in something else having to do with the TargetDataLines that were fetched.
Oddly enough, removing Globals.mySleep(waitTime) worked in fixing the SSCCE, but not the real program it was supposed to represent.
I tried both draining and flushing the Line, instead of replacing it, but neither of these seemed to work, though I may have been using them wrong.
So the problem was: The DataLine's buffer was getting filled up and while the program was not playing, the buffer was not being emptied, so when it did start playing, it continued fetching data from the buffer at the usual play rate, causing it to lag behind.
The solution is: When the program starts playing, replace the DataLine.
--EDIT--
Further observation shows that when I ran from Eclipse, it seemed to be using a different JRE than when I ran as a jar file. I set the default java program to be java-6-sun instead of java-6-openjdk and it works fine from the jar file.
Also, I tried running the method of replacing the DataLine on a different computer. On this computer, I was getting a nasty break in the signal. It seemed to be taking longer to pull a new DataLine, so I decided that wasn't going to work. Now, I simply read from the DataLine at all times. I just don't send the signal anywhere if the system is paused.
我发现在这种情况下最好的办法是使用分析器,http://www.quest.com/jprobe/software_download.aspx 你可以得到这个java分析器的免费试用版,它会逐行告诉你花费了多少时间和多少当它执行时,您应该能够准确地查明是什么导致您的代码速度减慢。
希望这有帮助,
埃蒙
I find the best thing to do in situations like this, where your code is slow but you dont know why is to use a profiler, http://www.quest.com/jprobe/software_download.aspx you can get a free trail of this java profiler and it will tell you line by line how much time is spent and how many times it is executed, you should be able to pinpoint exactly what is slowing you code down with this.
Hope this helps,
Eamonn
我怀疑这里的“正确”
waitTime
是“0”。如果您想要的不仅仅是怀疑,我建议您发布 SSCCE (不带行号)。
I suspect the 'proper'
waitTime
here is '0'.If you want something more than suspicions, I recommend you post an SSCCE (without the line numbers).