【Android】从SurfaceFlinger中获取各layer图片(2)

本文探讨了在Android系统中,通过SurfaceFlinger获取各Layer图像数据的过程与挑战。介绍了尝试通过共享内存fd访问图像数据的初步想法,讨论了遇到的权限、映射和访问问题,并分享了成功通过GraphicBuffer对象传送实现目标的经验。
部署运行你感兴趣的模型镜像

在前面的【Android】从SurfaceFlinger中获取各layer图片(1)里面,提到通过获取surfaceFlinger中的GraphicBuffer结构中

的共享内存的fd来达到访问图像数据的内存地址,进而实现访问对应的图像数据,

理论上是可行的,但是实际处理会有问题,最终需要通过传送GraphicBuffer对象来达到这个目的。

这里记录下探索过程

 

LINUX/android/frameworks/native$ find . -name BufferQueueCore.cpp

./libs/gui/BufferQueueCore.cpp

 

在里面传送fd

 

Unknown:/data/local/tmp # ./screenget

add myService

onTransact called, case 4

Segmentation fault

 

使用char* base = new char[w*h*4];代替mmap操作,

可以保存图像,没有段错误,说明保存图像的方法没有问题

 

 

                        void* mappedAddress= mmap(0, w*h*4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

                        if (mappedAddress == MAP_FAILED) {

                       printf("Could not mmap %s \n\n", strerror(errno));

                       //return -errno;

                           }

 

 

Unknown:/data/local/tmp # ./screenget

add myService

onTransact called, case 4

fd=6, w=720, h=1280, format=1

1

Could not mmap Permission denied

 

====== base not null

 

 

 

可能是由于gralloc模块的内存不允许直接访问

 

改成read方法也报错,

if (read(fd, mappedAddress, w*h*4) == -1)

       {printf("Could not read, %s \n\n", strerror(errno));}

 

Could not read, Invalid argument

 

 

59struct private_handle_t : public native_handle {

60#else

61struct private_handle_t {

62    struct native_handle nativeHandle;

63#endif

64

65    enum {

66        PRIV_FLAGS_FRAMEBUFFER = 0x00000001

67    };

68

69    // file-descriptors

70    int     fd;

71    // ints

72    int     magic;

73    int     flags;

74    int     size;

75    int     offset;

76

77    // FIXME: the attributes below should be out-of-line

78    uint64_t base __attribute__((aligned(8)));

 

尝试去获取base指针,报错

         //error: cast from 'const int *' to 'uint64_t *' (aka 'unsigned long *')

         // increases required alignment from 4 to 8 [-Werror,-Wcast-align]

 

         //const int* pint = &(buffer->handle->data[5]);

         //uint64_t* ptr = (uint64_t*)pint;

         //void* vaddr = (void*)(*ptr);

  

 

 

 

Memcpy报错

 

01-01 13:24:54.387 15611 15611 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x100000500

01-01 13:24:54.388 15611 15611 F DEBUG   :     x0   000000748617f000  x1   0000000100000500  x2   0000000000384000  x3   000000748617f000

01-01 13:24:54.388 15611 15611 F DEBUG   :     x4   0000000100384500  x5   0000007486503000  x6   000000000000000a  x7   000000000000000a

01-01 13:24:54.388 15611 15611 F DEBUG   :     x8   000000748bb7266c  x9   0000000000000000  x10  0000000000000001  x11  0000000000000000

 

01-01 13:24:54.439 15611 15611 F DEBUG   : backtrace:

01-01 13:24:54.440 15611 15611 F DEBUG   :     #00 pc 000000000001c418  /system/lib64/libc.so (memcpy+232)

01-01 13:24:54.441 15611 15611 F DEBUG   :     #01 pc 000000000005ce1c  /system/lib64/libgui.so (android::BufferQueueCore::dumpState(android::String8 const&, android::String8*) const+1112)

01-01 13:24:54.441 15611 15611 F DEBUG   :     #02 pc 000000000005b1d8  /system/lib64/libgui.so (android::BufferQueueConsumer::dumpState(android::String8 const&, android::String8*) const+172)

01-01 13:24:54.443 15611 15611 F DEBUG   :     #03 pc 0000000000065f64  /system/lib64/libgui.so (android::ConsumerBase::dumpLocked(android::String8&, char const*) const+120)

01-01 13:24:54.443 15611 15611 F DEBUG   :     #04 pc 0000000000065ed4  /system/lib64/libgui.so (android::ConsumerBase::dumpState(android::String8&, char const*) const+60)

 

 

这个base指针的内容有问题?

 

 

查看log

==== the fd = 101, handle numFds=3, size=0, offset=720

 

为什么fd的数量是3,

看来有必要把fd都打印出来,

         outResult->appendFormat("==== the fd = %d, handle numFds=%d, data[1]=%d,data[2]=%d size=%d, offset=%d\n\n",

         buffer->handle->data[0], buffer->handle->numFds,

         buffer->handle->data[1], buffer->handle->data[2],

         buffer->handle->data[3], buffer->handle->data[4]);

 

 

打印结果

==== the fd = 87, handle numFds=3, data[1]=89,data[2]=90 size=0, offset=320

 

查看surfaceflinger进程的fd

Unknown:/ $ ps -ef|grep sur

system         479     1 9 11:58:19 ?     00:00:28 surfaceflinger

shell         6460  6456 7 12:03:29 pts/13 00:00:00 grep sur

 

adb shell ls -l /proc/479/fd > fd_sf.txt

 

 

 

87是anon_inode:gralloc_extra

90是/dev/ashem

难道data[2]才是需要传递的共享内存fd?

 

接下来测试,

writePNG called

Bus error

 

 

传送fd的方式报错,

Unknown:/data/local/tmp # ./screenget

add myService

onTransact called, case 4

onTransact called, case 4

fd=6, w=320, h=700, format=1

1

====== base not null

====== base[2]=

==base[0]=255, base[1]=255, base[2]=255, base[3]=255, base[4]=255

 

i=0

i=1

i=2

Bus error

 

 

 

这个fd的映射空间不能访问

直接在surfaceflinger进程中看看相应的内存是否可以访问

 

导入private_handle_t,可以直接获取到数据

const private_handle_t* pt = (private_handle_t*)buffer->handle;

              uint64_t value=0;

              memcpy(&value, &(buffer->handle->data[5]), sizeof(uint64_t));

                                          void* vaddr = (void*)value;

                                          void* vaddr2 = (void*)pt->base;

              outResult->appendFormat("==== vaddr=%p, vaddr2=%p ", vaddr, vaddr2);

 

vaddr与vaddr2的值一样,说明使用memcpy得到的指针是没有问题的

 

char c;

char* base = (char*)vaddr2;

for (int i = 0; i < h; i++)

 {

      for (int j = 0; j < w*4; j++)

      {

             c = base[i*w*4 + j];

      }

      ALOGD("==== i=%d, \n", i);

 

这段代码执行会出问题,说明这个base指针不能直接使用,可能和gralloc里面的处理有关。

 

5-16

考虑传送GraphicBuffer对象,写入到parcel中

 

                        Parcel data;

                        Parcel reply;

//                     int fd = buffer->handle->data[2];

//                     data.writeFileDescriptor(fd);

                        data.writeInt32(buffer->width);

                        data.writeInt32(buffer->height);

                        data.writeInt32(buffer->format);

     sp<GraphicBuffer> gbuffer = buffer;

           

            if (buffer != 0) {

                data.write(*gbuffer);

            }                  

                        

                    int err = service->transact(5, data, &reply, 0);

 

 

 

接收端

 

                       

                        sp<GraphicBuffer> buf = new GraphicBuffer();;

                        data.read(*buf);

                       

                        Region newDirtyRegion;

                        const Rect bounds(w, h);

                        newDirtyRegion.set(bounds);

                       

                        int fenceFd = -1;

                        void* vaddr;

                        //GRALLOC_USAGE_SW_READ_OFTEN 0x00000003U

                        //GRALLOC_USAGE_SW_WRITE_OFTEN 0x00000030U

        status_t res = buf->lockAsync(0x00000003U | 0x00000030U,

                newDirtyRegion.bounds(), &vaddr, fenceFd);

               

              printf("==== vaddr=%p\n", vaddr);

             

              char pname[32] = {0};

                        sprintf(pname, "%02d.png", icode++);

                        char* base = (char*)vaddr;                

                        writePNG(pname, base, w, h, format, s);

 

通过dumpsys进行触发调用,可以保存各surface的图像了

 

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>我们正在处理一个关于Android图形系统的问题,具体是SurfaceFlinger如何将当前帧替换为纯黑色图片。用户特别提到了Android 15版本。 根据用户提供的引用片段,我们得知在实现变暗和模糊效果时,可以通过获取一个纯色Layer对应的SurfaceControl,然后设置其alpha和backgroundBlurRadius等属性。这提示我们,替换当前帧为纯黑色图片可能可以通过创建一个纯黑色的Layer并覆盖在整个屏幕上来实现。 在Android的图形系统中,SurfaceFlinger负责合成各个Layer。因此,我们可以考虑以下步骤: 1. 创建一个纯黑色的Layer2. 将这个Layer设置为覆盖整个屏幕。 3. 确保这个Layer位于最上层(即Z-order最高),这样它会覆盖其他所有内容。 但是,用户要求的是替换当前帧为纯黑色图片,而不是叠加一个黑色层(因为叠加黑色层会导致整个屏幕变黑,但可能不是完全替换,而是覆盖)。然而,如果只是要显示黑色,覆盖一个黑色层与替换为黑色图片在视觉上是一样的。 另外,我们还需要考虑性能:如果只是要显示黑色,我们也可以直接清除当前帧缓冲(framebuffer)为黑色,而不需要合成所有图层。SurfaceFlinger中有一个功能叫做“空白屏幕”(blanking),它可以直接将屏幕输出设置为黑色,而不进行任何合成。在Android中,这通常是通过硬件混合器(HWComposer)的blank操作实现的。 因此,有两种可能的方法: 方法一:创建一个全屏的黑色Layer,并设置其Z-order为最高,这样它会覆盖所有其他Layer。但是,这种方法仍然会进行合成,只是结果是一个黑色屏幕。 方法二:使用SurfaceFlinger的blankDisplay功能,直接让屏幕输出黑色,跳过合成过程。 在Android 15中,我们可以探索第二种方法,因为它更高效。 具体实现思路: 1. 在SurfaceFlinger中,我们可以调用硬件混合器的blankDisplay函数。在Android中,HWComposer负责与硬件交互,包括空白屏幕。 2. 在SurfaceFlinger的合成过程中,如果检测到需要显示黑色(比如系统进入睡眠状态),就会调用blank操作。 但是,用户要求的是将当前帧替换为纯黑色图片,这可能是临时性的,比如截图替换。那么,我们可能需要考虑在截图的时候返回黑色图片?还是说在屏幕上显示黑色? 根据问题,用户可能是在屏幕上显示黑色,而不是截图。所以,我们重点放在如何让SurfaceFlinger输出黑色。 在SurfaceFlinger中,有一个函数`blankDisplay`,它通过HWComposer来实现。我们可以查看SurfaceFlinger的源码,特别是`SurfaceFlinger::blankDisplay`函数。 然而,在最新的Android版本中,SurfaceFlinger的架构有所变化。在Android 15中,可能使用新的方式。但基本思路是类似的。 具体步骤(基于方法二): 1. 获取当前的显示ID(通常主屏幕的ID为`HWC_DISPLAY_PRIMARY`)。 2. 调用HWComposer的blankDisplay方法。 在代码中,我们可以这样实现(假设在SurfaceFlinger的上下文中): ```cpp sp<android::DisplayDevice> display = getDefaultDisplayDevice(); if (display) { auto displayId = display->getId(); auto& hwc = getHwComposer(); status_t err = hwc.blank(displayId, true); // true表示blank,false表示unblank if (err != NO_ERROR) { ALOGE("Failed to blank display"); } } ``` 但是,用户要求的是替换当前帧,这可能是在每一帧合成时都输出黑色。那么,我们可能需要修改合成过程,在合成时直接清除为黑色而不处理其他Layer。 另一种思路是,在SurfaceFlinger的合成循环中,如果设置了某个标志,就跳过所有Layer的合成,直接清除帧缓冲为黑色。 在SurfaceFlinger的合成函数`doComposition`中,我们可以添加: ```cpp void SurfaceFlinger::doComposition() { ... if (mBlankScreen) { // 对于每个显示设备,清除为黑色 for (const auto& display : displays) { const auto displayId = display->getId(); const auto& state = display->getCompositionDisplay()->getState(); const auto& displaySurface = display->getDisplaySurface(); // 开始帧 displaySurface->beginFrame(); // 设置整个屏幕为黑色 displaySurface->prepareFrame(); const Rect& viewport = state.viewport; const Rect& frame = state.frame; // 使用OpenGL ES清除为黑色 if (displaySurface->isAllocated()) { const auto& renderArea = display->getRenderArea(); renderArea->setViewport(); glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); displaySurface->swapBuffers(); } } return; } ... // 正常的合成过程 } ``` 但是,这种方法需要修改SurfaceFlinger的合成流程,并且可能不是最优的,因为它使用了OpenGL ES,而实际设备可能使用其他渲染引擎(如Skia Vulkan)。 因此,更推荐使用HWComposer的blank功能,因为它由硬件直接支持,效率更高。 总结:在Android 15中,我们可以通过调用HWComposer的blankDisplay函数来将屏幕输出设置为黑色。具体步骤是获取显示设备ID,然后调用`hwc.blank(displayId, true)`。 另外,用户提到的引用[^1]中关于纯色Layer的设置,我们也可以采用创建一个全屏黑色Layer的方法。这种方法在需要与其他Layer混合(比如部分变暗)时更灵活,但如果只是要全黑,blankDisplay更高效。 现在,根据用户要求,生成相关问题:</think>在 Android 15 中,SurfaceFlinger 可以通过以下步骤将当前帧替换为纯黑色图片: ### 实现方法 1. **创建纯黑色 Layer** 使用 `SurfaceControl` 创建纯黑色 Layer 并设置属性: ```cpp sp<SurfaceControl> blackSurface = SurfaceComposerClient::createSurface( "BlackSurface", displayWidth, displayHeight, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceColor); ``` 设置纯黑色参数: ```cpp SurfaceComposerClient::Transaction t; t.setLayer(blackSurface, INT_MAX); // 置于最顶层 t.setColor(blackSurface, half3(0, 0, 0)); // RGB(0,0,0) t.setAlpha(blackSurface, 1.0f); // 完全不透明 t.show(blackSurface); // 显示Layer t.apply(); ``` 2. **覆盖屏幕截图** 当需要替换当前帧时: ```cpp // 1. 隐藏所有其他Layer SurfaceComposerClient::Transaction t; t.hide(nonBlackLayers); // 2. 强制SurfaceFlinger合成 t.setFrameTimelineInfo(frameTimelineInfo); t.apply(); // 此时屏幕将只显示纯黑色Layer ``` 3. **恢复原始状态** 恢复显示时隐藏黑色Layer并显示原始内容: ```cpp t.hide(blackSurface); t.show(originalLayers); t.apply(); ``` ### 关键机制 - **颜色 Layer (eFXSurfaceColor)** 专用于显示纯色的 Layer 类型,无纹理开销,直接由 GPU 渲染纯色[^1]。 - **Z-order 控制** 通过 `setLayer(INT_MAX)` 确保黑色 Layer 位于合成队列最顶层。 - **事务处理** 使用 `SurfaceComposerClient::Transaction` 批量提交属性变更,保证原子生效。 ### 性能优化 - 复用黑色 Layer 避免重复创建 - 使用 `setColor()` 而非位图节省内存 - 通过 `eFXSurfaceColor` 减少 GPU 负载 > 此方法适用于系统级功能(如息屏动画、隐私保护模式),需在 `system_server` 进程或具有 `ACCESS_SURFACE_FLINGER` 权限的进程中执行[^1]。 --- ### 相关问题 1. 如何在不影响性能的情况下动态切换 SurfaceFlinger 的合成内容? 2. AndroidLayer 合成机制中 Z-order 的优先级规则是什么? 3. 除了纯黑色,如何实现其他颜色的全屏覆盖效果? 4. SurfaceControl 的 `eFXSurfaceColor` 与其他 Surface 类型有何本质区别?
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值