Android 利用Camera2实现相机预览、拍照、保存图片

1、布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

2、在AndroidManifest.xml中增加权限

<uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera2.full" />
    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

3、MainActivity的详细代码,包括设置分辨率、横竖屏旋转适配(此适配方案需根据不同设备调整),每五帧自动保存一张图片到相册(此需求需要增加保存图片的权限),如需点击按钮拍照,可根据自动拍照的逻辑进行一些修改。

package com.kgzn.aicamera;

import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
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.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.Display;
import android.view.TextureView;

//import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int REQUEST_CAMERA_PERMISSION = 200;
    private TextureView textureView;
    private CameraDevice cameraDevice;
    private CameraCaptureSession cameraCaptureSession;
    private CaptureRequest.Builder captureRequestBuilder;
    private String cameraId;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private ImageReader imageReader;
    private int sensorOrientation;
    private Size targetSize;
    private int frameCounter = 0; // 帧计数器
    private NcnnDetectHand ncnnDetectHand;

    private int current_cpugpu = 0;
    private boolean use_gpu = false;
    private TextView gestureText;
    private Boolean isInit = false;
    private List<String> resultList = Collections.synchronizedList(new ArrayList<>());

    private Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull android.os.Message msg) {
            if (msg.what == 0) {
                // 清空 gestureText 的文本
                gestureText.setText("");
            }
        }
    };

    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.d(TAG, "onSurfaceTextureAvailable: textureView = " + textureView + ", cameraDevice = " + cameraDevice);
            configureTransform(width, height);
            openCamera(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            Log.d(TAG, "onSurfaceTextureAvailable: textureView = " + textureView + ", cameraDevice = " + cameraDevice);
            // 可以在这里处理纹理视图大小改变的情况
            configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            // 纹理视图更新时调用
        }
    };

    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.d(TAG, "onOpened: ");
            cameraDevice = camera;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.d(TAG, "onDisconnected: ");
            cameraDevice.close();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.d(TAG, "onError: ");
            cameraDevice.close();
            cameraDevice = null;
        }
    };

    private final ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireLatestImage();
            if (image != null) {
                Image.Plane[] planes = image.getPlanes();
                ByteBuffer buffer = planes[0].getBuffer();
                byte[] data = new byte[buffer.capacity()];
                buffer.get(data);
                // 将 byte[] 数据转换为 Bitmap
//                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

                frameCounter++;
                if (frameCounter % 5 == 0) {
                    Log.d(TAG, "onImageAvailable: frameCounter = " + frameCounter);
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                        if (bitmap != null) {
                            // 获取设备方向
                            int rotation = getDeviceRotation();
                            int rotationDegrees = getBitmapOrientation(rotation);

                            // 处理镜像和旋转
                            Bitmap processedBitmap = processBitmap(bitmap, rotationDegrees);
                             saveBitmapToGallery(processedBitmap);
                    }
                   

                }
                // 在这里可以对每一帧图片数据进行操作
                // 例如进行图像处理、特征提取等
                image.close();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 隐藏状态栏和导航栏
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);

        textureView = findViewById(R.id.textureView);
        textureView.setSurfaceTextureListener(textureListener);
        gestureText = findViewById(R.id.gesture_text);

    }

    @Override
    protected void onResume() {
        super.onResume();
        startBackgroundThread();
        if (textureView.isAvailable()) {
            openCamera(textureView.getWidth(), textureView.getHeight());

        } else {
            textureView.setSurfaceTextureListener(textureListener);
        }
    }

    @Override
    protected void onPause() {
        releaseCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ncnnDetectHand.unloadModel();
    }

    private void openCamera(int width, int height) {
        CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
        try {
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    this.cameraId = cameraId;
                    sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    if (map != null) {
//                        Size largest = getLargestSize(map.getOutputSizes(ImageFormat.JPEG));
//                        imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG,
//                                10);
                        // 假设目标分辨率为 800x600
                        int targetWidth = 800;
                        int targetHeight = 600;
                        // 传入 JPEG 格式支持的尺寸列表
                        targetSize = chooseOptimalSize(map.getOutputSizes(ImageFormat.JPEG), targetWidth, targetHeight);
                        imageReader = ImageReader.newInstance(targetSize.getWidth(), targetSize.getHeight(), ImageFormat.JPEG, 10);
                        imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler);
                    }
                    break;
                }
            }

            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
                return;
            }
            manager.openCamera(cameraId, stateCallback, backgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void createCameraPreview() {
        try {
            SurfaceTexture texture = textureView.getSurfaceTexture();
            texture.setDefaultBufferSize(textureView.getWidth(), textureView.getHeight());
            Surface surface = new Surface(texture);

            captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            captureRequestBuilder.addTarget(surface);
            captureRequestBuilder.addTarget(imageReader.getSurface());

//            // 设置目标帧率范围为 30fps
//            android.util.Range<Integer> fpsRange = new android.util.Range<>(30, 30);
//            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);

            cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    if (cameraDevice == null) {
                        return;
                    }
                    cameraCaptureSession = session;
                    try {
                        captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();
                }
            }, backgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void startBackgroundThread() {
        backgroundThread = new HandlerThread("CameraBackground");
        backgroundThread.start();
        backgroundHandler = new Handler(backgroundThread.getLooper());
    }

    private void stopBackgroundThread() {
        backgroundThread.quitSafely();
        try {
            backgroundThread.join();
            backgroundThread = null;
            backgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void configureTransform(int viewWidth, int viewHeight) {
        if (null == textureView) {
            return;
        }
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        Log.d(TAG, "configureTransform: rotation = " + rotation);
        android.graphics.Matrix matrix = new android.graphics.Matrix();
        android.graphics.RectF viewRect = new android.graphics.RectF(0, 0, viewWidth, viewHeight);
        android.graphics.RectF bufferRect = new android.graphics.RectF(0, 0, textureView.getHeight(), textureView.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, android.graphics.Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / textureView.getHeight(),
                    (float) viewWidth / textureView.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180, centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    private Size getLargestSize(Size[] sizes) {
        Size largest = sizes[0];
        for (Size size : sizes) {
            if (size.getWidth() * size.getHeight() > largest.getWidth() * largest.getHeight()) {
                largest = size;
            }
        }
        return largest;
    }

    /**
     * 选择最接近目标分辨率的尺寸
     * @param sizes 相机支持的尺寸列表
     * @param targetWidth 目标宽度
     * @param targetHeight 目标高度
     * @return 最接近目标分辨率的尺寸
     */
    private Size chooseOptimalSize(Size[] sizes, int targetWidth, int targetHeight) {
        Size optimalSize = null;
        int minDiff = Integer.MAX_VALUE;

        for (Size size : sizes) {
            int widthDiff = Math.abs(size.getWidth() - targetWidth);
            int heightDiff = Math.abs(size.getHeight() - targetHeight);
            int diff = widthDiff + heightDiff;

            if (diff < minDiff) {
                minDiff = diff;
                optimalSize = size;
            }
        }

        return optimalSize;
    }

    @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) {
                Toast.makeText(this, "你拒绝了相机权限", Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    private void saveBitmapToGallery(Bitmap bitmap) {
        ContentResolver contentResolver = getContentResolver();
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "CameraFrame_" + System.currentTimeMillis() + ".jpg");
        contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
        Uri imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
        if (imageUri != null) {
            try {
                OutputStream outputStream = contentResolver.openOutputStream(imageUri);
                if (outputStream != null) {
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                    outputStream.close();
                    Toast.makeText(this, "图片已保存到图库", Toast.LENGTH_SHORT).show();
                }
            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(this, "保存图片失败", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void releaseCamera() {
        try {
            if (null != cameraCaptureSession) {
                cameraCaptureSession.close();
                cameraCaptureSession = null;
            }
            if (null != cameraDevice) {
                cameraDevice.close();
                cameraDevice = null;
            }
            if (null != imageReader) {
                imageReader.close();
                imageReader = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private int getDeviceRotation() {
        Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        int rotation = display.getRotation();
//        LogUtil.i(TAG,"getDeviceRotation rotation:"+rotation);
        switch (rotation) {
            case Surface.ROTATION_0:
                return 270;
            case Surface.ROTATION_180:
                return 90;
            case Surface.ROTATION_270:
                return 180;
            default:
                return 0;
        }
    }

    public int getBitmapOrientation(int deviceOrientation){
        if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return sensorOrientation;
        deviceOrientation = (deviceOrientation + 45) / 90 * 90;
        int jpegOrientation = (sensorOrientation - deviceOrientation + 360) % 360 + 90;
        return jpegOrientation;
    }

    private Bitmap processBitmap(Bitmap bitmap, int rotation) {
        Matrix matrix = new Matrix();

        // 镜像处理(水平镜像)
        matrix.preScale(-1, 1);

        // 旋转处理
        matrix.postRotate(rotation);

        // 创建新的 Bitmap
//        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

        // 创建新的 Bitmap
        Bitmap processedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

        // 压缩图片质量
        Bitmap compressedBitmap = compressBitmap(processedBitmap, 50); // 50 表示压缩质量为 50%

        // 回收原始的 processedBitmap
        processedBitmap.recycle();

        return compressedBitmap;
    }


    private Bitmap compressBitmap(Bitmap bitmap, int quality) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
    }

}

### Android Camera2 API 使用教程 #### 1. 初步介绍 Android Camera2 API 提供了一种更灵活的方式来控制设备上的摄像头功能。相比旧版的 `Camera` API,`Camera2` 更加复杂但也更为强大[^1]。 #### 2. 配置环境 为了成功运行基于 Camera2 API 的应用,开发人员需要确保目标设备支持该 API 并设置正确的权限声明。如果项目配置失败,则可能是因为缺少必要的依赖项或未正确处理权限请求。 - **权限声明**: 在 `AndroidManifest.xml` 文件中添加以下权限: ```xml <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera.any" /> ``` - **动态权限申请**: 对于 Android 6.0 (API Level 23) 及以上版本的应用,需在运行时向用户请求访问相机的权限。 #### 3. 手动调整曝光参数 通过 Camera2 API 能够实现对手动曝光的支持。这通常涉及修改传感器的感光度以及调节镜头的实际开合时间来达到理想的亮度效果[^2]。 以下是简单的代码片段展示如何获取并设定当前活动中的曝光补偿等级: ```java private void setAutoExposure(CameraCharacteristics characteristics){ Range<Integer> range = characteristics.get( CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); Float stepSize = characteristics.get( Camera Characteristics.CONTROL_AE_COMPENSATION_STEP); CaptureRequest.Builder builder; try { builder = cameraDevice.createCaptureRequest(...); int compensationIndex = ...; // Calculate desired index within 'range' float finalCompensationValue = stepSize * compensationIndex; builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, Math.round(finalCompensationValue)); } catch(Exception e){ /* Handle exception */ } } ``` #### 4. 解决拍照过程中的典型障碍 当利用 Camera2 构建自定义摄像机界面时常会碰到一些棘手的技术难题,比如预览画面冻结或者保存图像质量差等问题[^3]。这些问题往往源于资源管理不当或是未能妥善同步异步操作的结果。 针对上述提到的闪光灯无法正常开启的情况,可以尝试如下方式修正逻辑错误[^4]: ```java public void toggleTorch(boolean enable) throws CameraAccessException{ mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, null); if(enable && !isFlashSupported()) return; mPreviewBuilder.set(CaptureRequest.FLASH_MODE, enable ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF ); } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值