diff options
Diffstat (limited to 'portmidi/pm_test/latency.c')
| -rwxr-xr-x | portmidi/pm_test/latency.c | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/portmidi/pm_test/latency.c b/portmidi/pm_test/latency.c new file mode 100755 index 0000000..06ea80d --- /dev/null +++ b/portmidi/pm_test/latency.c | |||
| @@ -0,0 +1,287 @@ | |||
| 1 | /* latency.c -- measure latency of OS */ | ||
| 2 | |||
| 3 | #include "porttime.h" | ||
| 4 | #include "portmidi.h" | ||
| 5 | #include "stdlib.h" | ||
| 6 | #include "stdio.h" | ||
| 7 | #include "string.h" | ||
| 8 | #include "assert.h" | ||
| 9 | |||
| 10 | /* Latency is defined here to mean the time starting when a | ||
| 11 | process becomes ready to run, and ending when the process | ||
| 12 | actually runs. Latency is due to contention for the | ||
| 13 | processor, usually due to other processes, OS activity | ||
| 14 | including device drivers handling interrupts, and | ||
| 15 | waiting for the scheduler to suspend the currently running | ||
| 16 | process and activate the one that is waiting. | ||
| 17 | |||
| 18 | Latency can affect PortMidi applications: if a process fails | ||
| 19 | to wake up promptly, MIDI input may sit in the input buffer | ||
| 20 | waiting to be handled, and MIDI output may not be generated | ||
| 21 | with accurate timing. Using the latency parameter when | ||
| 22 | opening a MIDI output port allows the caller to defer timing | ||
| 23 | to PortMidi, which in most implementations will pass the | ||
| 24 | data on to the OS. By passing timestamps and data to the | ||
| 25 | OS kernel, device driver, or even hardware, there are fewer | ||
| 26 | sources of latency that can affect the ultimate timing of | ||
| 27 | the data. On the other hand, the application must generate | ||
| 28 | and deliver the data ahead of the timestamp. The amount by | ||
| 29 | which data is computed early must be at least as large as | ||
| 30 | the worst-case latency to avoid timing problems. | ||
| 31 | |||
| 32 | Latency is even more important in audio applications. If an | ||
| 33 | application lets an audio output buffer underflow, an audible | ||
| 34 | pop or click is produced. Audio input buffers can overflow, | ||
| 35 | causing data to be lost. In general the audio buffers must | ||
| 36 | be large enough to buffer the worst-case latency that the | ||
| 37 | application will encounter. | ||
| 38 | |||
| 39 | This program measures latency by recording the difference | ||
| 40 | between the scheduled callback time and the current real time. | ||
| 41 | We do not really know the scheduled callback time, so we will | ||
| 42 | record the differences between the real time of each callback | ||
| 43 | and the real time of the previous callback. Differences that | ||
| 44 | are larger than the scheduled difference are recorded. Smaller | ||
| 45 | differences indicate the system is recovering from an earlier | ||
| 46 | latency, so these are ignored. | ||
| 47 | Since printing by the callback process can cause all sorts of | ||
| 48 | delays, this program records latency observations in a | ||
| 49 | histogram. When the program is stopped, the histogram is | ||
| 50 | printed to the console. | ||
| 51 | |||
| 52 | Optionally the system can be tested under a load of MIDI input, | ||
| 53 | MIDI output, or both. If MIDI input is selected, the callback | ||
| 54 | thread will read any waiting MIDI events each iteration. You | ||
| 55 | must generate events on this interface for the test to actually | ||
| 56 | put any appreciable load on PortMidi. If MIDI output is | ||
| 57 | selected, alternating note on and note off events are sent each | ||
| 58 | X iterations, where you specify X. For example, with a timer | ||
| 59 | callback period of 2ms and X=1, a MIDI event is sent every 2ms. | ||
| 60 | |||
| 61 | |||
| 62 | INTERPRETING RESULTS: Time is quantized to 1ms, so there is | ||
| 63 | some uncertainty due to rounding. A microsecond latency that | ||
| 64 | spans the time when the clock is incremented will be reported | ||
| 65 | as a latency of 1. On the other hand, a latency of almost | ||
| 66 | 1ms that falls between two clock ticks will be reported as | ||
| 67 | zero. In general, if the highest nonzero bin is numbered N, | ||
| 68 | then the maximum latency is N+1. | ||
| 69 | |||
| 70 | CHANGE LOG | ||
| 71 | |||
| 72 | 18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive | ||
| 73 | MIDI during test, and made period user-settable. | ||
| 74 | */ | ||
| 75 | |||
| 76 | #define HIST_LEN 21 /* how many 1ms bins in the histogram */ | ||
| 77 | |||
| 78 | #define STRING_MAX 80 /* used for console input */ | ||
| 79 | |||
| 80 | #define INPUT_BUFFER_SIZE 100 | ||
| 81 | #define OUTPUT_BUFFER_SIZE 0 | ||
| 82 | |||
| 83 | #ifndef max | ||
| 84 | #define max(a, b) ((a) > (b) ? (a) : (b)) | ||
| 85 | #endif | ||
| 86 | #ifndef min | ||
| 87 | #define min(a, b) ((a) <= (b) ? (a) : (b)) | ||
| 88 | #endif | ||
| 89 | |||
| 90 | int get_number(const char *prompt); | ||
| 91 | |||
| 92 | PtTimestamp previous_callback_time = 0; | ||
| 93 | |||
| 94 | int period; /* milliseconds per callback */ | ||
| 95 | |||
| 96 | int histogram[HIST_LEN]; | ||
| 97 | int max_latency = 0; /* worst latency observed */ | ||
| 98 | int out_of_range = 0; /* how many points outside of HIST_LEN? */ | ||
| 99 | |||
| 100 | int test_in, test_out; /* test MIDI in and/or out? */ | ||
| 101 | int output_period; /* output MIDI every __ iterations if test_out true */ | ||
| 102 | int iteration = 0; | ||
| 103 | PmStream *in, *out; | ||
| 104 | int note_on = 0; /* is the note currently on? */ | ||
| 105 | |||
| 106 | /* callback function for PortTime -- computes histogram */ | ||
| 107 | void pt_callback(PtTimestamp timestamp, void *userData) | ||
| 108 | { | ||
| 109 | PtTimestamp difference = timestamp - previous_callback_time - period; | ||
| 110 | previous_callback_time = timestamp; | ||
| 111 | |||
| 112 | /* allow 5 seconds for the system to settle down */ | ||
| 113 | if (timestamp < 5000) return; | ||
| 114 | |||
| 115 | iteration++; | ||
| 116 | /* send a note on/off if user requested it */ | ||
| 117 | if (test_out && (iteration % output_period == 0)) { | ||
| 118 | PmEvent buffer[1]; | ||
| 119 | buffer[0].timestamp = Pt_Time(); | ||
| 120 | if (note_on) { | ||
| 121 | /* note off */ | ||
| 122 | buffer[0].message = Pm_Message(0x90, 60, 0); | ||
| 123 | note_on = 0; | ||
| 124 | } else { | ||
| 125 | /* note on */ | ||
| 126 | buffer[0].message = Pm_Message(0x90, 60, 100); | ||
| 127 | note_on = 1; | ||
| 128 | } | ||
| 129 | Pm_Write(out, buffer, 1); | ||
| 130 | iteration = 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | /* read all waiting events (if user requested) */ | ||
| 134 | if (test_in) { | ||
| 135 | PmError status; | ||
| 136 | PmEvent buffer[1]; | ||
| 137 | do { | ||
| 138 | status = Pm_Poll(in); | ||
| 139 | if (status == TRUE) { | ||
| 140 | Pm_Read(in,buffer,1); | ||
| 141 | } | ||
| 142 | } while (status == TRUE); | ||
| 143 | } | ||
| 144 | |||
| 145 | if (difference < 0) return; /* ignore when system is "catching up" */ | ||
| 146 | |||
| 147 | /* update the histogram */ | ||
| 148 | if (difference < HIST_LEN) { | ||
| 149 | histogram[difference]++; | ||
| 150 | } else { | ||
| 151 | out_of_range++; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (max_latency < difference) max_latency = difference; | ||
| 155 | } | ||
| 156 | |||
| 157 | |||
| 158 | int main() | ||
| 159 | { | ||
| 160 | int i; | ||
| 161 | int len; | ||
| 162 | int choice; | ||
| 163 | PtTimestamp stop; | ||
| 164 | printf("Latency histogram.\n"); | ||
| 165 | period = 0; | ||
| 166 | while (period < 1) { | ||
| 167 | period = get_number("Choose timer period (in ms, >= 1): "); | ||
| 168 | } | ||
| 169 | printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", | ||
| 170 | "1. No MIDI traffic", | ||
| 171 | "2. MIDI input", | ||
| 172 | "3. MIDI output", | ||
| 173 | "4. MIDI input and output"); | ||
| 174 | choice = get_number("? "); | ||
| 175 | switch (choice) { | ||
| 176 | case 1: test_in = 0; test_out = 0; break; | ||
| 177 | case 2: test_in = 1; test_out = 0; break; | ||
| 178 | case 3: test_in = 0; test_out = 1; break; | ||
| 179 | case 4: test_in = 1; test_out = 1; break; | ||
| 180 | default: assert(0); | ||
| 181 | } | ||
| 182 | if (test_in || test_out) { | ||
| 183 | /* list device information */ | ||
| 184 | for (i = 0; i < Pm_CountDevices(); i++) { | ||
| 185 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | ||
| 186 | if ((test_in && info->input) || | ||
| 187 | (test_out && info->output)) { | ||
| 188 | printf("%d: %s, %s", i, info->interf, info->name); | ||
| 189 | if (info->input) printf(" (input)"); | ||
| 190 | if (info->output) printf(" (output)"); | ||
| 191 | printf("\n"); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | /* open stream(s) */ | ||
| 195 | if (test_in) { | ||
| 196 | int i = get_number("MIDI input device number: "); | ||
| 197 | Pm_OpenInput(&in, | ||
| 198 | i, | ||
| 199 | NULL, | ||
| 200 | INPUT_BUFFER_SIZE, | ||
| 201 | (PmTimestamp (*)(void *)) Pt_Time, | ||
| 202 | NULL); | ||
| 203 | /* turn on filtering; otherwise, input might overflow in the | ||
| 204 | 5-second period before timer callback starts reading midi */ | ||
| 205 | Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); | ||
| 206 | } | ||
| 207 | if (test_out) { | ||
| 208 | int i = get_number("MIDI output device number: "); | ||
| 209 | PmEvent buffer[1]; | ||
| 210 | Pm_OpenOutput(&out, | ||
| 211 | i, | ||
| 212 | NULL, | ||
| 213 | OUTPUT_BUFFER_SIZE, | ||
| 214 | (PmTimestamp (*)(void *)) Pt_Time, | ||
| 215 | NULL, | ||
| 216 | 0); /* no latency scheduling */ | ||
| 217 | |||
| 218 | /* send a program change to force a status byte -- this fixes | ||
| 219 | a problem with a buggy linux MidiSport driver, and shouldn't | ||
| 220 | hurt anything else | ||
| 221 | */ | ||
| 222 | buffer[0].timestamp = 0; | ||
| 223 | buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ | ||
| 224 | Pm_Write(out, buffer, 1); | ||
| 225 | |||
| 226 | output_period = get_number( | ||
| 227 | "MIDI out should be sent every __ callback iterations: "); | ||
| 228 | |||
| 229 | assert(output_period >= 1); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | printf("Latency measurements will start in 5 seconds. " | ||
| 234 | "Type return to stop: "); | ||
| 235 | Pt_Start(period, &pt_callback, 0); | ||
| 236 | while (getchar() != '\n') ; | ||
| 237 | stop = Pt_Time(); | ||
| 238 | Pt_Stop(); | ||
| 239 | |||
| 240 | /* courteously turn off the last note, if necessary */ | ||
| 241 | if (note_on) { | ||
| 242 | PmEvent buffer[1]; | ||
| 243 | buffer[0].timestamp = Pt_Time(); | ||
| 244 | buffer[0].message = Pm_Message(0x90, 60, 0); | ||
| 245 | Pm_Write(out, buffer, 1); | ||
| 246 | } | ||
| 247 | |||
| 248 | /* print the histogram */ | ||
| 249 | printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); | ||
| 250 | printf("Latency(ms) Number of occurrences\n"); | ||
| 251 | /* avoid printing beyond last non-zero histogram entry */ | ||
| 252 | len = min(HIST_LEN, max_latency + 1); | ||
| 253 | for (i = 0; i < len; i++) { | ||
| 254 | printf("%2d %10d\n", i, histogram[i]); | ||
| 255 | } | ||
| 256 | printf("Number of points greater than %dms: %d\n", | ||
| 257 | HIST_LEN - 1, out_of_range); | ||
| 258 | printf("Maximum latency: %d milliseconds\n", max_latency); | ||
| 259 | printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); | ||
| 260 | printf("than the numbers reported here.\n"); | ||
| 261 | printf("Type return to exit..."); | ||
| 262 | while (getchar() != '\n') ; | ||
| 263 | |||
| 264 | if(choice == 2) | ||
| 265 | Pm_Close(in); | ||
| 266 | else if(choice == 3) | ||
| 267 | Pm_Close(out); | ||
| 268 | else if(choice == 4) | ||
| 269 | { | ||
| 270 | Pm_Close(in); | ||
| 271 | Pm_Close(out); | ||
| 272 | } | ||
| 273 | return 0; | ||
| 274 | } | ||
| 275 | |||
| 276 | |||
| 277 | /* read a number from console */ | ||
| 278 | int get_number(const char *prompt) | ||
| 279 | { | ||
| 280 | int n = 0, i; | ||
| 281 | fputs(prompt, stdout); | ||
| 282 | while (n != 1) { | ||
| 283 | n = scanf("%d", &i); | ||
| 284 | while (getchar() != '\n') ; | ||
| 285 | } | ||
| 286 | return i; | ||
| 287 | } | ||
