MTK Camera开发实战:从Sensor驱动到JpegNode的Flip/Mirror全链路解析
当你在调试手机前置摄像头时,是否遇到过这样的场景:预览画面看起来正常,但拍出来的照片却是镜像翻转的?或者更糟,预览和拍照方向都不对?这种问题往往涉及从硬件Sensor到上层应用的完整图像处理链路。本文将带你深入MTK Camera HAL的Flip/Mirror实现机制,从底层寄存器配置到Jpeg编码,构建完整的调试视角。
1. Sensor驱动的Flip/Mirror基础配置
在Camera系统中,图像方向的第一次调整发生在Sensor驱动层。这里定义了图像采集的初始方向,直接影响后续所有处理流程。以常见的GC8034 Sensor为例,其寄存器配置决定了图像输出的基本方向特性。
GC8034的镜像模式通过GC8034_MIRROR寄存器控制,典型配置如下:
#define GC8034_MIRROR_NORMAL #undef GC8034_MIRROR_H #undef GC8034_MIRROR_V #undef GC8034_MIRROR_HV #if defined(GC8034_MIRROR_NORMAL) #define GC8034_MIRROR 0xc0 #elif defined(GC8034_MIRROR_H) #define GC8034_MIRROR 0xc1 // 水平镜像 #elif defined(GC8034_MIRROR_V) #define GC8034_MIRROR 0xc2 // 垂直翻转 #elif defined(GC8034_MIRROR_HV) #define GC8034_MIRROR 0xc3 // 水平+垂直翻转 #endif不同Sensor厂商的实现方式可能有所差异。例如SC500CS的配置更为简洁:
#define SC500CS_MIRROR_FLIP_ENABLE 0 #if SC500CS_MIRROR_FLIP_ENABLE #define SC500CS_MIRROR 0x66 // mirror&flip #else #define SC500CS_MIRROR 0x00 // normal #endif注意:Sensor层的Flip/Mirror设置会影响所有输出流,包括预览和拍照。修改前需确认是否会对其他功能模块产生影响。
调试技巧:
- 使用
adb shell dumpsys media.camera查看当前Sensor配置 - 通过内核日志确认寄存器值是否成功写入
- 前置摄像头通常需要启用水平镜像以获得"镜子"效果
2. Framework层的Transform处理
当图像数据离开Sensor后,Android Framework会根据设备方向、摄像头朝向等因素进行进一步的方向调整。这部分逻辑分散在API1和API2的不同实现中。
2.1 API1的Transform处理
在传统的Camera API1中,方向转换由Parameters.cpp中的degToTransform函数处理:
int Parameters::degToTransform(int degrees, bool mirror) { if (!mirror) { if (degrees == 0) return 0; else if (degrees == 90) return HAL_TRANSFORM_ROT_90; // ... 其他角度处理 } else { // 前置摄像头需要水平镜像 if (degrees == 0) { return HAL_TRANSFORM_FLIP_H; // 水平翻转 } else if (degrees == 90) { return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90; } // ... 其他角度处理 } }关键参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| degrees | int | 设备物理方向(0,90,180,270) |
| mirror | bool | 是否为前置摄像头 |
| 返回值 | int | 组合的Transform标志 |
2.2 API2的Transform处理
Camera API2的实现位于CameraUtils.cpp,处理逻辑更为复杂:
bool mirror = (entryFacing.data.u8[0] == ANDROID_LENS_FACING_FRONT); int orientation = entry.data.i32[0]; if (!mirror) { switch (orientation) { case 0: flags = 0; break; case 90: flags = NATIVE_WINDOW_TRANSFORM_ROT_90; break; // ... 其他角度 } } else { // 前置摄像头处理 switch (orientation) { case 0: flags = NATIVE_WINDOW_TRANSFORM_FLIP_H; break; case 90: flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^ NATIVE_WINDOW_TRANSFORM_ROT_270; break; // ... 其他角度 } }API2新增了基于Surface的Transform机制,允许不同输出流应用不同的变换,这为预览和拍照设置不同方向提供了可能。
3. JpegNode中的图像变换实现
当图像进入拍照流程后,JpegNode负责对最终输出的JPEG图像应用旋转和翻转操作。MTK的实现中,这主要通过transform参数控制。
3.1 Jpeg大图的Transform处理
在JpegNode.cpp中,大图的编码过程会处理transform参数:
my_encode_params params; params.pSrc = pEncodeFrame->mpYUV_Main.get(); params.pDst = pEncodeFrame->mpJpeg_Main.get(); params.transform = 0; // 初始无变换 // 根据配置设置transform if (pEncodeFrame->mParams.flipMode) { params.transform = eTransform_FLIP_H; // 水平镜像 }支持的变换操作包括:
eTransform_None(0x00): 无变换eTransform_FLIP_H(0x01): 水平翻转eTransform_FLIP_V(0x02): 垂直翻转eTransform_ROT_90(0x04): 顺时针旋转90度eTransform_ROT_180(0x03): 旋转180度eTransform_ROT_270(0x07): 顺时针旋转270度
提示:这些标志可以通过位或操作组合使用,例如
eTransform_FLIP_H | eTransform_FLIP_V等效于180度旋转。
3.2 Thumbnail的Transform处理
缩略图的处理需要考虑与大图的协调一致,实现稍有不同:
MUINT32 transform = pEncodeFrame->mpYUV_MainStreamInfo->getTransform(); if (pEncodeFrame->mParams.flipMode || info.mFlip) { if (pEncodeFrame->mParams.orientation == 90 && transform & eTransform_ROT_90) { params.transform = eTransform_ROT_90 | eTransform_FLIP_V; std::swap(thumbsize.w, thumbsize.h); } // ... 其他角度处理 }特殊情况下,当图像已在P2A阶段做过翻转时,可能需要反向操作:
auto doUnflip = [&](MUINT32& trans, MINT32 dvOri) -> MVOID { if (dvOri == 270) { trans |= eTransform_FLIP_V; } else if (dvOri == 90) { trans &= ~eTransform_FLIP_V; } // ... 其他情况 };4. 动态控制Flip/Mirror的实践方法
在实际开发中,我们经常需要在不修改代码的情况下动态控制Flip/Mirror行为。MTK平台提供了多种灵活的配置方式。
4.1 通过Vendor Tag控制
APP可以通过特定的Vendor Tag来控制拍照的镜像效果:
private static final String FLIP_KEY_MODE_REQUEST = "com.mediatek.control.capture.flipmode"; if (value != null && captureBuilder != null) { int[] mode = new int[1]; mode[0] = Integer.parseInt(value); captureBuilder.set(mKeyMirrorRequestValue, mode); }底层通过检查MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE元数据来实现:
IMetadata::IEntry const& entryJpegFlip = pMetadata->entryFor(MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE); if (!entryJpegFlip.isEmpty()) { jpegFlip = entryJpegFlip.itemAt(0, Type2Type<MINT32>()); }4.2 使用系统属性调试
对于快速调试,可以通过ADB设置系统属性:
# 控制拍照镜像 adb shell setprop vendor.debug.camera.Jpeg.flip 1 # 控制视频镜像 adb shell setprop vendor.debug.camera.videocontrol.flip 1对应的代码实现:
int32_t jpegFlipProp = ::property_get_int32("vendor.debug.camera.Jpeg.flip", 0); int32_t videoFlip = ::property_get_int32("vendor.debug.camera.videocontrol.flip", 0);4.3 视频流的特殊处理
视频录制和VSS照片的镜像控制位于PipelineModelSessionBase.cpp:
int32_t videoFlip = ::property_get_int32("vendor.debug.camera.videocontrol.flip", 0); int32_t videoOrientation = ::property_get_int32("vendor.debug.camera.videocontrol.orientation", 90); if (it.second->getUsageForConsumer() & GRALLOC_USAGE_HW_VIDEO_ENCODER) { uint32_t reqTransform = 0; if (videoFlip) { if (0 == videoOrientation) { reqTransform = eTransform_FLIP_H; } else if (90 == videoOrientation) { reqTransform = eTransform_FLIP_V; } // ... 其他角度 } it.second->setTransform(reqTransform); }5. 典型问题排查指南
在实际开发中,Flip/Mirror相关的问题往往涉及多个环节。以下是常见问题及排查方法:
预览方向正确但拍照方向错误
- 检查JpegNode的transform参数是否被正确设置
- 确认APP是否通过Vendor Tag请求了特殊变换
- 验证缩略图处理逻辑是否影响了大图
前置摄像头镜像效果不一致
- 确认Sensor寄存器配置是否正确
- 检查Framework是否识别为前置摄像头(mirror=true)
- 验证不同旋转角度下的组合变换逻辑
视频录制方向异常
- 检查
GRALLOC_USAGE_HW_VIDEO_ENCODER流的transform - 确认视频特有的属性设置
- 验证编码器是否保留了元数据中的方向信息
- 检查
性能问题
- 旋转/翻转操作应尽量在硬件支持的情况下进行
- 避免在多个环节重复进行相同变换
- 考虑使用GPU加速处理大尺寸图像旋转
调试工具推荐:
dumpsys media.camera:查看Camera服务状态camxhaldump:MTK特有的Camera HAL调试工具adb logcat -b events:查看系统方向变化事件vndservice call camera 10 i32 <cameraId>:强制旋转测试
在MT8788平台上调试GC8034前置摄像头时,我们发现当同时启用GC8034_MIRROR_H和Framework层的FLIP_H时,图像会恢复为原始方向,这种双重翻转可能导致编码器性能下降。最终方案是仅在Sensor层做镜像,减少软件处理开销。