繁体   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