C++11 chrono库在音视频系统中的应用

基本概念

chrono包括三个基本的概念:间隔(Duration),时间(Clock),时间点(Time Point),通过这三个基本概念可以很好的将时间处理抽象出来。

引用C++11的文档说明

Durations
They measure time spans, like: one minute, two hours, or ten milliseconds.

Time points
A reference to a specific point in time, like one’s birthday, today’s dawn, or when the next train passes.

Clocks
A framework that relates a time point to real physical time.

Duration

C++11 标准中的对Duration的定义

template <class Rep, class Period = ratio<1> >
class duration;

A duration object expresses a time span by means of a count and a period.

使用基本的时间单元

其有六种预定义的时间单元:hours,minutes,seconds,milliseconds,microseconds,nanoseconds,如下例子:

minutes m1(3); //代表3分钟
minutes h1(60); //代表60分钟
//minutes 在chrono 中已经预定义
//typedef std::chrono::duration<long,std::ratio<60>> minutes;

milliseconds ms(10); //代表10毫秒
milliseconds ms(1000); //代表1000毫秒即1秒

定义有含意的时间单元

在音视频系统,在编码端会设置采集或编码的帧率,比如设置为25帧。那么通过duration定义如下时间单元:

//在25帧的情况下,代表每帧的时间间隔即为40ms
typdef std::chrono::duration<long,std::ratio<1,25>> FrameRate;

Clock

如定义所说是将时间点转换为系统时间,其包含三种类型及三种clock类型的作用说明如下:

system_clock

system_clock is useful when you need to correlate the time with a known epoch so you can convert it to a calendar time. Note the specific functions in the system_clock class

system_clock即为系统时间,如下代码是输出epoch value

std::chrono::system_clock::time_point epoch;
std::time_t tmEpoch = std::chrono::system_clock::to_time_t(epoch);
std::cout<<"value is "<<ctime(&now)<<std::endl;
//输出 Thu Jan  1 08:00:00 1970
//获取当前时间
system_clock::now();
steady_clock

steady_clock is useful when you need to wait for a specific amount of time. steady_clock time can not be reset. As other steady clocks, it is usually based on the processor tick.

steady_clock其实是tick时间,steady_clock::now的值绝不会小于上一次调用steady_clock::now时的值,即使改了系统时间,也会保证steady_clock::now产生正确的值。

high_resolution_clock

When available, high_resolution_clock is usually more expensive than the other system-wide clocks, so they are used only when the provided resolution is required to the application.

high_resolution_clock::now 返回系统支持最小单位的tick period

三种clock的精度

在每个clock类型都一个period的成员变量,它是一个ration类型(比率),以一秒为分子,精度为分母,可以通过打印period的den成员值来确定clock的精度,如下:

std::cout << system_clock::period::den<<std::endl;//输出100000000
std::cout << high_resolution_clock::period::den<<std::endl;//输出1000000000
std::cout << steady_clock::period::den << std::endl;//输出1000000000

std::cout << (double)high_resolution_clock::period::num/(double)high_resolution_clock::period::den<<std::endl;
//1e-09
std::cout << (double)system_clock::period::num/(double)system_clock::period::den<<std::endl;
//1e-07
std::cout << (double)steady_clock::period::num/(double)steady_clock::period::den << std::endl;
//1e-09

上面的代码是在vs2015上编译测试的结果,system_clock精度为100纳秒,high_resolution_clock精度为1e-09(应该是基于tick),steady_clock精度为1e-09(应该是基于tick)。精度取决于系统的支持,可能在linux下精度会不同了。

Time points

顾名思义代表一个时间点,结合duration,clock可以进行时间换算。将时间点加减一个时间间隔还是一个时间点,再通过clock将时间点换算成具体时间,如下代码:

using namespace std::chrono;

//定义了一个时间点,代表从epoch后的一秒钟
time_point <system_clock, duration<int>> tp_seconds(duration<int>(1));

//将time_point转换为clock
system_clock::time_point tp(tp_seconds);

