Android dump渲染和合成图层GraphicBuffer指南
引言
博客停更很久了,提起笔来渐感生疏啊!看来,还是得抽出时间来更新更新啊!好了,感慨也发完了,是时候切入正题了。本篇博客主要以本人在实际项目的开发中,为了定位Android显示异常究竟是GPU渲染,合成的问题,还是DRM端送显异常的问题而来。这里为了后续的复盘,也为可能有共同需求的朋友所以记录下来,最终发展成为如何dump Android渲染和合成图层GraphicBuffer,并通过YUV软件查看流程(主要是教大家如何把锅甩给队友)!通过本篇博客,读者将会至少学会如下两点:
- 通过dump Android渲染图层GraphicBuffer,查看Android渲染结果是否正确
- 通过dump Android合成图层GraphicBuffer,查看Android合成结果是否正确
这里需要注意一点的是,这里的合成指的是GPU(Client)的合成方式!
好了不多说了,直接开干!
能搜寻到这篇博客的,肯定是对Android graphci有一定掌握的朋友,所以这里就不会过多解释一些名词和代码逻辑了。总之这是一篇专业性比较强的文章(因为一般的朋友也不会搜这个)!
注意:本篇的介绍是基于Android 11®平台为基础的(其中Q的版本差异也不是很大),其中涉及的代码路径如下:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
frameworks/native/libs/renderengine/gl/GLESRenderEngine.cpp
frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
frameworks/base/cmds/screencap
frameworks/av/cmds/screenrecord
一.通过Android内置命令dump Android合成图层GraphicBuffer
在正式开始分析我们如何自行添加dump相关逻辑代码,进而dump渲染和合成图层GraphicBuffer之前,这里我简单介绍下如何使用Android内置的cmd命令,进行相关的dump逻辑,这里的dump逻辑仅仅只能dump GPU合成图层GraphicBuffer。
上述源码的逻辑,强烈建议读者参阅借鉴一下,这个很有必要,因为我的最终代码实现就是参考Android提供的这两个cmd命令而来!
1.1 Screencap dump一帧GPU合成图层GraphicBuffer
Screencap的命令格式如下,它将当前当前Android显示的图层以GPU合成的模式,并通过png格式保存下来。该命令的使用方法如下:
130|XXX:/ # screencap --help
screencap: invalid option -- -
usage: screencap [-hp] [-d display-id] [FILENAME]-h: this message-p: save the file as a png.-d: specify the physical display ID to capture (default: 0)see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
If FILENAME ends with .png it will be saved as a png.
If FILENAME is not given, the results will be printed to stdout.130|XXX:/ #screencap -p /sdcard/screencap.png
关于screencap的具体实现逻辑就不过多介绍,感兴趣的可以frameworks/base/cmds/screencap查看相关的源码逻辑!
1.2 Screenrecord dump一帧或多帧GPU合成图层GraphicBuffer
Screenrecord的命令格式如下,它将当前当前Android显示的图层以GPU合成的模式,并通过多种格式保存下来。该命令的使用方法如下:
1|XXX:/ # screenrecord --help
Usage: screenrecord [options] <filename>Android screenrecord v1.3. Records the device's display to a .mp4 file.Options:
--size WIDTHxHEIGHTSet the video size, e.g. "1280x720". Default is the device's maindisplay resolution (if supported), 1280x720 if not. For best results,use a size supported by the AVC encoder.
--bit-rate RATESet the video bit rate, in bits per second. Value may be specified asbits or megabits, e.g. '4000000' is equivalent to '4M'. Default 20Mbps.
--bugreportAdd additional information, such as a timestamp overlay, that is helpfulin videos captured to illustrate bugs.
--time-limit TIMESet the maximum recording time, in seconds. Default / maximum is 180.
--display-id IDspecify the physical display ID to record. Default is the primary display.see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
--verboseDisplay interesting information on stdout.
--helpShow this message.Recording continues until Ctrl-C is hit or the time limit is reached.1|XXX:/ #screenrecord --verbose --time-limit 10 --output-format raw-frames /sdcard/raw-frames.frames
//说明:录制屏幕,录制时间为10s,格式为裸BGR(FORMAT_FRAMES),不添加任何信息
1|XXX:/ #screenrecord --verbose --time-limit 10 --output-format frames /sdcard/frames.frames
//说明:录制屏幕,录制时间为10s,格式为裸BGR(FORMAT_FRAMES),只是每帧前面会加上用于描述帧信息的20字节头。
1|XXX:/ #screenrecord --verbose --time-limit 30 --output-format h264 /sdcard/demo.h264
//说明:录制屏幕,录制时间为30s,格式为h264(FORMAT_H264)。
1|XXX:/ #screenrecord /sdcard/demo.mp4
//说明:录制屏幕,录制时间为默认的180s,格式为MP4(FORMAT_MP4)。
关于screenrecord的具体实现逻辑就不过多介绍,感兴趣的可以frameworks/base/cmds/screencap查看相关的源码逻辑!
二.自定义逻辑dump Android渲染和合成图层GraphicBuffer指南
通过前面的章节,我们简单介绍了如何使用Android内置的cmds命令dump GPU合成图层的GraphicBuffer,本章节我们重点介绍如何自定义dump Android渲染图层和GPU合成图层。
2.1dump Android渲染图层GraphicBuffer
这块我们可以在GLESRenderEngine.cpp的如下方法中添加相关的逻辑,如下:
//frameworks/native/libs/renderengine/gl/GLESRenderEngine.cpp
static void dump_content_of_layers_to_file(const sp<GraphicBuffer>& target)
{ALOGE("dump_content_of_layers_to_file");// ALOGE("dump_content_of_layers_to_file");// ALOGE("dump_content_of_layers_to_file");int result = -1;void *addr = NULL;static int DumpSurfaceCount = 0;int32_t bufStride;FILE * pfile = NULL;char layername[100] ;memset(layername,0,sizeof(layername));uint32_t w, s, h, f;w = target->getWidth();h = target->getHeight();s = target->getStride();f = target->getPixelFormat();android_dataspace d;uint32_t buffer_size = 0;d = HAL_DATASPACE_UNKNOWN;buffer_size = s * h * bytesPerPixel(f);bufStride = bytesPerPixel(f);ALOGE("buffer_layer info w:%d h:%d s:%d f:%d d:%d size:%d", w, h, s, f, d, buffer_size);sprintf(layername,"/data/dump/buffer_layer_%d_frame_%d_%d_%d.bin",DumpSurfaceCount,w,h,bufStride);ALOGD("The dump file info : %s", layername);DumpSurfaceCount ++;pfile = fopen(layername,"w+");if(pfile){//获取FrameBufferSurface对应的ion地址result = target->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &addr);if(addr != NULL){ALOGE("The addr : %p", addr);int result = -1;// system("mkdir /data/dump && chmod 777 /data/dump");result = fwrite( (const void *)addr,(size_t)( (buffer_size)),1,pfile);if(result >0){ALOGD("fwrite success!");}else{ALOGE("fwrite failed error %d", result);}}else{ALOGE("lock buffer error!");}fclose(pfile);target->unlock();}
}status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,const std::vector<const LayerSettings*>& layers,ANativeWindowBuffer* const buffer,const bool useFramebufferCache, base::unique_fd&& bufferFence,base::unique_fd* drawFence) {ATRACE_CALL();if (layers.empty()) {ALOGV("Drawing empty layer stack");return NO_ERROR;}if (bufferFence.get() >= 0) {// Duplicate the fence for passing to waitFence.base::unique_fd bufferFenceDup(dup(bufferFence.get()));if (bufferFenceDup < 0 || !waitFence(std::move(bufferFenceDup))) {ATRACE_NAME("Waiting before draw");sync_wait(bufferFence.get(), -1);}}if (buffer == nullptr) {ALOGE("No output buffer provided. Aborting GPU composition.");return BAD_VALUE;}//dump layerschar pro_value[PROPERTY_VALUE_MAX];property_get("buffer.dump",pro_value,0);if(!strcmp(pro_value,"true")){ALOGD("dump_content_of_layers_to_file!");//dump_content_of_layers_to_file(mCurrentBuffer);//int layer_order = 0;for (auto const layer : layers) {if (layer->source.buffer.buffer != nullptr) {sp<GraphicBuffer> gBuf = layer->source.buffer.buffer;dump_content_of_layers_to_file(gBuf);//layer_order++;}}}...
}
当然上述仅仅是提供了一种思路,具体的上述源码逻辑用在什么地方,读者可以根据自己的需要自行调整。这里我们简单测试一下,看看生成的dump文件如下:
XXX:/data/dump # setprop buffer.dump true
XXX:/data/dump # ls
buffer_layer_0_frame_2880_2560_4.bin buffer_layer_3_frame_1920_56_4.bin buffer_layer_6_frame_1920_24_4.bin
buffer_layer_1_frame_1920_1080_4.bin buffer_layer_4_frame_2880_2560_4.bin buffer_layer_7_frame_1920_56_4.bin
buffer_layer_2_frame_1920_24_4.bin buffer_layer_5_frame_1920_1080_4.bin buffer_layer_8_frame_22_28_4.bin
2.2 dump Android GPU合成图层GraphicBuffer
这块我们可以在FramebufferSurface.cpp的如下方法中添加相关的逻辑,如下:
//frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cppvoid dump_content_of_layers_to_file(const sp<GraphicBuffer>& target)
{ALOGE("dump_content_of_layers_to_file");ALOGE("dump_content_of_layers_to_file");ALOGE("dump_content_of_layers_to_file");int result = -1;void *addr = NULL;static int DumpSurfaceCount = 0;int32_t bufStride;FILE * pfile = NULL;char layername[100] ;memset(layername,0,sizeof(layername));uint32_t w, s, h, f;w = target->getWidth();h = target->getHeight();s = target->getStride();f = target->getPixelFormat();android_dataspace d;uint32_t buffer_size = 0;d = HAL_DATASPACE_UNKNOWN;buffer_size = s * h * bytesPerPixel(f);bufStride = bytesPerPixel(f);ALOGE("FrameBufferSurface info w:%d h:%d s:%d f:%d d:%d size:%d", w, h, s, f, d, buffer_size);sprintf(layername,"/data/dump/hwc_layer_%d_frame_%d_%d_%d.bin",DumpSurfaceCount,w,h,bufStride);ALOGD("The dump file info : %s", layername);DumpSurfaceCount ++;pfile = fopen(layername,"w+");if(pfile){//获取FrameBufferSurface对应的ion地址result = target->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &addr);if(addr != NULL){ALOGE("The addr : %p", addr);int result = -1;system("mkdir /data/dump && chmod 777 /data/dump");result = fwrite( (const void *)addr,(size_t)( (buffer_size)),1,pfile);if(result >0){ALOGD("fwrite success!");}else{ALOGE("fwrite failed error %d", result);}}else{ALOGE("lock buffer error!");}fclose(pfile);target->unlock();usleep(1000 * 5);//延时5毫秒}
}#define HWC_DUMP_LAYER 1
status_t FramebufferSurface::nextBuffer(uint32_t& outSlot,sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence,Dataspace& outDataspace) {...#if HWC_DUMP_LAYERchar pro_value[PROPERTY_VALUE_MAX];property_get("hwc.dump",pro_value,0);if(!strcmp(pro_value,"true")){dump_content_of_layers_to_file(mCurrentBuffer);}#endifstatus_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);...
}
当然上述仅仅是提供了一种思路,具体的上述源码逻辑用在什么地方,读者可以根据自己的需要自行调整。这里我们简单测试一下,看看生成的dump文件如下:
XXX:/data/dump #setprop hwc.dump true
XXX:/data/dump # ls
hwc_layer_0_frame_1920_1080_4.bin hwc_layer_2_frame_1920_1080_4.bin hwc_layer_4_frame_1920_1080_4.bin hwc_layer_6_frame_1920_1080_4.bin
hwc_layer_1_frame_1920_1080_4.bin hwc_layer_3_frame_1920_1080_4.bin hwc_layer_5_frame_1920_1080_4.bin hwc_layer_7_frame_1920_1080_4.bin
2.3.dump渲染和合成图层GraphicBuffer指南
到这里dump的相关逻辑就告一段落了,细心的朋友也许会发现dump渲染和合成的图层代码逻辑基本完全一致。是的,这是因为无论是渲染的图层还是合成之后的图层它们都是通过GraphicBuffer来进行存储指向的。这里我们简单总结下dump SurfaceFlinger中各种图层GraphicBuffer内容的思路:
- 首先要获取各种图层所指向的GraphicBuffer
- 获取GraphicBuffer的存储地址(通常是通过内存映射过来的)
- 然后将获取到的地址中的内容,以一定的格式进行存储,通常是GRB888格式(也可以进行相关的封装)
这里重点补充一下,关于dump GraphicBuffer获取的信息大小,格式,以及存储计算规则是否正确可以通过dumpsys SurfaceFlinger进行查看,如下:
GraphicBufferAllocator buffers:
0xb400edc6f16e8500: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0#0
0xb400edc6f16e8780: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0#0
0xb400edc6f16e8a00: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar#0
0xb400edc6f16e8aa0: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0#0
0xb400edc6f16e8be0: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0#0
0xb400edc6f16e9040: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar#0
0xb400edc6f16e9400: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar#0
0xb400edc6f16e95e0: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar#0
0xb400edc6f16e9900: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher#0
0xb400edc6f16e9e00: 28800.00 KiB | 2880 (2880) x 2560 | 1 | 2 | 0xb00 | com.android.systemui.ImageWallpaper#0
0xb400edc6f16ea620: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher#0
0xb400edc6f16eaf80: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0x1a00 | FramebufferSurface
0xb400edc6f16eb660: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher#0
0xb400edc6f16eb8e0: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher#0
0xb400edc6f2126240: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0x1a00 | FramebufferSurface
0xb400edc777c22740: 0.12 KiB | 1 ( 32) x 1 | 1 | 1 | 0x300 | placeholder
Total allocated by GraphicBufferAllocator (estimate): 79800.12 KB
Imported gralloc buffers:
+ name:com.android.launcher3/com.android.launcher3.uioverrides.Quickst, id:849, size:8.1e+03KiB, w/h:1920x1080, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:com.android.launcher3/com.android.launcher3.uioverrides.Quickst, id:844, size:8.1e+03KiB, w/h:1920x1080, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:com.android.launcher3/com.android.launcher3.uioverrides.Quickst, id:840, size:8.1e+03KiB, w/h:1920x1080, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:com.android.launcher3/com.android.launcher3.uioverrides.Quickst, id:747, size:8.1e+03KiB, w/h:1920x1080, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:FramebufferSurface, id:443, size:8.1e+03KiB, w/h:1920x1080, usage: 0x1a00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:FramebufferSurface, id:418, size:8.1e+03KiB, w/h:1920x1080, usage: 0x1a00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:com.android.systemui.ImageWallpaper#0, id:332, size:2.9e+04KiB, w/h:2880x2560, usage: 0xb00, req fmt:263, fourcc/mod:875713112/0, compressed: falseplanes: R/G/B: w/h:2880x2560, stride:11520 bytes, size:29491200
+ name:StatusBar#0, id:321, size:1.9e+02KiB, w/h:1920x24, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:StatusBar#0, id:317, size:1.9e+02KiB, w/h:1920x24, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0#0, id:315, size:4.3e+02KiB, w/h:1920x56, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:StatusBar#0, id:290, size:1.9e+02KiB, w/h:1920x24, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0#0, id:273, size:4.3e+02KiB, w/h:1920x56, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:NavigationBar0#0, id:265, size:4.3e+02KiB, w/h:1920x56, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:StatusBar#0, id:204, size:1.9e+02KiB, w/h:1920x24, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0#0, id:175, size:4.3e+02KiB, w/h:1920x56, usage: 0xb00, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:placeholder, id:72, size:12KiB, w/h:1x1, usage: 0x300, req fmt:1, fourcc/mod:875708993/0, compressed: falseplanes: R/G/B/A: w/h:1x1, stride:128 bytes, size:128
Total imported by gralloc: 8e+04KiB
三.通过7yuv软件查看dump出来的GraphicBuffer
通过上面一顿猛如虎一样的操作,我们终于dump到了各个图层的GraphicBuffer图层,那么我们要怎么查看上面dump出来的内容,这里推荐Windows下面的7yuv软件查看,至于ubuntu下面的相关软件,这个我暂时没有找到合适的查看软件。这里我们通过7yuv软件打开上面dump出来的文件,如下:
这里需要重点注意的是,7yuv查看的时候的宽,高,Format格式的设置。这个是重点!上面的四个截图,就是Android合成的四个图层,应用图层,导航栏,状态栏,壁纸。
好了,到这里我想朋友们,都应该跃跃欲试自己尝试dump和查看了!
四.写在最后
至此Android dump渲染和合成图层GraphicBuffer阶段整个就完成了,读者是感到意犹未尽呢,还是想说一句尼玛,瞎扯淡呢!
好了,Android dump渲染和合成图层GraphicBuffer分析就告一段落了,各位青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!你们的鼓励和批评是博主前进路上最大的动力。
各位读友,千万不要喷我,因为我这也是第一次深入到Android显示这块的源码逻辑,为啥我深入到了这块,因为入职了一家原厂。所以我现在是菜鸟一杯,如果有对Android graphic刚兴趣的朋友,也可以联系我,一起学习进步!