android中是不是使用libgdx进行屏幕录制

Posted

技术标签:

【中文标题】android中是不是使用libgdx进行屏幕录制【英文标题】:Screen recording within android using libgdx or notandroid中是否使用libgdx进行屏幕录制 【发布时间】:2013-03-31 12:24:32 【问题描述】:

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

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

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

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

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

【问题讨论】:

【参考方案1】:

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

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;
    

【讨论】:

同意你的看法。分辨率较低的 FrameBuffer 速度更快。主要问题是如何将 FrameBuffer 保存为 PNG?像这样尝试但不成功: m_fbo = new FrameBuffer(Format.RGBA8888, (int)(w * m_fboScaler), (int)(h * m_fboScaler), false); m_fboRegion = new TextureRegion(m_fbo.getColorBufferTexture()); m_fboRegion.flip(假,真); m_fbo.begin();纹理lTx;纹理数据 lTxData; lTx = aTxRegion.getTexture(); lTxData = lTx.getTextureData(); pixmap = lTxData.consumePixmap(); 我已更新我的答案,向您展示如何保存到 PNG 的示例。您可能需要修改此代码,但它应该会有所帮助。 我最近看到应用会说话的汤姆具有以原始分辨率录制屏幕的音频视频功能。他们是如何达到这么好的帧率的?他们使用了哪个框架? Gdx.gl.glReadPixels(0, 0, 宽度, 高度, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, 像素);即使我减小宽度和高度也需要很长时间。我需要像 ios 一样支持硬件加速的屏幕视频录制。 Libgdx 无法通过硬件加速来帮助您进行视频录制。我怀疑该应用程序会使用 FFmpeg 进行录制,但那是 libgdx 之外的东西。这可能是一个很好的起点,但我不熟悉那个库。【参考方案2】:

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

如果有人对此感兴趣,请查看。

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

分三步完成: 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 类的 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/修改了它们

【讨论】:

您可以使用 ffmpeg 并使用以下命令 "ffmpeg -framerate 30 -i input%06d.png -codec copy output.mp4" 将 %06d 更改为您的计数器长度或将 mp4 更改为 mkv 您可以使用任何类型的屏幕录制软件在计算机上自动执行此操作。原始问题的重点是在 Android 手机上实时执行此操作。 @Jyro117 该代码也可以在安卓手机上运行,​​并且在高性能设备上运行流畅,但在低性能设备上也可以,而且屏幕录制软件会降低色彩质量和fps我的英特尔 i5 电脑和这种方法实际上在安卓和桌面上都能产生平滑的输出和实际的颜色。

以上是关于android中是不是使用libgdx进行屏幕录制的主要内容,如果未能解决你的问题,请参考以下文章

Libgdx 游戏在 Android 上崩溃

在 Android 屏幕录制中 - 如何获取每一帧?

Android Studio 无法以 1080p 录制屏幕

如何防止在 Android 应用程序中录制屏幕视频?

Android和ios的屏幕录制[关闭]

如何使用 Android Studio 录制屏幕