我用简单的Swing图形编写了一段时间的声音,但由于某些原因我的帧速率不稳定。
通常我在后台线程上执行以下操作:
for(;;) {
// do some drawing
aPanel.updateABufferedImage();
// ask for asynchronous repaint
aPanel.repaint();
// write the sound
aSourceDataLine.write(bytes, 0, bytes.length);
}
通过调试,我认为我已经将问题追溯到SourceDataLine#write
的阻止行为。其文件陈述如下:
如果调用者尝试写入的数据多于当前编写的数据[...],则此方法将一直阻塞,直到写入所请求的数据量为止。
所以,这似乎意味着SourceDataLine
实际上有自己的缓冲区,当我们将我们的缓冲区传递给write
时它正在填充。它只在其自己的缓冲区已满时才会阻塞。这似乎是持久性:让它以可预测的方式阻止。
为了证明这个问题,这是一个最小的例子:
SourceDataLine
(没有声音)并计时。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.sound.sampled.*;
class FrameRateWithSound implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new FrameRateWithSound());
}
volatile boolean soundOn = true;
PaintPanel panel;
@Override
public void run() {
JFrame frame = new JFrame();
JPanel content = new JPanel(new BorderLayout());
final JCheckBox soundCheck = new JCheckBox("Sound", soundOn);
soundCheck.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
soundOn = soundCheck.isSelected();
}
});
panel = new PaintPanel();
content.add(soundCheck, BorderLayout.NORTH);
content.add(panel, BorderLayout.CENTER);
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
new Thread(new Worker()).start();
}
class Worker implements Runnable {
@Override
public void run() {
AudioFormat fmt = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
44100f, 8, 1, 1, 44100f, true
);
// just 0's
byte[] buffer = new byte[1000];
SourceDataLine line = null;
try {
line = AudioSystem.getSourceDataLine(fmt);
line.open(fmt);
line.start();
for(;;) {
panel.drawNextPixel();
panel.repaint();
if(soundOn) {
// time the write
long t = System.currentTimeMillis();
line.write(buffer, 0, buffer.length);
t = ( System.currentTimeMillis() - t );
System.out.println("sound:\t" + t);
}
// just so it doesn't fly off the handle
Thread.sleep(2);
}
} catch(Exception e) {
// lazy...
throw new RuntimeException(e);
} finally {
if(line != null) {
line.close();
}
}
}
}
class PaintPanel extends JPanel {
Dimension size = new Dimension(200, 100);
BufferedImage img = new BufferedImage(
size.width, size.height, BufferedImage.TYPE_INT_RGB);
int x, y;
int repaints;
long begin, prev;
String fps = "0";
PaintPanel() {
setPreferredSize(size);
setOpaque(false);
Graphics2D g = img.createGraphics();
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, size.width, size.height);
g.dispose();
}
synchronized void drawNextPixel() {
img.setRGB(x, y, img.getRGB(x, y) ^ 0xFFFFFF); // flip
if( ( ++x ) == size.width ) {
x = 0;
if( ( ++y ) == size.height ) {
y = 0;
}
}
}
@Override
protected synchronized void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, size.width, size.height, null);
long curr = System.currentTimeMillis();
// time this cycle
long cycle = ( curr - prev );
System.out.println("paint:\t" + cycle);
++repaints;
// time FPS every 1 second
if(curr - begin >= 1000) {
begin = curr;
fps = String.valueOf(repaints);
repaints = 0;
}
prev = curr;
g.setColor(Color.RED);
g.drawString(fps, 12, size.height - 12);
}
}
}
如果您对此感到好奇,我建议您实际运行该示例。
“播放”期间的典型System.out
Feed类似于以下内容:
sound: 0
paint: 2
sound: 0
paint: 2
sound: 0
paint: 3
sound: 0
paint: 2
paint: 2
sound: 325 // <- 'write' seems to be blocking here
sound: 0
paint: 328
sound: 0
paint: 2
这显示了write
的行为非常清楚:它在大部分时间内旋转,然后在很长一段时间内阻挡,此时重新突出显示。 FPS仪表通常在播放期间显示~45,但动画显然不稳定。
当声音关闭时,FPS爬升,动画流畅。
那么有办法解决它吗?我究竟做错了什么?如何让write
定期阻止?
这种行为在Windows和OSX环境中都很明显。
我尝试过的一件事就是使用Thread.sleep
来规范它,但这并不是很好。它仍然不稳定。
答案 0 :(得分:2)
解决方案似乎是使用open(AudioFormat, int)
打开具有指定缓冲区大小的行。
line.open(fmt, buffer.length);
再次对其进行定时,我们可以看到write
阻止更加一致:
sound: 22
paint: 24
sound: 21
paint: 24
sound: 20
paint: 22
sound: 21
paint: 23
sound: 20
paint: 23
动画很流畅。
答案 1 :(得分:0)
我严重怀疑声音回放是罪魁祸首。请参阅我对主要问题的评论。音频write()方法中发生的阻塞与音频呈现给回放系统的速率有关。由于音频处理通常比音频系统可以播放的速度快一个数量级(限制为44100 fps),因此大部分时间用于BOTH SourceDataLine和Clip。在这种阻塞形式中,CPU可以自由地执行其他操作。它没有挂起。
我更担心你对图像使用同步,以及对图像进行编辑。我非常确定在某个级别上进行编辑会消除该图像的默认图形加速。
您可以在Java-Gaming.org上查看有关Graphics2D优化的链接 http://www.java-gaming.org/topics/java2d-clearing-up-the-air/27506/msg/0/view/topicseen.html#new
我发现它对优化我的2D图形非常有帮助。
我不确定为什么你会在你的特定情况下得到融合。对我来说几次问题就是当帧和组件的循环代码在同一个类中时。通过将“游戏循环”代码和组件放在不同的类中,问题总是会消失,所以我从不打扰进一步思考它。因此,我没有清楚地了解为什么会起作用,或者这个行动是否是一个因素。
[编辑:只是仔细查看您的音频代码。我认为还有优化空间。有计算被不必要地重做并且可能正在消耗cpu。例如,由于内循环中有最终值,为什么每次迭代都要重新计算该部分?取常数部分并将其计算为一次值,并仅计算内循环中的未知数。我建议重构以避免所有同步和优化音频数据的数据生成,然后查看是否仍有问题。]