3

我设法通过拍摄一系列屏幕截图并使用 ffmpeg 将它们转换为视频来做到这一点(为 android 编译的 ffmpeg 并包含所有 *so 到断言,将它们全部复制到 data/data/my.package/ 并从那里执行 ffmpeg)

但主要问题是截屏对屏幕渲染有很大影响,当应用程序执行这行代码时它会冻结一段时间(~0.1秒):

Gdx.gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixels);

我试图从另一个线程截取屏幕截图,但我只得到黑色屏幕截图。为什么???

你知道更有效的截图方法吗?

4

2 回答 2

3

屏幕捕获是一项相当昂贵的任务,当您想将其转换为视频时更是如此。

glReadPixels 方法是从显示缓冲区读回像素值的方法。这就是你想要的;然而,限制因素将是与 GPU 之间的带宽,尤其是对于移动设备。我建议创建一个分辨率较低的FrameBuffer(可能是宽度/高度的一半),以便降低带宽需求。然后,您将在应用程序处于活动状态时将其绘制到该缓冲区,然后读回像素值。Libgdx 提供了一个方便的类来读回ScreenUtils类中的像素值。(这些方法使用 glReadPixels 进行读取)。

即使这样,它也可能无法在低端设备上正常工作,特别是如果您正在考虑以每秒 60 帧的速度从设备录制视频并主动渲染复杂场景。但是,如果其他人知道在 android 设备上有效地执行此操作的方法,我会对看到更好的解决方案非常感兴趣。

要回答您的其他问题,您不能同时访问 OpenGL 上下文,并且应该只从渲染线程中进行。这就是为什么您会获得带有多个线程的黑色屏幕截图。

编辑:

为了跟进您关于如何导出到 PNG 的问题,我有您可以使用的代码:

public boolean export(final FileHandle target, final Graphics g) {
        boolean error = false;
        final Pixmap picture = new Pixmap(g.getWidth(), g.getHeight(), Format.RGBA8888);
        final FrameBuffer buffer = new FrameBuffer(Format.RGBA8888, g.getWidth(), g.getHeight(), false);
        try {
            Gdx.graphics.getGL20().glViewport(0, 0, g.getWidth(), g.getHeight());
            buffer.begin();
            g.render(); // Or however you normally draw it
            final byte[] data = this.readData(g.getWidth(), g.getHeight());
            buffer.end();
            picture.getPixels().put(data, 0, data.length);
            PixmapIO.writePNG(target, picture);
        } catch (final Exception e) {
            e.printStackTrace();
            error = true;
        } finally {
            picture.dispose();
            buffer.dispose();
        }
        return error;
    }

    // Adapted from ScreenUtil class
    public byte[] readData(final int width, final int height) {
        final int numBytes = width * height * 4;
        final ByteBuffer pixels = BufferUtils.newByteBuffer(numBytes);
        Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1);
        Gdx.gl.glReadPixels(0, 0, width, height, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);

        final byte[] lines = new byte[numBytes];
        final int numBytesPerLine = width * 4;
        for (int i = 0; i < height; i++) {
            pixels.position((height - i - 1) * numBytesPerLine);
            pixels.get(lines, i * numBytesPerLine, numBytesPerLine);
        }

        return lines;
    }
于 2013-03-31T22:47:03.860 回答
1

您好,我设法拍摄了 @ 30fps 的视频,或者我们甚至可以从我的想法中获得 @60 fps 的视频,而且我的视频非常流畅

如果有人对此感兴趣,请检查一下。

这个想法很简单,我们希望以 60fps 或 29.97fps(我的视频)的速度对每一帧进行屏幕截图。

分 3 步完成:1) 以 1/60 渲染所有内容作为屏幕的增量时间

2)拍摄每一帧的屏幕截图

3)制作所有这些png文件的视频(我使用adobe Premiere pro)

第 1 步)在 libgdx 中,我扩展了 Game 类并创建了一个新类 MyGame extends Game 或类 MyGame 扩展 ApplicationListener 现在更改其渲染方法以供临时使用

