[英]Choppy frame rate using a SourceDataLine
我用简单的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
(没有可听见的声音)并对其进行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
提要类似于以下内容:
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
来调节它,但它并不是很好。 它仍然不稳定。
解决方案似乎是使用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
动画很流畅。
我非常怀疑声音回放是罪魁祸首。 请参阅我对主要问题的评论。 音频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 #新
我发现它对优化我的2D图形非常有帮助。
我不确定为什么你会在你的特定情况下得到合并。 对我来说几次问题就是当帧和组件的循环代码在同一个类中时。 通过将“游戏循环”代码和组件放在不同的类中,问题总是会消失,所以我从不打扰进一步思考它。 因此,我没有清楚地了解为什么会起作用,或者该行动是否是一个因素。
[编辑:只是仔细看看你的音频代码。 我认为还有优化空间。 有计算被不必要地重做并且可能正在消耗cpu。 例如,由于内循环中有最终值,为什么每次迭代都要重新计算该部分? 取常数部分并将其计算为一次值,并仅计算内循环中的未知数。 我建议重构以避免所有同步和优化音频数据的数据生成,然后查看是否仍有问题。]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.