引言
博客停更很久了,提起笔来渐感生疏啊!看来,还是得抽出时间来更新更新啊!好了,感慨也发完了,是时候切入正题了。本篇博客将主要详细介绍如何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.
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]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:/ #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& 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 & 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 layers 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!"); //dump_content_of_layers_to_file(mCurrentBuffer); //int layer_order = 0; for (auto const layer : layers) { if (layer->source.buffer.buffer != nullptr) { sp 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.cpp void dump_content_of_layers_to_file(const sp& 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 & outBuffer, sp & 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
三.Android dump渲染和合成图层GraphicBuffer指南阶段总结
至此Android dump渲染和合成图层GraphicBuffer阶段就完成了,读者是感到意犹未尽呢,还是想说一句尼玛,瞎扯淡呢!
好了,Android dump渲染和合成图层GraphicBuffer分析就告一段落了,各位青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!你们的鼓励和批评是博主前进路上最大的动力。
各位读友,千万不要喷我,因为我这也是第一次深入到Android显示这块的源码逻辑,为啥我深入到了这块,因为入职了一家原厂。所以我现在是菜鸟一杯,如果有对Android graphic刚兴趣的朋友,也可以联系我,一起学习进步!



