Audio drift in Allegro 5.1.8
aureus

I've recently run into a problem with audio playback in Allegro 5.1.8: audio stream playback lags behind the clock by a factor of about 0.0000205. That might not sound like a lot, but it's significant in my case as I am working on a rhythm game. After ten minutes the audio and clock are out of sync by about 12 milliseconds, more than enough to make the rhythm element unusable.

This lag doesn't appear to depend on the stream I'm playing, its sample rate, the buffer size or count (as long as they're adequate to prevent buffer underflow), or the file format.

I have only tested this on Mac OS 10.9 so far, but at the bottom of this post I have included a test case I wrote. The "total drift" and "drift per second" displays will start out rather chaotic, but over time the total drift will gradually creep up and the drift per second will eventually converge to somewhere around 0.00002. I'd like to ask the following of other developers:

- Would you please run this test case yourself on other platforms and see if you get similar results? The longer it runs, the more accurate the drift per second figure will be. The white flash and the cymbal crash in the test loop become visibly out of sync after a half hour or so as well.

- Do you have any idea what the cause of this problem might be or, better yet, how to fix it?

The audio stream I used for the test case is attached to this post, but feel free to try other files. The font DejaVuSans.ttf can be found here.

#SelectExpand
1////////////////////////////////////////////////////////////////////// 2// Test case for audio drift in allegro 5.1.8. By Donald Goldin. // 3////////////////////////////////////////////////////////////////////// 4 5#include <allegro5/allegro.h> 6#include <allegro5/allegro_audio.h> 7#include <allegro5/allegro_acodec.h> 8#include <allegro5/allegro_font.h> 9#include <allegro5/allegro_ttf.h> 10#include <math.h> 11 12// The results of this test case are similar whether or not USE_PLAYED_SAMPLES is 13// defined, but getStreamTime is much more precise with USE_PLAYED_SAMPLES and 14// thus the drift per second measurement will converge more quickly 15 16#define USE_PLAYED_SAMPLES 17 18double getStreamTime(ALLEGRO_AUDIO_STREAM * stream) 19{ 20#ifdef USE_PLAYED_SAMPLES 21 uint64_t samples = al_get_audio_stream_played_samples(stream); 22 double syncTime = samples / ((double)al_get_audio_stream_frequency(stream)); 23 return syncTime; 24#else 25 return al_get_audio_stream_position_secs(stream); 26#endif 27} 28 29double arrayAverage(double * array, size_t arraySize) 30{ 31 if (arraySize == 0) 32 { 33 return 0.0; 34 } 35 else 36 { 37 double total = 0.0; 38 for (size_t i = 0; i < arraySize; i++) 39 { 40 total += array[i]; 41 } 42 return total / arraySize; 43 } 44} 45 46int main (int, char**); 47 48int main (int argc, char** argv) 49{ 50 // initialization 51 if (!al_init()) 52 { 53 printf("al_init failed!\n"); 54 return 1; 55 } 56 57 if (!al_install_audio()) 58 { 59 printf("error: couldn't install audio\n"); 60 return 1; 61 } 62 63 if (!al_init_acodec_addon()) 64 { 65 printf("error: couldn't init acodec addon\n"); 66 return 1; 67 } 68 69 if (!al_reserve_samples(16)) 70 { 71 printf("error: couldn't reserve samples\n"); 72 return 1; 73 } 74 75 if (!al_install_keyboard()) 76 { 77 printf("error: couldn't install keyboard\n"); 78 return 1; 79 } 80 81 if (!al_init_font_addon()) 82 { 83 printf("error: couldn't init font addon\n"); 84 return 1; 85 } 86 87 if (!al_init_ttf_addon()) 88 { 89 printf("error: couldn't init ttf addon\n"); 90 return 1; 91 } 92 93 ALLEGRO_FONT * font = al_load_ttf_font("DejaVuSans.ttf", 10, 0); 94 95 al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST); 96 ALLEGRO_DISPLAY * display = al_create_display(640, 480); 97 if (display == NULL) 98 { 99 printf("error: could not create display\n"); 100 } 101 102 ALLEGRO_MIXER * mixer = al_get_default_mixer(); 103 104 if (!mixer) 105 { 106 printf("error: couldn't get default mixer\n"); 107 return 1; 108 } 109 110 ALLEGRO_AUDIO_STREAM * stream = al_load_audio_stream("TestStream.ogg", 4, 1024); 111 112 const double k_loopLength = 8.0; 113 114 if (stream == NULL) 115 { 116 printf("error: failed to load stream\n"); 117 return 1; 118 } 119 al_attach_audio_stream_to_mixer(stream, mixer); 120 al_set_audio_stream_gain(stream, 1.0); 121 al_set_audio_stream_playmode(stream, ALLEGRO_PLAYMODE_LOOP); 122 al_set_audio_stream_loop_secs(stream, 0.0, k_loopLength); 123 al_set_audio_stream_playing(stream, true); 124 125 al_rest(2.0); // give the stream a couple seconds to buffer 126 127 if (!al_get_audio_stream_playing(stream)) 128 { 129 printf("error: stream isn't playing\n"); 130 return 1; 131 } 132 133 double startTime = al_get_time(); 134 double initialOffset = getStreamTime(stream) - startTime; 135 136 bool shouldExit = false; 137 ALLEGRO_KEYBOARD_STATE kbState; 138 139 char buf[128]; 140 141 ALLEGRO_EVENT_QUEUE * queue = al_create_event_queue(); 142 ALLEGRO_TIMER * timer = al_create_timer(1.0 / 60.0); 143 al_register_event_source(queue, al_get_timer_event_source(timer)); 144 al_register_event_source(queue, al_get_display_event_source(display)); 145 al_start_timer(timer); 146 147 ALLEGRO_EVENT event; 148 149 const size_t k_historySize = 60; 150 double driftHistory[k_historySize]; 151 for (size_t i = 0; i < k_historySize; i++) 152 { 153 driftHistory[i] = 0.0; 154 } 155 size_t historyPos = 0; 156 157 // main update loop 158 while (!shouldExit) 159 { 160 bool needsUpdate = false; 161 162 al_wait_for_event(queue, &event); 163 164 switch (event.type) 165 { 166 case ALLEGRO_EVENT_DISPLAY_CLOSE: 167 if (event.display.source == display) 168 { 169 shouldExit = true; 170 } 171 break; 172 case ALLEGRO_EVENT_TIMER: 173 if (event.timer.source == timer) 174 { 175 needsUpdate = true; 176 } 177 break; 178 default: 179 break; 180 } 181 182 if (needsUpdate) 183 { 184 al_get_keyboard_state(&kbState); 185 if (al_key_down(&kbState, ALLEGRO_KEY_Q)) 186 { 187 shouldExit = true; 188 } 189 190 if (al_key_down(&kbState, ALLEGRO_KEY_R)) 191 { 192 startTime = al_get_time(); 193 initialOffset = getStreamTime(stream) - startTime; 194 } 195 196 if (al_key_down(&kbState, ALLEGRO_KEY_M)) 197 { 198 al_set_audio_stream_gain(stream, 0.0); 199 } 200 201 if (al_key_down(&kbState, ALLEGRO_KEY_N)) 202 { 203 al_set_audio_stream_gain(stream, 1.0); 204 } 205 206 double clockTime = al_get_time(); 207 double streamTime = getStreamTime(stream); 208 209#ifdef USE_PLAYED_SAMPLES 210 double clockTimeLooped = initialOffset + clockTime; 211#else 212 double clockTimeLooped = initialOffset + clockTime - floor((initialOffset + clockTime) / k_loopLength) * k_loopLength; 213#endif 214 215 driftHistory[historyPos] = clockTimeLooped - streamTime; 216 historyPos++; 217 if (historyPos == k_historySize) 218 { 219 historyPos = 0; 220 } 221 222 double totalDrift = arrayAverage(driftHistory, k_historySize); 223 double driftPerSecond = totalDrift / (clockTime - startTime); 224 225#ifdef USE_PLAYED_SAMPLES 226 if (clockTimeLooped - floor(clockTimeLooped / k_loopLength) * k_loopLength < 0.125) 227#else 228 if (clockTimeLooped < 0.125) 229#endif 230 { 231 al_clear_to_color(al_map_rgb_f(1.f, 1.f, 1.f)); 232 } 233 else 234 { 235 al_clear_to_color(al_map_rgb_f(0.f, 0.f, 0.f)); 236 } 237 238 snprintf(buf, 128, "time elapsed: %f", clockTime - startTime); 239 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 8, 0, buf); 240 241 snprintf(buf, 128, "playback time according to clock: %f", clockTimeLooped); 242 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 24, 0, buf); 243 244 snprintf(buf, 128, "playback time according to stream: %f", streamTime); 245 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 40, 0, buf); 246 247 snprintf(buf, 128, "total drift: %f", totalDrift); 248 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 56, 0, buf); 249 250 snprintf(buf, 128, "drift per second: %.9f", driftPerSecond); 251 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 72, 0, buf); 252 253 snprintf(buf, 128, "press 'm' to mute, 'n' to unmute, 'r' to reset time elapsed, or 'q' to quit"); 254 al_draw_text(font, al_map_rgb_f(0.f, 0.5f, 0.f), 24, 104, 0, buf); 255 256 al_flip_display(); 257 } 258 } 259 260 al_uninstall_audio(); 261 al_destroy_display(display); 262 263 return 0; 264}

André Silva

In my rhythm game, PK Rhythm, I just control the time spent by using al_get_audio_stream_position_secs. This has a slight delay between the actual position time and real time, but it doesn't delay each time more with time. I made a thread about the delay here.

Also, you should look into how ALLEGRO_EVENT works with keyboard input; using keyboard states isn't really the best way to approach input when you're using events.

SiegeLord

Couldn't reproduce this on Linux... and André Silva's point makes me think that this might be related to Windows input lag issues. I'll try this on Windows later today before and after the patch I made to the input system (the lag is present in 5.1.8 but not in git 5.1 branch):

{"name":"608334","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/8\/0800cb49ed93bfc2291786a0e1fe7520.png","w":640,"h":480,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/0\/8\/0800cb49ed93bfc2291786a0e1fe7520"}608334

(note that I changed the font to al_get_builtin_font() but that shouldn't affect things)

EDIT: Oh wait, you said MacOSX... that'll be a pain to debug as I don't have any easy way to get to an OSX system :-/.

Thread #613969. Printed from Allegro.cc