1. 为什么选择Camera2 API在Android开发中处理摄像头功能时开发者通常会面临Camera API和Camera2 API的选择。传统Camera API虽然简单易用但随着Android系统的发展它已经暴露出诸多局限性。Camera2 API作为Android 5.0引入的全新架构提供了更精细的控制能力和更高的性能表现。我刚开始接触Camera2 API时也觉得很复杂但实际使用后发现它带来的灵活性完全值得学习成本。Camera2采用管道(Pipeline)设计模式将摄像头操作抽象为捕获请求(CaptureRequest)和捕获结果(CaptureResult)这种设计让开发者能够精确控制每个环节。Camera2相比Camera API有几个明显优势更低的延迟直接访问图像传感器数据减少中间处理环节更丰富的功能支持手动对焦、曝光控制、连拍等高级特性更好的性能异步处理机制避免阻塞UI线程更稳定的帧率可以精确控制帧率参数2. 基础环境搭建2.1 权限与布局配置首先需要在AndroidManifest.xml中声明相机权限uses-permission android:nameandroid.permission.CAMERA /对于Android 6.0及以上系统还需要动态申请权限。这里我推荐使用ActivityResult API来处理权限请求private val cameraPermissionLauncher registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted - if (isGranted) { setupCamera() } else { Toast.makeText(this, 需要相机权限, Toast.LENGTH_SHORT).show() } }布局文件只需要一个TextureView用于预览TextureView android:idid/textureView android:layout_widthmatch_parent android:layout_heightmatch_parent /2.2 Camera2核心组件Camera2 API有几个关键类需要了解CameraManager管理系统所有摄像头设备CameraCharacteristics描述摄像头特性CameraDevice代表物理摄像头设备CameraCaptureSession管理摄像头数据流CaptureRequest定义捕获参数配置初始化CameraManager的典型代码CameraManager manager (CameraManager) getSystemService(Context.CAMERA_SERVICE); String[] cameraIds manager.getCameraIdList(); String cameraId cameraIds[0]; // 通常后置摄像头是03. 配置图像采集管道3.1 选择合适的分辨率获取摄像头支持的分辨率列表CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] outputSizes map.getOutputSizes(SurfaceTexture.class);选择分辨率时需要考虑几个因素预览画面的宽高比要匹配显示区域编码性能与分辨率成正比不同设备支持的分辨率差异很大我通常的做法是选择最接近1080p的分辨率这样在画质和性能之间取得平衡。3.2 创建ImageReaderImageReader是我们获取YUV数据的关键ImageReader imageReader ImageReader.newInstance( width, height, ImageFormat.YUV_420_888, // Camera2使用这个格式而非NV21 2); // 缓冲区数量 imageReader.setOnImageAvailableListener({ reader - // 在这里处理每一帧图像 }, handler);YUV_420_888是Camera2的标准格式与Camera API的NV21不同需要注意转换处理。4. 视频编码实现4.1 MediaCodec初始化配置H264编码器MediaCodec codec MediaCodec.createEncoderByType(video/avc); MediaFormat format MediaFormat.createVideoFormat(video/avc, width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start();几个关键参数说明BIT_RATE影响视频质量和文件大小建议按分辨率设置FRAME_RATE通常设为30太高会导致编码压力大I_FRAME_INTERVAL关键帧间隔影响seek性能和文件大小4.2 YUV格式转换Camera2的YUV_420_888需要转换为MediaCodec支持的格式private byte[] convertYUV420888ToNV12(Image image) { ByteBuffer yBuffer image.getPlanes()[0].getBuffer(); ByteBuffer uBuffer image.getPlanes()[1].getBuffer(); ByteBuffer vBuffer image.getPlanes()[2].getBuffer(); int ySize yBuffer.remaining(); int uSize uBuffer.remaining(); int vSize vBuffer.remaining(); byte[] nv12 new byte[ySize uSize vSize]; // Y分量 yBuffer.get(nv12, 0, ySize); // UV分量交错排列 int pos ySize; for (int i 0; i uSize; i) { nv12[pos] vBuffer.get(i); nv12[pos] uBuffer.get(i); } return nv12; }4.3 编码流程完整的编码处理流程// 从ImageReader获取图像 Image image imageReader.acquireLatestImage(); byte[] nv12 convertYUV420888ToNV12(image); // 输入编码器 int inputBufferIndex codec.dequeueInputBuffer(TIMEOUT_US); if (inputBufferIndex 0) { ByteBuffer inputBuffer codec.getInputBuffer(inputBufferIndex); inputBuffer.put(nv12); codec.queueInputBuffer(inputBufferIndex, 0, nv12.length, System.nanoTime() / 1000, 0); } // 获取编码输出 MediaCodec.BufferInfo bufferInfo new MediaCodec.BufferInfo(); int outputBufferIndex codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); while (outputBufferIndex 0) { ByteBuffer outputBuffer codec.getOutputBuffer(outputBufferIndex); byte[] h264Data new byte[bufferInfo.size]; outputBuffer.get(h264Data); // 处理编码后的H264数据 saveToFile(h264Data); codec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); }5. 实战中的坑与解决方案5.1 图像方向问题摄像头传感器的自然方向通常是横向的而手机竖屏使用时需要旋转90度。Camera2中可以通过设置CaptureRequest的旋转参数来解决captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));辅助方法计算正确方向private int getOrientation(int rotation) { switch (rotation) { case Surface.ROTATION_0: return 90; case Surface.ROTATION_90: return 0; case Surface.ROTATION_180: return 270; case Surface.ROTATION_270: return 180; } return 0; }5.2 帧率不稳定问题在实际测试中我发现如果不做特殊处理帧率经常会波动。解决方案是在创建CaptureRequest时设置目标帧率范围captureRequestBuilder.set( CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range(frameRate, frameRate));5.3 内存泄漏预防Camera2 API使用不当很容易导致内存泄漏特别是没有正确关闭资源时。我的经验是建立完善的生命周期管理Override protected void onPause() { super.onPause(); closeCamera(); } private void closeCamera() { if (captureSession ! null) { captureSession.close(); captureSession null; } if (cameraDevice ! null) { cameraDevice.close(); cameraDevice null; } if (imageReader ! null) { imageReader.close(); imageReader null; } }6. 性能优化技巧6.1 选择合适的YUV格式不同设备支持的YUV格式可能不同通过查询编解码器能力选择最优格式MediaCodecInfo.CodecCapabilities caps codec.getCodecInfo() .getCapabilitiesForType(video/avc); int[] colorFormats caps.colorFormats;我通常优先选择COLOR_FormatYUV420Flexible它在Android 5.0上广泛支持。6.2 使用Surface提高效率直接将TextureView的Surface传递给MediaCodec可以避免CPU拷贝大幅提升性能Surface encoderSurface codec.createInputSurface(); captureRequestBuilder.addTarget(encoderSurface);6.3 动态码率调整根据网络状况或存储空间动态调整码率if (networkIsSlow) { format.setInteger(MediaFormat.KEY_BIT_RATE, lowBitrate); codec.setParameters(format); }7. 完整实现示例以下是核心代码的整合版本public class Camera2Encoder { private CameraDevice cameraDevice; private CameraCaptureSession captureSession; private ImageReader imageReader; private MediaCodec mediaCodec; private void setupCamera() { CameraManager manager (CameraManager) getSystemService(CAMERA_SERVICE); // 选择后置摄像头 String cameraId manager.getCameraIdList()[0]; // 创建ImageReader imageReader ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2); imageReader.setOnImageAvailableListener({ reader - processImage(reader.acquireLatestImage()); }, handler); // 初始化编码器 initEncoder(); // 打开摄像头 manager.openCamera(cameraId, new CameraDevice.StateCallback() { Override public void onOpened(NonNull CameraDevice camera) { cameraDevice camera; createCaptureSession(); } // ...其他回调方法 }, handler); } private void createCaptureSession() { ListSurface surfaces new ArrayList(); surfaces.add(imageReader.getSurface()); surfaces.add(mediaCodec.createInputSurface()); cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { captureSession session; startPreview(); } // ...其他回调方法 }, handler); } private void processImage(Image image) { // 转换格式并编码 byte[] nv12 convertYUV420888ToNV12(image); encodeFrame(nv12); image.close(); } private void encodeFrame(byte[] data) { // 编码实现... } }在实际项目中我发现合理设置缓冲区数量和选择合适的Handler线程对性能影响很大。通常我会使用专门的HandlerThread来处理相机回调避免阻塞主线程。