Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Camera Preview Using Surface Texture

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();
}

}
like image 890
KillaKem Avatar asked Dec 23 '14 18:12

KillaKem


People also ask

Which method of the camera class display the preview of the image captured?

Create a Preview Class - Create a camera preview class that extends SurfaceView and implements the SurfaceHolder interface. This class previews the live images from the camera.

What is camera preview screen?

PreviewView is a subclass of FrameLayout . To display the camera feed, it uses either a SurfaceView or TextureView , provides a preview surface to the camera when it's ready, tries to keep it valid as long as the camera is using it, and when released prematurely, provides a new surface if the camera is still in use.

How does camera app create preview class?

Select a camera and bind the lifecycle and use casesCreate a Preview . Specify the desired camera LensFacing option. Bind the selected camera and any use cases to the lifecycle. Connect the Preview to the PreviewView .

How do I change camera preview orientation on Android?

To force portrait orientation: set android:screenOrientation="portrait" in your AndroidManifest. xml and call camera. setDisplayOrientation(90); before calling camera.


1 Answers

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​.

like image 150
fadden Avatar answered Oct 28 '22 23:10

fadden