不一致 chrono::high_resolution_clock 延迟
Inconsistent chrono::high_resolution_clock delay
我正在尝试实现类似 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 毫秒的偏差太大了。
我看到一个大错误和一个小错误。大错误是您的代码假定 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
}
也就是说,您 "sleeping" 需要循环迭代的时间,然后在此基础上进行处理。
小错误是假设您可以用整数纳秒来表示理想的循环迭代时间。这个错误是如此之小,以至于它并不重要。然而,我通过向人们展示他们如何也可以摆脱这个错误来取悦自己。 :-)
首先让我们通过 exactly 代表理想化循环迭代时间来纠正小错误:
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
中循环的每次迭代所花费的精确时间。
要纠正较大的错误,您需要休眠 直到 一个 time_point
,而不是休眠 个 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};
}
}
我最终使用了延迟的@howard-hinnant 版本,并在 openal-soft 中减小了缓冲区大小,这产生了巨大的差异,现在 1/16 的波动约为 +-5 毫秒120BPM(125 毫秒周期)和四分拍 +-1 毫秒。有很多不足之处,但我觉得还可以
我正在尝试实现类似 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 毫秒的偏差太大了。
我看到一个大错误和一个小错误。大错误是您的代码假定 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
}
也就是说,您 "sleeping" 需要循环迭代的时间,然后在此基础上进行处理。
小错误是假设您可以用整数纳秒来表示理想的循环迭代时间。这个错误是如此之小,以至于它并不重要。然而,我通过向人们展示他们如何也可以摆脱这个错误来取悦自己。 :-)
首先让我们通过 exactly 代表理想化循环迭代时间来纠正小错误:
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
中循环的每次迭代所花费的精确时间。
要纠正较大的错误,您需要休眠 直到 一个 time_point
,而不是休眠 个 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};
}
}
我最终使用了延迟的@howard-hinnant 版本,并在 openal-soft 中减小了缓冲区大小,这产生了巨大的差异,现在 1/16 的波动约为 +-5 毫秒120BPM(125 毫秒周期)和四分拍 +-1 毫秒。有很多不足之处,但我觉得还可以