After this long time i decided to give this answer, that i'm using as an alternative for now. I found that FFMpegFrameGrabber
of JavaCPP could be used to play both rtsp streams
or video files, however you have two problems here:
FFMpegFrameGrabber.Grab()
that read the direct next frame available, is too slow so that i was able to grab no more than 6 frames per second on my device (CPU: 1.5 GHz 64Bit Octa Core ARM Cortex-A53)
FFMpegFrameGrabber
doesn't have rendering abilities, it just grabs the current video frame into an OpenCV Mat
object or Javacv Frame
one (you can use the class AndroidFrameConverter
of the same library to convert the Frame object into a Bitmap
).
Concerning the first problem, i can go clean with it where i don't need more than 5 fps
in my case.
For the second one, i developed an OpenGL Bitmap based renderer
that can render the bitmap images grabbed by the grabber in almost no time (it's very fast). Here is my code:
app.gradle:
implementation group: 'org.bytedeco', name: 'javacv-platform', version: '1.4.3'
implementation group: 'org.bytedeco', name: 'javacv', version: '1.4.3'
grabber:
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) {
}
}
}
Renderer:
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;
}
}