??????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: 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的具体实现逻辑就不过多介绍,感兴趣的可以frameworks/base/cmds/screencap查看相关的源码逻辑!
1.2 Screenrecord dump一帧或多帧GPU合成图层GraphicBuffer
??Screenrecord的命令格式如下,它将当前当前Android显示的图层以GPU合成的模式,并通过多种格式保存下来。该命令的使用方法如下:
1|XXX:/
Usage: screenrecord [options] <filename>
Android screenrecord v1.3. Records the device's display to a .mp4 file.
Options:
--size WIDTHxHEIGHT
Set the video size, e.g. "1280x720". Default is the device's main
display resolution (if supported), 1280x720 if not. For best results,
use a size supported by the AVC encoder.
--bit-rate RATE
Set the video bit rate, in bits per second. Value may be specified as
bits or megabits, e.g. '4000000' is equivalent to '4M'. Default 20Mbps.
--bugreport
Add additional information, such as a timestamp overlay, that is helpful
in videos captured to illustrate bugs.
--time-limit TIME
Set the maximum recording time, in seconds. Default / maximum is 180.
--display-id ID
specify the physical display ID to record. Default is the primary display.
see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
--verbose
Display interesting information on stdout.
--help
Show this message.
Recording continues until Ctrl-C is hit or the time limit is reached.
1|XXX:/
//说明:录制屏幕,录制时间为10s,格式为裸BGR(FORMAT_FRAMES),不添加任何信息
1|XXX:/
//说明:录制屏幕,录制时间为10s,格式为裸BGR(FORMAT_FRAMES),只是每帧前面会加上用于描述帧信息的20字节头。
1|XXX:/
//说明:录制屏幕,录制时间为30s,格式为h264(FORMAT_H264)。
1|XXX:/
//说明:录制屏幕,录制时间为默认的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的如下方法中添加相关的逻辑,如下:
static void dump_content_of_layers_to_file(const sp<GraphicBuffer>& target)
{
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)
{
result = target->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &addr);
if(addr != NULL){
ALOGE("The addr : %p", addr);
int result = -1;
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) {
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;
}
char pro_value[PROPERTY_VALUE_MAX];
property_get("buffer.dump",pro_value,0);
if(!strcmp(pro_value,"true"))
{
ALOGD("dump_content_of_layers_to_file!");
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);
}
}
}
...
}
当然上述仅仅是提供了一种思路,具体的上述源码逻辑用在什么地方,读者可以根据自己的需要自行调整。这里我们简单测试一下,看看生成的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的如下方法中添加相关的逻辑,如下:
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("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)
{
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);
}
}
#define HWC_DUMP_LAYER 1
status_t FramebufferSurface::nextBuffer(uint32_t& outSlot,
sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence,
Dataspace& outDataspace) {
...
#if HWC_DUMP_LAYER
char pro_value[PROPERTY_VALUE_MAX];
property_get("hwc.dump",pro_value,0);
if(!strcmp(pro_value,"true"))
{
dump_content_of_layers_to_file(mCurrentBuffer);
}
#endif
status_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
0xb400edc6f16e8780: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0
0xb400edc6f16e8a00: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar
0xb400edc6f16e8aa0: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0
0xb400edc6f16e8be0: 420.00 KiB | 1920 (1920) x 56 | 1 | 1 | 0xb00 | NavigationBar0
0xb400edc6f16e9040: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar
0xb400edc6f16e9400: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar
0xb400edc6f16e95e0: 180.00 KiB | 1920 (1920) x 24 | 1 | 1 | 0xb00 | StatusBar
0xb400edc6f16e9900: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher
0xb400edc6f16e9e00: 28800.00 KiB | 2880 (2880) x 2560 | 1 | 2 | 0xb00 | com.android.systemui.ImageWallpaper
0xb400edc6f16ea620: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher
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
0xb400edc6f16eb8e0: 8100.00 KiB | 1920 (1920) x 1080 | 1 | 1 | 0xb00 | com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher
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: false
planes: 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: false
planes: 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: false
planes: 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: false
planes: 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: false
planes: 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: false
planes: R/G/B/A: w/h:1920x1080, stride:7680 bytes, size:8294400
+ name:com.android.systemui.ImageWallpaper
planes: R/G/B: w/h:2880x2560, stride:11520 bytes, size:29491200
+ name:StatusBar
planes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:StatusBar
planes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0
planes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:StatusBar
planes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0
planes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:NavigationBar0
planes: R/G/B/A: w/h:1920x56, stride:7680 bytes, size:430080
+ name:StatusBar
planes: R/G/B/A: w/h:1920x24, stride:7680 bytes, size:184320
+ name:NavigationBar0
planes: 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: false
planes: R/G/B/A: w/h:1x1, stride:128 bytes, size:128
Total imported by gralloc: 8e+04KiB
三.写在最后
??至此Android dump渲染和合成图层GraphicBuffer阶段整个就完成了,读者是感到意犹未尽呢,还是想说一句尼玛,瞎扯淡呢!
好了,Android dump渲染和合成图层GraphicBuffer分析就告一段落了,各位青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!你们的鼓励和批评是博主前进路上最大的动力。
各位读友,千万不要喷我,因为我这也是第一次深入到Android显示这块的源码逻辑,为啥我深入到了这块,因为入职了一家原厂。所以我现在是菜鸟一杯,如果有对Android graphic刚兴趣的朋友,也可以联系我,一起学习进步!
|