mame/3rdparty/portmidi/pm_test/latency.c
2015-01-10 16:58:11 +01:00

290 lines
9.7 KiB
C

/* latency.c -- measure latency of OS */
#include "porttime.h"
#include "portmidi.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "assert.h"
/* Latency is defined here to mean the time starting when a
process becomes ready to run, and ending when the process
actually runs. Latency is due to contention for the
processor, usually due to other processes, OS activity
including device drivers handling interrupts, and
waiting for the scheduler to suspend the currently running
process and activate the one that is waiting.
Latency can affect PortMidi applications: if a process fails
to wake up promptly, MIDI input may sit in the input buffer
waiting to be handled, and MIDI output may not be generated
with accurate timing. Using the latency parameter when
opening a MIDI output port allows the caller to defer timing
to PortMidi, which in most implementations will pass the
data on to the OS. By passing timestamps and data to the
OS kernel, device driver, or even hardware, there are fewer
sources of latency that can affect the ultimate timing of
the data. On the other hand, the application must generate
and deliver the data ahead of the timestamp. The amount by
which data is computed early must be at least as large as
the worst-case latency to avoid timing problems.
Latency is even more important in audio applications. If an
application lets an audio output buffer underflow, an audible
pop or click is produced. Audio input buffers can overflow,
causing data to be lost. In general the audio buffers must
be large enough to buffer the worst-case latency that the
application will encounter.
This program measures latency by recording the difference
between the scheduled callback time and the current real time.
We do not really know the scheduled callback time, so we will
record the differences between the real time of each callback
and the real time of the previous callback. Differences that
are larger than the scheduled difference are recorded. Smaller
differences indicate the system is recovering from an earlier
latency, so these are ignored.
Since printing by the callback process can cause all sorts of
delays, this program records latency observations in a
histogram. When the program is stopped, the histogram is
printed to the console.
Optionally the system can be tested under a load of MIDI input,
MIDI output, or both. If MIDI input is selected, the callback
thread will read any waiting MIDI events each iteration. You
must generate events on this interface for the test to actually
put any appreciable load on PortMidi. If MIDI output is
selected, alternating note on and note off events are sent each
X iterations, where you specify X. For example, with a timer
callback period of 2ms and X=1, a MIDI event is sent every 2ms.
INTERPRETING RESULTS: Time is quantized to 1ms, so there is
some uncertainty due to rounding. A microsecond latency that
spans the time when the clock is incremented will be reported
as a latency of 1. On the other hand, a latency of almost
1ms that falls between two clock ticks will be reported as
zero. In general, if the highest nonzero bin is numbered N,
then the maximum latency is N+1.
CHANGE LOG
18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive
MIDI during test, and made period user-settable.
*/
#define HIST_LEN 21 /* how many 1ms bins in the histogram */
#define STRING_MAX 80 /* used for console input */
#define INPUT_BUFFER_SIZE 100
#define OUTPUT_BUFFER_SIZE 0
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef min
#define min(a, b) ((a) <= (b) ? (a) : (b))
#endif
int get_number(char *prompt);
PtTimestamp previous_callback_time = 0;
int period; /* milliseconds per callback */
int histogram[HIST_LEN];
int max_latency = 0; /* worst latency observed */
int out_of_range = 0; /* how many points outside of HIST_LEN? */
int test_in, test_out; /* test MIDI in and/or out? */
int output_period; /* output MIDI every __ iterations if test_out true */
int iteration = 0;
PmStream *in, *out;
int note_on = 0; /* is the note currently on? */
/* callback function for PortTime -- computes histogram */
void pt_callback(PtTimestamp timestamp, void *userData)
{
PtTimestamp difference = timestamp - previous_callback_time - period;
previous_callback_time = timestamp;
/* allow 5 seconds for the system to settle down */
if (timestamp < 5000) return;
iteration++;
/* send a note on/off if user requested it */
if (test_out && (iteration % output_period == 0)) {
PmEvent buffer[1];
buffer[0].timestamp = Pt_Time(NULL);
if (note_on) {
/* note off */
buffer[0].message = Pm_Message(0x90, 60, 0);
note_on = 0;
} else {
/* note on */
buffer[0].message = Pm_Message(0x90, 60, 100);
note_on = 1;
}
Pm_Write(out, buffer, 1);
iteration = 0;
}
/* read all waiting events (if user requested) */
if (test_in) {
PmError status;
PmEvent buffer[1];
do {
status = Pm_Poll(in);
if (status == TRUE) {
Pm_Read(in,buffer,1);
}
} while (status == TRUE);
}
if (difference < 0) return; /* ignore when system is "catching up" */
/* update the histogram */
if (difference < HIST_LEN) {
histogram[difference]++;
} else {
out_of_range++;
}
if (max_latency < difference) max_latency = difference;
}
int main()
{
char line[STRING_MAX];
int i;
int len;
int choice;
PtTimestamp stop;
printf("Latency histogram.\n");
period = 0;
while (period < 1) {
period = get_number("Choose timer period (in ms, >= 1): ");
}
printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
"1. No MIDI traffic",
"2. MIDI input",
"3. MIDI output",
"4. MIDI input and output");
choice = get_number("? ");
switch (choice) {
case 1: test_in = 0; test_out = 0; break;
case 2: test_in = 1; test_out = 0; break;
case 3: test_in = 0; test_out = 1; break;
case 4: test_in = 1; test_out = 1; break;
default: assert(0);
}
if (test_in || test_out) {
/* list device information */
for (i = 0; i < Pm_CountDevices(); i++) {
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
if ((test_in && info->input) ||
(test_out && info->output)) {
printf("%d: %s, %s", i, info->interf, info->name);
if (info->input) printf(" (input)");
if (info->output) printf(" (output)");
printf("\n");
}
}
/* open stream(s) */
if (test_in) {
int i = get_number("MIDI input device number: ");
Pm_OpenInput(&in,
i,
NULL,
INPUT_BUFFER_SIZE,
(PmTimestamp (*)(void *)) Pt_Time,
NULL);
/* turn on filtering; otherwise, input might overflow in the
5-second period before timer callback starts reading midi */
Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
}
if (test_out) {
int i = get_number("MIDI output device number: ");
PmEvent buffer[1];
Pm_OpenOutput(&out,
i,
NULL,
OUTPUT_BUFFER_SIZE,
(PmTimestamp (*)(void *)) Pt_Time,
NULL,
0); /* no latency scheduling */
/* send a program change to force a status byte -- this fixes
a problem with a buggy linux MidiSport driver, and shouldn't
hurt anything else
*/
buffer[0].timestamp = 0;
buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
Pm_Write(out, buffer, 1);
output_period = get_number(
"MIDI out should be sent every __ callback iterations: ");
assert(output_period >= 1);
}
}
printf("%s%s", "Latency measurements will start in 5 seconds. ",
"Type return to stop: ");
Pt_Start(period, &pt_callback, 0);
fgets(line, STRING_MAX, stdin);
stop = Pt_Time();
Pt_Stop();
/* courteously turn off the last note, if necessary */
if (note_on) {
PmEvent buffer[1];
buffer[0].timestamp = Pt_Time(NULL);
buffer[0].message = Pm_Message(0x90, 60, 0);
Pm_Write(out, buffer, 1);
}
/* print the histogram */
printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
printf("Latency(ms) Number of occurrences\n");
/* avoid printing beyond last non-zero histogram entry */
len = min(HIST_LEN, max_latency + 1);
for (i = 0; i < len; i++) {
printf("%2d %10d\n", i, histogram[i]);
}
printf("Number of points greater than %dms: %d\n",
HIST_LEN - 1, out_of_range);
printf("Maximum latency: %d milliseconds\n", max_latency);
printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
printf("than the numbers reported here.\n");
printf("Type return to exit...");
fgets(line, STRING_MAX, stdin);
if(choice == 2)
Pm_Close(in);
else if(choice == 3)
Pm_Close(out);
else if(choice == 4)
{
Pm_Close(in);
Pm_Close(out);
}
return 0;
}
/* read a number from console */
int get_number(char *prompt)
{
char line[STRING_MAX];
int n = 0, i;
printf(prompt);
while (n != 1) {
n = scanf("%d", &i);
fgets(line, STRING_MAX, stdin);
}
return i;
}