我们都知道,MTK平台Camera在拍照时生成的照片中包含相应照片的Exif信息,而这些信息是从什么地方来的呢?今天我们就来捋一下Code思路。
这里以v2.0的JpegNode.cpp举例
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/hwnode/JpegNode/v2.0/JpegNode.cpp
static void onProcessRequest(
JpegNodeImp& jpg,
Request& req
)
{
//(1)prepare
CAM_TRACE_NAME(__FUNCTION__);
sp<encode_frame> enc_frame = req.getEncodeFrame();
// lock buffer
if (req.lockMeta() != OK) {
MY_LOGE("lock request meta failed!");
req.mbIsEncodeJpegError = MTRUE;
return;
}
if (req.prepareMeta() != OK) {
MY_LOGE("prepareMeta failed!");
req.mbIsEncodeJpegError = MTRUE;
return;
}
if (req.lockInImg() != OK) {
MY_LOGE("lock request input image failed");
req.mbIsEncodeJpegError = MTRUE;
return;
}
if (req.prepareExif() != OK) {
MY_LOGE("prepareExif failed!");
return;
}
if (req.lockOutImg() != OK) {
MY_LOGE("lock request output image failed");
req.mbIsEncodeJpegError = MTRUE;
return;
}
enc_frame->muJpegSwMode = jpg.mPropJpegSwMode;
// (2)encode flow
if (!jpg.mbAppnOnly || jpg.mIsIspHidlHeif) {
std::thread encodeThumbThread;
if (enc_frame->mbHasThumbnail) {
encodeThumbThread = std::thread(&JpegNodeImp::encodeThumbnailJpeg, &jpg, std::ref(enc_frame));
}
jpg.encodeMainJpeg(enc_frame);
if(enc_frame->mbHasThumbnail &&
encodeThumbThread.joinable()) {
encodeThumbThread.join();
}
} else {
if (enc_frame->mbHasThumbnail)
jpg.encodeThumbnailJpeg(enc_frame);
}
req.updateMetadata(enc_frame->mParams);
if (req.unlock() != OK) {
MY_LOGE("request unlock failed!");
}
// (3)encode exif
jpg.encodeExif(enc_frame);
//...
return;
}
我们这里主要关注exif信息是如何产生的?所以跟踪第三步encode exif。
MVOID
JpegNodeImp::
encodeExif(
sp<encode_frame>& pEncodeFrame)
{
//...
MUINT const usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN;
MBOOL ret = pOutImageBuffer->lockBuf(getNodeName(), usage);
MY_LOGA_IF(!ret, "%s", "lockBuffer failed, please check VA usage!");
size_t exifSize = 0;
MINT8 * pExifBuf = reinterpret_cast<MINT8*>(pOutImageBuffer->getBufVA(0));
//这里主要关注是如何make Exif信息
if( pExifBuf == NULL
|| OK != ExifUtils::makeExifHeader(pEncodeFrame, pExifBuf, exifSize, mbAppnOnly)
) {
pEncodeFrame->mbSuccess = MFALSE;
MY_LOGE("frame %u make exif header failed: buf %p, size %zu",
pEncodeFrame->mpFrame->getFrameNo(),
pExifBuf, exifSize);
}
if( pExifBuf )
pExifBuf = NULL;
size_t JpegBitStreamSize = 0;
if (pEncodeFrame->mpJpeg_Main != NULL) {
JpegBitStreamSize = pEncodeFrame->exif.getHeaderSize() +
pEncodeFrame->mpJpeg_Main->getBitstreamSize();
pEncodeFrame->mpJpeg_Main->setBitstreamSize(
JpegBitStreamSize);
} else {
JpegBitStreamSize = pEncodeFrame->exif.getHeaderSize();
}
//...
}
我们进入makeExifHeader函数。
MERROR
ExifUtils::
makeExifHeader(
sp<encode_frame> rpEncodeFrame,
MINT8 * const pOutExif,
size_t& rOutExifSize, // [IN/OUT] in: exif buf size, out: exif header size
MBOOL const isAppnOnly
)
{
MERROR ret;
MBOOL needSOI = MTRUE;
ret = rpEncodeFrame->exif.make((MUINTPTR)pOutExif, rOutExifSize, needSOI);
rpEncodeFrame->exif.uninit();
return ret;
}
这里的exif类型是StdExif,所以我们可以定位到StdExif文件。
我们搜索一下Code,发现StdExif有好几处,如下:
//vendor/mediatek/proprietary/hardware/mtkcam/legacy/exif/camera/StdExif.cpp
//vendor/mediatek/proprietary/hardware/mtkcam/legacy/exif/v3/StdExif.cpp
//vendor/mediatek/proprietary/hardware/mtkcam/utils/exif/v3/StdExif.cpp
//vendor/mediatek/proprietary/hardware/mtkcam-core/utils/exif/v3/StdExif.cpp
那我们如何确定是走的哪个文件中的呢?可通过如下Log来判断:
05-31 23:43:27.593 973 19585 I mtkcam/exif: [make] out buffer(0x7252bc3000)
05-31 23:43:27.593 973 19585 I mtkcam/exif: [updateStdExif] strDateTime(2022:05:31 23:43:27), strSubSecTime(579)
05-31 23:43:27.593 973 19585 I mtkcam/exif: [updateStdExif] property: make(UTIME), model(TM152)
05-31 23:43:27.594 973 19585 E mtkcam/exif: [make] not support ICC profile 0 (make){#642:vendor/mediatek/proprietary/hardware/mtkcam/utils/exif/v3/StdExif.cpp}
一旦拍照之后就会出现make exif信息的Log,可通过如上Log来看能否辨别相应的文件。
我们接下来进入StdExif.cpp文件的make函数。
//vendor/mediatek/proprietary/hardware/mtkcam/utils/exif/v3/StdExif.cpp
status_t
StdExif::
make(
MUINTPTR const outputExifBuf,
size_t& rOutputExifSize,
MBOOL enableSOI
)
{
int ret = 0;
mpOutputExifBuf = outputExifBuf;
// set 0 first for error return
rOutputExifSize = 0;
MY_LOGI("out buffer(%#" PRIxPTR ")", getBufAddr());
unsigned int u4OutputExifSize = 0;
//这里主要有如下两个exif信息相关的结构体
exifAPP1Info_s exifApp1Info;
exifImageInfo_s exifImgInfo;
// (1) Fill exifApp1Info
updateStdExif(&exifApp1Info);
// (2) Fill exifImgInfo
::memset(&exifImgInfo, 0, sizeof(exifImageInfo_t));
exifImgInfo.bufAddr = getBufAddr();
exifImgInfo.mainWidth = mExifParam.u4ImageWidth;
exifImgInfo.mainHeight = mExifParam.u4ImageHeight;
exifImgInfo.thumbSize = getThumbnailSize();
ret = mpBaseExif->exifApp1Make(&exifImgInfo, &exifApp1Info, &u4OutputExifSize, enableSOI);
rOutputExifSize = (size_t)u4OutputExifSize;
// (4) Append App2
int app2 = 2;
unsigned int app2ReturnSize = 0;
int size = mICCSize; // Data(n bytes)
unsigned char *pAddr = (unsigned char*)getBufAddr()+ getStdExifSize() + getThumbnailSize();
MY_LOGD_IF(mLogLevel,"offset %zu buf %p ", getStdExifSize() + getThumbnailSize() , pAddr);
if(mICCIdx == EXIF_ICC_PROFILE_SRGB) {
ret = mpBaseExif->exifAppnMake(app2, pAddr, (unsigned char*)&icc_profile_srgb, size, &app2ReturnSize, 0);
}
else if(mICCIdx == EXIF_ICC_PROFILE_DCI_P3) {
ret = mpBaseExif->exifAppnMake(app2, pAddr, (unsigned char*)&icc_profile_display_p3, size, &app2ReturnSize, 0);
}
else
MY_LOGE("not support ICC profile %d", mICCIdx);
// return app2ReturnSize is mICCSize + 2 +2 (Data(n bytes) + Data size(2 bytes) + Data tag(2 bytes))
// (3) Append debug exif
if ( isEnableDbgExif() )
{
updateDbgExif();
}
return (status_t)ret;
}
相关exif信息的部分结构体如下:
//vendor/mediatek/proprietary/hardware/mtkcam/utils/exif/common/IBaseExif.h
typedef struct exifImageInfo_s {
MUINTPTR bufAddr;
unsigned int mainWidth;
unsigned int mainHeight;
unsigned int thumbSize;
} exifImageInfo_t;
typedef struct exifAPP1Info_s {
unsigned int imgIndex; // mtk definition: the index of continuous shot image.
unsigned int groupID; // mtk definition: group ID for continuous shot.
unsigned int bestFocusH; // mtk definition: focus value (H) for best shot.
unsigned int bestFocusL; // mtk definition: focus value (L) for best shot.
unsigned int refocusPos; // mtk definition: for image refocus. JPEG(main sensor) in left or right position.
unsigned char strJpsFileName[32]; // mtk definition: for image refocus. JPS file name for calculating depth map.
unsigned int exposureTime[2];
unsigned int fnumber[2];
unsigned int shutterSpeed[2];
int brightness[2];
int exposureBiasValue[2];
unsigned int focalLength[2];
unsigned int maxApertureValue[2];
unsigned short focalLength35mm;
unsigned short orientation;
unsigned short exposureProgram;
unsigned short isoSpeedRatings;
unsigned short isoType;
unsigned int isoSpeed;
unsigned short meteringMode;
unsigned short flash;
unsigned short whiteBalanceMode;
unsigned short reserved;
unsigned char strImageDescription[32];
unsigned char strMake[32];
unsigned char strModel[32];
unsigned char strSoftware[32];
unsigned char strDateTime[20];
unsigned char strSubSecTime[4];
unsigned char gpsLatitudeRef[2];
unsigned char gpsLongitudeRef[2];
unsigned char reserved02;
unsigned int digitalZoomRatio[2];
unsigned short sceneCaptureType;
unsigned short lightSource;
unsigned char strFlashPixVer[8];
unsigned short exposureMode;
unsigned short reserved03;
int gpsIsOn;
int gpsAltitude[2];
int gpsLatitude[8];
int gpsLongitude[8];
int gpsTimeStamp[8];
unsigned char gpsDateStamp[12];
unsigned char gpsProcessingMethod[64];
} exifAPP1Info_t;
这里我们主要看updateStdExif(&exifApp1Info)函数,看主要有那些exif信息被产生。
void
StdExif::
updateStdExif(exifAPP1Info_s* exifApp1Info)
{
::memset(exifApp1Info, 0, sizeof(exifAPP1Info_s));
/*********************************************************************************
GPS
**********************************************************************************/
if (mExifParam.u4GpsIsOn == 1) {
float latitude = atof((char*)mExifParam.uGPSLatitude);
float longitude = atof((char*)mExifParam.uGPSLongitude);
long long timestamp = atol((char*)mExifParam.uGPSTimeStamp);
char const*pgpsProcessingMethod = (char*)mExifParam.uGPSProcessingMethod;
//
// Set GPS Info
if (latitude >= 0) {
strncpy((char *)exifApp1Info->gpsLatitudeRef, "N", sizeof(exifApp1Info->gpsLatitudeRef)/sizeof(char));
}
else {
strncpy((char *)exifApp1Info->gpsLatitudeRef, "S", sizeof(exifApp1Info->gpsLatitudeRef)/sizeof(char));
latitude *= -1; // make it positive
}
if (longitude >= 0) {
strncpy((char *)exifApp1Info->gpsLongitudeRef, "E", sizeof(exifApp1Info->gpsLongitudeRef)/sizeof(char));
}
else {
strncpy((char *)exifApp1Info->gpsLongitudeRef, "W", sizeof(exifApp1Info->gpsLongitudeRef)/sizeof(char));
longitude *= -1; // make it positive
}
exifApp1Info->gpsIsOn = 1;
// Altitude
exifApp1Info->gpsAltitude[0] = mExifParam.u4GPSAltitude;
exifApp1Info->gpsAltitude[1] = 1;
// Latitude
exifApp1Info->gpsLatitude[0] = (int) latitude;
exifApp1Info->gpsLatitude[1] = 1;
latitude -= exifApp1Info->gpsLatitude[0];
latitude *= 60;
exifApp1Info->gpsLatitude[2] = (int) latitude;
exifApp1Info->gpsLatitude[3] = 1;
latitude -= exifApp1Info->gpsLatitude[2];
latitude *= 60;
latitude *= 10000;
exifApp1Info->gpsLatitude[4] = (int) latitude;
exifApp1Info->gpsLatitude[5] = 10000;
// Longtitude
exifApp1Info->gpsLongitude[0] = (int) longitude;
exifApp1Info->gpsLongitude[1] = 1;
longitude -= exifApp1Info->gpsLongitude[0];
longitude *= 60;
exifApp1Info->gpsLongitude[2] = (int) longitude;
exifApp1Info->gpsLongitude[3] = 1;
longitude -= exifApp1Info->gpsLongitude[2];
longitude *= 60;
longitude *= 10000;
exifApp1Info->gpsLongitude[4] = (int) longitude;
exifApp1Info->gpsLongitude[5] = 10000;
// Timestamp
if ( timestamp >= 0 )
{
time_t tim = (time_t) timestamp;
struct tm *ptime = gmtime(&tim);
if (ptime != NULL) {
exifApp1Info->gpsTimeStamp[0] = ptime->tm_hour;
exifApp1Info->gpsTimeStamp[1] = 1;
exifApp1Info->gpsTimeStamp[2] = ptime->tm_min;
exifApp1Info->gpsTimeStamp[3] = 1;
exifApp1Info->gpsTimeStamp[4] = ptime->tm_sec;
exifApp1Info->gpsTimeStamp[5] = 1;
if (0 > snprintf((char *)exifApp1Info->gpsDateStamp, sizeof(exifApp1Info->gpsDateStamp),
"%04d:%02d:%02d", ptime->tm_year + 1900, ptime->tm_mon + 1, ptime->tm_mday)
) {
MY_LOGW("snprintf failed");
}
} else {
MY_LOGD("gmtime might not be implemented");
}
}
else
{
MY_LOGE("wrong timestamp(%lld)", timestamp);
}
// ProcessingMethod
const char exifAsciiPrefix[] = { 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 }; // ASCII
int len1, len2, maxLen;
len1 = sizeof(exifAsciiPrefix);
memcpy(exifApp1Info->gpsProcessingMethod, exifAsciiPrefix, len1);
maxLen = sizeof(exifApp1Info->gpsProcessingMethod) - len1;
len2 = strlen(pgpsProcessingMethod);
if (len2 > maxLen) {
len2 = maxLen;
}
memcpy(&exifApp1Info->gpsProcessingMethod[len1], pgpsProcessingMethod, len2);
}
/*********************************************************************************
common
**********************************************************************************/
// software information
memset(exifApp1Info->strSoftware, 0, 32);
char _strSoftware[] = "MediaTek Camera Application";
strncpy((char *)exifApp1Info->strSoftware, "MediaTek Camera Application", sizeof(_strSoftware)/sizeof(char));
// get datetime
// [SubsecTime]
// convert microsecond to second
// if microsecond is not full 6 digits, it will be filled with "0" from head
struct tm *tm;
if ((tm = localtime(&mExifParam.seconds)) != NULL)
{
size_t stret = strftime((char *)exifApp1Info->strDateTime,
sizeof(exifApp1Info->strDateTime), "%Y:%m:%d %H:%M:%S", tm);
if (stret == 0) {
MY_LOGE("strftime failed!");
}
int snpret = 0;
snpret = snprintf((char *)exifApp1Info->strSubSecTime,
sizeof(exifApp1Info->strSubSecTime), "%06ld\n", mExifParam.microSeconds);
if (snpret < 0) {
MY_LOGE("snprintf failed!");
}
MY_LOGI("strDateTime(%s), strSubSecTime(%s)", exifApp1Info->strDateTime, exifApp1Info->strSubSecTime);
}
// [digital zoom ratio]
exifApp1Info->digitalZoomRatio[0] = (unsigned int)mExifParam.u4ZoomRatio;
exifApp1Info->digitalZoomRatio[1] = 100;
// [orientation]
exifApp1Info->orientation = (unsigned short)determineExifOrientation(
mExifParam.u4Orientation,
mExifParam.u4Facing
);
/*********************************************************************************
3A
**********************************************************************************/
// [f number]
//
// [max aperture value]
// aperture value = 2 log2(f number)
exifApp1Info->fnumber[0] = (unsigned int)mExifParam.u4FNumber;
exifApp1Info->fnumber[1] = FNUMBER_PRECISION;
double apertureValue = 2 * log(mExifParam.u4FNumber / FNUMBER_PRECISION) / log(2);
exifApp1Info->maxApertureValue[0] = static_cast<unsigned int>(apertureValue * 100);
exifApp1Info->maxApertureValue[1] = 100;
// [focal length]
exifApp1Info->focalLength[0] = (unsigned int)mExifParam.u4FocalLength;
exifApp1Info->focalLength[1] = 1000;
// [35mm focal length]
exifApp1Info->focalLength35mm = (unsigned short)mExifParam.u4FocalLength35mm;
// [iso speed]
// For super high ISO case(>65535), the isoSpeedRatings will be equal to 65535 due to its SHORT type
// Therefore, we record real ISO value in another variable isoSpeed, which is also EXIF_TAG_RECOMMENDEDEXPOSUREINDEX
// isoType indicates the tag we use for recording real ISO value, and we use EXIF_TAG_RECOMMENDEDEXPOSUREINDEX here which isoType = 2
// isoType: 1=StandardOutputSensitivity, 2=RecommendedExposureIndex, 3=ISOSpeed
if ( mExifParam.u4AEISOSpeed > 65535) {
exifApp1Info->isoSpeedRatings = 65535;
exifApp1Info->isoType = 2;
exifApp1Info->isoSpeed = (unsigned int)mExifParam.u4AEISOSpeed;
} else {
exifApp1Info->isoSpeedRatings = (unsigned short)mExifParam.u4AEISOSpeed;
}
// [exposure time]
//
// [shutter speed value]
// shutterspeedvalue = -log2(exp time)
if(mExifParam.u4CapExposureTime == 0){
//YUV sensor
exifApp1Info->exposureTime[0] = 0;
exifApp1Info->exposureTime[1] = 0;
exifApp1Info->shutterSpeed[0] = 0;
exifApp1Info->shutterSpeed[1] = 0;
}
else{
// RAW sensor
if (mExifParam.u4CapExposureTime > 1000000) { //1 sec
exifApp1Info->exposureTime[0] = mExifParam.u4CapExposureTime / 100000;
exifApp1Info->exposureTime[1] = 10;
}
else{ // us
exifApp1Info->exposureTime[0] = mExifParam.u4CapExposureTime;
exifApp1Info->exposureTime[1] = 1000000;
}
double shutterSpeedValue = log(1000000/mExifParam.u4CapExposureTime) / log(2);
exifApp1Info->shutterSpeed[0] = static_cast<unsigned int>(shutterSpeedValue * 1000);
exifApp1Info->shutterSpeed[1] = 1000;
}
// [flashlight]
// bit coding of the exif flash tag
// |6|5|4|3|2|1|0|
//
// use 0~6 bit determine the flash value
// 0: indicates the flash firing status(on, off)
// 1,2: indicates the flash return status
// 3,4: indicates the flash mode(auto, compulsory)
// 5: indicates the flash function
// 6: indicates the red eye mode
exifApp1Info->flash = (mExifParam.u4FlashFiringStatus << FLASH_FIRING_STATUS_SHIFT |
mExifParam.u4FlashReturnDetect << FLASH_RETURN_DETECT_SHIFT |
mExifParam.u4FlashMode << FLASH_MODE_SHIFT |
mExifParam.u4FlashFunction << FLASH_SUPPORT_SHIFT |
mExifParam.u4FlashRedEye << FLASH_REDEYE_SHIFT);
// [white balance mode]
exifApp1Info->whiteBalanceMode = mExifParam.u4AWBMode;
// [light source]
exifApp1Info->lightSource = mExifParam.u4LightSource;
// [metering mode]
exifApp1Info->meteringMode = mExifParam.u4AEMeterMode;
// [exposure program] , [scene mode]
exifApp1Info->exposureProgram = mExifParam.u4ExpProgram;
exifApp1Info->sceneCaptureType = mExifParam.u4SceneCapType;
// [Brightness value]
exifApp1Info->brightness[0] = mExifParam.i4Brightness;
exifApp1Info->brightness[1] = 10;
// [Ev offset]
exifApp1Info->exposureBiasValue[0] = (unsigned int)mExifParam.i4AEExpBias;
exifApp1Info->exposureBiasValue[1] = 10;
/*********************************************************************************
update customized exif
**********************************************************************************/
{
char make[PROPERTY_VALUE_MAX] = {'\0'};
char model[PROPERTY_VALUE_MAX] = {'\0'};
property_get("ro.product.manufacturer", make, "0");
property_get("ro.product.model", model, "0");
MY_LOGI("property: make(%s), model(%s)", make, model);
// [Make]
if ( ::strcmp(make, "0") != 0 ) {
::memset(exifApp1Info->strMake, 0, 32);
::strncpy((char*)exifApp1Info->strMake, (const char*)make, 31);
}
// [Model]
if ( ::strcmp(model, "0") != 0 ) {
::memset(exifApp1Info->strModel, 0, 32);
::strncpy((char*)exifApp1Info->strModel, (const char*)model, 31);
}
}
/*********************************************************************************
MISC
**********************************************************************************/
// [flashPixVer]
memcpy(exifApp1Info->strFlashPixVer, "0100 ", 5);
// [exposure mode]
exifApp1Info->exposureMode = 0; // 0 means Auto exposure
}
可以看到有GPS、common、3A相关exif信息被写入,同时还有软件来源_strSoftware[] = "MediaTek Camera Application"以及最后的自定义系统属性等。
Exif信息的Tag都在如下类中定义:
//frameworks/base/media/java/android/media/ExifInterface.java
public static final String TAG_ORIENTATION = "Orientation"; 方向
public static final String TAG_DATETIME = "DateTime"; 时间日期
public static final String TAG_MAKE = "Make"; 设备制造商
public static final String TAG_MODEL = "Model"; 设备型号
public static final String TAG_FLASH = "Flash"; 闪光灯
public static final String TAG_IMAGE_WIDTH = "ImageWidth"; 图片宽
public static final String TAG_IMAGE_LENGTH = "ImageLength"; 图片长
public static final String TAG_GPS_LATITUDE = "GPSLatitude"; 纬度
public static final String TAG_GPS_LONGITUDE = "GPSLongitude"; 经度
public static final String TAG_EXPOSURE_TIME = "ExposureTime"; 曝光时间
public static final String TAG_APERTURE = "FNumber"; 光圈值
public static final String TAG_ISO = "ISOSpeedRatings";ISO
//...
应用如何读取和写入Exif信息:
//读取图片EXIF信息
ExifInterface exifInterface=new ExifInterface(imgPath);
String Orientation=exifInterface.getAttribute(ExifInterface.TAG_ORIENTATION);
String DateTime=exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
String Make=exifInterface.getAttribute(ExifInterface.TAG_MAKE);
String Model=exifInterface.getAttribute(ExifInterface.TAG_MODEL);
String Flash=exifInterface.getAttribute(ExifInterface.TAG_FLASH);
int ImageWidth=exifInterface.getAttributeInt(android.media.ExifInterface.TAG_IMAGE_WIDTH, 0);
int ImageLength=exifInterface.getAttributeInt(android.media.ExifInterface.TAG_IMAGE_LENGTH, 0);
String ExposureTime=exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
String FNumber=exifInterface.getAttribute(ExifInterface.TAG_APERTURE);
String ISOSpeedRatings=exifInterface.getAttribute(ExifInterface.TAG_ISO);
//...
//替换写入EXIF信息
android.media.ExifInterface exifInterface = new android.media.ExifInterface(mPath);
exifInterface.setAttribute(android.media.ExifInterface.TAG_IMAGE_WIDTH,"1920");//宽度修改无效
exifInterface.setAttribute(android.media.ExifInterface.TAG_IMAGE_LENGTH,"2560");//高度修改无效
exifInterface.setAttribute(android.media.ExifInterface.TAG_MODEL,"S123456");
exifInterface.setAttribute(android.media.ExifInterface.TAG_ISO,"8000");
exifInterface.saveAttributes();