Android摄像头采集选Camera1还是Camera2?

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
简介: Camera1与Camera2是Android平台上的两种摄像头API。Camera1(API1)在Android 5.0后被标记为过时,新项目应优先选用Camera2(API2)。Camera2提供了更精细的控制选项,如曝光时间、ISO感光度等;支持多摄像头管理;采用异步操作提高应用响应速度;并支持RAW图像捕获及实时图像处理。此外,它还具备更好的适配性和扩展性,适用于各类应用场景,如相机应用开发、视频通话和计算机视觉等。因此,在现代Android开发中推荐使用Camera2。

Camera1还是Camera2?

好多开发者纠结,Android平台采集摄像头,到底是用Camera1还是Camera2?实际上,Camera1和Camera2分别对应相机API1和相机API2。Android 5.0开始,已经弃用了Camera API1,新平台重点开发Camera API2,Camera API1 会逐渐被淘汰。Camera API2 框架为应用提供更接近底层的相机控件,包括高效的零复制连拍/视频流以及曝光、增益、白平衡增益、颜色转换、去噪、锐化等方面的每帧控件。

Camera2 相对Camera1有哪些优势?

1. 更灵活的相机控制

  • 细粒度参数调整:Camera2 API提供了更多的相机参数和配置选项,开发者可以更精确地控制相机的焦距、曝光时间、ISO感光度、白平衡等,以满足不同场景下的需求。
  • 多摄像头支持:能够同时管理和控制多个相机设备,包括前置摄像头、后置摄像头以及其他可用的摄像头,提高了系统的灵活性和扩展性。

2. 更高的性能

  • 异步操作:Camera2 API使用异步操作模式,减少了对UI线程的阻塞,提高了应用的响应速度和流畅性。
  • 并发访问:支持并发访问和操作多个相机设备,提高了相机的利用率和整体性能。

3. 更好的图像处理能力

  • RAW图像捕获:支持原生的RAW图像捕获和处理,开发者可以获取到相机传感器的原始数据,进行更高质量的图像处理和分析。
  • 实时预览和后处理:提供实时的图像预览和后处理功能,有助于开发者实现更丰富的图像效果。

4. 更好的适配性和扩展性

  • 统一的接口和架构:Camera2 API提供了更统一的接口和更清晰的架构,便于开发者进行相机功能的开发和适配。
  • 功能扩展:允许开发者通过CameraCharacteristics和CaptureRequest等接口获取和设置摄像头的各种功能和参数,支持自定义功能的开发。

5. 广泛的应用场景

  • 相机应用开发:Camera2 API提供了丰富的相机控制接口,可以用于开发各种相机应用,如拍照、录像、实时滤镜等。
  • 视频通话和实时视频应用:支持高质量的视频捕获和处理,适用于视频通话、视频会议等实时视频应用。
  • 计算机视觉和图像处理:结合OpenCV等图像处理库,可以进行实时的图像分析、特征提取、目标识别等计算机视觉任务。

如何使用Camera2进行相机操作

使用Android的Camera2 API来进行相机操作,包括预览、拍照等功能,是一个相对复杂但功能强大的过程。以下是一个基本的步骤指南,帮助你开始使用Camera2 API:

1. 添加权限

首先,你需要在AndroidManifest.xml文件中添加必要的权限,以便应用能够访问设备的相机。至少需要添加相机权限:

<uses-permission android:name="android.permission.CAMERA"/>

image.gif

如果你的应用还需要录制视频或音频,还需要添加相应的权限:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>  
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
<uses-feature android:name="android.hardware.camera" android:required="true"/>  
<uses-feature android:name="android.hardware.camera.autofocus"/>

image.gif

需要注意的是,从Android 6.0(API 级别 23)开始,需要在运行时请求这些权限,而不是仅仅在清单文件中声明。

2. 初始化CameraManager

在你的Activity或Fragment中,首先需要获取CameraManager的实例,这个类是用于管理设备上的相机资源:

CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

image.gif

3. 获取相机ID

使用CameraManagergetCameraIdList()方法获取设备上所有可用的相机列表,并选择一个相机ID进行后续操作。通常,后置摄像头的ID是"0",前置摄像头的ID是"1",但这不是绝对的,需要根据实际情况判断:

try {  
    String[] cameraIdList = cameraManager.getCameraIdList();  
    for (String cameraId : cameraIdList) {  
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);  
        // 根据需要选择合适的相机,例如选择后置摄像头  
        if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {  
            // 选择后置摄像头  
            break;  
        }  
    }  
} catch (CameraAccessException e) {  
    e.printStackTrace();  
}

image.gif

4. 打开相机

使用CameraManageropenCamera()方法打开选定的相机。这个方法是异步的,并且需要一个CameraDevice.StateCallback来接收相机打开的结果:

cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {  
    @Override  
    public void onOpened(@NonNull CameraDevice cameraDevice) {  
        // 相机打开成功,可以进行后续操作  
    }  
  
    @Override  
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {  
        // 相机断开连接  
    }  
  
    @Override  
    public void onError(@NonNull CameraDevice cameraDevice, int error) {  
        // 打开相机发生错误  
    }  
}, null);

image.gif

5. 创建CameraCaptureSession

一旦相机成功打开,你需要创建一个CameraCaptureSession来进行预览、拍照等操作。这个过程也是异步的,并且需要设置Surface来接收相机数据(如TextureView或SurfaceView):

cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {  
    @Override  
    public void onConfigured(@NonNull CameraCaptureSession session) {  
        // 会话创建成功,可以开始预览或拍照  
    }  
  
    @Override  
    public void onConfigureFailed(@NonNull CameraCaptureSession session) {  
        // 会话配置失败  
    }  
}, null);

image.gif

6. 预览和拍照

CameraCaptureSession配置成功后,你可以通过调用setRepeatingRequest()方法来开始预览,并通过调用capture()方法来拍照。这些操作都需要CaptureRequest对象,该对象描述了捕获请求的各种参数:

CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);  
// 设置预览的参数...  
previewRequestBuilder.addTarget(surface);  
  
// 预览  
cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null);  
  
// 拍照  
CaptureRequest.Builder captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);  
// 设置拍照的参数...  
captureRequestBuilder.addTarget(imageReader.getSurface());  
cameraCaptureSession.capture(captureRequestBuilder.build(), captureCallback, null);

image.gif

7. 释放资源

当相机不再需要时,你应该及时释放相关资源,避免内存泄漏等问题。

如何从Camera1和Camera2获取原始数据?

我们在做Android平台RTMP推送、轻量级RTSP服务、实时录像和GB28181设备对接模块的时候,都需要用到摄像头采集,早期,我们提供了Camera1的采集demo,后面碎渣Camera2的优势越来越明显,高版本设备已成主流,目前一般建议采用Camera2的采集。

先说针对Camera1的采集和数据投递处理:

image.gif

/*
 * CameraPublishActivity.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 */
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    frameCount++;
    if (frameCount % 3000 == 0) {
        Log.i("OnPre", "gc+");
        System.gc();
        Log.i("OnPre", "gc-");
    }
    if (data == null) {
        Parameters params = camera.getParameters();
        Size size = params.getPreviewSize();
        int bufferSize = (((size.width | 0x1f) + 1) * size.height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;
        camera.addCallbackBuffer(new byte[bufferSize]);
    } else {
        if (isRTSPPublisherRunning || isPushingRtmp || isRecording || isPushingRtsp) {
            if (1 == video_opt_) {
                 
                libPublisher.SmartPublisherOnCaptureVideoData(publisherHandle, data, data.length, currentCameraType, currentOrigentation);
            } else if (3 == video_opt_) {
                int w = videoWidth, h = videoHeight;
                int y_stride = videoWidth, uv_stride = videoWidth;
                int y_offset = 0, uv_offset = videoWidth * videoHeight;
                int is_vertical_flip = 0, is_horizontal_flip = 0;
                int rotation_degree = 0;
                // 镜像只用在前置摄像头场景下
                if (is_mirror && FRONT == currentCameraType) {
                    // 竖屏, (垂直翻转->顺时旋转270度)等价于(顺时旋转旋转270度->水平翻转)
                    if (PORTRAIT == currentOrigentation)
                        is_vertical_flip = 1;
                    else
                        is_horizontal_flip = 1;
                }
                if (PORTRAIT == currentOrigentation) {
                    if (BACK == currentCameraType)
                        rotation_degree = 90;
                    else
                        rotation_degree = 270;
                } else if (LANDSCAPE_LEFT_HOME_KEY == currentOrigentation) {
                    rotation_degree = 180;
                }
                int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
                libPublisher.PostLayerImageNV21ByteArray(publisherHandle, 0, 0, 0,
                        data, y_offset, y_stride, data, uv_offset, uv_stride, w, h,
                        is_vertical_flip, is_horizontal_flip, scale_w, scale_h, scale_filter_mode, rotation_degree);
            }
        }
        camera.addCallbackBuffer(data);
    }
}

image.gif

再说针对Camera2的数据投递处理:

image.gif

/*
 * Camera2Activity.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 */
@Override
public void onCameraImageData(Image image) {
    Image.Plane[] planes = image.getPlanes();
    int w = image.getWidth(), h = image.getHeight();
    int y_offset = 0, u_offset = 0, v_offset = 0;
    int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
    scale_filter_mode = 3;
    int rotation_degree = cameraImageRotationDegree_;
    if (rotation_degree < 0) {
        Log.i(TAG, "onCameraImageData rotation_degree < 0, may need to set orientation_ to 0, 90, 180 or 270");
        return;
    }
    for (LibPublisherWrapper i : publisher_array_)
        i.PostLayerImageYUV420888ByteBuffer(0, 0, 0,
            planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
            planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
            planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
            w, h, 0, 0,
            scale_w, scale_h, scale_filter_mode, rotation_degree);
}

image.gif

总结

Android Camera2 API控制更灵活,性能、图像处理能力优异、适配性和扩展性也好,在版本支持的前提下,一般建议采用Camera2实现摄像头采集技术诉求,以上是Camera1和Camera2技术扫盲和技术探讨,感兴趣的开发者,可以单独跟我沟通探讨。

相关文章
|
编解码 监控 API
Android平台GB28181设备接入侧音频采集推送示例
GB/T28181是广泛应用于视频监控行业的标准协议规范,可以在不同设备之间实现互联互通。今天我们主要探讨Android平台的Audio采集部分。
119 1
|
数据采集 前端开发 Android开发
Android平台RTMP推送或GB28181设备接入端如何实现采集audio音量放大?
我们在做Android平台RTMP推送和GB28181设备对接的时候,遇到这样的问题,有的设备,麦克风采集出来的audio,音量过高或过低,特别是有些设备,采集到的麦克风声音过低,导致播放端听不清前端采集的audio,这时候,就需要针对采集到的audio,做音量放大处理。
|
1月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
1月前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
5月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
302 0
|
编解码 监控 网络协议
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
154 2
|
数据采集 Android开发 开发者
Android平台GB28181设备接入模块摄像头采集方向不对怎么办?
我们在做Android平台GB28181设备接入模块的时候,有开发者提到这样的诉求:他们的智能头盔、执法记录仪等设备,采集到的图像,是旋转了90、180甚至270°的,设备本身无法针对图像做翻转或者旋转操作,问我们这种情况下需要如何处理?
|
传感器 前端开发 Java
Android流媒体开发之路一:Camera2采集摄像头原始数据并手动预览
Android流媒体开发之路一:Camera2采集摄像头原始数据并手动预览
644 0
|
API Android开发 内存技术
android 采集PCM音频数据并播放(支持USB摄像头MIC)
android 采集PCM音频数据并播放(支持USB摄像头MIC)
554 0