Java 渲染网络摄像头的图像占用太多 CPU



我正在编写一个连接和流式传输网络摄像头视频的应用程序。为此,我使用Sarxos网络摄像头库(链接在这里)来获取默认网络摄像头,然后使用WebcamPanel绘制图像。当我将应用程序交付给我的客户时,问题出现了,他们在一台旧机器上对其进行了测试,并抱怨该应用程序占用了太多 CPU。

以前从未注意到这一点,当我再次测试它时,令我惊讶的是,该应用程序占用了大约 33% 的 CPU,这对于仅连接网络摄像头并以 30 FPS 绘制图像的简单应用程序来说太多了。这是我的编程环境:Windows 7 64位,CoreI5-4460 CPU(3.2-3.4Ghz),Zotac Geforce GTX 650 Ti,Java 7u45。

我已经测试了指出哪个部分占用的 CPU 最多,那就是渲染。如果我只获取网络摄像头图像而不绘制它们,CPU 会占用 6-7%,但当我渲染它们时,CPU 会跃升至 30-33%。我查看了WebcamPanel课程,看看它们可能有问题,但到目前为止我什么也没发现。绘制方法如下:

    @Override
    public void paintImage(WebcamPanel owner, BufferedImage image, Graphics2D g2) {
        assert owner != null;
        assert image != null;
        assert g2 != null;
        int pw = getWidth();
        int ph = getHeight();
        int iw = image.getWidth();
        int ih = image.getHeight();
        Object antialiasing = g2.getRenderingHint(KEY_ANTIALIASING);
        Object rendering = g2.getRenderingHint(KEY_RENDERING);
        g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
        g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_SPEED);
        g2.setBackground(Color.BLACK);
        g2.setColor(Color.BLACK);
        g2.fillRect(0, 0, pw, ph);
        // resized image position and size
        int x = 0;
        int y = 0;
        int w = 0;
        int h = 0;
        switch (drawMode) {
            case NONE:
                w = image.getWidth();
                h = image.getHeight();
                break;
            case FILL:
                w = pw;
                h = ph;
                break;
            case FIT:
                double s = Math.max((double) iw / pw, (double) ih / ph);
                double niw = iw / s;
                double nih = ih / s;
                double dx = (pw - niw) / 2;
                double dy = (ph - nih) / 2;
                w = (int) niw;
                h = (int) nih;
                x = (int) dx;
                y = (int) dy;
                break;
        }
        if (resizedImage != null) {
            resizedImage.flush();
        }
        if (w == image.getWidth() && h == image.getHeight() && !mirrored) {
            resizedImage = image;
        } else {
            GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsConfiguration gc = genv.getDefaultScreenDevice().getDefaultConfiguration();
            Graphics2D gr = null;
            try {
                resizedImage = gc.createCompatibleImage(pw, ph);
                gr = resizedImage.createGraphics();
                gr.setComposite(AlphaComposite.Src);
                for (Map.Entry<RenderingHints.Key, Object> hint : imageRenderingHints.entrySet()) {
                    gr.setRenderingHint(hint.getKey(), hint.getValue());
                }
                gr.setBackground(Color.BLACK);
                gr.setColor(Color.BLACK);
                gr.fillRect(0, 0, pw, ph);
                int sx1, sx2, sy1, sy2; // source rectangle coordinates
                int dx1, dx2, dy1, dy2; // destination rectangle coordinates
                dx1 = x;
                dy1 = y;
                dx2 = x + w;
                dy2 = y + h;
                if (mirrored) {
                    sx1 = iw;
                    sy1 = 0;
                    sx2 = 0;
                    sy2 = ih;
                } else {
                    sx1 = 0;
                    sy1 = 0;
                    sx2 = iw;
                    sy2 = ih;
                }
                gr.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null);
            } finally {
                if (gr != null) {
                    gr.dispose();
                }
            }
        }
        g2.drawImage(resizedImage, 0, 0, null);
        if (isFPSDisplayed()) {
            String str = String.format("FPS: %.1f", webcam.getFPS());
            int sx = 5;
            int sy = ph - 5;
            g2.setFont(getFont());
            g2.setColor(Color.BLACK);
            g2.drawString(str, sx + 1, sy + 1);
            g2.setColor(Color.WHITE);
            g2.drawString(str, sx, sy);
        }
        if (isImageSizeDisplayed()) {
            String res = String.format("%du2A2F%d px", iw, ih);
            FontMetrics metrics = g2.getFontMetrics(getFont());
            int sw = metrics.stringWidth(res);
            int sx = pw - sw - 5;
            int sy = ph - 5;
            g2.setFont(getFont());
            g2.setColor(Color.BLACK);
            g2.drawString(res, sx + 1, sy + 1);
            g2.setColor(Color.WHITE);
            g2.drawString(res, sx, sy);
        }
        if (isDisplayDebugInfo()) {
            if (lastRepaintTime < 0) {
                lastRepaintTime = System.currentTimeMillis();
            } else {
                long now = System.currentTimeMillis();
                String res = String.format("DEBUG: repaints per second: %.1f", (double) 1000 / (now - lastRepaintTime));
                lastRepaintTime = now;
                g2.setFont(getFont());
                g2.setColor(Color.BLACK);
                g2.drawString(res, 6, 16);
                g2.setColor(Color.WHITE);
                g2.drawString(res, 5, 15);
            }
        }
        g2.setRenderingHint(KEY_ANTIALIASING, antialiasing);
        g2.setRenderingHint(KEY_RENDERING, rendering);
    }

我已经尝试了很多方法来优化我的渲染,但没有任何效果。我尝试过的事情:

  1. 创建兼容的缓冲图像以呈现 => 如您所见,已经在代码中完成。
  2. 使用 DoubleBuffer 策略 => 正如我所读到的,这种技术已经在paintComponent() 方法中完成。我也尝试使用这里答案中的代码实现它,但也没有结果。
  3. 打开、关闭 OpenGL,强制使用 VM 参数进行直接绘制。没有结果。

我正在考虑更改为OpenGL库来渲染图像,但这将是最后的选择,因为我对OpenGL一无所知,而且我认为Java2D对于我的应用程序来说绰绰有余。谁能帮我解决这个问题?

经过各种测试,包括使用 OpenGL 渲染网络摄像头的图像(花了我太多时间,因为我必须从头开始学习 OpenGL),我已经找到了解决方案。这些是我做过的事情:

  1. 使用 OpenImja 驱动程序而不是默认驱动程序。我使用 Maven 构建了 OpenImja,之后我拿了 4 个 jar 文件放入库路径:core-1.1.jar;核心映像-1.1.jar;核心视频-1.1.jar;核心视频捕获-1.1.jar
  2. 规模是行不通的。在以前的版本中,我使用了网络摄像头的最佳分辨率,并将其缩放以适应屏幕尺寸。大错。
  3. 强制应用使用 DirectDraw 和 -Dsun.java2d.noddraw=false 进行渲染。根据Oracle的说法,Swing使用DirectDraw和GDI管道的混合来渲染。但是由于我为Window编写了应用程序,因此最好只使用DirectDraw。对于Linux等其他平台,也许X11或OpenGL会更好。

在那之后,我的应用程序CPU百分比从33%下降到11%。希望这会有人遇到与我相同的问题。

最新更新