Daniel Smith
Daniel Smith

Reputation: 8579

ByteBuffer.wrap() in glTexImage2D causing excessive garbage collection

I am currently making the following openGL calls onDrawFrame() in an android GLSurfaceView():

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, cameraTexture[0]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, camPreviewSize.width, camPreviewSize.height, 0, GLES20.GL_LUMINANCE,     GLES20.GL_UNSIGNED_BYTE, ByteBuffer.wrap(yArray));
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);

I need to do this (or something like it) every frame because I am processing a live feed from an android camera preview with a custom callback method using android's setPreviewCallback() functionality within the camera, but my garbage collection is going absolutely wild (the following repeats about 10x a second!):

....

GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 18ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 22ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 25ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 20ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 20ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 16ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 22ms

....

yArray is a byte array, and I wrap it to a buffer. I've done profiling using DDMS, and indeed the majority of allocations are byte arrays, and from the reading I have done on the wrap function, it seems as though it may create an underlying byte[] on a call to wrap, which will then be collected by the GC after it is used as a texture.

How can I reduce the number of allocations? Should I change by GL calls somehow? It seems like I could just be reusing the same byte array instead, but I'm not sure how.

Any help would be so appreciated! This amount of garbage scares me!

Upvotes: 1

Views: 1275

Answers (2)

Daniel Smith
Daniel Smith

Reputation: 8579

There is a well documented android bug relating to the setPreviewCallback() method that causes excessive garbage collection. The solution to this problem is to use the newer method setPreviewCallbackWithBuffer where you preallocate a buffer that is added and re-added on every fram with the addCallbackBuffer method:

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
...
  mCamera.addCallbackBuffer(callbackBuffer);
  mCamera.setPreviewCallbackWithBuffer(class that implements PreviewCallback);
...
}

and in the onPreviewFrame function the buffer must be added again:

public void onPreviewFrame(byte[] yuvArray, Camera camera) {
...
  camera.addCallbackBuffer(callbackBuffer);
...
}

to address the initial suspicion that ByteBuffer.wrap() is allocating memory in the GL calls: it is not. It will actually use the underlying array (yArray in the provided code) and will not allocate new memory to be garbage collected.

Upvotes: 4

Stefan Hanke
Stefan Hanke

Reputation: 3528

You're creating a new ByteBuffer instance on every onDrawFrame call, which is immediately garbage and collected. You'd need this only when the size of the data changed, and thus the texture size dimensions itself. I don't think this's likely.

Preallocate the ByteBuffer one time and update its content via bulk methods.

Upvotes: 0

Related Questions