ScreenShotUtils screenShotUtils=new ScreenShotUtils();
@Override
    public void render () {
        //if (screen != null) screen.render(Gdx.graphics.getDeltaTime());
        //write your video fps as 1f/29.97f or 1f/30f
        if (screen != null){
            screen.render(1f/60f);
            screenShotUtils.update();
        }
    }

Step 2) 使用 ScreenShotUtils 类的 capture 方法截屏 当您需要截屏时调用 capture 方法只需在您的游戏中制作一个按钮来开始和停止录制

//call on start click
screenShotUtils.capture(true);
//call on stop click
screenShotUtils.capture(false);

注意:当你开始抓图游戏会卡顿,它会超级慢,但是渲染增量时间不会让 ui 更新低于 60 fps,你会得到 60 fps 的截图,现在在这个延迟期间玩游戏并且您将在视频发布过程中获得流畅的图像序列

public class ScreenShotUtils {
    String filefolder="D:/capturefolder";// your folder to put the images in
    String filenameprefix="";
    int frameid=0,captureLimit=-1;
    private boolean isCapturing=false;
    public void setCaptureLimit(int frames){
        captureLimit=frames;
    }
    public boolean isCapturing(){
        return isCapturing;
    }

    public void capture(boolean capture){
        if(capture){
            if(!isCapturing) {
                isCapturing = true;
                filenameprefix="Demo"+System.currentTimeMillis();//Images Prefix
                frameid=0;
            }
        }else{
            isCapturing=false;
            captureLimit=-1;
            frameid=0;
        }
    }
    public void capture(boolean capture, int frames){
        if(capture){
            if(!isCapturing) {
                isCapturing = true;
                filenameprefix="Demo"+System.currentTimeMillis();//Images Prefix
                frameid=0;
            }
            captureLimit=frames;
        }else{
            isCapturing=false;
            captureLimit=-1;
            frameid=0;
        }
    }

    public void update() {

        // render before capturing

        if(isCapturing) {
            if(captureLimit<0)
                saveScreenshot();
            else
                if(captureLimit==0){
                    isCapturing = false;
                    captureLimit=-1;
                }else{
                    saveScreenshot();
                    captureLimit--;
                }
        }
    }
    private void saveScreenshot() {
        int MAX_DIGITS = 6;
        String fname = "" + (frameid++);
        int zeros = MAX_DIGITS - fname.length();
        for (int i = 0; i < zeros; i++) {
            fname = "0" + fname;
        }

        FileHandle file = new FileHandle(filefolder+"/"+filenameprefix +"-"+ fname+".png");
        Pixmap pixmap = getScreenshot(0, 0, Gdx.graphics.getWidth(),
                Gdx.graphics.getHeight(), true);
        PixmapIO.writePNG(file, pixmap);
        pixmap.dispose();
    }
    private Pixmap getScreenshot(int x, int y, int w, int h, boolean flipY) {
        Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1);

        Pixmap pixmap = new Pixmap(w, h, Pixmap.Format.RGB888);
        ByteBuffer pixels = pixmap.getPixels();
        Gdx.gl.glReadPixels(x, y, w, h, GL20.GL_RGB, GL20.GL_UNSIGNED_BYTE, pixels);

        if (flipY) {
            final int numBytes = w * h * 3;
            byte[] lines = new byte[numBytes];
            final int numBytesPerLine = w * 3;
            for (int i = 0; i < h; i++) {
                pixels.position((h - i - 1) * numBytesPerLine);
                pixels.get(lines, i * numBytesPerLine, numBytesPerLine);
            }
            pixels.clear();
            pixels.put(lines);
        }     

        return pixmap;
    }
}

3)我使用adobe Premiere pro制作视频,你可以使用其他工具搜索google(将图像序列转换为视频) https://helpx.adobe.com/premiere-pro/using/importing-still-images.html 部分:将图像作为图像序列导入

注意:我使用格式为 RGB 而不是 RGBA 的屏幕截图,因为 alpha 位弄乱了图像的边界。你可以试试看

此外,您可以修改代码以将文件保存到 android sdcard 我使用桌面版本,只需更改 saveScreenshot 方法的文件句柄

免责声明:我使用了截图代码并从本网站修改了它们http://www.wendytech.de/2012/07/opengl-screen-capture-in-real-time/

于 2015-09-15T11:45:46.377 回答