//格式化输出
std::cout << "1 second since system_clock epoch = ";
std::cout << tp.time_since_epoch().count();
std::cout << " system_clock periods." << std::endl;

std::time_t tt = system_clock::to_time_t(tp);
std::cout << "time_point tp is: " << ctime(&tt);

应用

基于steady_clock的定时器

在我前面的一篇文章中介绍了,用boost asio deadline_timer实现的定时器,deadline_timer是基于系统时间的,所以存在一个问题,当人为的改动系统时间时,定时器就失效了。其实在asio中提供一个功能跟deadline_timer相同的定时器 basic_waitable_timer,其是为兼容std的chrono库而实现的,在asio中通过basic_waitable_timer预定义了三种类型定时器:boost::asio::steady_timer,boost::asio::system_timer,boost::asio::high_resolution_timer,这三种定时器就是使用std chrono库中的stready clock,system clock,high resolution clock来实现内部的时间计算。所以可以使用boost::asio::steady_timer去解决上述问题,因为steady clock是基于tick的,人为改动系统时间比不会导致定时器失效,代码如下:

#ifndef ASIO_TIMER_H  
#define ASIO_TIMER_H  
typedef std::function<void()> ProcessFun;
//以steady_timer替代原来的deadline_timer
typedef boost::shared_ptr <boost::asio::steady_timer> pSteadyTimer;
struct STimerUnit
{
    int id;
    int seconds;
    pSteadyTimer t;
    ProcessFun fun;
};

typedef boost::shared_ptr<STimerUnit> TimerUnitPtr;
class CTimer
{
public:
    CTimer() :m_ioWork(m_ioService), m_lID(0)
    {
    }

public:

    //添加一个定时业务,f为业务处理函数,arg为自定义参数,seconds为超时秒数  
    //返回生成的ID  
    int AddTimerUnit(ProcessFun f,int seconds);
    //每intervalSeconds秒数执行一次 f函数  
    int AddTimerIntervalUnit(ProcessFun f, int intervalSeconds);
    //删除指定定时器  
    void RemoveTimerUnit(int id);

    bool TimerisValid(int id);
    void Run();

private:
    void TimerProcess(int id, bool isIntervalTimer, const boost::system::error_code& e);

private:
    std::map<int, TimerUnitPtr> m_mapTimerUnits;
private:
    boost::asio::io_service m_ioService;
    boost::asio::io_service::work m_ioWork;

private:
    std::mutex m_mutexTimerUnit;

private:
    //分配timer id  
    std::vector<int> m_vecTimerUnitIDs;
    unsigned long long m_lID;
};
#endif 

#include "log.h"  
#include "asiotimer.h"  
  
int CTimer::AddTimerUnit(ProcessFun f,int seconds)
{
    TimerUnitPtr s(new STimerUnit);
    s->seconds = seconds;
    s->t.reset(new boost::asio::steady_timer(m_ioService, std::chrono::seconds(seconds)));
    s->fun = f;

    {
        std::lock_guard<std::mutex> lock(m_mutexTimerUnit);
        m_mapTimerUnits.insert(std::make_pair(++m_lID, s));
        s->t->async_wait(boost::bind(&CTimer::TimerProcess, this, m_lID,false, boost::asio::placeholders::error));
        return m_lID;
    }
}

int CTimer::AddTimerIntervalUnit(ProcessFun f, int intervalSeconds)
{
    TimerUnitPtr s(new STimerUnit);
    s->seconds = intervalSeconds;
    s->t.reset(new boost::asio::steady_timer(m_ioService, std::chrono::seconds(intervalSeconds)));
    s->fun = f;

    {
        std::lock_guard<std::mutex> lock(m_mutexTimerUnit);
        m_mapTimerUnits.insert(std::make_pair(++m_lID, s));
        s->t->async_wait(boost::bind(&CTimer::TimerProcess, this, m_lID, false, boost::asio::placeholders::error));
        return m_lID;
    }
}

void CTimer::RemoveTimerUnit(int id)
{
    std::lock_guard<std::mutex> lock(m_mutexTimerUnit);
    std::map<int, TimerUnitPtr>::iterator It = m_mapTimerUnits.find(id);
    if (It != m_mapTimerUnits.end())
    {
        It->second->t->cancel();
        m_mapTimerUnits.erase(It);
        return;
    }
}

bool CTimer::TimerisValid(int id)
{
    std::lock_guard<std::mutex> lock(m_mutexTimerUnit);
    std::map<int, TimerUnitPtr>::iterator It = m_mapTimerUnits.find(id);
    if (It != m_mapTimerUnits.end())
    {
        return true;
    }

    return false;
}

void CTimer::Run()
{
    m_ioService.run();
}

void CTimer::TimerProcess(int id, bool isIntervalTimer, const boost::system::error_code& e)
{
    if (e == boost::asio::error::operation_aborted)
    {
        return;
    }

    TimerUnitPtr pTimerUnit;

    {
        std::lock_guard<std::mutex> lock(m_mutexTimerUnit);
        std::map<int, TimerUnitPtr>::iterator It = m_mapTimerUnits.find(id);
        if (It != m_mapTimerUnits.end())
        {
            pTimerUnit = It->second;
            if (!isIntervalTimer)
            {
                m_mapTimerUnits.erase(It);
            }
        }

        //LOG_INFO << "=========>mapTimerUnits size " << m_mapTimerUnits.size() << std::endl;
    }

    if (pTimerUnit)
    {
        pTimerUnit->fun();
        if (isIntervalTimer)
        {
            pTimerUnit->t->expires_at(pTimerUnit->t->expires_at() + std::chrono::seconds(pTimerUnit->seconds));
            pTimerUnit->t->async_wait(boost::bind(&CTimer::TimerProcess, this, id,true, boost::asio::placeholders::error));
        }
    }
    else
    {
        //LOG_INFO << "TimerUnit pointer is NULL" << std::endl;
    }
}

通过Duration实现准确帧率

音视频系统中,在编码或采集或渲染时,通常需要以固定帧率去进行。比如以25帧进行采集,那么可通过如下代码来实现准确的时间间隔

//定义了25帧时的时间间隔即为40ms
typedef std::chrono::duration<long,std::ration<1,25>> FrameRate;

auto StartTime = std::chrono::steady_time::now();
auto EndTime = std::chrono::steady_time::now() + FrameRate(1);

采集操作

std::this_thread::sleep_until(EndTime);

## 参考资料

https://www.boost.org/doc/libs/1_67_0/doc/html/chrono/users_guide.html#chrono.users_guide.tutorial.duration.can_durations_overflow_

http://www.cplusplus.com/reference/chrono/
https://stackoverflow.com/questions/26501936/difference-between-deadline-timerand-waitable-timer-in-boost-asio

https://stackoverflow.com/questions/16354727/boost-deadline-timer-minimal-example-should-i-substitute-sleep/16364002#

https://stackoverflow.com/questions/20375140/c11-threads-sleep-for-a-remaining-time

以上。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
敬告:本课程基于最新版FFmpeg4.3.1开发,仅供学习参考,不可以直接商用(想直接购买商用源代码的同学,请放弃!)。我将带领大家从以下几个方面来深入剖析ffmpeg4.3:“拿来主义”、“为我所用”。通过学习本课程,您将收获:三大多媒体框架(DirectShow、GStreamer、FFmpeg)简介与分析FFmpeg4.3的开发环境搭建(重要讲解windows下的源码编译及vs2015的环境)ffmpeg.c(4.3.1)源码剖析(分析开源大师们的设计理念和编码技巧)亲手封装私有函数:获取音视频流信息(类似于MediaInfo)、获取实时转码进度(改善用户体验)两套实现代码: MFC和Qt。-------------------------------------------------------------------音视频是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢? 因为没有学习音视频的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。 梅老师从事音视频与流媒体行业18年;曾在永新视博、科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo4776

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值