天天看點

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

以上。

繼續閱讀