Существуют ли какие-либо быстрые альтернативы функции TextureView.getBitmap()?

Я использую android vlc (LibVLC) вместе с TextureView, чтобы воспроизводить прямой поток rtsp в моем приложении для Android. Все работает нормально, однако мне нужно каждый раз получать текущий воспроизводимый кадр для некоторых задач обнаружения объектов, где я использую для этого функцию getBitmap(). Проблема здесь в том, что эта функция слишком медленная и занимает все больше и больше времени по мере увеличения размера изображения, отображаемого в TextureView.

Есть ли другой способ сделать это быстрее?

Обратите внимание, что я пробовал функцию getDrawingCache() как для TextureView, так и для SurfaceView, но она всегда возвращала прозрачное растровое изображение, поэтому после небольшого исследования я понял, что это связано с тем, что VLC использует аппаратное ускорение для рендеринга кадров по текстуре поверхности.

Я также нашел много подобных решений для этот ответ fadden рассказывает об использовании функции glReadPixels() и указывает на графику в качестве справочного материала для примеров кода. Однако (и к сожалению) у меня почти нет навыков работы с OpenGL. Итак, если вы можете проверить связанный ответ, не могли бы вы затем связать меня с простым прямым образцом кода (касающимся моего случая)?

public class MainActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener,
    org.videolan.libvlc.media.MediaPlayer.OnBufferingUpdateListener,
    org.videolan.libvlc.media.MediaPlayer.OnCompletionListener,
    org.videolan.libvlc.media.MediaPlayer.OnPreparedListener,
    org.videolan.libvlc.media.MediaPlayer.OnVideoSizeChangedListener {

private AppCompatActivity me = this;    
private MediaPlayer mMediaPlayer;    
private TextureView mTextureViewmTextureView;
private String mUrl = "/storage/emulated/0/videos/test.mp4";
private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.content_main);

    mMediaPlayer = new MediaPlayer(VLCInstance.get());
    mTextureViewmTextureView = (TextureView) findViewById(R.id.player);
    mTextureView.setSurfaceTextureListener(this);
}

private void attachViewSurface() {
        final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
        mMediaPlayer.setScale(0);
        vlcVout.detachViews();
        vlcVout.setVideoView(mTextureView);
        vlcVout.setWindowSize(mTextureView.getWidth(), mTextureView.getHeight());
        vlcVout.attachViews();
        mTextureView.setKeepScreenOn(true);
}


private void play(String path) {
   try {
        Media media;
        if (new File(path).exists()) {
            media = new Media(VLCInstance.get(), path);
        } else {
            media = new Media(VLCInstance.get(), Uri.parse(path));
        }

        mMediaPlayer.setMedia(media);
        mMediaPlayer.play();
    } catch (Exception e) {
        Log.e(TAG, e.getMessage());
    }
}

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
   attachViewSurface();

   if (mMediaPlayer.hasMedia())
       mMediaPlayer.play();
   else
       play(mUrl);
}

public Bitmap getImage() {
   return mTextureView.getBitmap();
}

}

person peter bence    schedule 31.12.2018    source источник


Ответы (1)


После этого долгого времени я решил дать этот ответ, который я сейчас использую в качестве альтернативы. Я обнаружил, что FFMpegFrameGrabber из JavaCPP можно использовать для воспроизведения как rtsp streams, так и видеофайлов, однако у вас есть две проблемы. здесь:

  1. FFMpegFrameGrabber.Grab(), который считывает следующий доступный кадр, работает слишком медленно, поэтому я смог получить не более 6 кадров в секунду на своем устройстве (ЦП: 64-битный восьмиядерный процессор ARM Cortex с тактовой частотой 1,5 ГГц). A53)
  2. FFMpegFrameGrabber не имеет возможности рендеринга, он просто захватывает текущий видеокадр в объект OpenCV Mat или Javacv Frame (вы можете использовать класс AndroidFrameConverter той же библиотеки для преобразования объекта Frame в Bitmap).

Что касается первой проблемы, я могу решить ее начисто, поскольку в моем случае мне не нужно больше 5 fps.

Для второго я разработал OpenGL Bitmap based renderer, который может отображать растровые изображения, захваченные граббером, почти мгновенно (это очень быстро). Вот мой код:

app.gradle:

implementation group: 'org.bytedeco', name: 'javacv-platform', version: '1.4.3'
implementation group: 'org.bytedeco', name: 'javacv', version: '1.4.3'

граббер:

class Player extends AsyncTask<BitmapRenderer, Bitmap, Object> {
    BitmapRenderer glRenderer;
    FFmpegFrameGrabber grabber = null;

