在android下使用libgdx进行屏幕录制

时间:2013-03-31 12:24:33

标签: android video libgdx recording

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

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

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

我试图从另一个帖子中截取屏幕截图,但我只获得黑色屏幕截图。为什么???

您是否知道更有效的截屏方式?

2 个答案:

答案 0 :(得分: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;
    }

答案 1 :(得分:0)

您好我设法拍摄了@ 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();
        }
    }

步骤2)使用ScreenShotUtils类的捕获方法捕获屏幕截图 当您需要捕捉游戏中的按钮以开始和停止录制时调用捕获方法

//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方法的文件句柄

免责声明:我使用了screnshot代码并在本网站http://www.wendytech.de/2012/07/opengl-screen-capture-in-real-time/

中对其进行了修改