Android MediaCodec и GLSurfaceView

Я пытаюсь воспроизвести видео с помощью MediaCodec и визуализировать его через GLSurfaceView, чтобы у меня был обратный вызов кадра.

package com.alwaysinnovating.aimediacodec;

import java.nio.ByteBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.opengl.EGLConfig;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;

class PlayBackSurface extends GLSurfaceView {
    public MainRenderer mRenderer;

    public PlayBackSurface(Context context, AttributeSet attrs) {
    super(context, attrs);

        setEGLContextClientVersion(2);
        mRenderer = new MainRenderer(context, this);
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }
}

class MainRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
    private int[] hTex;
    private SurfaceTexture mSTexture;
    private boolean mUpdateST = false;
    private PlayerThread mPlayer = null;
    private GLSurfaceView mGLSV;

    MainRenderer(Context c, GLSurfaceView s) {
        mGLSV = s;
    }

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    }

    public void onDrawFrame(GL10 unused) {
        synchronized (this) {
            if (mUpdateST) {
                mSTexture.updateTexImage();
                mUpdateST = false;
            }
        }
    }

    public void doPrepare() {
        mUpdateST = false;

        if (mSTexture != null) {
            mSTexture.release();
            mSTexture = null;
        }

        if (hTex != null)
            GLES20.glDeleteTextures(1, hTex, 0);
        hTex = null;

        hTex = new int[1];
        GLES20.glGenTextures(1, hTex, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, hTex[0]);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
        mSTexture = new SurfaceTexture(hTex[0]);
        mSTexture.setOnFrameAvailableListener(this);

        Surface s = new Surface(mSTexture);
        mPlayer = new PlayerThread(s);
        mPlayer.start();
    }

    public void onSurfaceChanged(GL10 unused, int width, int height) {
        doPrepare();
    }

    public synchronized void onFrameAvailable(SurfaceTexture st) {
        mUpdateST = true;
    }

    public void onSurfaceCreated(GL10 arg0, javax.microedition.khronos.egl.EGLConfig arg1) {
    }
}

class PlayerThread extends Thread {
    private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/local-25fps.mp4";
    private MediaExtractor extractor;
    private MediaCodec decoder;
    private Surface surface;

    public PlayerThread(Surface surface) {
        this.surface = surface;
    }

    @Override
    public void run() {
        extractor = new MediaExtractor();
        try {
            extractor.setDataSource(SAMPLE);
        } catch (Exception e1) {
        }

        for (int i = 0; i < extractor.getTrackCount(); i++) {
            MediaFormat format = extractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("video/")) {
                extractor.selectTrack(i);
                decoder = MediaCodec.createDecoderByType(mime);
                decoder.configure(format, surface, null, 0);
                break;
            }
        }

        if (decoder == null) {
            Log.e("DecodeActivity", "Can't find video info!");
            return;
        }

        decoder.start();

        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
        BufferInfo info = new BufferInfo();
        boolean isEOS = false;
        long startMs = System.currentTimeMillis();

        while (!Thread.interrupted()) {
            if (!isEOS) {
                int inIndex = decoder.dequeueInputBuffer(10000);
                if (inIndex >= 0) {
                    ByteBuffer buffer = inputBuffers[inIndex];
                    int sampleSize = extractor.readSampleData(buffer, 0);
                    if (sampleSize < 0) {
                        // We shouldn't stop the playback at this point, just pass the EOS
                        // flag to decoder, we will get it again from the
                        // dequeueOutputBuffer
                        Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                        decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        isEOS = true;
                    } else {
                        decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                        extractor.advance();
                    }
                }
            }

            int outIndex = decoder.dequeueOutputBuffer(info, 10000);
            switch (outIndex) {
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
                outputBuffers = decoder.getOutputBuffers();
                break;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
                break;
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
                break;
            default:
                ByteBuffer buffer = outputBuffers[outIndex];
                Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);

                // We use a very simple clock to keep the video FPS, or the video
                // playback will be too fast
                while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
                decoder.releaseOutputBuffer(outIndex, true);
                break;
            }

