I am currently trying to output processed opencv mat images from openCV to SurfaceView at fps of 30 or more.
I am using asynctasks to process the mat images into bitmaps, with the results added to bitmap buffer in the MSurface class.
Then another repetitive thread will call outputDisplay of the same Msurface class which will pop the first bitmap from the bitmap buffer and output it SurfaceView through lockCanvas and unlockCanvasAndPost.
But only first or first few frames will be displayed before it freezes forever.
Code snippets below
In main activity
this.backGroundPool = new ScheduledThreadPoolExecutor(2);
this.backGroundPool.setMaximumPoolSize(2);
this.backgroundThread = new Runnable() {
@Override
public void run() {
try {
if (cameraTasks != null && mSurface != null) {
//Log.w("Output Task", "Processing");
if (outputTasks != null) {
if (outputTasks.size() <= cameraTasks.size()) {
//Log.w("Output Task", "Adding");
outputTasks.addLast(new OutputTask(mSurface));
outputTasks.getLast().execute(imageBuffer.getFinalImg());
}
if (outputTasks.getFirst().getStatus() == AsyncTask.Status.FINISHED) {
outputTasks.removeFirst();
}
}
}
if (cameraTasks == null) {
Log.w("CameraTask", "Not initialized");
}
if (mSurface == null) {
Log.w("mSurface View", "Not initialized");
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
this.backGroundPool.scheduleAtFixedRate(this.backgroundThread,
START_DELAY,
TIME_PERIOD,
TIME_UNIT);
this.outputThread = new Runnable() {
@Override
public void run() {
try {
while (true) {
mSurface.outputDisplay();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
this.backGroundPool.scheduleAtFixedRate(this.outputThread,
START_DELAY,
TIME_PERIOD,
TIME_UNIT);
In OutputTask class
public class OutputTask extends AsyncTask<Mat, Void, Bitmap> {
private MSurface mSurface;
public OutputTask(MSurface mSurface) {
this.mSurface = mSurface;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected Bitmap doInBackground(Mat... images) {
//Log.i("Camera Task", "Running");
if (images.length != 0 && images[0] != null) {
Bitmap bm = Bitmap.createBitmap(images[0].cols(), images[0].rows(), Bitmap.Config.ARGB_8888);;
Utils.matToBitmap(images[0], bm);
ByteArrayOutputStream out = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, out);
Bitmap compressedBM = BitmapFactory.decodeStream(new ByteArrayInputStream(out.toByteArray()));
return compressedBM;
}
else {
Log.w("Mat Image", "Not Found");
return null;
}
}
@Override
protected void onPostExecute(Bitmap result) {
try {
if (result != null) {
synchronized (this.mSurface.getHolder()) {
//Log.i("Buffer", "Updating");
this.mSurface.addBitmapBuffer(result);
}
}
} catch (Exception e) {
e.printStackTrace();
}
super.onPostExecute(result);
}
In MSurface class
public class MSurface extends SurfaceView implements SurfaceHolder.Callback {
private static int MAXBUFFERSIZE = 30;
public LinkedList<Bitmap> bitmapBuffer;
private boolean surfaceAvailable;
private int surfaceWidth;
private int surfaceHeight;
public MSurface(Context context, AttributeSet attrs) {
super(context, attrs);
this.getHolder().addCallback(this);
this.getHolder().setFormat(PixelFormat.RGBA_8888);
this.bitmapBuffer = new LinkedList<Bitmap>();
this.surfaceAvailable = false;
}
public void addBitmapBuffer(Bitmap bitmap) {
this.bitmapBuffer.addLast(bitmap);
if (this.bitmapBuffer.size() > MAXBUFFERSIZE) {
Bitmap bm = this.bitmapBuffer.pop();
bm.recycle();
}
}
public void outputDisplay() {
if (this.bitmapBuffer.size() > 0 && this.surfaceAvailable) {
Canvas canvas = getHolder().lockCanvas();
//Log.i("Canvas", "Drawing new frame");
//canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY);
Bitmap bm = this.bitmapBuffer.pop();
Rect src = new Rect(0, 0, bm.getWidth(), bm.getHeight());
Rect dst = new Rect((int)((this.surfaceWidth - bm.getWidth()) / 2),
((int)(this.surfaceHeight - bm.getHeight()) / 2),
((int)(this.surfaceWidth - bm.getWidth()) / 2 + bm.getWidth()),
((int)(this.surfaceHeight - bm.getHeight()) / 2 + bm.getHeight()));
canvas.drawColor( 0, PorterDuff.Mode.CLEAR );
canvas.drawBitmap(bm, src, dst, null);
this.getHolder().unlockCanvasAndPost(canvas);
bm.recycle();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
Log.i("Surface", "Changed");
Log.d("SurfaceHolder", String.valueOf(holder));
Log.i("Surface Format", String.valueOf(format));
Log.i("Surface Width", String.valueOf(width));
Log.i("Surface Height", String.valueOf(height));
this.surfaceWidth = width;
this.surfaceHeight = height;
if (holder.getSurface() == null) {
Log.i("Surface", "Missing");
}
setWillNotDraw(false);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("Surface", "Created");
Log.d("SurfaceHolder", String.valueOf(holder));
this.surfaceAvailable = true;
//this.setWillNotDraw(false);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
this.surfaceAvailable = false;
Log.i("Surface", "Destroyed");
}
}
Thanks in advance for those who are able to provide suggestions/solutions