summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaroslav Kysela <perex@perex.cz>2022-11-14 20:42:10 +0100
committerJaroslav Kysela <perex@perex.cz>2022-11-14 20:47:59 +0100
commit3e4aeba25bf4a4808183c4b64270f7321b436c13 (patch)
tree25654c9b9d312d77193f0df500aff2bd6acbb31b
parent425e4d1fbea4965ea0fb7529b1ee6cbb47eb7227 (diff)
downloadalsa-lib-3e4aeba25bf4a4808183c4b64270f7321b436c13.tar.gz
test: latency - add more realtime tests
Add '-x' and '-X' tests and '-U' - I/O update mode based on the system timing. It may be required to check the position updates for the specific hardware. Print the real time / stream time differences. Also include code to make valgrind happy (including the wrong memory llocation for the stream buffer). Signed-off-by: Jaroslav Kysela <perex@perex.cz>
-rw-r--r--test/latency.c163
1 files changed, 151 insertions, 12 deletions
diff --git a/test/latency.c b/test/latency.c
index 95b3c0ee..91bef1a1 100644
--- a/test/latency.c
+++ b/test/latency.c
@@ -33,10 +33,13 @@
#include <sched.h>
#include <errno.h>
#include <getopt.h>
+#include <time.h>
#include "../include/asoundlib.h"
#include <sys/time.h>
#include <math.h>
+typedef struct timespec timestamp_t;
+
char *pdevice = "hw:0,0";
char *cdevice = "hw:0,0";
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
@@ -50,10 +53,40 @@ int loop_sec = 30; /* seconds */
int block = 0; /* block mode */
int use_poll = 0;
int resample = 1;
+int sys_latency = 0; /* data I/O: use system timings instead driver wakeups */
+int pos_dump = 0; /* dump positions */
+int realtime_check = 0;
unsigned long loop_limit;
+snd_pcm_uframes_t playback_buffer_size;
snd_output_t *output = NULL;
+static inline long long frames_to_micro(size_t frames)
+{
+ return (long long)((frames * 1000000LL) + (rate / 2)) / rate;
+}
+
+void timestamp_now(timestamp_t *tstamp)
+{
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
+ printf("clock_gettime() failed\n");
+}
+
+long long timestamp_diff_micro(timestamp_t *tstamp)
+{
+ timestamp_t now, diff;
+ timestamp_now(&now);
+ if (tstamp->tv_nsec > now.tv_nsec) {
+ diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
+ diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
+ } else {
+ diff.tv_sec = now.tv_sec - tstamp->tv_sec;
+ diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
+ }
+ /* microseconds */
+ return (diff.tv_sec * 1000000) + ((diff.tv_nsec + 500L) / 1000L);
+}
+
int setparams_stream(snd_pcm_t *handle,
snd_pcm_hw_params_t *params,
const char *id)
@@ -96,6 +129,14 @@ int setparams_stream(snd_pcm_t *handle,
printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
return -EINVAL;
}
+ /* we do not want driver wakeups */
+ if (sys_latency > 0 && snd_pcm_hw_params_can_disable_period_wakeup(params)) {
+ err = snd_pcm_hw_params_set_period_wakeup(handle, params, 0);
+ if (err < 0) {
+ printf("Cannot disable period wakeups for %s\n", id);
+ return err;
+ }
+ }
return 0;
}
@@ -227,6 +268,7 @@ int setparams(snd_pcm_t *phandle, snd_pcm_t *chandle, int *bufsize)
goto __again;
snd_pcm_hw_params_get_buffer_size(p_params, &p_size);
+ playback_buffer_size = p_size;
if (p_psize * 2 < p_size) {
snd_pcm_hw_params_get_periods_min(p_params, &val, NULL);
if (val > 2) {
@@ -390,7 +432,7 @@ long writebuf(snd_pcm_t *handle, char *buf, long len, size_t *frames)
}
return 0;
}
-
+
#define FILTERSWEEP_LFO_CENTER 2000.
#define FILTERSWEEP_LFO_DEPTH 1800.
#define FILTERSWEEP_LFO_FREQ 0.2
@@ -434,6 +476,19 @@ void applyeffect(char* buffer,int r)
}
}
+static ssize_t get_avail(snd_pcm_t *pcm)
+{
+ ssize_t avail;
+
+ while (1) {
+ avail = snd_pcm_avail(pcm);
+ if (avail == -EAGAIN)
+ continue;
+ break;
+ }
+ return avail;
+}
+
void help(void)
{
int k;
@@ -444,6 +499,7 @@ void help(void)
"-C,--cdevice capture device\n"
"-m,--min minimum latency in frames\n"
"-M,--max maximum latency in frames\n"
+"-U,--updates I/O updates in milliseconds (0 = off)\n"
"-F,--frames frames to transfer\n"
"-f,--format sample format\n"
"-c,--channels channels\n"
@@ -454,6 +510,8 @@ void help(void)
"-b,--block block mode\n"
"-p,--poll use poll (wait for event - reduces CPU usage)\n"
"-e,--effect apply an effect (bandpass filter sweep)\n"
+"-x,--posdump dump buffer positions\n"
+"-X,--realtime do a realtime check (buffering)\n"
);
printf("Recognized sample formats are:");
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
@@ -480,6 +538,7 @@ int main(int argc, char *argv[])
{"cdevice", 1, NULL, 'C'},
{"min", 1, NULL, 'm'},
{"max", 1, NULL, 'M'},
+ {"updates", 1, NULL, 'U'},
{"frames", 1, NULL, 'F'},
{"format", 1, NULL, 'f'},
{"channels", 1, NULL, 'c'},
@@ -490,20 +549,23 @@ int main(int argc, char *argv[])
{"block", 0, NULL, 'b'},
{"poll", 0, NULL, 'p'},
{"effect", 0, NULL, 'e'},
+ {"posdump", 0, NULL, 'x'},
+ {"realtime", 0, NULL, 'X'},
{NULL, 0, NULL, 0},
};
snd_pcm_t *phandle, *chandle;
char *buffer;
int err, latency, morehelp;
- int ok;
+ int ok, first_avail;
snd_timestamp_t p_tstamp, c_tstamp;
- ssize_t r;
+ ssize_t r, cap_avail, cap_avail_max, pbk_fill, pbk_fill_min;
size_t frames_in, frames_out, in_max;
+ timestamp_t tstamp_start;
int effect = 0;
morehelp = 0;
while (1) {
int c;
- if ((c = getopt_long(argc, argv, "hP:C:m:M:F:f:c:r:B:E:s:bpen", long_option, NULL)) < 0)
+ if ((c = getopt_long(argc, argv, "hP:C:m:M:U:F:f:c:r:B:E:s:bpenxX", long_option, NULL)) < 0)
break;
switch (c) {
case 'h':
@@ -525,6 +587,10 @@ int main(int argc, char *argv[])
err = atoi(optarg) / 2;
latency_max = latency_min > err ? latency_min : err;
break;
+ case 'U':
+ err = atoi(optarg);
+ sys_latency = err <= 0 ? 0 : err;
+ break;
case 'f':
format = snd_pcm_format_value(optarg);
if (format == SND_PCM_FORMAT_UNKNOWN) {
@@ -564,6 +630,12 @@ int main(int argc, char *argv[])
case 'n':
resample = 0;
break;
+ case 'x':
+ pos_dump = 1;
+ break;
+ case 'X':
+ realtime_check = 1;
+ break;
}
}
@@ -579,15 +651,27 @@ int main(int argc, char *argv[])
loop_limit = loop_sec * rate;
latency = latency_min - 4;
- buffer = malloc((latency_max * snd_pcm_format_physical_width(format) / 8) * 2);
+ buffer = malloc((latency_max * 2 * snd_pcm_format_physical_width(format) / 8) * channels);
+
+ /* I/O updates based on a system timer */
+ if (sys_latency > 0) {
+ block = 0;
+ use_poll = 0;
+ }
setscheduler();
printf("Playback device is %s\n", pdevice);
printf("Capture device is %s\n", cdevice);
- printf("Parameters are %iHz, %s, %i channels, %s mode\n", rate, snd_pcm_format_name(format), channels, block ? "blocking" : "non-blocking");
- printf("Poll mode: %s\n", use_poll ? "yes" : "no");
- printf("Loop limit is %lu frames, minimum latency = %i, maximum latency = %i\n", loop_limit, latency_min * 2, latency_max * 2);
+ printf("Parameters are %iHz, %s, %i channels, %s mode, use poll %s\n",
+ rate, snd_pcm_format_name(format),
+ channels, block ? "blocking" : "non-blocking",
+ use_poll ? "yes" : "no");
+ printf("Loop limit is %lu frames, minimum latency = %i, maximum latency = %i",
+ loop_limit, latency_min * 2, latency_max * 2);
+ if (sys_latency > 0)
+ printf(", I/O updates %ims", sys_latency);
+ printf("\n");
if ((err = snd_pcm_open(&phandle, pdevice, SND_PCM_STREAM_PLAYBACK, block ? 0 : SND_PCM_NONBLOCK)) < 0) {
printf("Playback open error: %s\n", snd_strerror(err));
@@ -613,6 +697,9 @@ int main(int argc, char *argv[])
y[1] = (float*) malloc(channels*sizeof(float));
y[2] = (float*) malloc(channels*sizeof(float));
}
+
+ cap_avail_max = 0;
+ pbk_fill_min = latency * 2;
while (1) {
frames_in = frames_out = 0;
@@ -623,7 +710,7 @@ int main(int argc, char *argv[])
printf("Streams link error: %s\n", snd_strerror(err));
exit(0);
}
- if (snd_pcm_format_set_silence(format, buffer, latency*channels) < 0) {
+ if (snd_pcm_format_set_silence(format, buffer, latency * channels) < 0) {
fprintf(stderr, "silence error\n");
break;
}
@@ -640,6 +727,8 @@ int main(int argc, char *argv[])
printf("Go error: %s\n", snd_strerror(err));
exit(0);
}
+ if (realtime_check)
+ timestamp_now(&tstamp_start);
gettimestamp(phandle, &p_tstamp);
gettimestamp(chandle, &c_tstamp);
#if 0
@@ -651,16 +740,54 @@ int main(int argc, char *argv[])
ok = 1;
in_max = 0;
+ first_avail = 1;
while (ok && frames_in < loop_limit) {
- if (use_poll) {
+ cap_avail = latency;
+ if (sys_latency > 0) {
+ poll(NULL, 0, sys_latency);
+ cap_avail = get_avail(chandle);
+ if (cap_avail < 0) {
+ printf("Avail failed: %s\n", snd_strerror(cap_avail));
+ ok = 0;
+ break;
+ }
+ if (first_avail && realtime_check) {
+ long long diff = timestamp_diff_micro(&tstamp_start);
+ long long pos = frames_to_micro(cap_avail);
+ printf("POS FIRST CHECK: c=%zd (rt=%lldus)\n", cap_avail, pos - diff);
+ first_avail = 0;
+ }
+ } else if (use_poll) {
/* use poll to wait for next event */
snd_pcm_wait(chandle, 1000);
}
- if ((r = readbuf(chandle, buffer, latency, &frames_in, &in_max)) < 0)
+ if (pos_dump || realtime_check) {
+ if (sys_latency <= 0)
+ cap_avail = get_avail(chandle);
+ pbk_fill = get_avail(phandle);
+ if (pbk_fill >= 0)
+ pbk_fill = playback_buffer_size - pbk_fill;
+ if (cap_avail > cap_avail_max)
+ cap_avail_max = cap_avail;
+ if (pbk_fill >= 0 && pbk_fill < pbk_fill_min)
+ pbk_fill_min = pbk_fill;
+ if (realtime_check) {
+ long long diff = timestamp_diff_micro(&tstamp_start);
+ long long cap_pos = frames_to_micro(frames_in + cap_avail);
+ long long pbk_pos = frames_to_micro(frames_out - pbk_fill);
+ printf("POS: p=%zd (min=%zd, rt=%lldus) c=%zd (max=%zd, rt=%lldus)\n",
+ pbk_fill, pbk_fill_min, pbk_pos - diff,
+ cap_avail, cap_avail_max, cap_pos - diff);
+ } else if (pos_dump) {
+ printf("POS: p=%zd (min=%zd), c=%zd (max=%zd)\n",
+ pbk_fill, pbk_fill_min, cap_avail, cap_avail_max);
+ }
+ }
+ if ((r = readbuf(chandle, buffer, cap_avail, &frames_in, &in_max)) < 0)
ok = 0;
else {
if (effect)
- applyeffect(buffer,r);
+ applyeffect(buffer, r);
if (writebuf(phandle, buffer, r, &frames_out) < 0)
ok = 0;
}
@@ -677,6 +804,13 @@ int main(int argc, char *argv[])
if (p_tstamp.tv_sec == c_tstamp.tv_sec &&
p_tstamp.tv_usec == c_tstamp.tv_usec)
printf("Hardware sync\n");
+ if (realtime_check) {
+ long long diff = timestamp_diff_micro(&tstamp_start);
+ long long mtime = frames_to_micro(frames_in);
+ printf("Elapsed real time: %lldus\n", diff);
+ printf("Elapsed device time: %lldus\n", mtime);
+ printf("Test time diff (device - real): %lldus\n", mtime - diff);
+ }
snd_pcm_drop(chandle);
snd_pcm_nonblock(phandle, 0);
snd_pcm_drain(phandle);
@@ -698,5 +832,10 @@ int main(int argc, char *argv[])
}
snd_pcm_close(phandle);
snd_pcm_close(chandle);
+ snd_output_close(output);
+ snd_config_update_free_global();
+ free(buffer);
+ free(pdevice);
+ free(cdevice);
return 0;
}