当前位置:   article > 正文

Android 12(S) 图形显示系统 - 示例应用(二)_native_window_api_connect

native_window_api_connect

1 前言

为了更深刻的理解Android图形系统抽象的概念和BufferQueue的工作机制,这篇文章我们将从Native Level入手,基于Android图形系统API写作一个简单的图形处理小程序。透过这个小程序我们将学习如何使用Native API创建Surface,如何请求图形缓冲区,如何向图形缓冲区中写入数据等知识。Talk is cheap, show me the code。让我们马上开始吧!

注:本系列文章的分析及代码均基于Android 12(S) Source Code,可参考:AOSPXRef  或 http://aosp.opersys.com/

2 程序源码简介

  • 源码下载

地址:https://github.com/yrzroger/NativeSFDemo

注:可以git命令下载(比如git clone git@github.com:yrzroger/NativeSFDemo.git)或直接Download ZIP解压后使用

  • 源码编译

本demo程序是基于Android源码编译环境开发的,所以需要放到Android源码目录下编译。

将上一步中下载的的源码放到Android源码的合适目录下,然后执行mm进行编译,得到可执行档 NativeSFDemo

  • 源码运行

将可执行档NativeSFDemo放到目标测试平台/system/bin下(比如:adb push NativeSFDemo /system/bin/)

然后执行 adb shell NativeSFDemo

  • 效果展示

程序中去绘制单色背景: 红色->绿色->蓝色背景交替展示,如下图所示:

至此你已经收获一个可以供后续学习研究的demo小程序了 !!!


Tips:

Android源码是一个宝藏,即提供了丰富多彩的APIs供开发者使用,又可以在其中搜索到很多有价值的APIs使用实例。本文中提供的演示Demo亦是基于源码中的参考来完成的。

我把参考位置列于此:

参考1:/frameworks/av/media/libstagefright/SurfaceUtils.cpp 

参考2:/frameworks/native/libs/gui/tests/CpuConsumer_test.cpp


3 程序源码分析

在显示子系统中,Surface 是一个接口,供生产者与消费者交换缓冲区。通过Surface我们就能向BufferQueue请求Buffer,并和Android Native窗口系统建立连接。本文展示的demo就是基于Surface建立起来的。

  • 封装类NativeSurfaceWrapper

NativeSurfaceWrapper是对Surface的一层封装,用于获取屏幕参数并创建与配置Surface属性。

首先看到头文件中该类的定义:

  1. class NativeSurfaceWrapper : public RefBase {
  2. public:
  3. NativeSurfaceWrapper(const String8& name);
  4. virtual ~NativeSurfaceWrapper() {}
  5. virtual void onFirstRef();
  6. // Retrieves a handle to the window.
  7. sp<ANativeWindow> getSurface() const;
  8. int width() { return mWidth; }
  9. int height() { return mHeight; }
  10. private:
  11. DISALLOW_COPY_AND_ASSIGN(NativeSurfaceWrapper);
  12. ui::Size limitSurfaceSize(int width, int height) const;
  13. sp<SurfaceControl> mSurfaceControl;
  14. int mWidth;
  15. int mHeight;
  16. String8 mName;
  17. };

NativeSurfaceWrapper继承自Refbase,这样就可以使用智能指针sp,wp来管理其对象,避免内存泄漏。

同时可以重写onFirstRef方法,在创建NativeSurfaceWrapper对象第一次被引用时调用onFirstRef做一些初始化操作。

下面是onFirstRef的定义:

  1. void NativeSurfaceWrapper::onFirstRef() {
  2. sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
  3. status_t err = surfaceComposerClient->initCheck();
  4. if (err != NO_ERROR) {
  5. ALOGD("SurfaceComposerClient::initCheck error: %#x\n", err);
  6. return;
  7. }
  8. // Get main display parameters.
  9. sp<IBinder> displayToken = SurfaceComposerClient::getInternalDisplayToken();
  10. if (displayToken == nullptr)
  11. return;
  12. ui::DisplayMode displayMode;
  13. const status_t error =
  14. SurfaceComposerClient::getActiveDisplayMode(displayToken, &displayMode);
  15. if (error != NO_ERROR)
  16. return;
  17. ui::Size resolution = displayMode.resolution;
  18. resolution = limitSurfaceSize(resolution.width, resolution.height);
  19. // create the native surface
  20. sp<SurfaceControl> surfaceControl = surfaceComposerClient->createSurface(mName, resolution.getWidth(),
  21. resolution.getHeight(), PIXEL_FORMAT_RGBA_8888,
  22. ISurfaceComposerClient::eFXSurfaceBufferState,
  23. /*parent*/ nullptr);
  24. SurfaceComposerClient::Transaction{}
  25. .setLayer(surfaceControl, std::numeric_limits<int32_t>::max())
  26. .show(surfaceControl)
  27. .apply();
  28. mSurfaceControl = surfaceControl;
  29. mWidth = resolution.getWidth();
  30. mHeight = resolution.getHeight();
  31. }

onFirstRef中完成主要工作:

1. 创建一个SurfaceComposerClient对象,这是SurfaceFlinger的Client端,它将建立和SurfaceFlinger服务的通信;

2. 获取屏幕参数,SurfaceComposerClient::getActiveDisplayMode获取当前的DisplayMode,其中可以得到resolution信息;

3. 创建Surface & SurfaceControl,createSurface方法会通过Binder通信机制一直呼叫到SurfaceFlinger,SurfaceFlinger会进行创建Layer等操作;

4. createSurface时会设置width,height,format等信息;

5. setLayer,设置窗口的z-order,SurfaceFlinger根据z-Oder决定窗口的可见性及可见大小;

6. show,让当前窗口可见;

7. apply,使透过Transaction进行的设置生效,属性信息传给SurfaceFlinger;


Tips:

创建Surface的过程会涉及到与SurfaceFlinger的互动,SurfaceFlinger是一个系统级的服务,负责创建/管理/合成Surface对应的Layer,这部分我们本文暂不展开,之后文章中会陆续讲解。


limitSurfaceSize方法

该方法的作用是将width和height限制在设备GPU支持的范围内。

  1. ui::Size NativeSurfaceWrapper::limitSurfaceSize(int width, int height) const {
  2. ui::Size limited(width, height);
  3. bool wasLimited = false;
  4. const float aspectRatio = float(width) / float(height);
  5. int maxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
  6. int maxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
  7. if (maxWidth != 0 && width > maxWidth) {
  8. limited.height = maxWidth / aspectRatio;
  9. limited.width = maxWidth;
  10. wasLimited = true;
  11. }
  12. if (maxHeight != 0 && limited.height > maxHeight) {
  13. limited.height = maxHeight;
  14. limited.width = maxHeight * aspectRatio;
  15. wasLimited = true;
  16. }
  17. SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]",
  18. limited.width, limited.height, width, height);
  19. return limited;
  20. }

该方法会将屏幕的width/height和max_graphics_width/max_graphics_height进行比较,取较小者作为创建Surface的参数。

这一点也是Android 12引入的一个新特性。getActiveDisplayMode获取到的是屏幕的真实分辨率(real display resolution),但GPU可能不支持高分辨率的UI合成,所以必须对framebuffer size做出限制。

比如设备可以4K分辨率进行video的解码和渲染,但由于硬件限制application UI只能以1080P进行合成。

  • NativeSFDemo的main方法

main方法比较简单

1. signal函数注册监听SIGINT信号的handler,也就是保证Ctrl+C退出程序的完整性;

2. 创建NativeSurfaceWrapper对象,并调用drawNativeSurface进行图片的绘制;

  1. int main() {
  2. signal(SIGINT, sighandler);
  3. sp<NativeSurfaceWrapper> nativeSurface(new NativeSurfaceWrapper(String8("NativeSFDemo")));
  4. drawNativeSurface(nativeSurface);
  5. return 0;
  6. }

按下Ctrl+C退出程序时,呼叫到sighandler将mQuit这个标志设为true,这样会使图片的while循环就可以正常流程退出了

  1. void sighandler(int num) {
  2. if(num == SIGINT) {
  3. printf("\nSIGINT: Force to stop !\n");
  4. mQuit = true;
  5. }
  6. }

  • drawNativeSurface方法

绘制图片的核心逻辑都在这个方法中,我们先看一下代码:

  1. int drawNativeSurface(sp<NativeSurfaceWrapper> nativeSurface) {
  2. status_t err = NO_ERROR;
  3. int countFrame = 0;
  4. ANativeWindowBuffer *nativeBuffer = nullptr;
  5. ANativeWindow* nativeWindow = nativeSurface->getSurface().get();
  6. // 1. connect the ANativeWindow as a CPU client. Buffers will be queued after being filled using the CPU
  7. err = native_window_api_connect(nativeWindow, NATIVE_WINDOW_API_CPU);
  8. if (err != NO_ERROR) {
  9. ALOGE("ERROR: unable to native_window_api_connect\n");
  10. return EXIT_FAILURE;
  11. }
  12. // 2. set the ANativeWindow dimensions
  13. err = native_window_set_buffers_user_dimensions(nativeWindow, nativeSurface->width(), nativeSurface->height());
  14. if (err != NO_ERROR) {
  15. ALOGE("ERROR: unable to native_window_set_buffers_user_dimensions\n");
  16. return EXIT_FAILURE;
  17. }
  18. // 3. set the ANativeWindow format
  19. err = native_window_set_buffers_format(nativeWindow, PIXEL_FORMAT_RGBX_8888);
  20. if (err != NO_ERROR) {
  21. ALOGE("ERROR: unable to native_window_set_buffers_format\n");
  22. return EXIT_FAILURE;
  23. }
  24. // 4. set the ANativeWindow usage
  25. err = native_window_set_usage(nativeWindow, GRALLOC_USAGE_SW_WRITE_OFTEN);
  26. if (err != NO_ERROR) {
  27. ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err);
  28. return err;
  29. }
  30. // 5. set the ANativeWindow scale mode
  31. err = native_window_set_scaling_mode(nativeWindow, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
  32. if (err != NO_ERROR) {
  33. ALOGE("native_window_set_scaling_mode failed: %s (%d)", strerror(-err), -err);
  34. return err;
  35. }
  36. // 6. set the ANativeWindow permission to allocte new buffer, default is true
  37. static_cast<Surface*>(nativeWindow)->getIGraphicBufferProducer()->allowAllocation(true);
  38. // 7. set the ANativeWindow buffer count
  39. int numBufs = 0;
  40. int minUndequeuedBufs = 0;
  41. err = nativeWindow->query(nativeWindow,
  42. NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBufs);
  43. if (err != NO_ERROR) {
  44. ALOGE("error: MIN_UNDEQUEUED_BUFFERS query "
  45. "failed: %s (%d)", strerror(-err), -err);
  46. goto handle_error;
  47. }
  48. numBufs = minUndequeuedBufs + 1;
  49. err = native_window_set_buffer_count(nativeWindow, numBufs);
  50. if (err != NO_ERROR) {
  51. ALOGE("error: set_buffer_count failed: %s (%d)", strerror(-err), -err);
  52. goto handle_error;
  53. }
  54. // 8. draw the ANativeWindow
  55. while(!mQuit) {
  56. // 9. dequeue a buffer
  57. int hwcFD = -1;
  58. err = nativeWindow->dequeueBuffer(nativeWindow, &nativeBuffer, &hwcFD);
  59. if (err != NO_ERROR) {
  60. ALOGE("error: dequeueBuffer failed: %s (%d)",
  61. strerror(-err), -err);
  62. break;
  63. }
  64. // 10. make sure really control the dequeued buffer
  65. sp<Fence> hwcFence(new Fence(hwcFD));
  66. int waitResult = hwcFence->waitForever("dequeueBuffer_EmptyNative");
  67. if (waitResult != OK) {
  68. ALOGE("dequeueBuffer_EmptyNative: Fence::wait returned an error: %d", waitResult);
  69. break;
  70. }
  71. sp<GraphicBuffer> buf(GraphicBuffer::from(nativeBuffer));
  72. // 11. Fill the buffer with black
  73. uint8_t* img = nullptr;
  74. err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
  75. if (err != NO_ERROR) {
  76. ALOGE("error: lock failed: %s (%d)", strerror(-err), -err);
  77. break;
  78. }
  79. //12. Draw the window
  80. countFrame = (countFrame+1)%3;
  81. fillRGBA8Buffer(img, nativeSurface->width(), nativeSurface->height(), buf->getStride(),
  82. countFrame == 0 ? 255 : 0,
  83. countFrame == 1 ? 255 : 0,
  84. countFrame == 2 ? 255 : 0);
  85. err = buf->unlock();
  86. if (err != NO_ERROR) {
  87. ALOGE("error: unlock failed: %s (%d)", strerror(-err), -err);
  88. break;
  89. }
  90. // 13. queue the buffer to display
  91. int gpuFD = -1;
  92. err = nativeWindow->queueBuffer(nativeWindow, buf->getNativeBuffer(), gpuFD);
  93. if (err != NO_ERROR) {
  94. ALOGE("error: queueBuffer failed: %s (%d)", strerror(-err), -err);
  95. break;
  96. }
  97. nativeBuffer = nullptr;
  98. sleep(1);
  99. }
  100. handle_error:
  101. // 14. cancel buffer
  102. if (nativeBuffer != nullptr) {
  103. nativeWindow->cancelBuffer(nativeWindow, nativeBuffer, -1);
  104. nativeBuffer = nullptr;
  105. }
  106. // 15. Clean up after success or error.
  107. err = native_window_api_disconnect(nativeWindow, NATIVE_WINDOW_API_CPU);
  108. if (err != NO_ERROR) {
  109. ALOGE("error: api_disconnect failed: %s (%d)", strerror(-err), -err);
  110. }
  111. return err;
  112. }

处理的大概过程

1. 获取我们已经创建Surface的窗口ANativeWindow,作为CPU客户端来连接ANativeWindow,CPU填充buffer数据后入队列进行后续处理;

2. 设置Buffer的大小尺寸native_window_set_buffers_user_dimensions;

3. 设置Buffer格式,可选,之前创建Layer的时候已经设置了;

4. 设置Buffer的usage,可能涉及protected的内容,这里我们简单设为GRALLOC_USAGE_SW_WRITE_OFTEN;

5. 设置scale模式,如果上层给的数据,比如Video,超出Buffer的大小后,怎么处理,是截取一部分还是,缩小;

6. 设置permission允许分配新buffer,默认true;

7. 设置Buffer数量,即BufferQueue中有多少个buffer可以用;

8. 下面的流程就是请求buffer并进行绘制图像的过程

9. dequeueBuffer先请求一块可用的Buffer,也就是FREE的Buffer;

10. Buffer虽然是Free的,但是在异步模式下,Buffer可能还在使用中,需要等到Fence才能确保buffer没有在被使用;

11. lock方法可以获取这块GraphicBuffer的数据地址;

12. 绘制图像,即把图像颜色数据写入Buffer里面,我们这里使用fillRGBA8Buffer来填充纯色图片;

13. 将绘制好的Buffer,queue到Buffer队列中,入队列后的buffer就可以被消费者处理或显示了;

14. 错误处理,取消掉Buffer,cancelBuffer;

