I am trying to build a simple camera2 app to record videos and store on internal storage.
So on I have the following code:
package com.example.newcameraapp;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CAMERA_PERMISSION = 200;
private static final String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
private CameraManager cameraManager;
private CameraDevice cameraDevice;
private Size videoSize;
private MediaRecorder mediaRecorder;
private CaptureRequest.Builder captureRequestBuilder;
private CameraCaptureSession cameraCaptureSession;
private String videoFilePath;
private boolean isRecording = false;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private TextureView previewView;
private static final int FIXED_FRAME_RATE = 30; // Fixed frame rate in frames per second
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
previewView = findViewById(R.id.previewView);
Button recordButton = findViewById(R.id.recordButton);
recordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!isRecording) {
startRecording();
} else {
stopRecording();
}
}
});
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA_PERMISSION);
}
}
@SuppressLint("MissingPermission")
private void startCamera() {
cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
try {
String cameraId = cameraManager.getCameraIdList()[0]; // Use the first camera
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
videoSize = chooseVideoSize(characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(MediaRecorder.class));
cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
MainActivity.this.cameraDevice = cameraDevice;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
// Handle camera error
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private Size chooseVideoSize(Size[] choices) {
for (Size size : choices) {
if (size.getWidth() == 1920 && size.getHeight() == 1080) {
return size;
}
}
return choices[choices.length - 1]; // Return the largest available size by default
}
private void startPreview() {
try {
closePreviewSession();
SurfaceTexture texture = previewView.getSurfaceTexture();
texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());
Surface previewSurface = new Surface(texture);
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
captureRequestBuilder.addTarget(previewSurface);
cameraDevice.createCaptureSession(Collections.singletonList(previewSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if (cameraDevice == null) {
return;
}
cameraCaptureSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// Handle configuration failure
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void updatePreview() {
if (cameraDevice == null) {
return;
}
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void startRecording() {
if (cameraDevice == null || !previewView.isAvailable() || videoSize == null) {
return;
}
try {
closePreviewSession();
setUpMediaRecorder();
SurfaceTexture texture = previewView.getSurfaceTexture();
texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());
Surface previewSurface = new Surface(texture);
List<Surface> surfaces = new ArrayList<>();
surfaces.add(previewSurface);
surfaces.add(mediaRecorder.getSurface());
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
for (Surface surface : surfaces) {
captureRequestBuilder.addTarget(surface);
}
cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if (cameraDevice == null) {
return;
}
cameraCaptureSession = session;
updatePreview();
runOnUiThread(new Runnable() {
@Override
public void run() {
mediaRecorder.start();
isRecording = true;
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// Handle configuration failure
}
}, null);
} catch (CameraAccessException | IOException e) {
e.printStackTrace();
}
}
private void setUpMediaRecorder() throws IOException {
mediaRecorder = new MediaRecorder();
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(getOutputMediaFile());
mediaRecorder.setVideoEncodingBitRate(10000000); // Adjust as needed
mediaRecorder.setVideoFrameRate(FIXED_FRAME_RATE); // Set the fixed frame rate
mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.prepare();
}
private void closePreviewSession() {
if (cameraCaptureSession != null) {
cameraCaptureSession.close();
cameraCaptureSession = null;
}
}
private void stopRecording() {
if (isRecording) {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
mediaRecorder = null;
isRecording = false;
startPreview();
}
}
private String getOutputMediaFile() {
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM), "MyCameraApp");
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
videoFilePath = mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4";
return videoFilePath;
}
private boolean allPermissionsGranted() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
layout xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<Button
android:id="@+id/recordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Record"
android:onClick="onClick"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
After running the code (also checked all necessary permissions), following error occurs:
FATAL EXCEPTION: main
Process: com.example.newcameraapp, PID: 6357
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.SurfaceTexture.setDefaultBufferSize(int, int)' on a null object reference
at com.example.newcameraapp.MainActivity.startPreview(MainActivity.java:136)
at com.example.newcameraapp.MainActivity.access$400(MainActivity.java:41)
at com.example.newcameraapp.MainActivity$2.onOpened(MainActivity.java:101)
at android.hardware.camera2.impl.CameraDeviceImpl$1.run(CameraDeviceImpl.java:151)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Error is caused by texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());
My layout xml seems correct and I called TextureView correctly too.
Can someone help?
You should put previewView.getSurfaceTexture()
into onSurfaceTextureAvailable()
of TextureView.SurfaceTextureListener
.
For example, replacing the original onOpened(@NonNull CameraDevice cameraDevice)
with the following code can resolve it:
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
MainActivity.this.cameraDevice = cameraDevice;
previewView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
startPreview();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
});
}