blob: 534176c5510479698e4b70f3386383a1c8878138 [file] [log] [blame]
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +08001/*
2 * Copyright 2016 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7#include <alsa/asoundlib.h>
8#include <getopt.h>
9#include <math.h>
10#include <pthread.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <sys/time.h>
14#include <unistd.h>
15
16static unsigned rate = 48000;
17static unsigned channels = 2;
18static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
19static char *play_dev = "hw:0,0";
20/* Buffer size will be the maximum supported by hardware. */
21static snd_pcm_uframes_t buffer_frames;
22static snd_pcm_uframes_t period_size = 240;
23
24/* Fill frames of zeros. */
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +080025static void pcm_fill(snd_pcm_t *handle, snd_pcm_uframes_t frames,
26 int16_t value) {
27 int16_t *play_buf;
28 int err, i;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +080029
30 play_buf = calloc(frames * channels, sizeof(play_buf[0]));
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +080031 for (i = 0; i < frames * channels; i++)
32 play_buf[i] = value;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +080033
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +080034 printf("Write %ld of value %d into device\n", frames, (int)value);
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +080035
36 if ((err = snd_pcm_mmap_writei(handle, play_buf, frames))
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +080037 != frames) {
38 fprintf(stderr, "write to audio interface failed (%s)\n",
39 snd_strerror(err));
40 }
41
42 free(play_buf);
43}
44
45static void pcm_hw_param(snd_pcm_t *handle) {
46 int err;
47 snd_pcm_hw_params_t *hw_params;
48
49 if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
50 fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
51 snd_strerror(err));
52 exit(1);
53 }
54
55 if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
56 fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
57 snd_strerror(err));
58 exit(1);
59 }
60
61 if ((err = snd_pcm_hw_params_set_access(handle, hw_params,
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +080062 SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) {
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +080063 fprintf(stderr, "cannot set access type (%s)\n",
64 snd_strerror(err));
65 exit(1);
66 }
67
68 if ((err = snd_pcm_hw_params_set_format(handle, hw_params,
69 format)) < 0) {
70 fprintf(stderr, "cannot set sample format (%s)\n",
71 snd_strerror(err));
72 exit(1);
73 }
74
75 if ((err = snd_pcm_hw_params_set_rate_near(
76 handle, hw_params, &rate, 0)) < 0) {
77 fprintf(stderr, "cannot set sample rate (%s)\n",
78 snd_strerror(err));
79 exit(1);
80 }
81
82 if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, 2)) < 0) {
83 fprintf(stderr, "cannot set channel count (%s)\n",
84 snd_strerror(err));
85 exit(1);
86 }
87
88 /* Makes sure buffer frames is even, or snd_pcm_hw_params will
89 * return invalid argument error. */
90 if ((err = snd_pcm_hw_params_get_buffer_size_max(
91 hw_params, &buffer_frames)) < 0) {
92 fprintf(stderr, "get buffer max (%s)\n", snd_strerror(err));
93 exit(1);
94 }
95
96 buffer_frames &= ~0x01;
97 if ((err = snd_pcm_hw_params_set_buffer_size_max(
98 handle, hw_params, &buffer_frames)) < 0) {
99 fprintf(stderr, "set_buffer_size_max (%s)\n", snd_strerror(err));
100 exit(1);
101 }
102
103 printf("buffer size set to %u\n", (unsigned int)buffer_frames);
104
105 if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) {
106 fprintf(stderr, "cannot set parameters (%s)\n",
107 snd_strerror(err));
108 exit(1);
109 }
110
111 snd_pcm_hw_params_free(hw_params);
112}
113
114static void pcm_sw_param(snd_pcm_t *handle) {
115 int err;
116 snd_pcm_sw_params_t *swparams;
117 snd_pcm_uframes_t boundary;
118
119 snd_pcm_sw_params_alloca(&swparams);
120
121 err = snd_pcm_sw_params_current(handle, swparams);
122 if (err < 0) {
123 fprintf(stderr, "sw_params_current: %s\n", snd_strerror(err));
124 exit(1);
125 }
126
127 err = snd_pcm_sw_params_get_boundary(swparams, &boundary);
128 if (err < 0) {
129 fprintf(stderr, "get_boundary: %s\n", snd_strerror(err));
130 exit(1);
131 }
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800132 printf("boundary = %lu\n", boundary);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800133
134 err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, boundary);
135 if (err < 0) {
136 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
137 exit(1);
138 }
139
140 /* Don't auto start. */
141 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, boundary);
142 if (err < 0) {
143 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
144 exit(1);
145 }
146
147 /* Disable period events. */
148 err = snd_pcm_sw_params_set_period_event(handle, swparams, 0);
149 if (err < 0) {
150 fprintf(stderr, "set_period_event: %s\n", snd_strerror(err));
151 exit(1);
152 }
153
154 err = snd_pcm_sw_params(handle, swparams);
155 if (err < 0) {
156 fprintf(stderr, "sw_params: %s\n", snd_strerror(err));
157 exit(1);
158 }
159}
160
161static void pcm_init(snd_pcm_t *handle)
162{
163 int err;
164 pcm_hw_param(handle);
165 pcm_sw_param(handle);
166
167 if ((err = snd_pcm_prepare(handle)) < 0) {
168 fprintf(stderr, "cannot prepare audio interface (%s)\n",
169 snd_strerror(err));
170 exit(1);
171 }
172
173 if ((err = snd_pcm_start(handle)) < 0) {
174 fprintf(stderr, "cannot start audio interface (%s)\n",
175 snd_strerror(err));
176 exit(1);
177 }
178}
179
180/* Waits for target_periods periods and logs time stamp and snd_pcm_avail
181 * value in each period.
182 */
183static void wait_for_periods(snd_pcm_t *handle, unsigned int target_periods)
184{
185 unsigned int num_periods = 0;
186 unsigned int wake_period_us = period_size * 1E6 / rate;
187 struct timespec now;
188 snd_pcm_sframes_t avail_frames;
Cheng-Yi Chianga566ab82016-06-24 15:22:25 +0800189 while (1) {
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800190 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
191 printf("time: %ld.%09ld", (long)now.tv_sec, (long)now.tv_nsec);
192 avail_frames = snd_pcm_avail(handle);
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800193 printf(" state: %d, avail frames: %ld, hw_level: %ld\n",
194 (int)snd_pcm_state(handle), avail_frames,
195 buffer_frames - avail_frames);
Cheng-Yi Chianga566ab82016-06-24 15:22:25 +0800196 /* Breaks here so we can print the last timestamp and frames. */
197 if (num_periods == target_periods)
198 break;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800199 num_periods++;
200 usleep(wake_period_us);
201 }
202}
203
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800204void check_hw_level_in_range(snd_pcm_sframes_t hw_level,
205 snd_pcm_sframes_t min,
206 snd_pcm_sframes_t max)
207{
208 printf("Expected range: %ld - %ld\n", min, max);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800209 if (hw_level <= max && hw_level >= min) {
210 printf("hw_level is in the expected range\n");
211 } else {
212 fprintf(stderr,
213 "hw_level is not in the expected range\n");
214 exit(1);
215 }
216}
217
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800218void move_appl_ptr(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
219{
220 int err = 0;
221 snd_pcm_sframes_t to_move, hw_level, avail_frames;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800222
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800223 avail_frames = snd_pcm_avail(handle);
224 printf("Available frames: %ld\n", avail_frames);
225 hw_level = buffer_frames - avail_frames;
226 printf("hw_level frames: %ld\n", hw_level);
227
228 /* We want to move appl_ptr to hw_ptr plus fuzz such that hardware can
229 * play the new samples as quick as possible.
230 * The difference between hw_ptr and app can be inferred from snd_pcm_avail.
231 * avail = buffer_frames - appl_ptr + hw_ptr
232 * => hw_ptr - appl_ptr = avail - buffer_frames.
233 * The amount to forward is fuzz - hw_level = fuzz - appl_ptr - hw_ptr.
234 * Depending on the sign of this value, we need to forward or rewind
235 * appl_ptr. Check go/cros-low-latency for detailed explanation.
236 */
237 to_move = fuzz + avail_frames - buffer_frames;
238 if (to_move > 0) {
239 printf("forward by %ld\n", to_move);
240 err = snd_pcm_forward(handle, to_move);
241 } else if (to_move < 0) {
242 printf("rewind by %ld\n", -to_move);
243 err = snd_pcm_rewind(handle, -to_move);
244 } else {
245 printf("no need to move\n");
246 return;
247 }
248
249 if (err < 0) {
250 fprintf(stderr, "cannot move appl ptr (%s)\n",
251 snd_strerror(err));
252 exit(1);
253 }
254}
255
256void check_appl_ptr(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
257{
258 snd_pcm_sframes_t hw_level, avail_frames;
259 int periods_after_move = 10;
Cheng-Yi Chiang547ee7b2016-06-30 15:50:10 +0800260 struct timespec time_1, time_2;
261 snd_pcm_sframes_t level_1, level_2;
262 float time_diff, measure_rate;
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800263
264 /* Checks the result after moving. The hw_level should be in the range
265 * 0 - fuzz. */
266 avail_frames = snd_pcm_avail(handle);
267 printf("Available frames after move: %ld\n", avail_frames);
268 hw_level = buffer_frames - avail_frames;
269 printf("hw_level after moving: %ld\n", hw_level);
270
271 check_hw_level_in_range(hw_level, 0, fuzz);
272
273 /* Fills some zeros after moving to make sure PCM still plays fine. */
274 pcm_fill(handle, period_size * periods_after_move, 0);
Cheng-Yi Chiang547ee7b2016-06-30 15:50:10 +0800275 clock_gettime(CLOCK_MONOTONIC_RAW, &time_1);
276 printf("time: %ld.%09ld", (long)time_1.tv_sec, (long)time_1.tv_nsec);
277 level_1 = buffer_frames - snd_pcm_avail(handle);
278 printf(" hw_level after filling %d period is %ld\n",
279 periods_after_move, level_1);
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800280
281 wait_for_periods(handle, periods_after_move - 1);
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800282
Cheng-Yi Chiang547ee7b2016-06-30 15:50:10 +0800283 clock_gettime(CLOCK_MONOTONIC_RAW, &time_2);
284 printf("time: %ld.%09ld", (long)time_2.tv_sec, (long)time_2.tv_nsec);
285 level_2 = buffer_frames - snd_pcm_avail(handle);
286 printf(" hw_level after playing %d period is %ld\n",
287 periods_after_move - 1, level_2);
288
289 /* Checks the device consumption rate in this duration is reasonable. */
290 time_diff = (time_2.tv_sec - time_1.tv_sec) +
291 (float)(time_2.tv_nsec - time_1.tv_nsec) * 1E-9;
292 measure_rate = (level_1 - level_2) / time_diff;
293
294 if (fabsf(measure_rate - rate) <= 1000) {
295 printf("rate %f is in the expected range near %u\n",
296 measure_rate, rate);
297 } else {
298 fprintf(stderr, "rate %f is not in the expected range near %u\n",
299 measure_rate, rate);
300 exit(1);
301 }
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800302}
303
304void move_and_check(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
305{
306 move_appl_ptr(handle, fuzz);
307 check_appl_ptr(handle, fuzz);
308}
309
310void alsa_move_test(unsigned int wait_periods)
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800311{
312 int err;
313 snd_pcm_t *handle;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800314 snd_pcm_sframes_t fuzz = 50;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800315
316 if ((err = snd_pcm_open(&handle, play_dev,
317 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
318 fprintf(stderr, "cannot open audio device %s (%s)\n",
319 play_dev, snd_strerror(err));
320 exit(1);
321 }
322
323 pcm_init(handle);
324
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800325 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800326
327 wait_for_periods(handle, wait_periods);
328
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800329 move_and_check(handle, fuzz);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800330
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800331 if ((err = snd_pcm_close(handle)) < 0) {
332 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800333 exit(1);
334 }
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800335}
336
337/* Checks if snd_pcm_drop resets the hw_ptr to 0. See bug crosbug.com/p/51882.
338 */
339void alsa_drop_test()
340{
341 int err;
342 snd_pcm_t *handle;
343 snd_pcm_sframes_t frames;
344 snd_pcm_sframes_t fuzz = 50;
345 unsigned int wait_periods = 50;
346
347 if ((err = snd_pcm_open(
348 &handle, play_dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
349 fprintf(stderr, "cannot open audio device %s (%s)\n",
350 play_dev, snd_strerror(err));
351 exit(1);
352 }
353
354 pcm_init(handle);
355
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800356 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800357
358 wait_for_periods(handle, wait_periods);
359
360 /* Drop the samples. */
361 if ((err = snd_pcm_drop(handle)) < 0) {
362 fprintf(stderr, "cannot drop audio interface (%s)\n",
363 snd_strerror(err));
364 exit(1);
365 }
366
367 /* Prepare and start playback again. */
368 if ((err = snd_pcm_prepare(handle)) < 0) {
369 fprintf(stderr, "cannot prepare audio interface (%s)\n",
370 snd_strerror(err));
371 exit(1);
372 }
373
374 if ((err = snd_pcm_start(handle)) < 0) {
375 fprintf(stderr, "cannot start for the second time (%s)\n",
376 snd_strerror(err));
377 exit(1);
378 }
379
380 /* The avail should be a reasonable value that is slightly larger than
381 * buffer level. avail = buffer - (appl_ptr - hw_ptr).
382 * The expected behavior:
383 * snd_pcm_drop: hw_ptr should be 0.
384 * snd_pcm_prepare: appl_ptr should be the same as hw_ptr, which is 0.
385 * snd_pcm_start: hw_ptr gets synced to hardware, should be a small number.
386 * So, the new avail should be slightly larger than buffer. */
387 frames = snd_pcm_avail(handle);
388
389 printf("Avail frames after drop, prepare, start: %d\n", (int)frames);
390
391 if ((err = snd_pcm_close(handle)) < 0) {
392 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
393 exit(1);
394 }
395
396 printf("Expected avail frames after drop, prepare, start is 0 - %d\n",
397 (int)(buffer_frames + fuzz));
398
399 if (frames < 0 || frames > buffer_frames + fuzz) {
400 fprintf(stderr, "Avail frames after drop, prepare, start is %d\n",
401 (int)frames);
402 exit(1);
403 }
404}
405
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800406void alsa_fill_test()
407{
408 int err;
409 snd_pcm_t *handle;
410 unsigned int wait_periods = 10;
411 const snd_pcm_channel_area_t *my_areas;
412 snd_pcm_uframes_t offset, frames;
413 int16_t *dst, *zeros;
414 int n_bytes;
415
416 if ((err = snd_pcm_open(&handle, play_dev,
417 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
418 fprintf(stderr, "cannot open audio device %s (%s)\n",
419 play_dev, snd_strerror(err));
420 exit(1);
421 }
422
423 pcm_init(handle);
424
425 /* Write nonzero values into buffer. */
426 pcm_fill(handle, buffer_frames, 1);
427
428 /* Play for some periods. */
429 wait_for_periods(handle, wait_periods);
430
431 /* Get the mmap area. */
432 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
433 if (err < 0) {
434 fprintf(stderr, "cannot mmap begin (%s)\n", snd_strerror(err));
435 exit(1);
436 }
437
438 /* Fill whole buffer with zeros without committing it.
439 * The number of bytes is buffer_frames * channel * 2 (16 bit sample) */
440 n_bytes = buffer_frames * channels * 2;
441 memset((int8_t *)my_areas[0].addr, 0, n_bytes);
442 printf("Filled mmap buffer with zeros\n");
443
444 /* Play for some periods. */
445 wait_for_periods(handle, wait_periods);
446
447 /* Check the samples in buffer are all zeros. */
448 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
449 if (err < 0) {
450 fprintf(stderr, "cannot mmap begin the second time (%s)\n",
451 snd_strerror(err));
452 exit(1);
453 }
454 dst = (int16_t *)my_areas[0].addr;
455
456 zeros = calloc(buffer_frames * channels, sizeof(zeros[0]));
457
458 if (memcmp(zeros, dst, n_bytes)) {
459 fprintf(stderr, "buffer is not all zeros\n");
460 free(zeros);
461 exit(1);
462 }
463 free(zeros);
464 printf("Buffer is filled with zeros\n");
465}
466
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800467int main(int argc, char *argv[])
468{
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800469 int c, drop_test = 0, move_test = 0, fill_test = 0;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800470 const char *short_opt = "hd:rm";
471 struct option long_opt[] =
472 {
473 {"help", no_argument, NULL, 'h'},
474 {"device", required_argument, NULL, 'd'},
475 {"drop", no_argument, NULL, 'r'},
476 {"move", no_argument, NULL, 'm'},
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800477 {"fill", no_argument, NULL, 'f'},
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800478 {NULL, 0, NULL, 0 }
479 };
480
481 while(1) {
482 c = getopt_long(argc, argv, short_opt, long_opt, NULL);
483 if (c == -1)
484 break;
485 switch(c) {
486 case 'd':
487 play_dev = optarg;
488 printf("Assign play_dev to %s\n", play_dev);
489 break;
490
491 case 'r':
492 drop_test = 1;
493 printf("Test snd_pcm_drop\n");
494 break;
495
496 case 'm':
497 move_test = 1;
498 printf("Test snd_pcm_forward\n");
499 break;
500
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800501 case 'f':
502 fill_test = 1;
503 printf("Test snd_pcm_mmap_begin and filling buffer.\n");
504 break;
505
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800506 case 'h':
507 printf("Usage: %s [OPTIONS]\n", argv[0]);
508 printf(" --device <Device> Device, default to hw:0,0\n");
509 printf(" -h, --help Print this help and exit\n");
510 printf(" --drop Test snd_pcm_drop\n");
511 printf(" --move Test snd_pcm_forward\n");
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800512 printf(" --fill Test snd_pcm_mmap_begin\n");
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800513 printf("\n");
514 return(0);
515 break;
516
517 case ':':
518 case '?':
519 fprintf(stderr, "Try `%s --help' for more information.\n",
520 argv[0]);
521 exit(EXIT_FAILURE);
522
523 default:
524 fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);
525 fprintf(stderr, "Try `%s --help' for more information.\n",
526 argv[0]);
527 exit(EXIT_FAILURE);
528 }
529 }
530
531 if (drop_test) {
532 alsa_drop_test();
533 exit(0);
534 }
535
536 if (move_test) {
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800537 /* Test rewind and forward.
538 * - Waiting 10 periods: appl_ptr is still ahead of hw_ptr, test
539 * snd_pcm_rewind call.
540 * - Waiting 1000 periods: hw_ptr is ahead of appl_ptr, test
541 * snd_pcm_forward call.
542 */
543 alsa_move_test(10);
544 alsa_move_test(1000);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800545 exit(0);
546 }
547
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800548 if (fill_test) {
549 alsa_fill_test();
550 exit(0);
551 }
552
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800553 exit(0);
554}