Android 显示surfaceFlinger vsync 获取

2023-09-18 23:51:52

vsync 的概念

vsync简单理解就是一帧图像在显示设备这边显示完成之后(图像从左上角扫描到了右下角了) 发送的第一个硬件vsync信号, 显示设备重新回到左上角开始显示的时候会在发第二个vsync信号。在发送第一个vsync信号出来的时候,上层要开始准备合成处理好的图像buffer。而且这个必须在下一个vsync到达之前准备完成 否则显示会出现异常。 vsync 信号主要应用app刷新,视频显示刷新。

问题:

  1. vsync信号是硬件发出的,那上层应该怎么去获取?
  2. vsync 如何从底层传递到应用?

vsync应用层获取的方式

网上资料,有些太旧运行不了,最好的参考的代码是google的源码的test。

  • google 参考方法

    源码位置:frameworks\native\services\surfaceflinger\tests\vsync\vsync.cpp

简要流程:硬件的vsync抽象在上层是一个fd。当有vsync的时候,上层looper会调用receiver 回调,在回调中就可以获取到count 和timestamp。回调函数必须是 下面三个参数这样的。

int receiver(int /*fd*/, int /*events*/, void* data)

addFd 将displayEvent的fd、要添加的事件也即有事件输入,有事件之后的回调函数,disPlayEvent自身(用于传递到回调函数获取)

实现时需要创建一个线程,在线程中队DisplayEventReceiver的fd进行pollOnce 操作。
fd没有poll_input的消息时,线程会堵塞。会消息的时候pooll返回 然后调用回调函数receiver,在receriver可以获取vysnc的count和timestamp。

    DisplayEventReceiver myDisplayEvent;
    sp<Looper> loop = new Looper(false);
    loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver, &myDisplayEvent);

int receiver(int /*fd*/, int /*events*/, void* data) {
    DisplayEventReceiver* q = (DisplayEventReceiver*)data;
    ssize_t n;
    DisplayEventReceiver::Event buffer[1];
    static nsecs_t oldTimeStamp = 0;
    while ((n = q->getEvents(buffer, 1)) > 0) {
        for (int i = 0; i < n; i++) {
            ALOGD("buffer[i].header.type is :%d \n", buffer[i].header.type);
            if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
                ALOGD("event vsync: count=%d\t", buffer[i].vsync.count);
            }
            if (oldTimeStamp) {
                float t = float(buffer[i].header.timestamp - oldTimeStamp) / s2ns(1);
                ALOGD("%f ms (%f Hz)\n", t * 1000, 1.0 / t);
            }
            oldTimeStamp = buffer[i].header.timestamp;
        }
    }
    return 1;
}

vsync 信号传递

vsync 从hwc 获取到之后 回调到surfaceflinger, surfaceFlinger 通过socket发送到应用。

  • 应用注册fd到surfacefinger流程

    主要在DisplayEventReceiver构造函数中实现。

    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if (sf != nullptr) {
        mEventConnection = sf->createDisplayEventConnection(vsyncSource, eventRegistration);
        if (mEventConnection != nullptr) {
            mDataChannel = std::make_unique<gui::BitTube>();
            mEventConnection->stealReceiveChannel(mDataChannel.get());
        }
    }

构造函数流程:

  1. 获取surfaceflinger的服务。
  2. 通过surfaceflinger的createDisplayEventConnection的函数创建一个gui::BitTube对象,
    gui::BitTube管理一对无名的socket, 这个socket会对BitTube的mReceived 和 mSendFd
    进行初始化。
  3. 应用端也会创建一个gui::BitTube,并传递到surfaceflinger中,surfaceflinger 中将已经创建好的BitTube中mReceived赋值给外部创建好BitTube中的mReceived。
    这样外部应用端和surfaceFlinger建立了连续。发送到surfaceflinger BitTube的mSendFd的消息,应用端可以监听并接收。

DisplayEventDispatcher继承自LooperCallback,看LooperCallback的实现可以知道addFd
的有INPUT的event后回调到的是handleEvent。

class DisplayEventDispatcher : public LooperCallback

    if (mLooper != nullptr) {
        int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
        if (rc < 0) {
            return UNKNOWN_ERROR;
        }
    }

  • surfaceFlinger 注册vsync回调到hwc以及 回调上报的路径
  1. 首先hwc是一个hal层的服务 其接口定义在这个目录 hardware/interfaces/graphics/composer/2.1。其对应的service 是android.hardware.graphics.composer@2.1-service。 surfaceflinger提供这个composer的service提供的接口 可以注册回调到hwcomposer 的hal层中。
mCompositionEngine->getHwComposer().setCallback(this);

 mComposer->registerCallback(
            sp<ComposerCallbackBridge>::make(callback, mComposer->isVsyncPeriodSwitchSupported()));


void Composer::registerCallback(const sp<IComposerCallback>& callback)
{
    android::hardware::setMinSchedulerPolicy(callback, SCHED_FIFO, 2);
    auto ret = [&]() {
        if (mClient_2_4) {
            return mClient_2_4->registerCallback_2_4(callback);
        }
        return mClient->registerCallback(callback);
    }();
    if (!ret.isOk()) {
        ALOGE("failed to register IComposerCallback");
    }
}

而在hal层的 vsync的实现各不相同。 有一种实现方式运行一个线程,然后在线程中使用
drmWaitVBlank等待vsync,vsync产生后回调到之前surfaceflinger 注册进来的callback中。

在surfaceflinger中会创建一个DispSyncThread线程,这个线程收到vsync事件后也就是回调到onComposerHalVsync中时。通过之前注册进来的EventThread的callback
构造了一个DisplayEventReceiver::Event类型事件,类型为DISPLAY_EVENT_VSYNC,还有displayId,时间戳以及接收到的次数。构造好之后会唤醒EventThread线程,这个线程从mPendingEvents中的事件调用dispatchEvent将事件分发给监听者,监听者即是向EventThread请求了Vsync的EventThreadConnection并addfd的配置回调函数的。

void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                        std::optional<hal::VsyncPeriodNanos> vsyncPeriod)
    mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed);

参考链接:
https://blog.csdn.net/qq_34211365/article/details/105123790

更多推荐

CSS中去掉li前面的圆点方法

1.引言在网页开发中,我们经常会使用无序列表(<ul>)来展示一系列的项目。默认情况下,每个列表项(<li>)前面都会有一个圆点作为标记。然而,在某些情况下,我们可能希望去掉这些圆点,以满足设计需求或者个性化要求。本文将介绍几种常见的方法来去掉<li>前面的圆点。2.使用CSS属性我们可以使用CSS的list-styl

CSS中的定位

position的属性与含义CSS中的position属性用于控制元素在页面中的定位方式,有四个主要的取值,每个取值都会影响元素的布局方式,它们是:static(默认值):这是所有元素的初始定位方式。在静态定位下,元素会按照它们在文档流中的顺序依次排列,不受top、right、bottom、left等属性的影响。静态定

实例说明接口测试的关键是什么?(含文档+视频)

接口测试的关键在于验证应用程序接口(API)是否按照预期工作,并且在不同组件之间传输数据的正确性和可靠性。以下是接口测试的一些关键要点,后面会实例说明。1.请求和响应验证:接口测试需要验证发送到API的请求和API返回的响应是否符合预期。这包括检查请求的参数、HTTP状态码和响应的数据结构等方面。2.数据一致性:确保A

JUC下的异步编程工具使用详情以及源码分析(FutureTask、CompletableFuture)

异步编程一、FutureTask应用&源码分析1.1FutureTask介绍FutureTask是一个可以取消异步任务的类。FutureTask对Future做的一个基本实现。可以调用方法区开始和取消一个任务一般是配合Callable去使用异步任务启动之后,可以获取一个绑定当前异步任务的FutureTask可以基于Fu

RK3568开发笔记(八):开发板烧写buildroot固件(支持hdmi屏),搭建Qt交叉编译开发环境,编译一个Demo,目标板运行Demo测试

若该文为原创文章,转载请注明原文出处本文章博客地址:https://hpzwl.blog.csdn.net/article/details/132826197红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…瑞芯微开

QT JSON数据格式讲解

文章目录前言一、JSON是什么二、JSON在线解析三、QT中的JSON类四、构建JSON字符串五、解析JSON数据六.核心类QJsonDocument类详解总结前言本篇文章开始带大家学习一下什么是JSON,并且学习QT当中的JSON使用。一、JSON是什么JSON(JavaScriptObjectNotation)是一

Spring-AOP+入门案例(注解)+AOP切入点语法+AOP通知类型

一、简介+工作流程。简介SpringAop实际上就是代理模式工作流程二、导入依赖1.spring-aop包该包是在spring-context依赖下的子包,所以有context就有aop<dependency><groupId>org.springframework</groupId><artifactId>sprin

Qt day2

作业:点击登录按钮后,判断账号(admin)和密码(123456)是否一致,如果匹配失败,则弹出错误对话框,文本内容“账号密码不匹配,是否重新登录”,给定两个按钮ok和cancel,点击ok后,会清除密码框中的内容,继续进行登录;如果点击cancel按钮,则关闭界面。如果账号和密码匹配,则弹出信息对话框,给出提示信息为

面试算法3:前n个数字二进制形式中1的个数

题目输入一个非负数n,请计算0到n之间每个数字的二进制形式中1的个数,并输出一个数组。例如,输入的n为4,由于0、1、2、3、4的二进制形式中1的个数分别为0、1、1、2、1,因此输出数组[0,1,1,2,1]。分析1很多人在面试的时候都能想到直观的解法,使用一个for循环来计算从0到n的每个整数i的二进制形式中1的个

模拟实现C语言--memcpy函数和memmove函数

模拟实现C语言–memcpy函数和memmove函数文章目录模拟实现C语言--memcpy函数和memmove函数一、memcpy函数和memmove函数1.1memcpy函数是什么1.1memmove函数是什么二、使用示例2.1从起始位置复制2.2从任意位置复制三、模拟实现3.1模拟实现1--memcpy函数3.2针

MySQL数据库——索引(2)-B+Tree、Hash结构,索引分类(聚集索引、二级索引)

目录索引结构(2)B+TreeHash思考索引分类索引分类聚集索引&二级索引查找过程思考索引结构(2)B+TreeB+Tree是B-Tree的变种,我们以一颗最大度数为4的b+树为例,来看一下其结构示意图:我们可以看到两部分:绿色虚线圈起来的部分,是所引部分,仅仅起到索引数据的作用,不存储数据。红色虚线圈起来的部分,是

热文推荐