15. 断开BufferQueue和窗口的连接,native_window_api_disconnect。

  • fillRGBA8Buffer
  1. void fillRGBA8Buffer(uint8_t* img, int width, int height, int stride, int r, int g, int b) {
  2. for (int y = 0; y < height; y++) {
  3. for (int x = 0; x < width; x++) {
  4. uint8_t* pixel = img + (4 * (y*stride + x));
  5. pixel[0] = r;
  6. pixel[1] = g;
  7. pixel[2] = b;
  8. pixel[3] = 0;
  9. }
  10. }
  11. }

fillRGBA8Buffer用指定的RGBA填充buffer数据,我们设置的颜色格式为PIXEL_FORMAT_RGBX_8888,所以每个像素点均由4个字节组成,前3个字节分别为R/G/B颜色分量。


我们可以通过执行 dumpsys SurfaceFlinger 来查看图层的信息

  1. Display 4629995328241972480 HWC layers:
  2. ---------------------------------------------------------------------------------------------------------------------------------------------------------------
  3. Layer name
  4. Z | Window Type | Comp Type | Transform | Disp Frame (LTRB) | Source Crop (LTRB) | Frame Rate (Explicit) (Seamlessness) [Focused]
  5. ---------------------------------------------------------------------------------------------------------------------------------------------------------------
  6. bbq-wrapper#0
  7. rel 0 | 0 | CLIENT | 0 | 0 0 1920 1080 | 0.0 0.0 1920.0 1080.0 | [ ]
  8. ---------------------------------------------------------------------------------------------------------------------------------------------------------------

看到了没,一个名字为“bbq-wrapper#0”的Layer显示在最上层,也就是我们应用显示的图层,看到这里你一定有个疑问,我们设置的Surface Name不是“NativeSFDemo”吗 ?

dumpsys SurfaceFlinger信息中,我们还可以看到如下内容:

  1. + BufferStateLayer (NativeSFDemo#0) uid=0
  2. Region TransparentRegion (this=0 count=0)
  3. Region VisibleRegion (this=0 count=0)
  4. Region SurfaceDamageRegion (this=0 count=0)
  5. layerStack= 0, z=2147483647, pos=(0,0), size=( -1, -1), crop=[ 0, 0, -1, -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=0, dataspace=Default, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000000, tr=[0.00, 0.00][0.00, 0.00]
  6. parent=none
  7. zOrderRelativeOf=none
  8. activeBuffer=[ 0x 0: 0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00], shadowRadius=0.000,
  9. + BufferStateLayer (bbq-wrapper#0) uid=0
  10. Region TransparentRegion (this=0 count=0)
  11. Region VisibleRegion (this=0 count=1)
  12. [ 0, 0, 1920, 1080]
  13. Region SurfaceDamageRegion (this=0 count=0)
  14. layerStack= 0, z= 0, pos=(0,0), size=(1920,1080), crop=[ 0, 0, -1, -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=1, invalidate=0, dataspace=Default, defaultPixelFormat=RGBx_8888, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000100, tr=[0.00, 0.00][0.00, 0.00]
  15. parent=NativeSFDemo#0
  16. zOrderRelativeOf=none
  17. activeBuffer=[1920x1080:1920,RGBx_8888], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={dequeueTime:700243748286}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00], shadowRadius=0.000,

两个BufferStateLayer:BufferStateLayer (NativeSFDemo#0)  和  BufferStateLayer (bbq-wrapper#0),其中bbq-wrapper#0的parent就是NativeSFDemo#0,这其中的关系我们之后的文章中会陆续分析。

4 小结

至此,我们已经建立起来了一个简单的图形图像处理的简单Demo,当让我们目前还是只从应用的较多介绍了基本图形APIs的使用逻辑,接下来的我们就基于此demo,深入底层逻辑探究其中的奥秘。



必读:

Android 12(S) 图形显示系统 - 开篇


作者:二的次方

出处:Android 12(S) 图形显示系统 - 示例应用(二) - 二的次方 - 博客园

本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/97160
推荐阅读
相关标签
  

闽ICP备14008679号