簡體   English   中英

使用SourceDataLine的低幀速率

[英]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 它只在自己的緩沖區已滿時才會阻塞。 這似乎是持久性:讓它以可預測的方式阻止。

為了演示這個問題,這是一個最小的例子:

  • 將0寫入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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM