Android Camera2 Pipeline 核心原理与实战:从概念到高性能相机应用开发
2026/8/24 4:02:33 网站建设 项目流程

1. 从Camera1到Camera2:为什么我们需要Pipeline?

如果你是从Camera1 API时代过来的Android开发者,第一次接触Camera2 API时,大概率会被它那套复杂的CameraCaptureSessionCaptureRequestImageReader给绕晕。Camera1时代,我们调用takePicture,相机就拍照,调用startPreview,画面就显示,一切看起来简单直接。但这种“简单”背后,是系统帮你做了所有决定,你就像一个坐在自动驾驶汽车里的乘客,只能按几个固定的按钮,无法干预驾驶过程。当你想实现一些高级功能,比如手动对焦、手动曝光、高速连拍,或者处理原始图像数据时,就会处处碰壁。

Camera2 API的设计哲学,正是为了解决这个根本问题:将相机的控制权交还给开发者。它不再把相机看作一个“黑盒”,而是将其抽象为一个由多个组件(Sensor、ISP、3A算法等)构成的、可配置的数据处理流水线,也就是我们常说的Pipeline

理解Camera2 Pipeline,是掌握Camera2 API的钥匙。它不是一个具体的类,而是一套概念模型。简单来说,你可以把它想象成一个现代化的、高度自动化的相机工厂。图像传感器(Sensor)是原料采集车间,它持续不断地采集光线信号(RAW数据)。这些原始数据随后被送入图像信号处理器(ISP)车间,进行降噪、色彩插值、白平衡、色调映射等一系列复杂的加工。加工后的成品(如YUV或JPEG格式的图像)被分拣到不同的出货口,一个口连着屏幕(预览),一个口连着存储器(拍照),还可能有一个口连着网络(视频编码推流)。

Camera2 API让你成为了这个工厂的调度员。你可以:

  1. 配置流水线:决定开几条生产线(创建几个CaptureSession),每条生产线生产什么规格的产品(配置Surface输出目标,如SurfaceViewImageReaderMediaCodecSurface)。
  2. 下达生产指令:通过CaptureRequest告诉工厂,下一批产品要按照什么参数生产(例如,ISO设为800,对焦模式设为AF-C,启用光学防抖)。
  3. 处理产品:从指定的出货口(ImageReader)领取加工好的图像数据,进行自定义的后处理或分析。

这套模型带来的核心优势是灵活性与高性能。你可以为预览、拍照、录像分别创建最优化的Pipeline,实现零快门延迟拍照(ZSL);可以同时向多个目标输出不同格式的数据;可以精细控制每一个成像参数。但与之对应的,是陡峭的学习曲线和更高的复杂度。很多初学者照着Demo把预览跑起来后,一旦想加入拍照功能,或者处理YUV数据,就会陷入IllegalStateException或图像方向错误的泥潭,其根本原因就是对Pipeline的工作机制理解不透。

接下来的内容,我将结合自己踩过的坑和项目中的实际应用,为你彻底拆解Camera2 Pipeline的各个环节,从创建到配置,从请求到回调,让你不仅能“跑通”代码,更能“驾驭”这套强大的系统。

2. Pipeline的核心组件与生命周期:Session、Request与Surface

要搭建一个Camera2应用,你需要和几个核心类打交道,它们共同构成了Pipeline的骨架。理解它们各自的职责和生命周期,是避免各种诡异Bug的前提。

2.1 CameraManager、CameraDevice与CameraCharacteristics

这是Pipeline的“入口”和“蓝图”。

  • CameraManager: 系统服务,用于发现和连接相机设备。通过getCameraIdList()获取可用的相机ID(如“0”代表后置主摄,“1”代表前置摄像头)。
  • CameraDevice: 代表一个已打开的物理相机设备。它是所有操作的起点,通过它的createCaptureSession方法来创建Pipeline的核心——CameraCaptureSession
  • CameraCharacteristics: 相机的“能力说明书”。在打开相机前,你必须通过CameraManager.getCameraCharacteristics(cameraId)获取它。这份说明书至关重要,它告诉你这个相机支持哪些分辨率、有哪些可用的传感器、支持哪些对焦模式、最大帧率是多少等等。任何不查询CameraCharacteristics就硬编码参数的行为,都是在给自己挖坑。比如,不是所有相机都支持CONTROL_AF_MODE_CONTINUOUS_PICTURE(连续对焦),也不是所有相机都能输出RAW格式数据。

2.2 CameraCaptureSession:流水线工厂

这是Pipeline的实体,也是最容易出状态问题的地方。你可以通过CameraDevice.createCaptureSession来创建它,但这个过程是异步的。

创建Session时,你需要传入一个List<Surface>。这个列表定义了这条流水线的所有输出目的地。这里有一个关键约束:一旦Session创建成功,其输出的Surface集合就被固定下来,在整个Session生命周期内无法增删,只能替换(通过setRepeatingRequestcapture方法传入的CaptureRequest所携带的Surface,必须是创建Session时提供的那个集合的子集)。

这就像你建工厂时规划了三条出货传送带(分别对应预览SurfaceView、拍照ImageReader、录像MediaCodec Surface),工厂建好后,你不能凭空增加或拆除一条传送带,但你可以决定每次生产时,让产品走哪几条传送带(通过CaptureRequestaddTarget方法)。

Session的生命周期状态需要仔细处理:

  • 配置中:调用createCaptureSession后,进入此状态。此时不能进行其他操作。
  • 就绪onConfigured回调被触发,Session创建成功。此时可以开始发送CaptureRequest。
  • 关闭:调用close()方法后,或CameraDevice断开后。进入此状态的Session不能再被使用,所有关联的Surface也可能被释放。

一个常见的错误是,在Activity的onPause时没有正确关闭Session和CameraDevice,导致下次onResume时发生状态冲突。最佳实践是,在onPause中关闭Session和CameraDevice,在onResume中重新走一遍完整的初始化流程。

2.3 CaptureRequest与CaptureResult:生产指令与质检报告

  • CaptureRequest.Builder: 用于构建生产指令。你可以通过CameraDevice.createCaptureRequest(int templateType)来获取一个构建器。templateType参数是一个捷径,它提供了一些预设的配置,如:
    • TEMPLATE_PREVIEW: 适用于预览的默认设置。
    • TEMPLATE_STILL_CAPTURE: 适用于高质量静态照片的默认设置(通常会触发自动对焦和测光,并采用最高画质JPEG编码)。
    • TEMPLATE_RECORD: 适用于视频录制的默认设置(通常会锁定对焦和曝光,并采用适合视频的帧率和防抖配置)。 但请注意,这只是个起点,你完全可以(也经常需要)覆盖这些预设参数。
  • CaptureRequest: 构建好的、不可变的生产指令。它包含了所有成像参数(如CONTROL_AF_MODE,SENSOR_EXPOSURE_TIME,JPEG_QUALITY)以及本次生产的目标Surface列表(通过addTarget(Surface)添加)。
  • CaptureResult: 当一帧图像被传感器捕获时,相机硬件和算法实际采用的参数报告。你可以在CaptureCallback.onCaptureCompleted中拿到它。CaptureRequest是你“要求”的参数,CaptureResult是相机“实际执行”的参数。在复杂光照或快速场景变化下,两者可能不完全一致。例如,你请求的曝光时间可能因为超出传感器范围而被自动修正。

2.4 Surface:产品的目的地

Surface是Android中用于跨进程传递图像数据的缓冲区生产者接口。在Camera2中,它代表Pipeline输出图像的终点。常见的Surface来源有:

  1. SurfaceView / TextureView: 用于预览。系统会自动管理其缓冲队列,你只需要获取它的Surface并传给Session即可。
  2. ImageReader: 这是获取图像数据用于自定义处理的最重要工具。你可以创建指定格式(如ImageFormat.JPEG,ImageFormat.YUV_420_888)、指定尺寸和最大图像数量的ImageReader,然后从其getSurface()方法获取Surface。当有新的图像可用时,ImageReader的回调会被触发,你可以在其中通过acquireLatestImage()acquireNextImage()获取Image对象,进而访问其中的图像数据。
  3. MediaCodec.createInputSurface(): 用于视频录制,将相机数据直接喂给编码器。
  4. Allocation.getSurface()(RenderScript) 或OpenGL ES 的 EGLSurface: 用于直接进行GPU处理。

一个极其重要的原则是:你必须确保这些Surface的生命周期被妥善管理,并且在它们无效(如SurfaceView被销毁)后,不再被Camera2 Pipeline使用。否则会导致应用崩溃或相机服务无响应。

3. 构建一个健壮的双重Pipeline:预览与拍照

理论说再多,不如看实战。我们来实现一个最常见的场景:同时支持预览和拍照。这个场景需要构建一个包含两个输出目标的Pipeline。

3.1 初始化与权限检查

首先,当然是清单文件权限和动态权限申请,这个基础步骤不再赘述。我们直接从获取相机特性开始。

private void setUpCamera(int cameraId) { CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); try { // 1. 获取相机特性,这是所有决策的基础 CameraCharacteristics characteristics = manager.getCameraCharacteristics(String.valueOf(cameraId)); // 2. 获取相机传感器方向,用于后续纠正图像显示方向 Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); mSensorOrientation = sensorOrientation != null ? sensorOrientation : 0; // 3. 获取流配置映射,它告诉我们该相机支持哪些输出尺寸 StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { throw new RuntimeException("Cannot get stream configuration map."); } // 4. 选择预览尺寸:通常选择与屏幕比例最接近的、小于等于1080P的尺寸,以平衡性能与画质 Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class); // 对于TextureView Size optimalPreviewSize = chooseOptimalSize(previewSizes, 1920, 1080, new Size(16, 9)); mPreviewSize = optimalPreviewSize; // 5. 选择拍照尺寸:选择最大可用的JPEG尺寸,或者你需要的特定高分辨率 Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size largestJpegSize = Collections.max(Arrays.asList(jpegSizes), new CompareSizesByArea()); mPhotoSize = largestJpegSize; // 6. 检查相机是否支持所需的硬件级别和功能(例如,是否支持手动控制) Integer hardwareLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); mIsLegacyDevice = hardwareLevel != null && hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; // 7. 打开相机设备(异步) manager.openCamera(String.valueOf(cameraId), mStateCallback, mBackgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "Camera access exception", e); } }

chooseOptimalSize是一个你需要自己实现的工具方法,用于根据比例、最大宽高等条件筛选出最合适的尺寸。

3.2 创建输出Surface与CaptureSession

相机打开成功后,在CameraDevice.StateCallback.onOpened回调中,我们开始创建Pipeline。

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { mCameraDevice = cameraDevice; // 创建预览和拍照的Surface createCameraPreviewSession(); } // ... onDisconnected, onError 处理 }; private void createCameraPreviewSession() { try { // 1. 配置TextureView用于预览 SurfaceTexture texture = mTextureView.getSurfaceTexture(); if (texture == null) return; // 设置TextureView的缓冲区尺寸为选定的预览尺寸 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); Surface previewSurface = new Surface(texture); // 2. 创建ImageReader用于拍照(JPEG格式) // 参数2:最大图像数量,建议设为2或3,以平衡内存和延迟。1可能导致缓冲区不足。 mImageReader = ImageReader.newInstance(mPhotoSize.getWidth(), mPhotoSize.getHeight(), ImageFormat.JPEG, 3); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); Surface photoSurface = mImageReader.getSurface(); // 3. 构建一个用于预览的CaptureRequest mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewRequestBuilder.addTarget(previewSurface); // 将预览Surface加入目标 // 4. 创建CaptureSession,同时传入预览和拍照两个Surface // 注意:此时photoSurface也被加入了Session,但预览请求并未将其设为目标。 // 这意味着流水线会为photoSurface分配资源,但默认的预览流不会向它输出数据。 mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, photoSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCaptureSession = session; // Session创建成功,开始发送预览请求 try { // 设置自动对焦模式为连续对焦(适用于预览) mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置自动曝光模式为连续测光 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 构建请求 CaptureRequest previewRequest = mPreviewRequestBuilder.build(); // 设置重复请求,驱动预览持续进行 mCaptureSession.setRepeatingRequest(previewRequest, null, mBackgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "Failed to start preview.", e); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Toast.makeText(getContext(), "Failed to create camera session.", Toast.LENGTH_SHORT).show(); } }, null // 可以传入Handler,null则使用当前线程的Looper ); } catch (CameraAccessException e) { Log.e(TAG, "Camera access exception during session creation", e); } }

关键点解析:

  • createCaptureSession的Surface列表:我们传入了previewSurfacephotoSurface。这意味着这条流水线有能力向这两个目标输出数据。但具体向哪个目标输出,由每次的CaptureRequest决定。
  • 预览请求的目标mPreviewRequestBuilder.addTarget(previewSurface),所以这个重复请求只会把图像数据送到预览的SurfaceView,不会送到ImageReader。这节省了不必要的编码和内存拷贝开销。
  • ImageReader的缓冲区:我们设置了最大图像数为3。如果处理太慢(比如拍照后保存到磁盘耗时过长),缓冲区被占满,相机新的帧就无法写入,可能导致预览卡顿或拍照失败。因此,在ImageReader的回调中,处理完Image后必须尽快调用image.close()释放缓冲区。

3.3 实现拍照:切换请求目标

当用户点击拍照按钮时,我们需要让Pipeline生产一张JPEG图片。

private void takePicture() { if (mCameraDevice == null || mCaptureSession == null) return; try { // 1. 首先,停止持续的预览请求,避免干扰 mCaptureSession.stopRepeating(); // 也可以调用mCaptureSession.abortCaptures()来取消所有未完成的请求,更彻底 // 2. 创建一个专门用于拍照的CaptureRequest.Builder // 使用TEMPLATE_STILL_CAPTURE模板,它包含了一些适合拍照的默认优化(如降噪、锐化) final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 3. 将拍照的Surface(ImageReader的Surface)设为目标 captureBuilder.addTarget(mImageReader.getSurface()); // 4. 设置JPEG方向。相机传感器图像可能是横屏的,需要根据设备方向旋转。 int rotation = getWindowManager().getDefaultDisplay().getRotation(); int jpegOrientation = (mSensorOrientation + ORIENTATIONS.get(rotation) + 360) % 360; captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation); // 5. 可以设置JPEG质量等参数 captureBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) 95); // 6. 对于静态拍照,通常希望使用单次对焦(AF-S)并锁定曝光(AE-LOCK) // 但在连续对焦模式下,也可以直接触发一次对焦。这里我们假设预览时已是连续对焦。 // 如果要精确控制,可以先发送一个对焦锁定请求,再发送拍照请求,构成一个序列。 // 7. 发送单次拍照请求 mCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { // 拍照捕获完成,但图像数据可能还在处理中,稍后会在ImageReader回调中收到 Log.d(TAG, "Capture completed."); // 拍照完成后,立即恢复预览 try { // 重新开始预览的重复请求 mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "Failed to restart preview after capture.", e); } } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { Log.e(TAG, "Capture failed: " + failure.getReason()); // 失败后也需要尝试恢复预览 try { mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "Failed to restart preview after failed capture.", e); } } }, mBackgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "Camera access exception during capture", e); } }

关键点与避坑指南:

  • 停止预览:在发送拍照请求前停止预览重复请求 (stopRepeating()) 是个好习惯。因为相机硬件处理能力有限,同时处理高帧率预览流和高分辨率拍照流可能导致资源竞争,增加拍照延迟或失败概率。对于高性能设备,也可以不停止,但这需要更精细的测试。
  • 目标切换:注意captureBuilder.addTarget(mImageReader.getSurface())。这个请求ImageReader的Surface设为目标,预览Surface不再是目标。因此,在拍照的瞬间,预览画面可能会冻结(因为没有数据输出了)。这就是为什么我们在onCaptureCompleted后要立刻恢复预览请求。
  • 方向处理JPEG_ORIENTATION元数据,它告诉JPEG解码器如何旋转图像。你必须在拍照请求中正确设置,否则保存的图片方向可能是错的。计算方向时,要结合传感器安装方向(SENSOR_ORIENTATION) 和设备当前朝向
  • 恢复预览:无论拍照成功还是失败,在CaptureCallback中恢复预览是保证用户体验连贯性的关键。否则预览将一直黑屏。

3.4 处理拍照结果:ImageReader的回调

拍照请求成功后,图像数据会被写入ImageReader的缓冲区。

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // 必须在后台线程中处理,避免阻塞回调线程(我们设置了Handler) try (Image image = reader.acquireLatestImage()) { if (image == null) { return; } ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); // 获取JPEG方向信息(从CaptureResult中获取更准确,这里简化处理) // 实际项目中,可以将方向信息通过某种方式(如AtomicInteger)从CaptureCallback传递过来。 int orientation = mLastJpegOrientation; // 假设已保存 // 在后台线程保存图片 saveImageToFile(bytes, orientation); } catch (Exception e) { Log.e(TAG, "Failed to process image", e); } // Image使用try-with-resources自动关闭,释放缓冲区 } };

重要提醒acquireLatestImage()会获取最新的一帧并丢弃旧的,适合拍照场景。对于视频帧分析,可能需要acquireNextImage()无论如何,获取Image后必须尽快处理并调用close(),否则缓冲区泄漏会导致相机后续帧无法写入,整个Pipeline卡死。

4. 高级话题:ZSL、YUV处理与性能优化

掌握了基础的双重Pipeline,我们可以探讨一些更高级的应用场景,这些场景更能体现Camera2 Pipeline的威力。

4.1 零快门延迟(ZSL)的实现思路

ZSL的核心是让拍照几乎在按下快门的瞬间完成,没有对焦和测光的等待时间。Camera2实现ZSL的典型方案是使用一个三路输出的Pipeline

  1. 预览流:低分辨率YUV或直接到SurfaceView。
  2. ZSL流:一个持续循环写入的、固定大小(如3-5帧)的ImageReader,格式为ImageFormat.PRIVATEImageFormat.YUV_420_888(取决于设备支持)。这个流以最高帧率运行,但分辨率可能低于拍照分辨率。
  3. 拍照流:高分辨率JPEG的ImageReader

工作流程:

  • 常时:相机同时向预览流和ZSL流输出数据。ZSL流像一个环形缓冲区,总是保存着最近几帧。
  • 拍照时:不发送新的拍照请求,而是从ZSL环形缓冲区中,选取一帧最清晰、曝光最合适的图像(通常结合陀螺仪数据选择抖动最小的一帧),将其重新处理(Reprocess)成高分辨率JPEG。
  • 重新处理:通过createReprocessCaptureRequest方法,将ZSL流中的Image作为输入,发送一个重新处理的请求,输出到拍照流的ImageReader

这样做的好处是,拍照动作避开了传感器捕获和ISP处理的时间,延迟极低。但实现复杂,需要处理TotalCaptureResult与图像的匹配、重新处理会话的创建等。

4.2 处理YUV_420_888数据

很多计算机视觉或高级滤镜功能需要处理YUV数据。ImageFormat.YUV_420_888是Android上表示YUV数据的通用格式。

当你从ImageReader获取到一个ImageFormat.YUV_420_888格式的Image时,它通常包含三个平面(Plane):Y平面、U平面、V平面。

Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuffer = planes[0].getBuffer(); // Y data ByteBuffer uBuffer = planes[1].getBuffer(); // U data ByteBuffer vBuffer = planes[2].getBuffer(); // V data int yRowStride = planes[0].getRowStride(); // 可能包含padding int yPixelStride = planes[0].getPixelStride(); // 通常为1 int uvRowStride = planes[1].getRowStride(); int uvPixelStride = planes[1].getPixelStride(); // 通常为2(因为UV是亚采样的)

最大的坑在于rowStridepixelStride。由于内存对齐要求,每一行数据的末尾可能有填充字节(padding)。你不能简单地假设data.length == width * height * 1.5。必须使用rowStridepixelStride来正确访问像素数据,否则图像会错乱。通常需要自己编写代码或使用RenderScriptlibyuv等库来正确裁剪和转换。

4.3 Pipeline性能调优与常见陷阱

  1. Surface尺寸与格式匹配:创建Session时传入的Surface尺寸,必须在相机的StreamConfigurationMap支持的范围之内。混合不同格式(如YUV和JPEG)的Surface时,要确保相机支持这种组合。有些旧设备或特定模式下,可能不支持同时输出高分辨率JPEG和高帧率YUV。

  2. Handler与线程管理:几乎所有Camera2的回调方法都可以传入一个Handler来指定回调线程。强烈建议使用一个专用的后台线程(HandlerThread)来处理这些回调,尤其是ImageReader的图像可用回调,因为图像处理(如保存、编码、分析)通常是耗时操作,放在主线程会导致UI卡顿甚至ANR。

  3. 状态同步:Camera2 API是高度异步且状态敏感的。你必须确保操作序列化。例如,不要在onConfigured回调进行中又去调用createCaptureSession。一个实用的模式是使用一个简单的状态机(如IDLE,OPENING,SESSION_CONFIGURING,PREVIEW,CAPTURING)来管理相机生命周期。

  4. 资源释放:这是内存泄漏和崩溃的重灾区。必须遵循严格的释放顺序:

    • 首先,在CaptureSession上调用close()
    • 然后,在CameraDevice上调用close()
    • 最后,释放ImageReader(close()) 和其他的Surface。 最好在Activity/Fragment的onPause中执行这些清理操作,并在onResume中重建。
  5. 兼容性处理:一定要通过CameraCharacteristics检查设备能力。例如,通过INFO_SUPPORTED_HARDWARE_LEVEL判断是LEGACY,LIMITED,FULL还是LEVEL_3设备。LEGACY设备对Camera2的支持非常有限,可能不支持手动控制或某些输出格式组合,你可能需要回退到更简单的配置或使用Camera1 API。

  6. 对焦与测光区域:Camera2允许你设置特定的对焦和测光区域(CONTROL_AF_REGIONS,CONTROL_AE_REGIONS)。坐标系统是相对于传感器有效区域的(x, y, width, height),范围是[0, 1000)。你需要将屏幕触摸坐标转换到这个坐标系。设置后,相机可能会需要几帧来完成对焦和曝光调整,期间CaptureResult中的对焦状态 (CONTROL_AF_STATE) 会变化,需要监听这个状态来给用户反馈(如对焦框动画)。

理解并熟练运用Camera2 Pipeline,意味着你完全掌控了移动设备上最强大的图像采集能力。从简单的拍照预览,到复杂的多流处理、自定义算法集成,这套模型提供了坚实的基础。虽然初期学习成本较高,但一旦掌握,其灵活性和性能优势是旧API无法比拟的。在实际项目中,建议从官方Camera2Basic示例开始,逐步添加功能,并始终将设备兼容性和资源管理放在首位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询