相机预览流程:从Surface到屏幕的每一帧
引言:预览,不只是"看个大概"打开相机App的瞬间,你看到的那一帧实时画面,背后经历了什么?很多开发者以为相机预览就是"把摄像头的数据显示出来"——听起来简单,做起来却暗藏玄机。一个60fps的流畅预览背后,涉及HAL层数据采集、BufferQueue生产者/消费者模型、SurfaceFlinger合成渲染、以及AF/AE/AWB三大自动控制算法的持续收敛,任何一个环节出问题都可能导致画面卡顿、曝光忽明忽暗、焦点游移不定。本文将带你完整走一遍Camera2 API的预览流程,从Surface的创建到每一帧图像最终出现在屏幕上,把中间的每个技术环节都说清楚。如果你是相机开发的新手,看完本文你会对预览流程有系统性认识;如果你是老手,也许能从3A控制和性能优化部分找到一些新收获。一、预览的起点:Surface与SurfaceTexture在Camera2中,预览的本质是将相机数据输出到一个Surface。Surface是Android图形系统的核心抽象,它代表一块可以被渲染的缓冲区队列。预览用的Surface通常来自两个来源:TextureView和SurfaceView。1.1 TextureView vs SurfaceView这是Camera开发中最常见的选择题,两者各有利弊:特性TextureViewSurfaceView渲染方式在View树中渲染(主线程合成)独立Surface,SurfaceFlinger直接合成动画支持✅ 支持旋转、缩放、透明度动画❌ 不支持普通View动画性能开销较高(需要GPU合成到View树)较低(硬件直接合成)圆角/裁剪容易实现需要额外处理适用场景需要复杂UI效果的预览性能敏感场景、大尺寸预览// 方案一:使用 TextureViewpublicclassCameraPreviewActivityextendsAppCompatActivityimplementsTextureView.SurfaceTextureListener{privateTextureViewmTextureView;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);mTextureView=newTextureView(this);mTextureView.setSurfaceTextureListener(this);setContentView(mTextureView);}@OverridepublicvoidonSurfaceTextureAvailable(SurfaceTexturesurface,intwidth,intheight){// SurfaceTexture 就绪,可以开始打开相机openCamera(width,height);}@OverridepublicvoidonSurfaceTextureSizeChanged(SurfaceTexturesurface,intwidth,intheight){// 尺寸变化,需要重新配置预览(如旋转后)configureTransform(width,height);}@OverridepublicbooleanonSurfaceTextureDestroyed(SurfaceTexturesurface){// Surface 销毁,需要关闭相机closeCamera();returntrue;}@OverridepublicvoidonSurfaceTextureUpdated(SurfaceTexturesurface){// 每帧更新时回调,可用于帧率统计}}1.2 SurfaceTexture的内部机制TextureView内部持有一个SurfaceTexture,这是整个预览管道的关键组件。SurfaceTexture从BufferQueue拿到相机输出的图像帧后,会将其作为OpenGL ES纹理附着到GPU,再由TextureView渲染到屏幕。相机HAL → BufferQueue → SurfaceTexture → OpenGL纹理 → TextureView → SurfaceFlinger → 屏幕SurfaceTexture的核心接口是updateTexImage(),调用后会将最新的帧更新为当前的OpenGL纹理。TextureView会在每次onSurfaceTextureUpdated()回调时自动调用这个方法,你通常不需要手动调用——但如果你需要精确控制帧的消费时机(比如做帧率控制),可以手动接管这个流程。1.3 选择合适的预览尺寸这是一个容易踩坑的地方。相机支持的预览尺寸由CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP决定,你需要从中选出最合适的尺寸:privateSizechooseOptimalSize(Size[]choices,inttextureViewWidth,inttextureViewHeight,SizeaspectRatio){// 收集满足最小尺寸要求的候选项ListSizebigEnough=newArrayList();ListSizenotBigEnough=newArrayList();intw=aspectRatio.getWidth();inth=aspectRatio.getHeight();for(Sizeoption:choices){// 宽高比必须匹配(允许小误差)if(option.getHeight()==option.getWidth()*h/w){if(option.getWidth()=textureViewWidthoption.getHeight()=textureViewHeight){bigEnough.add(option);}else{notBigEnough.add(option);}}}// 优先选择满足条件的最小尺寸(节省带宽),否则选最大的if(!bigEnough.isEmpty()){returnCollections.min(bigEnough,newCompareSizesByArea());}elseif(!notBigEnough.isEmpty()){returnCollections.max(notBigEnough,newCompareSizesByArea());}else{Log.e(TAG,"找不到合适的预览尺寸");returnchoices[0];}}关键原则:预览尺寸的宽高比必须与拍照尺寸的宽高比保持一致,否则预览画面会出现拉伸变形。这是一个让很多新手困惑的问题——“我的预览怎么看起来胖了?”——十有八九就是宽高比没对齐。二、配置预览管道:CaptureSession的建立有了Surface之后,下一步是建立CameraCaptureSession,这是Camera2中代表"相机配置状态"的核心类。2.1 打开相机设备privatevoidopenCamera(intwidth,intheight){// 权限检查(必须在运行时申请 CAMERA 权限)if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)!=PackageManager.PERMISSION_GRANTED){requestCameraPermission();return;}// 设置预览尺寸(宽高比对齐拍照尺寸)setUpCameraOutputs(width,height);// 调整TextureView的变换矩阵,处理旋转问题configureTransform(width,height);CameraManagermanager=(CameraManager)getSystemService(Context.CAMERA_SERVICE);try{// 获取相机ID(通常0是后置,1是前置)StringcameraId=manager.getCameraIdList()[0];// 异步打开相机,结果通过 CameraDevice.StateCallback 回调manager.openCamera(cameraId,mStateCallback,mBackgroundHandler);}catch(CameraAccessExceptione){e.printStackTrace();}}privatefinalCameraDevice.StateCallbackmStateCallback=newCameraDevice.StateCallback(){@OverridepublicvoidonOpened(@NonNullCameraDevicecameraDevice){// 相机已打开,保存设备引用,开始创建预览会话mCameraDevice=cameraDevice;createCameraPreviewSession();}@OverridepublicvoidonDisconnected(@NonNullCameraDevicecameraDevice){cameraDevice.close();mCameraDevice=null;}@OverridepublicvoidonError(@NonNullCameraDevicecameraDevice,interror){cameraDevice.close();mCameraDevice=null;// 错误码:ERROR_CAMERA_IN_USE, ERROR_MAX_CAMERAS_IN_USE 等Log.e(TAG,"相机打开失败,错误码:"+error);}};2.2 创建CaptureSessioncreateCameraPreviewSession()是配置预览的核心方法。Camera2采用"会话"模型:你告诉相机"我要输出到这些Surface",相机硬件会据此配置数据通路:privatevoidcreateCameraPreviewSession(){try{SurfaceTexturetexture=mTextureView.getSurfaceTexture();// 设置默认缓冲区尺寸(必须!否则缓冲区大小是默认值)texture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2486461.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!