在移动设备相机开发中,图像翻转(Flip/Mirror)功能看似简单,实则涉及从Sensor驱动到应用层的完整数据流处理。特别是在MTK平台上,不同层级对图像方向的处理逻辑相互影响,稍有不慎就会导致预览与拍照效果不一致、缩略图方向错乱等问题。本文将基于真实项目经验,拆解MTK Camera HAL中Flip/Mirror的实现全链路,提供可落地的调试方案。
Sensor驱动是图像处理的第一站,其配置直接影响所有输出流。以GC8034和SC500CS两款常见Sensor为例:
GC8034寄存器配置示例(路径:kernel-4.19/drivers/misc/mediatek/imgsensor/src/mtxxxx/gc8034mipi_Sensor.c):
c复制/* 镜像模式定义 */
#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
SC500CS配置差异:
c复制/* 是否启用镜像翻转 */
#define SC500CS_MIRROR_FLIP_ENABLE 0
#if SC500CS_MIRROR_FLIP_ENABLE
#define SC500CS_MIRROR 0x66 // 镜像+翻转组合值
#else
#define SC500CS_MIRROR 0x00 // 正常模式
#endif
注意:Sensor层的修改会影响所有输出流(包括RAW数据),通常需要重新校准图像质量参数。
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 预览与拍照方向不一致 | Sensor配置被上层覆盖 | 检查HAL层transform参数 |
| 图像出现色偏 | 镜像模式未同步调整Bayer模式 | 更新StartX/StartY寄存器 |
| 对焦异常 | 翻转后AF区域计算错误 | 调整AF区域映射算法 |
在API1路径下(文件路径:frameworks/av/services/camera/libcameraservice/api1/client2/Parameters.cpp),关键转换逻辑如下:
cpp复制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 == 180) return HAL_TRANSFORM_ROT_180;
else if (degrees == 270) return HAL_TRANSFORM_ROT_270;
} else {
// 前置摄像头需要水平镜像
if (degrees == 0) return HAL_TRANSFORM_FLIP_H;
else if (degrees == 90)
return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
else if (degrees == 180)
return HAL_TRANSFORM_FLIP_V;
else if (degrees == 270)
return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
}
return -1; // 错误值
}
API2路径(frameworks/av/camera/CameraUtils.cpp)采用不同的标志位组合:
cpp复制if (!mirror) {
switch (orientation) {
case 0: flags = 0; break;
case 90: flags = NATIVE_WINDOW_TRANSFORM_ROT_90; break;
case 180: flags = NATIVE_WINDOW_TRANSFORM_ROT_180; break;
case 270: flags = NATIVE_WINDOW_TRANSFORM_ROT_270; 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;
case 180: flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^
NATIVE_WINDOW_TRANSFORM_ROT_180; break;
case 270: flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^
NATIVE_WINDOW_TRANSFORM_ROT_90; break;
}
}
关键差异对比:
| 特性 | API1 | API2 |
|---|---|---|
| 旋转标志 | HAL_TRANSFORM_* | NATIVE_WINDOW_TRANSFORM_* |
| 组合方式 | 位或( | ) |
| 前置摄像头处理 | 显式判断 | 通过mirror参数控制 |
在JpegNode(路径:vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/hwnode/JpegNode/v2.0/JpegNode.cpp)中,transform参数决定最终输出效果:
cpp复制my_encode_params params;
params.pSrc = pEncodeFrame->mpYUV_Main.get();
params.pDst = pEncodeFrame->mpJpeg_Main.get();
params.transform = 0; // 初始无变换
// 应用变换示例
if (needFlip) {
params.transform = eTransform_FLIP_H; // 水平镜像
} else if (needRotate) {
params.transform = eTransform_ROT_90; // 顺时针90度
}
支持的变换组合:
| 变换类型 | 宏定义 | 实际效果 |
|---|---|---|
| 无变换 | eTransform_None | 原始图像 |
| 水平翻转 | eTransform_FLIP_H | 镜像效果 |
| 垂直翻转 | eTransform_FLIP_V | 上下颠倒 |
| 90度旋转 | eTransform_ROT_90 | 顺时针旋转 |
| 组合变换 | eTransform_FLIP_H|eTransform_FLIP_V | 等效180度旋转 |
缩略图需要额外考虑方向同步问题:
cpp复制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);
}
// 其他角度处理...
}
常见缩略图问题解决方案:
mParams.orientation与主图transform的同步通过自定义Vendor Tag实现动态控制(路径:vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/policy/request/CaptureStreamUpdaterPolicy.cpp):
cpp复制// 读取Vendor Tag配置
IMetadata::IEntry const& entryJpegFlip =
pMetadata->entryFor(MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE);
if (!entryJpegFlip.isEmpty()) {
jpegFlip = entryJpegFlip.itemAt(0, Type2Type<MINT32>());
}
// 应用变换逻辑
if (jpegFlip) {
if (0 == jpegOrientation) {
reqTransform = eTransform_FLIP_H;
} else if (90 == jpegOrientation) {
reqTransform = eTransform_ROT_90 | eTransform_FLIP_V;
}
// 其他角度处理...
}
通过系统属性临时调试:
bash复制# 设置Jpeg镜像模式
adb shell setprop vendor.debug.camera.Jpeg.flip 1
# 设置视频镜像模式
adb shell setprop vendor.debug.camera.videocontrol.flip 1
属性控制优先级:
针对视频流的动态控制实现:
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) {
if (videoFlip) {
if (0 == videoOrientation) {
reqTransform = eTransform_FLIP_H;
} else if (90 == videoOrientation) {
reqTransform = eTransform_FLIP_V;
}
// 其他角度处理...
}
it.second->setTransform(reqTransform);
}
在MTK Camera HAL开发中,Flip/Mirror功能的完整实现需要理解从Sensor到应用层的数据流向。通过本文介绍的多层级配置方案,开发者可以精准控制图像方向,避免常见的预览拍照不一致问题。实际项目中建议优先使用Vendor Tag方案,并通过属性调试快速验证效果。