As title says, I am trying to preview the two rear cameras on my phone on two separate textureviews. What I can do is preview the front camera and any of the rear cameras, but when I try to preview both rear cameras I get an "ERROR_MAX_CAMERAS_IN_USE" from the onError state callback method for the last-opened camera. By searching on the Internet and on here I couldn't really find any pointers on what I'm doing wrong.
For completeness here's the code of my main activity java class, in which I setup the cameras and obtain the previews:
public class MainActivity extends AppCompatActivity {
private final TextureView[] textureViews = new TextureView[2];
protected CameraDevice[] cameraDevices = new CameraDevice[2];
protected CameraCaptureSession[] cameraCaptureSessions = new CameraCaptureSession[2];
protected CaptureRequest.Builder[] captureRequestBuilders = new CaptureRequest.Builder[2];
private Size imageDimension;
private static final int REQUEST_CAMERA_PERMISSION = 200;
private final Handler[] mBackgroundHandlers = new Handler[2];
private final HandlerThread[] mBackgroundThreads = new HandlerThread[2];
private final int[] cameraIDs = {0, 2};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureViews[0] = findViewById(R.id.left);
textureViews[1] = findViewById(R.id.right);
assert textureViews[0] != null;
assert textureViews[1] != null;
textureViews[0].setSurfaceTextureListener(textureListener0);
textureViews[1].setSurfaceTextureListener(textureListener1);
}
TextureView.SurfaceTextureListener textureListener0 = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
openCamera(0);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { }
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
};
TextureView.SurfaceTextureListener textureListener1 = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
openCamera(1);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { }
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
};
private final CameraDevice.StateCallback stateCallback0 = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
if(Debug.LOG) {
Log.e(Debug.TAG, camera.getId()+ " onOpened.");
}
cameraDevices[0] = camera;
createCameraPreview(0);
}
@Override
public void onDisconnected(CameraDevice camera) {
cameraDevices[0].close();
}
@Override
public void onError(CameraDevice camera, int error) {
cameraDevices[0].close();
cameraDevices[0] = null;
}
};
private final CameraDevice.StateCallback stateCallback1 = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
if(Debug.LOG) {
Log.e(Debug.TAG, camera.getId() + " onOpened.");
}
cameraDevices[1] = camera;
createCameraPreview(1);
}
@Override
public void onDisconnected(CameraDevice camera) {
cameraDevices[1].close();
}
@Override
public void onError(CameraDevice camera, int error) {
cameraDevices[1].close();
cameraDevices[1] = null;
}
};
protected void startBackgroundThreads() {
mBackgroundThreads[0] = new HandlerThread("Left camera Background");
mBackgroundThreads[0].start();
mBackgroundHandlers[0] = new Handler(mBackgroundThreads[0].getLooper());
mBackgroundThreads[1] = new HandlerThread("Right camera Background");
mBackgroundThreads[1].start();
mBackgroundHandlers[1] = new Handler(mBackgroundThreads[1].getLooper());
}
protected void stopBackgroundThreads() {
mBackgroundThreads[0].quitSafely();
mBackgroundThreads[1].quitSafely();
try {
mBackgroundThreads[0].join();
mBackgroundThreads[0] = null;
mBackgroundHandlers[0] = null;
mBackgroundThreads[1].join();
mBackgroundThreads[1] = null;
mBackgroundHandlers[1] = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected void createCameraPreview(int cameraID) {
try {
SurfaceTexture texture = textureViews[cameraID].getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
Surface surface = new Surface(texture);
captureRequestBuilders[cameraID] = cameraDevices[cameraID].createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilders[cameraID].addTarget(surface);
cameraDevices[cameraID].createCaptureSession(Collections.singletonList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (null == cameraDevices[cameraID]) {
return;
}
cameraCaptureSessions[cameraID] = cameraCaptureSession;
updatePreview(cameraID);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "Configuration change", Toast.LENGTH_SHORT).show();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void openCamera(int cameraID) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
if(Debug.LOG) {
Log.e(Debug.TAG, "openCamera " + cameraIDs[cameraID]);
}
try {
String cameraId = manager.getCameraIdList()[cameraIDs[cameraID]];
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
// Add permission for camera and let user grant the permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
return;
}
manager.openCamera(cameraId, (cameraID == 0) ? stateCallback0 : stateCallback1, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
if(Debug.LOG) {
Log.e(Debug.TAG, "openCamera exited");
}
}
protected void updatePreview(int cameraID) {
if (null == cameraDevices[cameraID]) {
if(Debug.LOG) {
Log.e(Debug.TAG, "updatePreview error, return");
}
}
captureRequestBuilders[cameraID].set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
cameraCaptureSessions[cameraID].setRepeatingRequest(captureRequestBuilders[cameraID].build(), null, mBackgroundHandlers[cameraID]);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void closeCamera(int cameraID) {
if (null != cameraDevices[cameraID]) {
cameraDevices[cameraID].close();
cameraDevices[cameraID] = null;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
finish();
}
}
}
@Override
protected void onResume() {
super.onResume();
startBackgroundThreads();
if(textureViews[0].isAvailable()) {
openCamera(0);
} else {
textureViews[0].setSurfaceTextureListener(textureListener0);
}
if(textureViews[1].isAvailable()) {
openCamera(1);
} else {
textureViews[1].setSurfaceTextureListener(textureListener1);
}
}
@Override
protected void onPause() {
closeCamera(0);
closeCamera(1);
stopBackgroundThreads();
super.onPause();
}