簡體   English   中英

MediaCodec:將圖像轉換為視頻

[英]MediaCodec: Convert image to video

我希望能夠使用MediaCodec將位圖寫入視頻。 我希望視頻長3秒,每秒30幀。 我的目標是Android API 21。

我有一個做繪圖的課:

public class ImageRenderer {
    private static final String NO_FILTER_VERTEX_SHADER = "" +
            "attribute vec4 position;\n" +
            "attribute vec4 inputTextureCoordinate;\n" +
            " \n" +
            "varying vec2 textureCoordinate;\n" +
            " \n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = position;\n" +
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +
            "}";
    private static final String NO_FILTER_FRAGMENT_SHADER = "" +
            "varying highp vec2 textureCoordinate;\n" +
            " \n" +
            "uniform sampler2D inputImageTexture;\n" +
            " \n" +
            "void main()\n" +
            "{\n" +
            "     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +
            "}";

    private int mGLProgId;
    private int mGLAttribPosition;
    private int mGLUniformTexture;
    private int mGLAttribTextureCoordinate;

    private static final int NO_IMAGE = -1;
    private static final float CUBE[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f,
    };

    private int mGLTextureId = NO_IMAGE;
    private final FloatBuffer mGLCubeBuffer;
    private final FloatBuffer mGLTextureBuffer;

    private Bitmap bitmap;

    private static final float TEXTURE_NO_ROTATION[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f,
    };

    public ImageRenderer(Bitmap bitmap) {
        this.bitmap = bitmap;

        mGLCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLCubeBuffer.put(CUBE).position(0);

        mGLTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLTextureBuffer.put(TEXTURE_NO_ROTATION).position(0);

        GLES20.glClearColor(0, 0, 0, 1);
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);

        mGLProgId = OpenGlUtils.loadProgram(NO_FILTER_VERTEX_SHADER, NO_FILTER_FRAGMENT_SHADER);
        mGLAttribPosition = GLES20.glGetAttribLocation(mGLProgId, "position");
        mGLUniformTexture = GLES20.glGetUniformLocation(mGLProgId, "inputImageTexture");
        mGLAttribTextureCoordinate = GLES20.glGetAttribLocation(mGLProgId,
                "inputTextureCoordinate");

        GLES20.glViewport(0, 0, bitmap.getWidth(), bitmap.getHeight());
        GLES20.glUseProgram(mGLProgId);
    }

    public void drawFrame() {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        // Draw bitmap
        mGLTextureId = OpenGlUtils.loadTexture(bitmap, mGLTextureId, false);

        GLES20.glUseProgram(mGLProgId);

        mGLCubeBuffer.position(0);
        GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 0, mGLCubeBuffer);
        GLES20.glEnableVertexAttribArray(mGLAttribPosition);
        mGLTextureBuffer.position(0);
        GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0,
                mGLTextureBuffer);
        GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate);
        if (mGLTextureId != OpenGlUtils.NO_TEXTURE) {
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mGLTextureId);
            GLES20.glUniform1i(mGLUniformTexture, 0);
        }
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glDisableVertexAttribArray(mGLAttribPosition);
        GLES20.glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }
}

我還有一個InputSurface連接到我的視頻編碼器和多路復用器。

在處理開始時,此后每次成功復用幀時,我都會調用:

inputSurface.makeCurrent();
imageRenderer.drawFrame();
inputSurface.setPresentationTime(presentationTimeNs);
inputSurface.swapBuffers();
inputSurface.releaseEGLContext();

其中inputSurfaceimageRenderer是上述類的實例,而presentationTimeNs我根據所需的幀速率進行計算。

這通常是可行的,但感覺效率很低。 我感覺我不必要一遍又一遍地重繪位圖,即使我知道它沒有改變。 我嘗試在一開始只調用一次或兩次drawFrame() ,但是隨后在所有三星測試設備上輸出的視頻均閃爍為黑色。

有沒有一種更有效的方法可以將相同的位圖一遍又一遍地繪制到編碼器輸入表面?

在效率方面,輸入框的繪制可能會達到所需要的效率。 每次將幀提交給編碼器時,您都無法真正假設有關輸入表面緩沖區內容的任何信息(我認為),因此您需要以某種方式將要編碼的內容復制到其中,這幾乎可以做到。

如果您跳過繪圖,則需要記住,要繪圖的表面不僅是單個緩沖區,而是一組許多緩沖區(通常為4-10個緩沖區)。 使用編碼器的直接緩沖區訪問模式時,編碼器會准確告訴您池中哪個緩沖區可以填充您的緩沖區,在這種情況下,如果您願意跳過繪圖,可能會更好。之前已經填充了緩沖區(並希望編碼器沒有使內容無效)。

使用表面輸入時,您無需知道要寫入哪個緩沖區。 在這種情況下,您可以嘗試只進行前N次繪制。 我認為您無法獲得實際的緩沖區數量-您可以嘗試調用不贊成使用的getInputBuffers()方法,但我認為無法將其與表面輸入結合使用。

但是,關於性能,絕對最大的問題和(缺乏)性能的原因是,您正在同步進行所有操作。 你說

在處理開始時,然后每次我成功地對幀進行多路復用時,我都會調用

硬件編碼器通常會有一點延遲,並且如果您一次開始編碼多個幀,則從開始到結束對單個幀進行編碼所花費的時間將比每幀的平均時間更長。

假設您在異步模式下使用MediaCodec,我建議只在一個線程中串行地對所有90幀進行編碼,並在回調中獲取輸出數據包時將這些數據包寫入復用器。 那應該使編碼器管道繁忙。 (一旦編碼器的輸入緩沖區已用盡,inputSurface方法將阻塞,直到編碼器完成一幀並釋放另一個輸入緩沖區為止。)您可能還希望將輸出數據包緩沖在隊列中並異步寫入到多路復用器(我記得讀過一些有關MediaMuxer偶爾會阻塞更長的時間的案例)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM