How to get frame of SurfaceView each second? - android

I want to take screenshot from camera preview each second.
I show preview of my Camera using SurfaceView. I need to get preview each second(screenshot) but not using photo taking.
I know about method mCamera.setPreviewCallbackWithBuffer but I can take frame only one time from it. To make it updating every second I need to start MediaRecorder and record video. But For video I need to set outputFile, which means that it can use a lot of memory.
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
int width = parameters.getPreviewSize().width;
int height = parameters.getPreviewSize().height;
YuvImage yuv = new YuvImage(data, parameters.getPreviewFormat(), width, height, null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] bytes = out.toByteArray();
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
});
How can I do it without setting outputfile and taking photo each second?

If you are using SurfaceView to display your camera preview you may try calling Camera#takePicture every second.
To schedule it approximately every one second, you may use postDelayed method of any view. For example:
private Runnable capturePreview = new Runnable() {
#Override
public void run() {
camera.takePicture(null, null, callback);
// Run again after approximately 1 second.
surfaceView.postDelayed(this, 1000);
}
}
private Camera.PictureCallback callback = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
// Do whatever you need with your bitmap.
// Consider freeing the memory afterwards.
bitmap.recycle();
}
}
And you can start it whenever preview is ready by calling:
surfaceView.postDelayed(capturePreview, 1000);
And stop whenever preview is no longer displayed:
surfaceView.removeCallbacks(capturePreview);
If you are using TextureView you can simply use getBitmap() which allows you to easily grab its current contents.
Therefore the code above becomes something like:
private Runnable capturePreview = new Runnable() {
#Override
public void run() {
Bitmap preview = textureView.getBitmap();
// Do whatever you need with the bitmap.
// Run again after approximately 1 second.
textureView.postDelayed(this, 1000);
}
}, 1000);
And again start:
textureView.postDelayed(capturePreview, 1000);
and stop:
textureView.removeCallbacks(capturePreview);

Related

Take a picture every 20 seconds forever

I have an old Android device (Android 4.1.2, API 16) which I want to use as a "CCTV-like" device. I need to take a picture every 20 seconds and to do this I have implemented the following code on an Activity.
The onCreate() method is as the following:
//To keep the display on (the phone is always attached to the power supply)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
FrameLayout preview = findViewById(R.id.camera_preview);
initializeCamera();
Timer timerPhoto = new Timer();
timerPhoto.schedule(new TimerTask() {
#Override
public void run() {
mCamera.takePicture(null, null, mPictureCallback);
mCamera.startPreview();
}
}, 5000, 20000); //start after 5 seconds, take pic every 20 secs
The following method for the camera initialisation:
private void initializeCamera() {
if (mCamera == null) {
mCamera = getCameraInstance();
Timber.d("Camera initialized");
}
mCameraPreview = new IaCameraHelper(this, mCamera);
preview.addView(mCameraPreview);
}
And this is the callback which handles the picture:
private static Bitmap originalPhotoBitmap = null;
Camera.PictureCallback mPictureCallback = (data, camera) -> {
originalPhotoBitmap = addDateTimeToImage(rotateBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), 180));
//Sending the bitmap string to the server
controller.sendImage(bitmapToString(originalPhotoBitmap));
originalPhotoBitmap.recycle();
BitmapManager.rotatedBitmap.recycle();
BitmapManager.modBitmap.recycle();
originalPhotoBitmap = null;
BitmapManager.rotatedBitmap = null;
BitmapManager.modBitmap = null;
};
In the BitmapManager, I have the following methods which have been used to manipulate the bitmap:
public static Bitmap rotatedBitmap = null;
public static Bitmap modBitmap = null;
public static Bitmap rotateBitmap(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
rotatedBitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
return rotatedBitmap;
}
public static Bitmap addDateTimeToImage(Bitmap originalPhoto) {
modBitmap = Bitmap.createBitmap(originalPhoto.getWidth(), originalPhoto.getHeight(), Bitmap.Config.ARGB_8888);
String dateTime = sdf.format(Calendar.getInstance().getTime());
Canvas cs = new Canvas(modBitmap);
Paint tPaint = new Paint();
tPaint.setTextSize(30);
tPaint.setColor(Color.RED);
tPaint.setStyle(Paint.Style.FILL);
cs.drawBitmap(originalPhoto, 0f, 0f, null);
float height = tPaint.measureText("yY");
cs.drawText(dateTime, 20f, height + 15f, tPaint);
return modBitmap;
}
public static String bitmapToString(Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b = baos.toByteArray();
return Base64.encodeToString(b, Base64.DEFAULT);
}
As you can see, I send the bitmap string to a controller which sends the image to a local server in the network.
Generally, the application works like a charm, but sometimes (generally after 6-7 hours) I face the following errors (out-of-memory) that I don't know how to solve.
02-21 13:12:03.292 29621-29632/com.rbapp E/dalvikvm-heap: Out of memory on a 127547-byte allocation.
02-21 13:12:03.302 29621-29632/com.rbapp E/Camera-JNI: Couldn't allocate byte array for JPEG data
As you can see, I try to recycle all the generated bitmaps and I set them to null as soon as I send them to the controller, but the error persists.
When the errors occur, the application continues to run (also the camera preview), but the images are not sent (there are try-catches which allows the execution of the app) but as I press the "back" button on the phone, the application doesn't respond at all.

Get bitmap without transformations from TextureView

I am currently developing an application which use Camera2.
I display the preview on a TextureView which is scaled and translated (I only need to display a part of the image). My problem is that I need to analyze the entire image.
What I have in my CameraDevice.StateCallback :
#Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
SurfaceTexture texture = mTextureView.getSurfaceTexture();
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);
try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e){
e.printStackTrace();
}
try {
mCameraDevice.createCaptureSession(Arrays.asList(surface), mPreviewStateCallback, null);
mPreviewBuilder.addTarget(surfaceFull);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
and in my SurfaceTextureListener :
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
my_analyze(mTextureView.getBitmap());
}
});
thread.start();
}
And the bitmap is only what I see in the TextureView (which is logical) but I want the entire image.
Is it possible ?
Thanks,
NiCLO
You can send the frames to a SurfaceTexture you create, rather than one that's part of TextureView, then get the pixels by rendering them to a GLES pbuffer and reading them back with glReadPixels().
If you can work in YUV rather than RGB, you can get to the data faster and more simply by directing the Camera2 output to an ImageReader.
Grafika has some useful examples, e.g. "texture from camera".
For me, the next implementation works fine for me:
Bitmap bitmap = mTextureView.getBitmap(mWidth, mHeight);
int[] argb = new int[mWidth * mHeight];
// get ARGB pixels and then proccess it with 8UC4 OpenCV convertion
bitmap.getPixels(argb, 0, mWidth, 0, 0, mWidth, mHeight);
// native method (NDK or CMake)
processFrame8UC4(argb, mWidth, mHeight);
A complete implementation for Camera API2 and NDK (OpenCV) here: https://stackoverflow.com/a/49331546/471690

Why do i always get a whole black picture which screenshoot by GLSurfaceView?

I use the GLSurfaceView as the view to display the camera preview data. I use createBitmapFromGLSurface aim at grabPixels and save it to Bitmap.
However, I always get a whole black picture after save the bitmap to file. Where am i wrong?
Following is my code snippet.
#Override
public void onDrawFrame(GL10 gl) {
if (mIsNeedCaptureFrame) {
mIsNeedCaptureFrame = false;
createBitmapFromGLSurface(width, height);
}
}
private void createBitmapFromGLSurface(int w, int h) {
ByteBuffer buf = ByteBuffer.allocateDirect(w * h * 4);
buf.position(0);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glReadPixels(0, 0, w, h,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
buf.rewind();
Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
Log.i(TAG, "createBitmapFromGLSurface w:" + w + ",h:" + h);
mFrameCapturedCallback.onFrameCaptured(bmp);
}
Update:
public void captureFrame(FrameCapturedCallback callback) {
mIsNeedCaptureFrame = true;
mCallback = callback;
}
public void takeScreenshot() {
final int width = mIncomingWidth;
final int height = mIncomingHeight;
EglCore eglCore = new EglCore(EGL14.eglGetCurrentContext(), EglCore.FLAG_RECORDABLE);
OffscreenSurface surface = new OffscreenSurface(eglCore, width, height);
surface.makeCurrent();
Bitmap bitmap = surface.captureFrame();
for (int x = 0, y = 0; x < 100; x++, y++) {
Log.i(TAG, "getPixel:" + bitmap.getPixel(x, y));
}
surface.release();
eglCore.release();
mCallback.onFrameCaptured(bitmap);
}
#Override
public void onDrawFrame(GL10 gl) {
mSurfaceTexture.updateTexImage();
if (mIsNeedCaptureFrame) {
mIsNeedCaptureFrame = false;
takeScreenshot();
return;
}
....
}
The logs are following:
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
...
This won't work.
To understand why, bear in mind that a SurfaceView Surface is a queue of buffers with a producer-consumer relationship. When displaying camera preview data, the Camera is the producer, and the system compositor (SurfaceFlinger) is the consumer. Only the producer can send data to the Surface -- there can be only one producer at a time -- and only the consumer can examine buffers from the Surface.
If you were drawing on the Surface yourself, so your app would be the producer, you would be able to see what you've drawn, but only while inonDrawFrame(). When it returns, GLSurfaceView calls eglSwapBuffers(), and the frame you've drawn is sent off to the consumer. (Technically, because the buffers are in a pool and are re-used, you can read frames outside onDrawFrame(); but what you're reading would be stale data from 1-2 frames back, not the one you just drew.)
What you're doing here is reading data from an EGLSurface that has never been drawn on and isn't connected to the SurfaceView. That's why it's always reading black. The Camera doesn't "draw" the preview, it just takes a buffer of YUV data and shoves it into the BufferQueue.
If you want to show the preview and capture frames using GLSurfaceView, see the "show + capture camera" example in Grafika. You can replace the MediaCodec code with a glReadPixels() (see EglSurfaceBase.saveFrame(), which looks very much like what you have).

Android: how to save the first preview frame from SurfaceView

I was wondering if it is possible to save the first frame from camera preview, visible on a SurfaceView.
Within my activity I've currently implemented all the SurfaceHolder.Callback methods. When I call startPreview on camera object and than takePicture the callback is executed correctly, but I need to take and save the picture more quickly, so take the first visible frame of the preview would be very useful.
What can I do?
Any suggestion would be greatly appreciated.
It might seem incredibly simple, but I would do something like this to save the first frame into a Bitmap.
mCamera.setPreviewCallback(new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
if (image == null) {
out = new ByteArrayOutputStream();
yuvImage = new YuvImage(data, ImageFormat.NV21, camera
.getParameters().getPreviewSize().width, camera
.getParameters().getPreviewSize().height, null);
yuvImage.compressToJpeg(new Rect(0, 0, camera
.getParameters().getPreviewSize().width, camera
.getParameters().getPreviewSize().height), 0,
out);
imageBytes = out.toByteArray();
image = BitmapFactory.decodeByteArray(imageBytes, 0,
imageBytes.length);
}
}
});

Best way to scale size of camera picture before saving to SD

The code below is executed as the jpeg picture callback after TakePicture is called. If I save data to disk, it is a 1280x960 jpeg. I've tried to change the picture size but that's not possible as no smaller size is supported. JPEG is the only available picture format.
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
FileOutputStream out = null;
Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
Bitmap sbm = Bitmap.createScaledBitmap(bm, 640, 480, false);
data.Length is something like 500k as expected. After executing BitmapFactory.decodeByteArray(), bm has a height and width of -1 so it appears the operation is failing.
It's unclear to me if Bitmap can handle jpeg data. I would think not but I have seem some code examples that seem to indicate it is.
Does data need to be in bitmap format before decoding and scaling?
If so, how to do this?
Thanks!
On your surfaceCreated, you code set the camara's Picture Size, as shown the code below:
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
try {
camera.setPreviewDisplay(holder);
Camera.Parameters p = camera.getParameters();
p.set("jpeg-quality", 70);
p.setPictureFormat(PixelFormat.JPEG);
p.setPictureSize(640, 480);
camera.setParameters(p);
} catch (IOException e) {
e.printStackTrace();
}
}

Categories

Resources