经过这么长时间,我决定给出这个答案,我现在将其用作替代方案。我发现FFMpegFrameGrabber
JavaCPP可以用来播放视频rtsp streams
文件,但是这里有两个问题:
FFMpegFrameGrabber.Grab()
读取可用的直接下一帧,太慢了,以至于我能够在我的设备上每秒抓取不超过 6 帧( CPU:1.5 GHz 64Bit Octa Core ARM Cortex-A53)
FFMpegFrameGrabber
没有渲染能力,它只是将当前视频帧抓取到一个OpenCV Mat
对象或Javacv Frame
一个对象中(您可以使用AndroidFrameConverter
同一库的类将 Frame 对象转换为Bitmap
)。
关于第一个问题,我可以在不需要比5 fps
我的情况更多的地方清理它。
对于第二个,我开发了一个OpenGL Bitmap based renderer
几乎可以立即渲染抓取器抓取的位图图像(速度非常快)。这是我的代码:
应用程序.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;
}
}