3

一連のスクリーンショットを撮り、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);

別のスレッドからスクリーンショットを撮ろうとしましたが、黒いスクリーンショットしか取得できません。どうして???

スクリーンショットを撮るより効率的な方法を知っていますか?

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

こんにちは、なんとか 30 fps でビデオを撮影できました。または、私のアイデアから 60 fps でビデオを取得することもできます。ビデオは非常にスムーズです。

興味のある方はこちらをチェックしてみてください。

アイデアは単純で、60fps または 29.97fps (私のビデオ) のすべてのフレームのスクリーン ショットが必要です。

3 つの手順で実行します: 1) 画面のデルタ時間として 1/60 ですべてをレンダリングします。

2) すべてのフレームのスクリーンショットを撮る

3)これらすべてのpngファイルのビデオを作成します(私はAdobe Premier Proを使用しています)

ステップ 1) libgdx で Game クラスを拡張し、新しいクラス MyGame extends Game または class MyGame extends 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) 動画作成には Adob​​e Premier Pro を使用しました。他のツールを使用して Google 検索 (画像シーケンスを動画に変換) https://helpx.adobe.com/premiere-pro/using/importing-still-images.html セクション:イメージをイメージ シーケンスとしてインポートする

注:アルファビットが画像の境界線をいじっていたので、RGBAではなくRGB形式のスクリーンショットを使用しています。あなたはそれを試すことができます

さらに、ファイルを Android SD カードに保存するようにコードを変更することもできます。私はデスクトップ バージョンを使用しました。saveScreenshot メソッドのファイル ハンドルを変更するだけです。

免責事項: 私は screnshot コードを使用し、この Web サイトhttp://www.wendytech.de/2012/07/opengl-screen-capture-in-real-time/から変更しました。

于 2015-09-15T11:45:46.377 に答える