I am trying to use a SurfaceTexture to display a camera preview from the back camera of my device but I keep getting the error
bindTextureImage : error binding external texture image 0x2:0x502
Looking at the line that says
SurfaceTexture.UpdateTexImage()
Looking at the android documentation, it seems as though this might be caused by calling UpdateTexImage on the SurfaceTexture.OnFrameAvailableListener
's OnFrameAvailable method.According to the documentation "the callback (SurfaceTexture.OnFrameAvailableListener) may be called on an arbitrary thread, so it is not safe to call updateTexImage() without first binding the OpenGL ES context to the thread invoking the callback".
How do I "bind the OpenGL ES context" to the thread invoking the callback?
I have just been trying to closely follow the "Texture from Camera" activity from the Grafika project to try and get clues on how I can achieve this task.Any help is appreciated.
My Full Code is pasted below:
public class CameraPreviewFromTextureActivity extends Activity
{
private static String TAG = "CameraPreviewFromTexture";
private static SurfaceHolder mySurfaceHolder = null;
private SurfaceTexture mCameraTexture = null;
private Camera mCamera = null;
private EglCore mEglCore;
private WindowSurface mWindowSurface;
private int mWindowSurfaceWidth;
private int mWindowSurfaceHeight;
private int mCameraPreviewWidth, mCameraPreviewHeight;
private float mCameraPreviewFps;
private Texture2dProgram mTexProgram;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_preview_from_texture);
mySurfaceHolder = ((SurfaceView)this.findViewById(R.id.surfaceView)).getHolder();
mySurfaceHolder.addCallback(mySurfaceHolderCallback);
//Run Thread Methods
mEglCore = new EglCore(null, 0);
openCamera(328, 288, 30);
}
private SurfaceHolder.Callback mySurfaceHolderCallback = new SurfaceHolder.Callback()
{
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
releaseGl();
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
surfaceAvailable(holder, true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Log.d(TAG, "SurfaceChanged " + width + "x" + height);
mWindowSurfaceWidth = width;
mWindowSurfaceHeight = height;
finishSurfaceSetup();
}
};
private void surfaceAvailable(SurfaceHolder holder, boolean newSurface)
{
Surface surface = holder.getSurface();
mWindowSurface = new WindowSurface(mEglCore, surface, false);
mWindowSurface.makeCurrent();
// Create and configure the SurfaceTexture, which will receive frames from the
// camera. We set the textured rect's program to render from it.
mTexProgram = new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT);
int textureId = mTexProgram.createTextureObject();
mCameraTexture = new SurfaceTexture(textureId);
//mRect.setTexture(textureId);
if (!newSurface) {
// This Surface was established on a previous run, so no surfaceChanged()
// message is forthcoming. Finish the surface setup now.
//
// We could also just call this unconditionally, and perhaps do an unnecessary
// bit of reallocating if a surface-changed message arrives.
mWindowSurfaceWidth = mWindowSurface.getWidth();
mWindowSurfaceHeight = mWindowSurface.getHeight();
finishSurfaceSetup();
}
mCameraTexture.setOnFrameAvailableListener(myOnFrameListner);
}
private SurfaceTexture.OnFrameAvailableListener myOnFrameListner = new SurfaceTexture.OnFrameAvailableListener()
{
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture)
{
surfaceTexture.updateTexImage(); //Problem Occurs Here
draw();
}
};
private void draw()
{
GlUtil.checkGlError("draw start");
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//mRect.draw(mTexProgram, mDisplayProjectionMatrix);
mWindowSurface.swapBuffers();
GlUtil.checkGlError("draw done");
}
private void finishSurfaceSetup()
{
int width = mWindowSurfaceWidth;
int height = mWindowSurfaceHeight;
// Use full window.
GLES20.glViewport(0, 0, width, height);
// Ready to go, start the camera.
Log.d(TAG, "starting camera preview");
try {
mCamera.setPreviewTexture(mCameraTexture);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
mCamera.startPreview();
}
private void openCamera(int desiredWidth, int desiredHeight, int desiredFps)
{
if (mCamera != null)
{
throw new RuntimeException("camera already initialized");
}
Camera.CameraInfo info = new Camera.CameraInfo();
try
{
mCamera = Camera.open();
}
catch(Exception ex)
{
ex.printStackTrace();
}
Camera.Parameters parms = mCamera.getParameters();
// Try to set the frame rate to a constant value.
int thousandFps = chooseFixedPreviewFps(parms, desiredFps * 1000);
// Give the camera a hint that we're recording video. This can have a big
// impact on frame rate.
parms.setRecordingHint(true);
mCamera.setParameters(parms);
int[] fpsRange = new int[2];
Camera.Size mCameraPreviewSize = parms.getPreviewSize();
parms.getPreviewFpsRange(fpsRange);
String previewFacts = mCameraPreviewSize.width + "x" + mCameraPreviewSize.height;
if (fpsRange[0] == fpsRange[1]) {
previewFacts += " @" + (fpsRange[0] / 1000.0) + "fps";
} else {
previewFacts += " @[" + (fpsRange[0] / 1000.0) +
" - " + (fpsRange[1] / 1000.0) + "] fps";
}
Log.i(TAG, "Camera config: " + previewFacts);
mCameraPreviewWidth = mCameraPreviewSize.width;
mCameraPreviewHeight = mCameraPreviewSize.height;
mCameraPreviewFps = desiredFps;
}
public static int chooseFixedPreviewFps(Camera.Parameters parms, int desiredThousandFps)
{
List<int[]> supported = parms.getSupportedPreviewFpsRange();
for (int[] entry : supported) {
//Log.d(TAG, "entry: " + entry[0] + " - " + entry[1]);
if ((entry[0] == entry[1]) && (entry[0] == desiredThousandFps)) {
parms.setPreviewFpsRange(entry[0], entry[1]);
return entry[0];
}
}
int[] tmp = new int[2];
parms.getPreviewFpsRange(tmp);
int guess;
if (tmp[0] == tmp[1]) {
guess = tmp[0];
} else {
guess = tmp[1] / 2; // shrug
}
Log.d(TAG, "Couldn't find match for " + desiredThousandFps + ", using " + guess);
return guess;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.camera_preview_from_texture, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void releaseGl() {
GlUtil.checkGlError("releaseGl start");
if (mWindowSurface != null) {
mWindowSurface.release();
mWindowSurface = null;
}
if (mTexProgram != null) {
mTexProgram.release();
mTexProgram = null;
}
GlUtil.checkGlError("releaseGl done");
mEglCore.makeNothingCurrent();
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
Log.d(TAG, "releaseCamera -- done");
}
}
@Override
public void onDestroy()
{
releaseCamera();
releaseGl();
mEglCore.release();
}
}
Each thread has a "current" EGL context, referenced by the GLES driver through thread-local storage. Any GLES commands you issue, including binding of textures, operate in this context.
A quick look through your code suggests that you're trying to do everything on the UI thread. If you create and make-current your own context, it will be fighting with the UI code, which is going to have its own EGL context for hardware-accelerated View rendering. Life is a bit simpler if you create a separate thread, make your EGL context current there, and then just leave it alone.
You can see that the "texture from camera" activity's frame-available handler is just:
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mHandler.sendFrameAvailable();
}
Because the frame-available message can arrive on an arbitrary thread, you don't know what EGL context will be current there (or if the thread will have one at all). You can call eglMakeCurrent()
(via EglCore#makeCurrent()
) to make yours current, but you aren't allowed to use the same EGL context from more than one thread, so if it's current somewhere else at the same time you could have a problem. So it's easier to just forward the frame-available message to the thread where you know your EGL context is current.
FWIW, 0x0502 is GL_INVALID_OPERATION
.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments