【问题标题】:Is there any fast alternatives for TextureView.getBitmap() function?TextureView.getBitmap() 函数有什么快速的替代方法吗?
【发布时间】:2018-12-31 14:42:06
【问题描述】:

我正在使用 android vlc (LibVLC) 和 TextureView,以便在我的 android 应用程序中播放实时 rtsp 流。一切正常,但是对于某些对象检测任务,我需要每次都获取当前播放帧,我正在使用getBitmap() 函数来执行此操作。这里的问题是这个函数太慢了,随着TextureView中渲染的图像大小增加,它需要越来越多的时间。

那么还有其他更快的方法吗?

请注意,我在 TextureView 和 SurfaceView 上都尝试了 getDrawingCache() 函数,但它总是返回一个透明的位图,所以经过小研究后我意识到这是因为 VLC 使用硬件加速来渲染帧表面纹理。

我也确实找到了许多与this answer by fadden 类似的解决方案,谈论使用glReadPixels() 函数并指向grafika 作为代码示例的参考。但是(不幸的是)我在使用 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();
}

}

【问题讨论】:

    标签: java android libvlc android-textureview vlc-android


    【解决方案1】:

    经过这么长时间,我决定给出这个答案,我现在将其用作替代方案。我发现JavaCPPFFMpegFrameGrabber 可以用来播放rtsp streams 或视频文件,但是这里有两个问题:

    1. FFMpegFrameGrabber.Grab() 直接读取可用的下一帧,太慢了,以至于我能够在我的设备上抓取每秒不超过 6 帧CPU:1.5 GHz 64Bit Octa Core ARM Cortex-A53)
    2. FFMpegFrameGrabber 没有渲染能力,它只是将当前视频帧抓取到OpenCV Mat 对象或Javacv Frame 之一(您可以使用同一库的类AndroidFrameConverter 将Frame 对象转换为@987654329 @)。

    关于第一个问题,在我不需要超过 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;
        }
    }
    

    【讨论】:

    • FFmpeg 支持在 Android 上解码的硬件加速,但它不会自动使用它。试试看,例如:FFmpegFrameGrabber.setVideoCodecName("h264_mediacodec")
    猜你喜欢
    • 1970-01-01
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 2021-01-05
    • 2021-08-20
    • 2017-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多