[英]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.