chrono :: high_resolution_clock延迟不一致

时间:2019-06-01 13:40:01

标签: c++ delay midi chrono

我正在尝试实现类似MIDI的时钟采样播放器。

有一个计时器,可增加脉冲计数器,每480个脉冲为四分之一,因此,脉冲周期为1041667 ns,每分钟120次。 计时器不是基于睡眠的,而是在单独的线程中运行,但是延迟时间似乎不一致:在测试文件中播放的样本之间的时间间隔波动+-20毫秒(在某些情况下时间稳定且稳定,我找不到排除这种效应的依赖。

排除了音频后端影响:我已经尝试过OpenAL和SDL_mixer。

void Timer_class::sleep_ns(uint64_t ns){
    auto start = std::chrono::high_resolution_clock::now();
    bool sleep = true;

    while(sleep)
    {
        auto now = std::chrono::high_resolution_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(now - start);
        if (elapsed.count() >= ns) {
                TestTime = elapsed.count();
                sleep = false;
                //break;
        }
    }
}

void Timer_class::Runner(void){
// this running as thread
    while(1){
        sleep_ns(BPMns);
        if (Run) Transport.IncPlaybackMarker(); // marker increment
        if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
            Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
            Player.PlayFile(1); // period of this event fluctuates severely 
        }
    }
};

void Player_class::PlayFile(int FileNumber){
    #ifdef AUDIO_SDL_MIXER
        if(Mix_PlayChannel(-1, WaveData[FileNumber], 0)==-1) {
        printf("Mix_PlayChannel: %s\n",Mix_GetError());
    }
    #endif // AUDIO_SDL_MIXER
}

我在方法方面做错了吗?有没有更好的方法来实现这种计时器? 对于音频,高于4-5 ms的偏差太大。

2 个答案:

答案 0 :(得分:4)

我看到一个大错误和一个小错误。最大的错误是您的代码假定Runner中的主要处理持续花费零时间:

    if (Run) Transport.IncPlaybackMarker(); // marker increment
    if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
        Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
        Player.PlayFile(1); // period of this event fluctuates severely 
    }

也就是说,您在“希望”循环迭代所需的时间里“睡觉”,然后在此之上进行处理。

这个小错误是假定您可以用整数纳秒表示理想的循环迭代时间。该错误非常小,因此并不重要。但是,我通过向人们展示如何摆脱这种错误来使自己感到有趣。 :-)

首先,通过精确表示理想的循环迭代时间来纠正小错误:

using quarterPeriod = std::ratio<1, 2>;
using iterationPeriod = std::ratio_divide<quarterPeriod, std::ratio<480>>;
using iteration_time = std::chrono::duration<std::int64_t, iterationPeriod>;

我对音乐一无所知,但是我猜上面的代码是正确的,因为如果将iteration_time{1}转换为nanoseconds,则会得到大约1041667ns。 iteration_time{1}是指您希望Timer_class::Runner中的每次循环迭代所花费的确切时间。

要纠正较大的错误,您需要休眠{em> 到time_point,而不是休眠 for 进入duration。这是一个通用的实用程序,可以帮助您做到这一点:

template <class Clock, class Duration>
void
delay_until(std::chrono::time_point<Clock, Duration> tp)
{
    while (Clock::now() < tp)
        ;
}

现在,如果您将Timer_class::Runner编码为使用delay_until而不是sleep_ns,我认为,您会得到更好的结果:

void
Timer_class::Runner()
{
    auto next_start = std::chrono::steady_clock::now() + iteration_time{1};

    while (true)
    {
        if (Run) Transport.IncPlaybackMarker(); // marker increment
        if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
            Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
            Player.PlayFile(1);
        }
        delay_until(next_start);
        next_start += iteration_time{1};
    }
}

答案 1 :(得分:0)

我最终使用了@ howard-hinnant版本的delay,并减小了openal-soft中的缓冲区大小,这产生了巨大的变化,现在在120BPM(125 ms周期)内,1/16的波动约为+ -5 ms四分之一拍为+ -1 ms。有很多不足之处,但我想还可以