IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 组件:c语言基于统计的反比例负反馈定时器 -> 正文阅读

[C++知识库]组件:c语言基于统计的反比例负反馈定时器

背景介绍

通常游戏帧率的微小浮动并不太会影响用户体验,人的视觉感受能力比较低。一般仅需要使用简单的休眠来控制帧率就满足需求。

但在音频播放时,这对定时器迎来巨大的挑战。人的耳朵能感受到的音乐质量通常很高,微小的频率震荡都可能引起不适。曲有误,周郎顾。说的就是这种情况。

更苛刻的是声卡接口的特殊性,对于以60帧采样的音频流,每一块长度为 1 / 60秒。如果播放频率大于60帧,那么由于播放过快,导致块之间形成空隙,听起来会有颤音。如果播放过慢,那么播放缓冲区将随着时间逐渐填满,并逐渐与原始音轨发生滞后差异,造成音画不同步,当缓冲区满,会发生一次强烈的断层。

基本思路

在定时器设计中,完全达到帧间隔保持一致是十分困难的,这完全取决于系统提供的关于时间的接口精度。

这些接口统共两类
第一类,计时器,记录两个时间点的间隔。
第二类,睡眠器,休眠一段时间。

使用这两类时间接口就可以以统计学方式,让一个线程以帧每秒的单位度量,平稳的保持到一个期望的频率。当总任务量小于运算效能时,这在理论上总是成立的。

第一个元件,2幂长度环形队列帧率统计
我们使用定长环形队列来存储每帧时间间隔,取平均值作为统计频率。通常以2的幂作为环形队列长度有很大优势,能使用位移运算提高速度,而64作为渲染频率60帧最接近的长度是首选。首先我们在设置期望频率时将全长缓存变为期望间隔。平均值结算发生在插入新值的地方,仅出队和入队一次加法和一次减法。
组件:c语言2幂采样平均帧率统计_登高思危-少言勿躁的博客-CSDN博客

第二个元件,生成回调的定时循环
我们将回调前的时间点距离上次定时器重置时间记为wt。将回调后的时间点距离上次重置,与上一帧做差,得出当前帧的业务耗时间隔ht,使用帧率统计反馈的统计帧率记为R,期望帧率记为P,则我们使用期望频率平方为基准的反比例函数得出修正帧率为
X=(P x P) / R
修正频率间隔即为xt = 1 / X
本帧休眠间隔即为st=ht - xt

期望频率P为常数,所以这是一个反比例函数。采用这种方式负反馈。在统计帧率越接近期望帧率时修正幅度越小,反之越大。这样能快速修正到期望值。

这样我们的定时器可以轻松恒定在期望帧率小幅震荡。突发性的某几帧异常也能被64长度缓冲区抹平。

1.最终使用案例

https://bitbucket.org/mm_longcheng/mm-core/src/dev/mm/src/core/mmFrameTimer.h

static void MyFrameTimerUpdate(void* obj, double interval)
{
    printf("interval: %f\n", interval);
}

static void MyTestTimer(void)
{
    int i;
    struct mmFrameTimerCallback callback;
    struct mmFrameScheduler fs;

    mmFrameScheduler_Init(&fs);
    mmFrameScheduler_SetFrequency(&fs, 60.0);

    callback.obj = &fs;
    callback.Update = &MyFrameTimerUpdate;
    mmFrameScheduler_SetCallback(&fs, &callback);

    for (i = 0; i < 60; i++)
    {
        mmFrameScheduler_Update(&fs);

        // <other process>

        mmFrameScheduler_Timewait(&fs);
    }

    mmFrameScheduler_Destroy(&fs);
}

2.前置数据结构组件

// 字符串
struct mmString;

// 秒表
struct mmClock;

// 帧率统计
struct mmFrameStats;

// 条件超时等待
mmTimewait_USecNearby();

// 最小堆定时任务组件
struct mmTimerHeap;

3.主要数据结构

// obj      is mmFrameTimer.
// interval is msec interval.
typedef void(*mmFrameTimerUpdateFunc)(void* obj, double interval);

struct mmFrameTimerCallback
{
    mmFrameTimerUpdateFunc Update;
    // weak ref. user data for callback.
    void* obj;
};

// we adjustment the update frame interval.
//   1. dynamic frequency modulation.
//   2. complement frame.
struct mmFrameTimer
{
    struct mmString name;
    struct mmClock clock;
    struct mmFrameStats status;
    struct mmFrameTimerCallback callback;
    //
    mmAtomic_t locker;

    // flag for mmFrameTimerBackground_t. default is MM_FRAME_TIMER_BACKGROUND_RUNING.
    mmUInt8_t background;
    // flag for active, 1 is active, 0 is deactive, default is 1.
    mmUInt8_t active;
    //
    double frequency_factor;
    //
    double frequency;
    double frequency_dynamic;
    double frequency_l;
    double frequency_r;
    double frame_interval_usec;
    //
    double wait_usec;
    double frame_cost_usec;
};

// Single thread frame timer scheduler.
struct mmFrameScheduler
{
    struct mmFrameTimer frame_timer;
    struct mmTimerHeap timer_heap;
    struct mmClock clock;

    struct timeval next_delay;

    struct timeval ntime;
    struct timespec otime;

    pthread_mutex_t signal_mutex;
    pthread_cond_t signal_cond;

    mmAtomic_t locker;

    double update_cost_time;
    // default is MM_FRAME_TIMER_MAX_IDLE_DEFAULT
    double max_idle_usec;

    double frame_cost_usec;

    int sleep_usec;
    
    // smooth if we not need timewait.
    int smooth_mode;

    mmULong_t nearby_time;

    // mmFrameTimerMotionStatus_t, default is MM_FRAME_TIMER_MOTION_STATUS_FOREGROUND(0)
    mmSInt8_t motion_status;
    // mmThreadState_t, default is MM_TS_CLOSED(0)
    mmSInt8_t state;

    // user data.
    void* u;
};

4.主要处理函数

MM_EXPORT_DLL void mmFrameTimer_Update(struct mmFrameTimer* p, double* update_cost_time)
{
    if (1 == p->active)
    {
        double interval = 0;

        double wait_usec = 0;

        interval = (double)(mmClock_Microseconds(&p->clock));

        wait_usec = (p->wait_usec - interval);

        // wait the timer is timeout (-inf, 1000) usec.
        // most of system msleep(1) not enough precision.
        if (1000 >= wait_usec)
        {
            double wait_usec_mod = 0;
            double current_interval = 0;

            mmFrameStats_Update(&p->status);

            (*(p->callback.Update))(p, (p->status.interval) / (double)(MM_USEC_PER_SEC));

            // frequency_dynamic update. dynamic frequency modulation.
            p->frequency_dynamic = (p->frequency * p->frequency) / p->status.average;
            // frequency_dynamic = clamp(frequency_dynamic, frequency_l, frequency_r)
            p->frequency_dynamic = p->frequency_dynamic > p->frequency_l ? p->frequency_dynamic : p->frequency_l;
            p->frequency_dynamic = p->frequency_dynamic < p->frequency_r ? p->frequency_dynamic : p->frequency_r;
            // frequency_dynamic_usec update.
            p->frame_interval_usec = (1.0 / p->frequency_dynamic) * (double)(MM_USEC_PER_SEC);

            current_interval = (double)(mmClock_Microseconds(&p->clock));
            mmClock_Reset(&p->clock);
            p->frame_cost_usec = current_interval - interval;

            wait_usec_mod = fmod(wait_usec, p->frame_interval_usec);
            p->wait_usec = p->frame_interval_usec - p->frame_cost_usec + wait_usec_mod;

            (*update_cost_time) += p->frame_cost_usec;
        }
    }
    else
    {
        p->wait_usec = p->frame_interval_usec;
    }
}

由于系统休眠函数的精度问题,注意wait_usec可能为负数。所以需要使用以下步骤做时间补偿。

            wait_usec_mod = fmod(wait_usec, p->frame_interval_usec);
            p->wait_usec = p->frame_interval_usec - p->frame_cost_usec + wait_usec_mod;

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-28 11:37:29  更:2022-04-28 11:38:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 23:27:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码