三方相机问题分析七:【datespace导致GPU异常】facebook 黑块和Instagram花图问题
【关注我,后续持续新增专题博文,谢谢!!!】
上一篇我们讲了:
这一篇我们开始讲:
目录
一、问题背景
二、:问题分析过程
2.1:基于原理分析
2.2 :camx hal dump分析
2.3 :三方APP分析
2.4 :显示模块分析
2.5 :高通分析
2.6 :不同平台对比分析
2.7 :dataspace差异分析
2.8 :gpu 获取dataspace分析
2.9 :dataspace设置分析
2.10 :Dataspace二次修改分析
2.11 :dump gfxinfo 对比差异
2.12 :camx hal分析Dataspace二次修改
2.10 :解决方案
【关注我,后续持续新增专题博文,谢谢!!!】
一、问题背景
【前提条件】【Prerequistes】下载安装Instagram
【操作步骤】【Operation steps】在首页点击加号开启直播,使用8K UItra HD 特效进行前后置切换
【实际结果】【Actual results】出现花图【出现次数/测试次数】【Occurrence Times/Test Times】必现
二、:问题分析过程
2.1:基于原理分析
由于是花图问题,日志一般很难分析具体根因,也无法知道具体哪个环节/算法出现的问题,也无法知道是哪个模块出现的问题,camera/display/app从上到下均有可能。因为涉及到相机app,一般都是优先camera hal来分析。
2.2 :camx hal dump分析
由于是必现问题,我们首先复现并dump camx hal YUV。发现IPE YUV是正常的 ,并没有花图,因此并非camera HAL的出图问题。因此要么是系统层的问题,要么是三方app自身问题。
2.3 :三方APP分析
由于问题转三方分析,一般需要内部对比机和外部对比机存在这个问题,才能确定是三方app的问题。由于此问题是必现的,而其他机型对比机无法复现此问题,因此无法确定是三方app自身问题,而且可能性不大。这时需要系统层来分析,特别是屏显组,也就是显示模块,包括GPU。
2.4 :显示模块分析
显示组抓到问题渲染,提case和高通确认一下,怀疑和 gpu 浮点精度有关。
本地尝试使用 RenderDoc 抓取到问题关键帧,但无法抓取到。dcap 提供给高通,看是否有方法拿到问题帧的绘制流程。高通目前认为该问题为三方 shader 的问题。但有一些疑问需要确认。
2.5 :高通分析
app 在暗色模式下做 gpu 渲染时,在对 yuv 数据采样之后,使用 texel.rgb = clamp(texel.rgb, 0.0, 1.0) 函数将输出结果 texel 限制到 [0,1] 范围内。
这里的重点在于输入的 yuv 数据为 full range, 但是预览的时候 dataspace 为 unknow.
为什么相机送过来的 yuv 数据不是 limit range, 而是 full range?
白色高亮区域的 Y 的值,全部都是 255。 符合 full range 的特征;但是 GPU 还是使用的默认的 BT605 + limit range 的转换矩阵,高亮部分像素值就会出现问题;
2.6 :不同平台对比分析
对比情况如下:
- 只有 6450有这个问题
- dcap 在 8750 回看也有问题
- 高亮区域的值也是 full range? GraphicBuffer 中的 dataspace也是 unknow 状态?
- 在 SM8650 平台抓取的 ubwc_nv12 的原始流,高亮的区域纯白画面的像素值来看,为 full range.
- 为什么 SM8650 平台没有出现黑块?为什么 SM8750 平台没有出现黑块?
2.7 :dataspace差异分析
在 SM8750 平台上加trace, 看 graphicBuffer 的 dataspace:
预览的时候,graphicBuffer 的 dataspace 为 FullRange
Color Standard: STANDARD_BT601_625 Color Transfer: TRANSFER_SMPTE_170M Color Range : RANGE_FULL在 SM8650 平台上加trace 的 dataspace
6450 平台上,gpu acqiure buffer 的时候,graphicBuffer 中的 dataspace 值为0。但是实际上 dump 出来的数据,是 full range 的数据;
通过trace分析,会发现Camera Server 进程送给 app 的预览流在 queueBuffer 阶段,从 graphicbuffer 的 metadata 中读取到的 dataspace 为0; 但是实际上的yuv raw 数据是 full range 类型,不可能 dataspace 为0;
而且sm8750 平台和 sm8650 平台可以拿到正确的 dataspace 信息;
2.8 :gpu 获取dataspace分析
app 在拿到 graphicBuffer (yuv 纹理之后)需要创建 EGLImage 对象绑定 buffer, 才能给 gpu 使用。在调用 eglCreateImageKHR() 函数的时候, 驱动会去读取 metadata 信息拿 dataspace 信息;但是驱动不通过 GraphicBuffer 的 getDataspace 或者 GraphicMapper 的 getmetadata() 而是直接通过 gralloc 的 QtiMapper5::GetMetadataPrivate 函数去拿到 metadata 信息;
// gpu 驱动直接通过 native handle 通过 gralloc 拿到 metadata 信息 int32_t QtiMapper5::GetMetadataPrivate(buffer_handle_t _Nonnull bufferHandle, int64_t metadataType,void *_Nonnull outData, size_t outDataSize,bool isStandard) {char trace_tag[64] = {0, };snprintf(trace_tag, 64, "GetMetadataPrivate[%d] %p", metadataType, bufferHandle);ALOGE("[yjh] getMetadataPrivate[%d] from handle %p", metadataType, bufferHandle);ATRACE_BEGIN(trace_tag);if (!(bufferHandle)) {ALOGW("Failed to %s. Null buffer_handle_t.", __func__);return -AIMAPPER_ERROR_BAD_BUFFER;}if (!snap_helper_ || !snap_alloc_enable_) {return -AIMAPPER_ERROR_NO_RESOURCES;}int32_t size_required = outDataSize;auto snap_error =snap_helper_->GetMetadata(const_cast<native_handle_t *>(bufferHandle), metadataType, outData,false, false, isStandard, &size_required);if (snap_error == SnapError::NONE) {ATRACE_END();return size_required;} else {ATRACE_END();return -(static_cast<int32_t>(snap_error));} }
所以 GraphicBuffer 的 metadata 信息必须设置正确;并且必须在创建 EGLImage 之前就设置好;因为GraphicBuffer 和 EGLImage 是绑定的,一般 app也只需要创建一次, 所以 dataspace 信息只会读取一次;中途改变 dataspace, 必须要重新创建 EGLImage;
2.9 :dataspace设置分析
所有针对buffer 的 dataspace 修改,最终都会调用到QtiMapper 的 setMetadata() 接口或者setStandardMetadata() 接口;
1. camera hal 写 dataspace 是直接拿 buffer handle 直接调用 setMetadata() 接口去设置;
dataspace.colorPrimaries = static_cast<SnapColorPrimaries>(pGralocMetadata->colorPrimaries); dataspace.range = static_cast<SnapColorRange>(pGralocMetadata->range); dataspace.transfer = static_cast<SnapGammaTransfer>(pGralocMetadata->transfer);// 在这里写入 dataspace auto mapper_err = STABLEMAPPER(pGrallocIntf->qtiAidlMapper).setMetadata(reinterpret_cast<buffer_handle_t>(phNativeHandle), VENDOR_QTI_METADATA(SnapMetadataType::DATASPACE),static_cast<VOID *>(&dataspace), sizeof(dataspace));
2.surface 和 GraphicBuffer 则是通过 GraphicMapper 的 setDataspace 接口去进行设置;通过getDataspace 接口去进行读取:
status_t GraphicBufferMapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) {return mMapper->setDataspace(bufferHandle, dataspace); }status_t GraphicBufferMapper::getDataspace(buffer_handle_t bufferHandle,ui::Dataspace* outDataspace) {ATRACE_NAME("GBM::getDataspace");return mMapper->getDataspace(bufferHandle, outDataspace); }
3. 那么如何确定底层camera hal 写入的 buffer 和上层 surface 读取的 buffer 是同一个 buffer?
buffer 的 native_handle_t 指针在不同的进程中地址是不一样的,native_handle_t 中的 fd 在不同的进程中也是不一样的;但是对于同一个buffer, 其 fd 的 inode 号是一致的。 所以可以在QtiMapper 的 setMetadata 和 getMetadata 函数内打印对应 native handle 的 inode。
Error QtiMapper5::setStandardMetadata(buffer_handle_t _Nonnull bufferHandle,int64_t standardTypeRaw, const void *_Nonnull metadata,size_t metadataSize) {char trace_tag[64] = {0, };metadataSize = (metadataSize == 0 && metadata == nullptr) ? 1 : metadataSize;struct stat statA;if (bufferHandle->numFds >= 1) {int fdA = bufferHandle->data[0];fstat(fdA, &statA);}// 拿到 fd 的 inode 号snprintf(trace_tag, 64, "QtiMapper5::setStandardMetadata[%d] - [%lu,%lu]", standardTypeRaw, statA.st_ino, statA.st_dev);ALOGE("QtiMapper5::setStandardMetadata[%d] - [%lu,%lu]", standardTypeRaw, statA.st_ino, statA.st_dev);ATRACE_BEGIN(trace_tag);if (standardTypeRaw == 17) { // metadata 对应 dataspace// dataspaceDataspace *dataspace = (Dataspace*)(metadata);int colorPrimaries = dataspace->colorPrimaries;int range = dataspace->range;int transfer = dataspace->transfer;// 解析和获取 dataspacesnprintf(trace_tag, 64, "color:%d, range:%d, transfer:%d", colorPrimaries, range, transfer);ATRACE_BEGIN(trace_tag);ATRACE_END();}Error err = (SetMetadataPrivate(bufferHandle, standardTypeRaw, metadata, metadataSize, true));snprintf(trace_tag, 64, "err: %d", err);ATRACE_BEGIN(trace_tag);ATRACE_END();ATRACE_END();return err; }
通过上面的trace 打印,可以很快定位到所有修改过我们需要查询的 native_handle 的 metadata 在哪些地方被修改。
2.10 :Dataspace二次修改分析
从抓取的 systrace 中来看,buffer 在被 app 消费之前,metadata 一共被修改过两次:
- camera hal 通过 Qtimapper 将 metadata 数据写入到 buffer 的 metadata 中;这次写入的值是正常的。
- cameraserver preview 线程在做 queueBuffer(Surface.cpp) 时,会将 mDataspace 重新再写入一次;这次写入的是0,是有问题的;
这里写入的值来自哪里:
Surface::queueBuffer -> applyGrallocMetadataLocked(buffer, input) void Surface::applyGrallocMetadataLocked(android_native_buffer_t* buffer,const IGraphicBufferProducer::QueueBufferInput& queueBufferInput) {ATRACE_CALL();char trace_tag[64] = {0, };auto& mapper = GraphicBufferMapper::get();snprintf(trace_tag, 64, "Surface[%p] setDataspace [%d]", this, (uint32_t)(queueBufferInput.dataSpace));ATRACE_NAME(trace_tag);// 在这里会覆盖一次 dataspace 信息;这里写入的是 0(错误值)mapper.setDataspace(buffer->handle, static_cast<ui::Dataspace>(queueBufferInput.dataSpace));
这里的 dataspace 的值来自于 queueBufferInput,可以追踪代码,其最终来自 Surface 的 mDataspace 成员变量;
唯一修改 mDataspace 的位置:
int Surface::setBuffersDataSpace(Dataspace dataSpace) {char trace_tag[64];snprintf(trace_tag, 64, "Surface[%p]::setBuffersDataSpace[%d]", this, (uint32_t)(dataSpace));ATRACE_NAME(trace_tag);ALOGV("Surface::setBuffersDataSpace");Mutex::Autolock lock(mMutex);mDataSpace = dataSpace; // 在这里被修改;return NO_ERROR; }
根据 trace 打印,定位到是 cameraserver 的 preview 线程在 configStreams() 的时候,会给 Surface 设置 dataspace;cameraserver 在 configure streams 时,会写入 dataspace, 但写入的是一个错误的值;调用 Surface::perform()。
2.11 :dump gfxinfo 对比差异
cameraserver 查看流配置信息,dataspace 设置情况是0x102。
0x102 我们可以看到其定义为:HAL_DATASPACE_BT601_625 = 258;这里的 dataspace 只给出了 Standard, 没有给出 Transfer 和 Range 的信息;
相机完整的 yuv 数据的 dataspace 为:
HAL_DATASPACE_V0_JFIF = 146931712, // ((STANDARD_BT601_625 | TRANSFER_SMPTE_170M) | RANGE_FULL)
cameraserver 在 setDataspace 的时候,直接传入了 HAL_DATASPACE_BT601_625 这个数值,是一个不完整的 dataspace; 实际上 metadata 并没有实际写入;此时,buffer 中的 dataspace 将会保持 camera hal 写入的原始的 HAL_DATASPACE_V0_JFIF 正确值;
但是写入 0x00(UNKNOW) 这个默认值,QtiMapper 的写入将会成功;所有 buffer 中的 dataspace 写入的正确的 dataspace 将会被抹除掉。
通过dump gfxinfo 对比差异
- 这里可以看到送 gpu 渲染的 buffer 的 dataspace 信息,为0x8c20000, 即 bt601 full range;
- 从 app 的 gfxinfo 对比可以看出,surfacetexture 中 buffer 的 dataspace 在6450 平台和在 8750 平台上的差异是不一样的。
通过显示模块分析,最终确定是相机模块二次修改dataspace导致的问题,camx hal接力分析。
2.12 :camx hal分析Dataspace二次修改
通过trace分析,第二次在cameraserver GUI模块set的dataspace参数值,GUI模块的说是从camera configstream的时候set到GUI的,
目前大概可以确定dataspace的问题,且出现在cameraserver configstream的时候,下面需要排查下camera configstream的时候,为啥set dataspace的有问题,尽快流转或解决这个参数的问题。
通过加日志,最终定位到:
在平台逻辑PipelineDescriptor* ChiContext::CreatePipelineDescriptor(
的时候设置dataspace的时候错误,具体是由于某种场景下少set了一路steam的dataspace值,因为之前的时候三方并不会使用这个,SM8550等平台,由于高通的代码及时的更新修复这块的逻辑,所以没有问题。在SM6450上属于三方新功能使问题暴露出来了。
2.10 :解决方案
分析:{GPU:白色高亮区域的 Y 的值,全部都是 255。 符合 full range 的特征;
但是 GPU 还是使用的默认的 BT605 + limit range 的转换矩阵,高亮部分像素值就会出现问题;
输入的 yuv 数据为 full range, 但是预览的时候 dataspace 为 unknow,这时候的dataSpace应该为DataspaceBT601_625:0x102}
方案:{平台在configstream会根据条件对stream set dataSpace,而问题场景没有set。
三方新功能,平台逻辑老,添加部分逻辑在该场景set stream dataSpace为0x102。
提Qcom Case和qcom确认可以如此修改。也和GPU模块确认过.目前三方的预览流dataspace都是0x102才是正常的diff --git a/src/core/chi/camxchicontext.cpp b/src/core/chi/camxchicontext.cpp index baa8130..06a024f 100755 --- a/src/core/chi/camxchicontext.cpp +++ b/src/core/chi/camxchicontext.cpp @@ -4874,6 +4874,24 @@}}+#if (defined(CAMX_ANDROID_API) && (CAMX_ANDROID_API >= 32)) // Android-T or better + // For Android T new API, we should check preview based on its own per stream hdr profile + if (((GrallocUsageHwComposer == (GetGrallocUsage(pChiStream) & GrallocUsageHwComposer)) || + (GrallocUsageHwTexture == (GetGrallocUsage(pChiStream) & GrallocUsageHwTexture ))) && + (0 == (GetGrallocUsage(pChiStream) & GrallocUsageHwVideoEncoder))) + { + if ((FALSE == overrideImpDefinedFormat.isHDR) && + (StreamHDRMode::HDRModeNone == HDRModeValue)) + { + // Set default preview stream dataspace + pChiStream->dataspace = DataspaceBT601_625; + } + + CAMX_LOG_INFO(CamxLogGroupCore, "pChiStream:%p,override dataspace:%x", + pChiStream, pChiStream->dataspace); + } +#endif // Android-T or better +// HEIC streams are snapshot streams, data space should not be updated for HEIC Streamif ((DataspaceHEIF != pChiStream->dataspace) &&(GrallocUsageHwImageEncoder != (GetGrallocUsage(pChiStream) & GrallocUsageHwImageEncoder)) &&
【关注我,后续持续新增专题博文,谢谢!!!】
下一篇讲解: