In the previous article, I have introduced how to use CameraView, which is the official library provided by Google and has related demo. Because Android is too fragmented, the official also provides CameraView for your study and reference.
Source code analysis
Source structure
API Level | Camera API | Preview View |
---|---|---|
9-13 | Camera1 | SurfaceView |
14 to 20 | Camera1 | TextureView |
21-23 | Camera2 | TextureView |
24 | Camera2 | SurfaceView |
The implementation is all in the CameraView class.
The class diagram
The relationships between the main classes involved in the source code are viewed from the latest source code (unlike the table on the official website, the latest source SDK version is 14) :
- Camera1 is used for Android5.0(21) and Camera2 is used for Android5.0(21)
- The Preview View: SurfaceView SurfaceView SurfaceView SurfaceView(>23) All others use TextureView, the latest source SDK minimum version 14.
CameraView
Using CameraView is very simple, and using it in CameraView has been explained in detail in that article.
Initialize the
<com.google.android.cameraview.CameraView
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keepScreenOn="true"
android:adjustViewBounds="true"
app:autoFocus="true"
app:aspectRatio="4:3"
app:facing="back"
app:flash="auto"/>
Copy the code
Look directly at the CameraView constructor:
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); .// Internal setup
1. Create a preview view
final PreviewImpl preview = createPreviewImpl(context);
mCallbacks = new CallbackBridge();
// 2. Select a Camera based on the Android SDK version
if (Build.VERSION.SDK_INT < 21) {
mImpl = new Camera1(mCallbacks, preview);
} else if (Build.VERSION.SDK_INT < 23) {
mImpl = new Camera2(mCallbacks, preview, context);
} else {
mImpl = new Camera2Api23(mCallbacks, preview, context);
}
// Attributes
// 3. Read from the definition View property, set the camera position, preview picture scale, focus mode, flash
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,
R.style.Widget_CameraView);
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK));
String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio);
if(aspectRatio ! =null) {
setAspectRatio(AspectRatio.parse(aspectRatio));
} else {
setAspectRatio(Constants.DEFAULT_ASPECT_RATIO);
}
setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true));
setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO));
a.recycle();
// Display orientation detector
// 4. Add the rotation direction monitor and set the camera rotation direction
mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
@Override
public void onDisplayOrientationChanged(int displayOrientation) { mImpl.setDisplayOrientation(displayOrientation); }}; }Copy the code
CreatePreviewImpl implementation:
private PreviewImpl createPreviewImpl(Context context) {
PreviewImpl preview;
if (Build.VERSION.SDK_INT >= 23) {
preview = new SurfaceViewPreview(context, this);
} else {
preview = new TextureViewPreview(context, this);
}
return preview;
}
Copy the code
Here’s a look at the CameraView version selection strategy from the main code point of view, as explained at the beginning. Camera select: API <21, use Camera1, >=21 use Camera2, there is no problem at all. API >=23 uses SurfaceView, but API 24 uses SurfaceView.
Starting in platform version N, SurfaceView’s Window Position is updated synchronously with other View rendering. This means that Translating and Moderating scaling a SurfaceView on screen will not cause rendering artifacts. Such artifacts may occur on previous versions of the platform when its window is positioned asynchronously.
API >23 uses SurfaceView, otherwise uses TextureView, because minSdkVersion = 14 is defined in the project, API 14-23 uses TextureView, and the table can be updated to:
API Level | Camera API | Preview View |
---|---|---|
14 to 20 | Camera1 | TextureView |
21-23 | Camera2 | TextureView |
24 | Camera2 | SurfaceView |
The Preview is defined
PreviewImpl encapsulates the operation method of the preview control. SurfaceViewPreview and TextureViewPreview correspond to the PreviewImpl implementation of SurfaceView and TextureView respectively.
SurfaceViewPreview
The implementation is simple, loading the layout directly with the SurfaceView control and encapsulating the SurfaceHolder operation
TextureViewPreview
Direct load TextureView control layout, and to monitor TextureView. SurfaceTextureListener.
Camera is defined
CameraViewImpl defines various camera operations, Camera1, Camera2, Camera2Api23 are the concrete implementation of CameraViewImpl. Take a look at their constructor: Camera1
Camera1(Callback callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged(a) {
if(mCamera ! =null) { setUpPreview(); adjustCameraParameters(); }}}); }Copy the code
Added Callback to set preview and Camera parameters while SurfaceChanged. Camera2
Camera2(Callback callback, PreviewImpl preview, Context context) {
super(callback, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
mPreview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged(a) { startCaptureSession(); }}); }Copy the code
2. Add Callback to CaptureSession while SurfaceChanged
Camera2Api23 inherits Camera2 and reuses the constructs of Camera2. Camera1 and Camera2 implementation and use process, can refer to the previous several articles, the next introduction to the implementation of several important methods of CameraView.
start
public void start(a) {
if(! mImpl.start()) {//store the state ,and restore this state after fall back o Camera1
Parcelable state = onSaveInstanceState();
// Camera2 uses legacy hardware layer; fall back to Camera1
mImpl = newCamera1(mCallbacks, createPreviewImpl(getContext())); onRestoreInstanceState(state); mImpl.start(); }}Copy the code
To start the camera, check whether it can be started. If it cannot be started, Camera1 is used by default.
Camera1.start
boolean start(a) {
//1. Select a camera
chooseCamera();
//2. Open the camera
openCamera();
//3. Set preview
if (mPreview.isReady()) {
setUpPreview();
}
mShowingPreview = true;
//4. Start preview
mCamera.startPreview();
return true;
}
Copy the code
For details about the process, you can refer to Camera1. Here are some important methods. ChooseCamera will walk through all the cameras and compare them against the value passed in when CameraView is initialized. The default is the FACING_BACK rear camera. 2. OpenCamera method, here is a detailed introduction:
private void openCamera(a) {
if(mCamera ! =null) {
releaseCamera();
}
//1. Open the camera
mCamera = Camera.open(mCameraId);
mCameraParameters = mCamera.getParameters();
// Supported preview sizes
mPreviewSizes.clear();
//2. Get all supported preview sizes
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
Log.d("DEBUG"."###### SupportedPreviewSizes: width=" + size.width + ", height="
+ size.height);
mPreviewSizes.add(new Size(size.width, size.height));
}
// Supported picture sizes;
mPictureSizes.clear();
//3. Get all supported camera sizes
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
Log.d("DEBUG"."###### SupportedPictureSizes: width=" + size.width + ", height="
+ size.height);
mPictureSizes.add(new Size(size.width, size.height));
}
// AspectRatio
if (mAspectRatio == null) {
mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
}
//4. Set the preview scale
adjustCameraParameters();
mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
mCallback.onCameraOpened();
}
Copy the code
3. SetUpPreview set preview
void setUpPreview(a) {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
} else{ mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture()); }}catch (IOException e) {
throw newRuntimeException(e); }}Copy the code
Use SurfaceView preview or TextureView preview, depending on the mPreview type. 4. McAmera.startpreview () Starts preview
Camera2.start
boolean start(a) {
//1. Select a camera. By default, the rear camera is installed
if(! chooseCameraIdByFacing()) {return false;
}
//2. Set preview size, preview size ratio, photo size and other related Settings
collectCameraInfo();
//3. Initialize ImageReader and set the callback
prepareImageReader();
//4. Open the camera
startOpeningCamera();
return true;
}
Copy the code
For details, refer to Camera2. Only important methods are introduced here. 1. ChooseCameraIdByFacing go through the list of supported cameras and select the specified camera according to the conditions. The default rear camera is installed. 2. PrepareImageReader method, get all supported preview size, and photo size, and get the supported preview size ratio
private void prepareImageReader(a) {
if(mImageReader ! =null) {
mImageReader.close();
}
Size largest = mPictureSizes.sizes(mAspectRatio).last();
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /* maxImages */ 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);
}
Copy the code
Set the output format to JPEG, and add ImageAvailableListener callback listener. 4. StartOpeningCamera method, open the camera, and set the CameraDevice. StateCallback listening in
private void startOpeningCamera(a) {
try {
mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null);
} catch (CameraAccessException e) {
throw new RuntimeException("Failed to open camera: "+ mCameraId, e); }}private final CameraDevice.StateCallback mCameraDeviceCallback
= new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCamera = camera;
mCallback.onCameraOpened();
// Open the camera, open the preview screen
startCaptureSession();
}
@Override
public void onClosed(@NonNull CameraDevice camera) {
mCallback.onCameraClosed();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
mCamera = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "onError: " + camera.getId() + "(" + error + ")");
mCamera = null; }};Copy the code
After the monitor camera is turned on, open the preview screen, startCaptureSession
void startCaptureSession(a) {
if(! isCameraOpened() || ! mPreview.isReady() || mImageReader ==null) {
return;
}
//1. Select the most appropriate preview size
Size previewSize = chooseOptimalSize();
mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = mPreview.getSurface();
try {
//2. Create a preview request
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//3. Request management target surface
mPreviewRequestBuilder.addTarget(surface);
// create CaptureSession and add Session listener
mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
mSessionCallback, null);
} catch (CameraAccessException e) {
throw new RuntimeException("Failed to start camera session"); }}Copy the code
Listen for a callback statement from the CameraCaptureSession state:
private final CameraCaptureSession.StateCallback mSessionCallback
= new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if (mCamera == null) {
return;
}
mCaptureSession = session;
updateAutoFocus();
updateFlash();
try {
//1. Enable preview and set the listening callback
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
mCaptureCallback, null);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to start camera preview.", e); }}@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "Failed to configure capture session.");
}
@Override
public void onClosed(@NonNull CameraCaptureSession session) {
if(mCaptureSession ! =null && mCaptureSession.equals(session)) {
mCaptureSession = null; }}};Copy the code
takePicture
public void takePicture(a) {
mImpl.takePicture();
}
Copy the code
Choose different implementations depending on the API
Camera1.takePicture
void takePicture(a) {
if(! isCameraOpened()) {throw new IllegalStateException(
"Camera is not ready. Call start() before takePicture().");
}
//1. Check whether auto focus is enabled
if (getAutoFocus()) {
mCamera.cancelAutoFocus();
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
/ / 2. Take photostakePictureInternal(); }}); }else {
/ / 2. Take photostakePictureInternal(); }}Copy the code
The actual method to take a photo is takePictureInternal:
void takePictureInternal(a) {
if(! isPictureCaptureInProgress.getAndSet(true)) {
//1. Take a photo and add callback
mCamera.takePicture(null.null.null.new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
isPictureCaptureInProgress.set(false);
//2. Pass data to the upper callbackmCallback.onPictureTaken(data); camera.cancelAutoFocus(); camera.startPreview(); }}); }}Copy the code
Add jpeg PictureCallback callback to takePicture, return data via callback to the upper layer
Camera2.takePicture
void takePicture(a) {
//1. Determine auto focus
if (mAutoFocus) {
lockFocus();
} else {
//2captureStillPicture(); }}// Set the focus
private void lockFocus(a) {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_START);
try {
mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to lock focus.", e); }}//PictureCaptureCallback
PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() {
@Override
public void onPrecaptureRequired(a) {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
setState(STATE_PRECAPTURE);
try {
mCaptureSession.capture(mPreviewRequestBuilder.build(), this.null);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to run precapture sequence.", e); }}@Override
public void onReady(a) {
//2captureStillPicture(); }};Copy the code
Call captureStillPicture ();
void captureStillPicture(a) {
try {
//1. Create a Capture request for TEMPLATE_STILL_CAPTURE
CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
/ / 2. Add a target
captureRequestBuilder.addTarget(mImageReader.getSurface());
//3. Set AF mode
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE));
//4. Set flash mode.// Calculate JPEG orientation.
//5. Calculate the direction of the photo taken
@SuppressWarnings("ConstantConditions")
int sensorOrientation = mCameraCharacteristics.get(
CameraCharacteristics.SENSOR_ORIENTATION);
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,
(sensorOrientation +
mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) +
360) % 360);
// Stop preview and capture a still picture.
// stop the preview
mCaptureSession.stopRepeating();
/ / 7. Take pictures
mCaptureSession.capture(captureRequestBuilder.build(),
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
//8. UnfocusunlockFocus(); }},null);
} catch (CameraAccessException e) {
Log.e(TAG, "Cannot capture a still picture.", e); }}// Unfocus and reset preview
void unlockFocus(a) {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
try {
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);
updateAutoFocus();
updateFlash();
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,
null);
mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to restart camera preview.", e); }}Copy the code
The actual data callback after the photo is in the OnImageAvailableListener that initializes the ImageReader
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
try (Image image = reader.acquireNextImage()) {
Image.Plane[] planes = image.getPlanes();
if (planes.length > 0) {
ByteBuffer buffer = planes[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
//1. Data is called back to the upper layermCallback.onPictureTaken(data); }}}};Copy the code
stop
If you don’t need to take a picture or exit the application, call the stop method
public void stop(a) {
mImpl.stop();
}
Copy the code
Camera1.stop
void stop(a) {
if(mCamera ! =null) {
//1. Stop preview
mCamera.stopPreview();
}
mShowingPreview = false;
//2. Release camera resources
releaseCamera();
}
// Release the camera resources
private void releaseCamera(a) {
if(mCamera ! =null) {
mCamera.release();
mCamera = null;
//1mCallback.onCameraClosed(); }}Copy the code
Camera2.stop
void stop(a) {
/ / 1. Close the session
if(mCaptureSession ! =null) {
mCaptureSession.close();
mCaptureSession = null;
}
/ / 2. Close the Camera
if(mCamera ! =null) {
mCamera.close();
mCamera = null;
}
/ / 3. Close the ImageReader
if(mImageReader ! =null) {
mImageReader.close();
mImageReader = null; }}Copy the code
CameraView source code related analysis is over, the library is a good encapsulation of the use of Camera1 and Camera2, this article focuses on the analysis of the method, explained its implementation principle, I hope to help you.
Reference:
- Android camera development — CameraView source code parsing
- Android Camera programming from entry to mastery
- Android – Google cameraview details