赞
踩
为了更深刻的理解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/
地址: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
在显示子系统中,Surface 是一个接口,供生产者与消费者交换缓冲区。通过Surface我们就能向BufferQueue请求Buffer,并和Android Native窗口系统建立连接。本文展示的demo就是基于Surface建立起来的。
NativeSurfaceWrapper是对Surface的一层封装,用于获取屏幕参数并创建与配置Surface属性。
首先看到头文件中该类的定义:
- class NativeSurfaceWrapper : public RefBase {
- public:
- NativeSurfaceWrapper(const String8& name);
- virtual ~NativeSurfaceWrapper() {}
-
- virtual void onFirstRef();
-
- // Retrieves a handle to the window.
- sp<ANativeWindow> getSurface() const;
-
- int width() { return mWidth; }
- int height() { return mHeight; }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(NativeSurfaceWrapper);
- ui::Size limitSurfaceSize(int width, int height) const;
-
- sp<SurfaceControl> mSurfaceControl;
- int mWidth;
- int mHeight;
- String8 mName;
- };
NativeSurfaceWrapper继承自Refbase,这样就可以使用智能指针sp,wp来管理其对象,避免内存泄漏。
同时可以重写onFirstRef方法,在创建NativeSurfaceWrapper对象第一次被引用时调用onFirstRef做一些初始化操作。
下面是onFirstRef的定义:
- void NativeSurfaceWrapper::onFirstRef() {
- sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
- status_t err = surfaceComposerClient->initCheck();
- if (err != NO_ERROR) {
- ALOGD("SurfaceComposerClient::initCheck error: %#x\n", err);
- return;
- }
-
- // Get main display parameters.
- sp<IBinder> displayToken = SurfaceComposerClient::getInternalDisplayToken();
- if (displayToken == nullptr)
- return;
-
- ui::DisplayMode displayMode;
- const status_t error =
- SurfaceComposerClient::getActiveDisplayMode(displayToken, &displayMode);
- if (error != NO_ERROR)
- return;
-
- ui::Size resolution = displayMode.resolution;
- resolution = limitSurfaceSize(resolution.width, resolution.height);
- // create the native surface
- sp<SurfaceControl> surfaceControl = surfaceComposerClient->createSurface(mName, resolution.getWidth(),
- resolution.getHeight(), PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
-
- SurfaceComposerClient::Transaction{}
- .setLayer(surfaceControl, std::numeric_limits<int32_t>::max())
- .show(surfaceControl)
- .apply();
-
- mSurfaceControl = surfaceControl;
- mWidth = resolution.getWidth();
- mHeight = resolution.getHeight();
- }
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支持的范围内。
- ui::Size NativeSurfaceWrapper::limitSurfaceSize(int width, int height) const {
- ui::Size limited(width, height);
- bool wasLimited = false;
- const float aspectRatio = float(width) / float(height);
-
- int maxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
- int maxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
-
- if (maxWidth != 0 && width > maxWidth) {
- limited.height = maxWidth / aspectRatio;
- limited.width = maxWidth;
- wasLimited = true;
- }
- if (maxHeight != 0 && limited.height > maxHeight) {
- limited.height = maxHeight;
- limited.width = maxHeight * aspectRatio;
- wasLimited = true;
- }
- SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]",
- limited.width, limited.height, width, height);
- return limited;
- }
该方法会将屏幕的width/height和max_graphics_width/max_graphics_height进行比较,取较小者作为创建Surface的参数。
这一点也是Android 12引入的一个新特性。getActiveDisplayMode获取到的是屏幕的真实分辨率(real display resolution),但GPU可能不支持高分辨率的UI合成,所以必须对framebuffer size做出限制。
比如设备可以4K分辨率进行video的解码和渲染,但由于硬件限制application UI只能以1080P进行合成。
main方法比较简单
1. signal函数注册监听SIGINT信号的handler,也就是保证Ctrl+C退出程序的完整性;
2. 创建NativeSurfaceWrapper对象,并调用drawNativeSurface进行图片的绘制;
- int main() {
- signal(SIGINT, sighandler);
-
- sp<NativeSurfaceWrapper> nativeSurface(new NativeSurfaceWrapper(String8("NativeSFDemo")));
- drawNativeSurface(nativeSurface);
- return 0;
- }
按下Ctrl+C退出程序时,呼叫到sighandler将mQuit这个标志设为true,这样会使图片的while循环就可以正常流程退出了
- void sighandler(int num) {
- if(num == SIGINT) {
- printf("\nSIGINT: Force to stop !\n");
- mQuit = true;
- }
- }
绘制图片的核心逻辑都在这个方法中,我们先看一下代码:
- int drawNativeSurface(sp<NativeSurfaceWrapper> nativeSurface) {
- status_t err = NO_ERROR;
- int countFrame = 0;
- ANativeWindowBuffer *nativeBuffer = nullptr;
- ANativeWindow* nativeWindow = nativeSurface->getSurface().get();
-
- // 1. connect the ANativeWindow as a CPU client. Buffers will be queued after being filled using the CPU
- err = native_window_api_connect(nativeWindow, NATIVE_WINDOW_API_CPU);
- if (err != NO_ERROR) {
- ALOGE("ERROR: unable to native_window_api_connect\n");
- return EXIT_FAILURE;
- }
-
- // 2. set the ANativeWindow dimensions
- err = native_window_set_buffers_user_dimensions(nativeWindow, nativeSurface->width(), nativeSurface->height());
- if (err != NO_ERROR) {
- ALOGE("ERROR: unable to native_window_set_buffers_user_dimensions\n");
- return EXIT_FAILURE;
- }
-
- // 3. set the ANativeWindow format
- err = native_window_set_buffers_format(nativeWindow, PIXEL_FORMAT_RGBX_8888);
- if (err != NO_ERROR) {
- ALOGE("ERROR: unable to native_window_set_buffers_format\n");
- return EXIT_FAILURE;
- }
-
- // 4. set the ANativeWindow usage
- err = native_window_set_usage(nativeWindow, GRALLOC_USAGE_SW_WRITE_OFTEN);
- if (err != NO_ERROR) {
- ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err);
- return err;
- }
-
- // 5. set the ANativeWindow scale mode
- err = native_window_set_scaling_mode(nativeWindow, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
- if (err != NO_ERROR) {
- ALOGE("native_window_set_scaling_mode failed: %s (%d)", strerror(-err), -err);
- return err;
- }
-
- // 6. set the ANativeWindow permission to allocte new buffer, default is true
- static_cast<Surface*>(nativeWindow)->getIGraphicBufferProducer()->allowAllocation(true);
-
- // 7. set the ANativeWindow buffer count
- int numBufs = 0;
- int minUndequeuedBufs = 0;
-
- err = nativeWindow->query(nativeWindow,
- NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBufs);
- if (err != NO_ERROR) {
- ALOGE("error: MIN_UNDEQUEUED_BUFFERS query "
- "failed: %s (%d)", strerror(-err), -err);
- goto handle_error;
- }
-
- numBufs = minUndequeuedBufs + 1;
- err = native_window_set_buffer_count(nativeWindow, numBufs);
- if (err != NO_ERROR) {
- ALOGE("error: set_buffer_count failed: %s (%d)", strerror(-err), -err);
- goto handle_error;
- }
-
- // 8. draw the ANativeWindow
- while(!mQuit) {
- // 9. dequeue a buffer
- int hwcFD = -1;
- err = nativeWindow->dequeueBuffer(nativeWindow, &nativeBuffer, &hwcFD);
- if (err != NO_ERROR) {
- ALOGE("error: dequeueBuffer failed: %s (%d)",
- strerror(-err), -err);
- break;
- }
-
- // 10. make sure really control the dequeued buffer
- sp<Fence> hwcFence(new Fence(hwcFD));
- int waitResult = hwcFence->waitForever("dequeueBuffer_EmptyNative");
- if (waitResult != OK) {
- ALOGE("dequeueBuffer_EmptyNative: Fence::wait returned an error: %d", waitResult);
- break;
- }
-
- sp<GraphicBuffer> buf(GraphicBuffer::from(nativeBuffer));
-
- // 11. Fill the buffer with black
- uint8_t* img = nullptr;
- err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- if (err != NO_ERROR) {
- ALOGE("error: lock failed: %s (%d)", strerror(-err), -err);
- break;
- }
-
- //12. Draw the window
- countFrame = (countFrame+1)%3;
- fillRGBA8Buffer(img, nativeSurface->width(), nativeSurface->height(), buf->getStride(),
- countFrame == 0 ? 255 : 0,
- countFrame == 1 ? 255 : 0,
- countFrame == 2 ? 255 : 0);
-
- err = buf->unlock();
- if (err != NO_ERROR) {
- ALOGE("error: unlock failed: %s (%d)", strerror(-err), -err);
- break;
- }
-
- // 13. queue the buffer to display
- int gpuFD = -1;
- err = nativeWindow->queueBuffer(nativeWindow, buf->getNativeBuffer(), gpuFD);
- if (err != NO_ERROR) {
- ALOGE("error: queueBuffer failed: %s (%d)", strerror(-err), -err);
- break;
- }
-
- nativeBuffer = nullptr;
- sleep(1);
- }
-
- handle_error:
- // 14. cancel buffer
- if (nativeBuffer != nullptr) {
- nativeWindow->cancelBuffer(nativeWindow, nativeBuffer, -1);
- nativeBuffer = nullptr;
- }
-
- // 15. Clean up after success or error.
- err = native_window_api_disconnect(nativeWindow, NATIVE_WINDOW_API_CPU);
- if (err != NO_ERROR) {
- ALOGE("error: api_disconnect failed: %s (%d)", strerror(-err), -err);
- }
-
- return err;
- }
处理的大概过程:
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。
- void fillRGBA8Buffer(uint8_t* img, int width, int height, int stride, int r, int g, int b) {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- uint8_t* pixel = img + (4 * (y*stride + x));
- pixel[0] = r;
- pixel[1] = g;
- pixel[2] = b;
- pixel[3] = 0;
- }
- }
- }
fillRGBA8Buffer用指定的RGBA填充buffer数据,我们设置的颜色格式为PIXEL_FORMAT_RGBX_8888,所以每个像素点均由4个字节组成,前3个字节分别为R/G/B颜色分量。
我们可以通过执行 dumpsys SurfaceFlinger 来查看图层的信息:
- Display 4629995328241972480 HWC layers:
- ---------------------------------------------------------------------------------------------------------------------------------------------------------------
- Layer name
- Z | Window Type | Comp Type | Transform | Disp Frame (LTRB) | Source Crop (LTRB) | Frame Rate (Explicit) (Seamlessness) [Focused]
- ---------------------------------------------------------------------------------------------------------------------------------------------------------------
- bbq-wrapper#0
- rel 0 | 0 | CLIENT | 0 | 0 0 1920 1080 | 0.0 0.0 1920.0 1080.0 | [ ]
- ---------------------------------------------------------------------------------------------------------------------------------------------------------------
-
看到了没,一个名字为“bbq-wrapper#0”的Layer显示在最上层,也就是我们应用显示的图层,看到这里你一定有个疑问,我们设置的Surface Name不是“NativeSFDemo”吗 ?
dumpsys SurfaceFlinger信息中,我们还可以看到如下内容:
- + BufferStateLayer (NativeSFDemo#0) uid=0
- Region TransparentRegion (this=0 count=0)
- Region VisibleRegion (this=0 count=0)
- Region SurfaceDamageRegion (this=0 count=0)
- 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]
- parent=none
- zOrderRelativeOf=none
- 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,
- + BufferStateLayer (bbq-wrapper#0) uid=0
- Region TransparentRegion (this=0 count=0)
- Region VisibleRegion (this=0 count=1)
- [ 0, 0, 1920, 1080]
- Region SurfaceDamageRegion (this=0 count=0)
- 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]
- parent=NativeSFDemo#0
- zOrderRelativeOf=none
- 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,这其中的关系我们之后的文章中会陆续分析。
至此,我们已经建立起来了一个简单的图形图像处理的简单Demo,当让我们目前还是只从应用的较多介绍了基本图形APIs的使用逻辑,接下来的我们就基于此demo,深入底层逻辑探究其中的奥秘。
必读:
作者:二的次方
出处:Android 12(S) 图形显示系统 - 示例应用(二) - 二的次方 - 博客园
本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。