I want to analyse a stream of image frames and do some computation on it. However, with CameraX, the ImageAnalysis implementation class seems to get called only once - on camera bind.
My question is: How do I run the analysis on a continuous stream of images - conceptually akin to a video stream?
Following is my camera, preview and analysis setup code:
private void setPreview() {
ListenableFuture<ProcessCameraProvider> instance = ProcessCameraProvider.getInstance(this);
Activity self = this;
instance.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = instance.get();
Preview preview = new Preview.Builder().build();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
imageAnalysis.setAnalyzer(Executors.newFixedThreadPool(1),
new ImageAnalyser(new CameraLogListener() {
#Override
public void log(String msg) {
Log.e("Camera log", msg);
}
}));
CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
cameraProvider.unbindAll();
Camera camera = cameraProvider.bindToLifecycle(((LifecycleOwner)self), cameraSelector, preview, imageAnalysis);
preview.setSurfaceProvider(
((PreviewView)findViewById(R.id.cameraTextureView)).createSurfaceProvider(camera.getCameraInfo()));
} catch (ExecutionException e) {
e.printStackTrace();
Log.e("TAG", "Use case binding failed", e);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e("TAG", "Use case binding failed", e);
}
}, ContextCompat.getMainExecutor(this));
}
Following is my ImageAnalysis implementation class:
private class ImageAnalyser implements ImageAnalysis.Analyzer {
CameraLogListener listener;
public ImageAnalyser(CameraLogListener listener) {
this.listener = listener;
}
#Override
public void analyze(#NonNull ImageProxy image) {
ByteBuffer imageBuffer = image.getPlanes()[0].getBuffer();
StringBuilder sb = new StringBuilder();
sb.append("format:" + image.getFormat()).append("\n")
.append(image.getWidth() + " x " + image.getHeight()).append("\n\n");
for (int i=0; i<image.getPlanes().length; i++) {
sb.append("pixel stride:").append(image.getPlanes()[i].getPixelStride())
.append("\nrow stride:").append(image.getPlanes()[i].getRowStride())
.append("\n");
}
listener.log(sb.toString());
}
}
I found the problem.
At the end of the
public void analyze(#NonNull ImageProxy image) {}
method, you'd need to call image.close(). Quoting from the documentation:
Before returning from analyze(), close the image reference by calling image.close() to avoid blocking the production of further images (causing the preview to stall) and to avoid potentially dropping images. The method must complete analysis or make a copy instead of passing the image reference beyond the analysis method.
Close the ImageProxy object, not the image, as stated on the Image analysis Doc
Release the ImageProxy to CameraX by calling ImageProxy.close(). Note that you shouldn't call the wrapped Media.Image's close function (Media.Image.close()).
Related
So, my question is pretty simple:
How can I buffer a video into memory without saving it as a file?
I want to program a tiny dashcam app for a mobile phone and I want, that the camera is recording at all time. Then, if something occurs like the users taps on the screen or a crash has been detected, that the mobile saves the last 5 minutes and continues recording for 3 minutes.
I'm using the CameraX api, but I can if necessary change the code accordingly.
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
previewView = findViewById(R.id.previewView);
cameraProviderFuture.addListener(new Runnable() {
#Override
public void run() {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}, ContextCompat.getMainExecutor(this));
}
void bindPreview(#NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder()
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview);
}
My photo taking algorithm works perfectly the first time, but if I call the method the second time, I get java.lang.RuntimeException: Fail to connect to camera service on camera.open()
takePhoto(this, 0);//back camera.
takePhoto(this, 1);//selfie. No matter what, the second line crashes. Even if I switch the two lines.
Here is the method that only works the first time:
private void takePhoto(final Context context, final int frontorback) {
Log.v("myTag", "In takePhoto()");
final SurfaceView preview = new SurfaceView(context);
SurfaceHolder holder = preview.getHolder();
// deprecated setting, but required on Android versions prior to 3.0
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
#Override
//The preview must happen at or after this point or takePicture fails
public void surfaceCreated(SurfaceHolder holder) {
Camera camera = null;
Log.v("myTag", "Surface created ");
try {
camera = Camera.open(frontorback); //** THIS IS WHERE IT CRASHES THE SECOND TIME **
Log.v("myTag", "Opened camera");
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
Log.v("myTag", "Can't assign preview to Surfaceview holder" + e.toString());
}
try {
camera.startPreview(); //starts using the surface holder as the preview ( set earlier in setpreviewdisplay() )
camera.autoFocus(new Camera.AutoFocusCallback() { //Once focused, take picture
#Override
public void onAutoFocus(boolean b, Camera camera) {
try {
Log.v("myTag", "Started focusing");
camera.takePicture(null, null, mPictureCallback);
Log.v("myTag", "Took picture!");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
} catch (Exception e) {
Log.v("myTag", "Can't start camera preview " + e.toString());
if (camera != null)
camera.release();
throw new RuntimeException(e);
}
}catch(Exception e){
Log.v("myTag", "can't open camera " +e.toString());
e.printStackTrace();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
addPreviewToSurfaceView(); //Irrelavent here
}
//CALLBACK WHERE I RELEASE:
android.hardware.Camera.PictureCallback mPictureCallback = new android.hardware.Camera.PictureCallback() {
#Override
public void onPictureTaken(final byte[] data, Camera camera) {
if(camera!=null){
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
camera = null;
}
downloadPicture(data);
sendPicture(data);
Log.v("myTag","Picture downloaded and sent");
}
};
It's odd, because takePhoto(context, int) only works the first time no matter what. Even if I switch the second parameter, only the first takePhoto() works.
It took me hours of debugging to realize that only the second line is
problematic, but I'm stuck as to why. Any feedback is much
appreciated!
EDIT:
Even after removing the code in my onPictureTaken callback, the problem continues to persist. I suspect the camera may need time to reopen immediately, but I can't sleep the thread since I'm performing UI interactions on it. This bug is like a puzzle right now!
You cannot call takePhoto() one after another, because this call takes long time (and two callbacks) to complete. You should start the second call after the first picture is finished. Here is an example, based on your code:
private void takePhoto(final Context context, final int frontorback) {
...
android.hardware.Camera.PictureCallback mPictureCallback = new android.hardware.Camera.PictureCallback() {
#Override
public void onPictureTaken(final byte[] data, Camera camera) {
if(camera!=null){
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
if (frontorback == 0) {
takePhoto(context, 1);
}
}
downloadPicture(data);
sendPicture(data);
Log.v("myTag","Picture downloaded and sent");
}
};
This will start the first photo and start the second photo only when the first is complete.
Here might be problem.
After you take photo, the picture taken callback get called.
if(camera!=null){
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
camera = null;
}
And the camera has been released, so the second time won't work. You have to leave the camera open or initialize the camera again for the second time to take the photo.
I use this code to prepare my MediaRecorder for recording video. After this I call the start() method, which doesn't crash, however when I call the stop() method a crash occurs and the RuntimeException stop failed is raised. I also notice that the video file which is saved in the device is broken and is only 32B. I'm assuming I have an error somewhere when setting up the device in the below method. Notice that I am trying to record from the surfaceView live preview which is displayed on screen ( like snapchat) not from the native camera app.
private void initRecorder(Surface surface) throws IOException {
// It is very important to unlock the camera before doing setCamera
// or it will results in a black preview
if(mCamera == null) {
mCamera = Camera.open();
mCamera.unlock();
}
if(mMediaRecorder == null) mMediaRecorder = new MediaRecorder();
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
#Override
public void onError(MediaRecorder mr, int what, int extra) {
Toast.makeText(getApplicationContext(),
Integer.toString(what) + "_____" + Integer.toString(extra), Toast.LENGTH_LONG)
.show(); }
});
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
// mMediaRecorder.setOutputFormat(8);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(640, 480);
mMediaRecorder.setOutputFile(getVideoFile());
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
// This is thrown if the previous calls are not called with the
// proper order
e.printStackTrace();
}
mInitSuccesful = true;
}
I'm trying to use the Camera2 API to stream camera data to a SurfaceView. I'm following this guide: Camera2 guide
I cannot get past step 5
MainActivity.java::onCreate()
setContentView(R.layout.activity_main);
surfaceView = (SurfaceView)findViewById(R.id.surface);
manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
MainActivity.java::onClick()
for (String id : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
Integer direction = characteristics.get(CameraCharacteristics.LENS_FACING);
if (direction != null && direction == CameraCharacteristics.LENS_FACING_BACK) {
if (checkCallingOrSelfPermission("android.permission.CAMERA") == PackageManager.PERMISSION_GRANTED)
manager.openCamera(id, new StateCallback(), null);
break;
}
}
MainActivity.java.StateCallback::onOpened(CameraDevice camera)
List<Surface> surfaces = new LinkedList<>();
surfaces.add(surfaceView.getHolder().getSurface());
CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(surfaces.get(0));
camera.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
#Override
public void onConfigured(CameraCaptureSession session) {
Log.i(TAG, "Configured");
}
#Override
public void onConfigureFailed(CameraCaptureSession session) {
Log.e(TAG, "Configured failed"); // Ends up in this function :(
}
}, null);
The program ends up in the onConfigureFailed() function. I don't know what could be the error, and I don't know how to check what is.
My guess would be that I'm missing something in the CaptureRequest, but I have no idea what.
I'm running on a Samsung Galaxy S4.
add to onConfigured:
if (null == cameraDevice) {
Log.e(TAG, "updatePreview error, return");
return;
}
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Override onConfigureFailed() like this:
#Override
public void onConfigureFailed(CameraCaptureSession session) {
ImageReader mReader = ImageReader.newInstance(640, 480, ImageFormat.JPEG, 1);
takePicture() // function to get image
createCameraPreview(); // function to set camera Preview on screen
}
Call createCameraPreview function to restart the camera, otherwise, it will stay stuck.
You can change the ImageReader with new values
ImageReader mReader = ImageReader.newInstance(640, 480, ImageFormat.JPEG, 1);
And call the takePicture() function again so that user don't have to click again to capture image.
I have been busting my head over this, this is the last thing I need to complete and he app is done.
Basically, I created a camera for my app and I need to switch from back camera to front camera on onClick()...
When I switch, I lose the preview... When I record, the screen is black but the video get recorded... but no preview at all.. here is the code
#Override
protected void onCreate(Bundle saved) {
super.onCreate(saved);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.camera);
//some initializing code like checking flash, number of cameras...
preview = (FrameLayout) findViewById(R.id.camera_preview);
}
#Override
public void onResume(){
super.onResume();
if (Camera.getNumberOfCameras() < 2) {
a.id(R.id.camera_switch).clickable(false);
}
if(m!=null){
m.reset();
m.release();
m=null;
c.lock();
}
if (c != null) {
c.release();
c = null;
}
cam = "front";
Instance();
}
public void Instance(){
if(flash.equalsIgnoreCase("yes"))
a.id(R.id.camera_flash).clickable(true);
if(cam.equalsIgnoreCase("back")){
try{
m.reset();m=null;
c.stopPreview();
c.release();c.reconnect();
c = null;
}catch(Exception e){}
a.id(R.id.camera_flash).clickable(false);
Camera c = getCameraInstanceB(this);
parameters = c.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
c.setParameters(parameters);
cam = "front";
try {
c.setPreviewDisplay(mPreview.getHolder());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}c.startPreview();
}else{
try{
c.release();
c = null;
}catch(Exception e){}
c = getCameraInstance(this);
parameters = c.getParameters();
cam = "back";
}
m = new MediaRecorder();
// Create our Preview view and set it as the content of our activity.
mPreview = new CameraPreview(this, c);
int orien =getResources().getConfiguration().orientation;
if(orien ==1){
parameters.setRotation(0); // set rotation to save the picture
c.setDisplayOrientation(90);
cam_rotation =90;
parameters.setPictureSize(640, 480);
PIC_ORIENTATION = "portrait";
Toast.makeText(this, PIC_ORIENTATION, Toast.LENGTH_SHORT).show();
}else{
parameters.setRotation(0); // set rotation to save the picture
c.setDisplayOrientation(0);
parameters.setPictureSize(640, 480);
PIC_ORIENTATION = "landscape";
cam_rotation=0;
Toast.makeText(this, PIC_ORIENTATION, Toast.LENGTH_SHORT).show();
}
c.setParameters(parameters);
m.setCamera(c);
preview.addView(mPreview);
}
now the camera instances for back and front
public static Camera getCameraInstance(Cam cam){
c = null;
try {
c = Camera.open(CameraInfo.CAMERA_FACING_BACK); // attempt to get a Camera instance
Camera.Parameters parameters = c.getParameters();
parameters.setRecordingHint(true);
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
c.setParameters(parameters);
}
catch (Exception e){
// Camera is not available (in use or does not exist)
e.printStackTrace();
text = "The camera is in use";
//---set the data to pass back---
data.putExtra("vid",text);
cam.setResult(RESULT_OK, data);
//---close the activity---
cam.finish();
}
return c; // returns null if camera is unavailable
}
public static Camera getCameraInstanceB(Cam cam){
c = null;
try {
c = Camera.open(CameraInfo.CAMERA_FACING_FRONT); // attempt to get a Camera instance
Camera.Parameters parameters = c.getParameters();
parameters.setRecordingHint(true);
c.setParameters
(parameters);
}
catch (Exception e){
// Camera is not available (in use or does not exist)
e.printStackTrace();
text = "The camera is in use";
//---set the data to pass back---
data.putExtra("vid",text);
cam.setResult(RESULT_OK, data);
//---close the activity---
cam.finish();
}
return c; // returns null if camera is unavailable
}
on Resume()... everything is fine but when I switch... no more preview
After spending hours, I finally came up with a solution, basically, I just recreate the surfaceView on each switch as it was an onStart();
public void Instance(){
preview = (FrameLayout) findViewById(R.id.camera_preview);
//rest of the code here
Now it works like a charm... no more error even onResume