    @Override
    protected Bitmap doInBackground(BitmapRenderer... objects) {
        glRenderer = objects[0];

        try {
            grabber = new FFmpegFrameGrabber("/storage/emulated/0/Download/test.mp4");
            grabber.start();
            OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
            Frame grabbedImage;
            while ((grabbedImage = grabber.grabImage()) != null) {
                Log.e("Android", "Frame Grabbed " + grabbedImage.imageWidth + "x" + grabbedImage.imageHeight);
                AndroidFrameConverter frameConverter = new AndroidFrameConverter();
                Bitmap bitmap = frameConverter.convert(grabbedImage);
                publishProgress(bitmap);

                opencv_core.Mat grabbedMat = converter.convert(grabbedImage);
                if (grabbedMat != null)
                    imwrite("/storage/emulated/0/Download/videoplayback.jpg", grabbedMat);
            }

        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
            Log.e("Android", e.getMessage(), e);
        }
        return null;
    }

    @Override
    protected void onProgressUpdate(Bitmap... values) {
        super.onProgressUpdate(values);
        glRenderer.draw(values[0]);
    }

    @Override
    protected void onPostExecute(Object objects) {
        super.onPostExecute(objects);
        try {
            grabber.stop();
            grabber.release();
        } catch (FrameGrabber.Exception e1) {
        }
    }
}

Визуализатор:

package com.example.gphspc.javacvtest;

import android.graphics.Bitmap;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.view.ViewGroup;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class BitmapRenderer implements GLSurfaceView.Renderer {

    private int[] textures;
    private Bitmap bitmap;
    private GLSurfaceView glSurfaceView;
    private int parentWidth, parentHeight;
    private boolean sizeModified = false;

    public BitmapRenderer(GLSurfaceView glSurfaceView) {
        this.glSurfaceView = glSurfaceView;
        this.glSurfaceView.setEGLContextClientVersion(1);
        this.glSurfaceView.setRenderer(this);
        this.glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    private static final float[] VERTEX_COORDINATES = new float[]{
            -1.0f, +1.0f, 0.0f,
            +1.0f, +1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
            +1.0f, -1.0f, 0.0f
    };

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

    private static final Buffer TEXCOORD_BUFFER = ByteBuffer.allocateDirect(TEXTURE_COORDINATES.length * 4)
            .order(ByteOrder.nativeOrder()).asFloatBuffer().put(TEXTURE_COORDINATES).rewind();
    private static final Buffer VERTEX_BUFFER = ByteBuffer.allocateDirect(VERTEX_COORDINATES.length * 4)
            .order(ByteOrder.nativeOrder()).asFloatBuffer().put(VERTEX_COORDINATES).rewind();

    public void draw(Bitmap bitmap) {
        if (bitmap == null)
            return;

        this.bitmap = bitmap;

        if (!sizeModified) {
            ViewGroup.LayoutParams layoutParams = glSurfaceView.getLayoutParams();
            Dimension newDims = getRelativeSize(new Dimension(bitmap.getWidth(), bitmap.getHeight()), glSurfaceView.getWidth(), glSurfaceView.getHeight());
            layoutParams.width = newDims.getWidth();
            layoutParams.height = newDims.getHeight();
            glSurfaceView.setLayoutParams(layoutParams);
            sizeModified = true;
        }

        glSurfaceView.requestRender();
    }

    public static Dimension getRelativeSize(Dimension dimension, int width, int height) {
        int toWidth = width, toHeight = height;

        int imgWidth = (int) dimension.getWidth();
        int imgHeight = (int) dimension.getHeight();

        if (imgWidth > imgHeight) {
            toWidth = (int) ((double) height / ((double) imgHeight / imgWidth));
            if (toWidth > width)
                toWidth = width;
            toHeight = (int) (toWidth * ((double) imgHeight / imgWidth));
        } else if (imgWidth < imgHeight) {
            toHeight = (int) ((double) width / ((double) imgWidth / imgHeight));
            if (toHeight > height)
                toHeight = height;
            toWidth = (int) (toHeight * ((double) imgWidth / imgHeight));
        }

        return new Dimension(toWidth, toHeight);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        textures = new int[1];
        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        ViewGroup.LayoutParams layoutParams = glSurfaceView.getLayoutParams();
        parentWidth = layoutParams.width;
        parentHeight = layoutParams.height;
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        gl.glViewport(0, 0, width, height);
//        gl.glOrthof(0f, width, 0f, height, -1f, 1f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        if (bitmap != null) {

            gl.glGenTextures(1, textures, 0);
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

            gl.glActiveTexture(GL10.GL_TEXTURE0);
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VERTEX_BUFFER);
            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, TEXCOORD_BUFFER);
            gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        }
    }
}

class Dimension {
    int width = 0, height = 0;

    public Dimension(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}
person peter bence    schedule 19.04.2019
comment
FFmpeg поддерживает аппаратное ускорение декодирования на Android, но не использует его автоматически. Попробуйте, например: FFmpegFrameGrabber.setVideoCodecName("h264_mediacodec") - person Samuel Audet; 10.05.2019