            // All decoded frames have been rendered, we can stop playing now
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                break;
            }
        }

        decoder.stop();
        decoder.release();
        extractor.release();
    }
}

Экран черный. как я:

        Surface s = new Surface(mSTexture);
        mPlayer = new PlayerThread(s);

Кадр рендерится в поверхность, которая никуда не денется. Что я делаю не так или как связать эту поверхность с моим GLSurfaceView?


person gregoiregentil    schedule 29.01.2016    source источник
comment
Ничего не отображается, потому что ваш onDrawFrame() метод ничего не отображает. См. Некоторые примеры в Grafika (github.com/google/grafika).   -  person fadden    schedule 30.01.2016
comment
Спасибо. Это действительно интересный код, но он не полностью отвечает на мой вопрос. Он использует TextureView, у которого нет обратного вызова отрисовки кадра. Возможно, мне следует переформулировать свой вопрос так: следует ли мне копировать содержимое mSTexture на свой SurfaceView и как я могу это сделать?   -  person gregoiregentil    schedule 30.01.2016
comment
SurfaceTexture преобразует входящий кадр графики (из MediaCodec, камеры, рендеринга OpenGL ES, рендеринга холста и т. Д.) Во внешнюю текстуру OpenGL ES. Вы можете визуализировать эту текстуру в GLSurfaceView с помощью команд визуализации GLES. (Вы также можете просто передать SurfaceView Surface в MediaCodec и пропустить промежуточный шаг, и в этом случае SurfaceView имеет больше смысла, чем GLSurfaceView, но это имеет смысл только в том случае, если вы не заинтересованы в изменении видеокадров.) См. Два действия для воспроизведения видео. в Grafika - один для SurfaceView, один для TextureView.   -  person fadden    schedule 31.01.2016
comment
Это ты написал Графику! Я не знал, что это ты. Красивый код :-) Ага, я только читал и тестировал PlayMovieActivity.java, хотя, полагаю, мне следовало исследовать PlayMovieSurfaceActivity.java ... Я собираюсь взглянуть на это ... Спасибо. Вы можете преобразовать свой комментарий в ответ, я отмечу его зеленым.   -  person gregoiregentil    schedule 31.01.2016
comment
Что ж, SurfaceView, как вы сказали, не позволит мне перехватить кадр, что я хочу сделать. Я по-прежнему считаю, что у меня правильный подход. В OnDraw мне нужно скопировать mSTexture в GLSurface с помощью API GL. Как я могу это сделать?   -  person gregoiregentil    schedule 31.01.2016
comment
Вы не сказали, чего именно вы пытаетесь достичь, но я предполагаю, что либо текстура из Activity камеры, либо из Activity захвата камеры в Grafika близка к тому, что вам нужно. Замените камеру на MediaCodec. Grafika предпочитает SurfaceView GLSurfaceView, потому что последний иногда мешает (это просто набор вспомогательных классов, обернутых вокруг первого); CameraCaptureActivity использует GLSurfaceView, чтобы доказать, что это возможно.   -  person fadden    schedule 31.01.2016


Ответы (1)


PS: Я пишу это из головы, поэтому могу ошибиться в названиях некоторых методов.

Вам необходимо зарегистрировать обратный вызов onFrameAvailable для текстуры поверхности. Внутри обратного вызова не вызывайте st.updateTexImage, поскольку метод должен вызываться в потоке OpenGL es.

Вызовите GLSurfaceView.requestRender, и будет вызван ваш метод onDrawFrame. Там вы можете обновить изображение текстуры поверхности, привязать к текстуре и делать с ней все, что захотите.

О, вам также придется изменить режим рендеринга на WHEN_DIRTY или что-то в этом роде.

person Adebayo Jagunmolu    schedule 05.03.2021