Android Camera2 + OpenGL 竖屏或横屏预览会有“轻微拉伸”
前言在进行 Android 相机底层开发Camera2 OpenGL ES时开发者经常会遇到各种拉伸问题。有一种最隐蔽的“轻微拉伸”画面方向正确预览也没变黑但人脸看起来明显比平时“瘦长”了一点点。本文将结合一次真实的生产环境日志排查解析其背后的硬件限制与逻辑陷阱。现象描述在开发一个 App 时由于需要支持竖屏模式9:16下的高清视频采集我们在设置中指定了 1080 x 1920 的视频分辨率。但在实测中原本标准的 16:9 画幅却被纵向拉长了。现场日志回放我们可以看到设置分辨率和系统检测的过程// 设置生效 2026-04-15 15:28:27.332 3580-3580 CameraController D Video resolution from settings: 1080x1920 // 关键硬件支持信息从 characteristics 读出 支持的录像尺寸 (PRIVATE): 4000x3000、3840x2160、3264x2448... 1920x1080、1280x720 // 关键点渲染器初始化与方向同步 2026-04-15 15:28:27.333 3580-3580 CameraGLRenderer D GL renderer already initialized at 1080x1920, skipping 2026-04-15 15:28:27.333 3580-3580 CameraGLRenderer D Display rotation set: 0 2026-04-15 15:28:27.335 3580-3580 CameraGLRenderer D Sensor orientation set: 270 2026-04-15 15:28:27.339 3580-3580 CameraGLRenderer D Orientation offset set: 90深入排查真相只有一个为什么设置了 1080x1920 会拉伸逻辑分析在代码中我们通常会告诉相机的缓冲区SurfaceTexture期望接收的分辨率// 创建相机纹理OES外部纹理 相机输入流 cameraTextureId TextureProgram.createExternalTexture() cameraTexture SurfaceTexture(cameraTextureId).apply { setDefaultBufferSize(height, width)正常手机此时 videoSize 是 1080x1920。真相揭秘硬件层面的 Landscape 特性Android 设备的摄像头传感器Sensor在硬件物理层面上几乎全都是横向放置的。相机底层驱动能处理的原始流Output Sizes绝大多数只支持宽大于高的横向格式如 1920x1080。不匹配的回退机制当我们向相机请求一个 1080(W) x 1920(H) 的输出流时Camera2 HAL硬件抽象层由于在其支持列表中找不到对应的“竖屏”尺寸它并不会报错而是会执行回退策略。4:3 与 16:9 的战争通过日志发现该设备最大支持 4000x30004:3比例。因为驱动识别不了竖着的 16:9它极大概率会选择传感器全像素尺寸或者一个默认的 4:3 预览尺寸如 1440x1080吐出数据。OpenGL 强制填满你的预览窗口GL Viewport是真正的 1080x1920 (9:16)。OpenGL 在渲染时如果不做裁切处理会将一个 1440x1080 的图像内容“揉碎并挤压”进 1080x1920 的矩阵里。数学上的畸变原始比例假设 4:31.33显示比例期望 16:9 纵向0.562 (即横向的 1.77)畸变倍数 1.77 / 1.33 1.33倍。这就是你人脸看起来“瘦长”了 30% 左右的由来。最终修复修复这个问题的核心不是去旋转图像而是欺骗硬件并在内存里进行逻辑矫正。核心步骤始终请求横向缓冲区告诉相机 SurfaceTexture 接收的分辨率时不论你显示是竖是横宽高一定要设为“大边x小边”。强制缓冲区规格使用相机硬件列表里真实存在的 Landscape 尺寸// 创建相机纹理OES外部纹理 相机输入流 cameraTextureId TextureProgram.createExternalTexture() cameraTexture SurfaceTexture(cameraTextureId).apply { // 正常手机是横向的必须要 横向分辨率,也是输出画面变形拉伸的根本原因 if (itsaWidescreen){ if ((displayRotationDegrees0 || displayRotationDegrees180)){ setDefaultBufferSize(height, width) }else{ setDefaultBufferSize(width, height) } }else{ // 记录仪 if (displayRotationDegrees0 || displayRotationDegrees180){ setDefaultBufferSize(width, height) }else{ setDefaultBufferSize(height, width) } } setOnFrameAvailableListener({ onFrameAvailable() }, glHandler) } cameraSurface Surface(cameraTexture!!) // 创建OSD纹理生成器 osdGenerator OsdTextureGenerator(width, height) osdGenerator!!.initialize() // 创建拍照用的FBO setupCaptureFbo(width, height)经验总结在处理相机相关的拉伸、旋转问题时可以总结为以下口诀硬件请求始终横向setDefaultBufferSize 永远是大数在前小数在后。坐标旋转丢给矩阵真正想要竖屏预览利用 OpenGL 的 Matrix.rotateM 旋转 combinedMvpMatrix。观察日志尊重元数据characteristics 报出来的 Supported Sizes 如果全都是长大于宽那千万不要试图通过设置去强行逆天而行。通过这个简单的尺寸掉转原本拉伸的 4:3 相机源会按照正确的物理尺寸填充轻微拉伸现象立刻消失
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2525424.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!