blob: a730d3840baf6f1eb7ca75756b89acb170b20b8b [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;
189 while (num_periods < target_periods) {
190 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 Chiangb8755a32016-04-13 10:17:56 +0800196 num_periods++;
197 usleep(wake_period_us);
198 }
199}
200
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800201void check_hw_level_in_range(snd_pcm_sframes_t hw_level,
202 snd_pcm_sframes_t min,
203 snd_pcm_sframes_t max)
204{
205 printf("Expected range: %ld - %ld\n", min, max);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800206 if (hw_level <= max && hw_level >= min) {
207 printf("hw_level is in the expected range\n");
208 } else {
209 fprintf(stderr,
210 "hw_level is not in the expected range\n");
211 exit(1);
212 }
213}
214
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800215void move_appl_ptr(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
216{
217 int err = 0;
218 snd_pcm_sframes_t to_move, hw_level, avail_frames;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800219
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800220 avail_frames = snd_pcm_avail(handle);
221 printf("Available frames: %ld\n", avail_frames);
222 hw_level = buffer_frames - avail_frames;
223 printf("hw_level frames: %ld\n", hw_level);
224
225 /* We want to move appl_ptr to hw_ptr plus fuzz such that hardware can
226 * play the new samples as quick as possible.
227 * The difference between hw_ptr and app can be inferred from snd_pcm_avail.
228 * avail = buffer_frames - appl_ptr + hw_ptr
229 * => hw_ptr - appl_ptr = avail - buffer_frames.
230 * The amount to forward is fuzz - hw_level = fuzz - appl_ptr - hw_ptr.
231 * Depending on the sign of this value, we need to forward or rewind
232 * appl_ptr. Check go/cros-low-latency for detailed explanation.
233 */
234 to_move = fuzz + avail_frames - buffer_frames;
235 if (to_move > 0) {
236 printf("forward by %ld\n", to_move);
237 err = snd_pcm_forward(handle, to_move);
238 } else if (to_move < 0) {
239 printf("rewind by %ld\n", -to_move);
240 err = snd_pcm_rewind(handle, -to_move);
241 } else {
242 printf("no need to move\n");
243 return;
244 }
245
246 if (err < 0) {
247 fprintf(stderr, "cannot move appl ptr (%s)\n",
248 snd_strerror(err));
249 exit(1);
250 }
251}
252
253void check_appl_ptr(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
254{
255 snd_pcm_sframes_t hw_level, avail_frames;
256 int periods_after_move = 10;
257
258 /* Checks the result after moving. The hw_level should be in the range
259 * 0 - fuzz. */
260 avail_frames = snd_pcm_avail(handle);
261 printf("Available frames after move: %ld\n", avail_frames);
262 hw_level = buffer_frames - avail_frames;
263 printf("hw_level after moving: %ld\n", hw_level);
264
265 check_hw_level_in_range(hw_level, 0, fuzz);
266
267 /* Fills some zeros after moving to make sure PCM still plays fine. */
268 pcm_fill(handle, period_size * periods_after_move, 0);
269 hw_level = buffer_frames - snd_pcm_avail(handle);
270 printf("hw_level after filling %d period is %ld\n",
271 periods_after_move, hw_level);
272
273 wait_for_periods(handle, periods_after_move - 1);
274 hw_level = buffer_frames - snd_pcm_avail(handle);
275 printf("hw_level after playing %d period is %ld\n",
276 periods_after_move - 1, hw_level);
277
278 /* After playing for periods_after_move - 1 periods, the hw_level
279 * should be less than one period + fuzz. */
280 check_hw_level_in_range(hw_level, 0, period_size + fuzz);
281}
282
283void move_and_check(snd_pcm_t *handle, snd_pcm_sframes_t fuzz)
284{
285 move_appl_ptr(handle, fuzz);
286 check_appl_ptr(handle, fuzz);
287}
288
289void alsa_move_test(unsigned int wait_periods)
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800290{
291 int err;
292 snd_pcm_t *handle;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800293 snd_pcm_sframes_t fuzz = 50;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800294
295 if ((err = snd_pcm_open(&handle, play_dev,
296 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
297 fprintf(stderr, "cannot open audio device %s (%s)\n",
298 play_dev, snd_strerror(err));
299 exit(1);
300 }
301
302 pcm_init(handle);
303
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800304 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800305
306 wait_for_periods(handle, wait_periods);
307
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800308 move_and_check(handle, fuzz);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800309
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800310 if ((err = snd_pcm_close(handle)) < 0) {
311 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800312 exit(1);
313 }
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800314}
315
316/* Checks if snd_pcm_drop resets the hw_ptr to 0. See bug crosbug.com/p/51882.
317 */
318void alsa_drop_test()
319{
320 int err;
321 snd_pcm_t *handle;
322 snd_pcm_sframes_t frames;
323 snd_pcm_sframes_t fuzz = 50;
324 unsigned int wait_periods = 50;
325
326 if ((err = snd_pcm_open(
327 &handle, play_dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
328 fprintf(stderr, "cannot open audio device %s (%s)\n",
329 play_dev, snd_strerror(err));
330 exit(1);
331 }
332
333 pcm_init(handle);
334
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800335 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800336
337 wait_for_periods(handle, wait_periods);
338
339 /* Drop the samples. */
340 if ((err = snd_pcm_drop(handle)) < 0) {
341 fprintf(stderr, "cannot drop audio interface (%s)\n",
342 snd_strerror(err));
343 exit(1);
344 }
345
346 /* Prepare and start playback again. */
347 if ((err = snd_pcm_prepare(handle)) < 0) {
348 fprintf(stderr, "cannot prepare audio interface (%s)\n",
349 snd_strerror(err));
350 exit(1);
351 }
352
353 if ((err = snd_pcm_start(handle)) < 0) {
354 fprintf(stderr, "cannot start for the second time (%s)\n",
355 snd_strerror(err));
356 exit(1);
357 }
358
359 /* The avail should be a reasonable value that is slightly larger than
360 * buffer level. avail = buffer - (appl_ptr - hw_ptr).
361 * The expected behavior:
362 * snd_pcm_drop: hw_ptr should be 0.
363 * snd_pcm_prepare: appl_ptr should be the same as hw_ptr, which is 0.
364 * snd_pcm_start: hw_ptr gets synced to hardware, should be a small number.
365 * So, the new avail should be slightly larger than buffer. */
366 frames = snd_pcm_avail(handle);
367
368 printf("Avail frames after drop, prepare, start: %d\n", (int)frames);
369
370 if ((err = snd_pcm_close(handle)) < 0) {
371 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
372 exit(1);
373 }
374
375 printf("Expected avail frames after drop, prepare, start is 0 - %d\n",
376 (int)(buffer_frames + fuzz));
377
378 if (frames < 0 || frames > buffer_frames + fuzz) {
379 fprintf(stderr, "Avail frames after drop, prepare, start is %d\n",
380 (int)frames);
381 exit(1);
382 }
383}
384
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800385void alsa_fill_test()
386{
387 int err;
388 snd_pcm_t *handle;
389 unsigned int wait_periods = 10;
390 const snd_pcm_channel_area_t *my_areas;
391 snd_pcm_uframes_t offset, frames;
392 int16_t *dst, *zeros;
393 int n_bytes;
394
395 if ((err = snd_pcm_open(&handle, play_dev,
396 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
397 fprintf(stderr, "cannot open audio device %s (%s)\n",
398 play_dev, snd_strerror(err));
399 exit(1);
400 }
401
402 pcm_init(handle);
403
404 /* Write nonzero values into buffer. */
405 pcm_fill(handle, buffer_frames, 1);
406
407 /* Play for some periods. */
408 wait_for_periods(handle, wait_periods);
409
410 /* Get the mmap area. */
411 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
412 if (err < 0) {
413 fprintf(stderr, "cannot mmap begin (%s)\n", snd_strerror(err));
414 exit(1);
415 }
416
417 /* Fill whole buffer with zeros without committing it.
418 * The number of bytes is buffer_frames * channel * 2 (16 bit sample) */
419 n_bytes = buffer_frames * channels * 2;
420 memset((int8_t *)my_areas[0].addr, 0, n_bytes);
421 printf("Filled mmap buffer with zeros\n");
422
423 /* Play for some periods. */
424 wait_for_periods(handle, wait_periods);
425
426 /* Check the samples in buffer are all zeros. */
427 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
428 if (err < 0) {
429 fprintf(stderr, "cannot mmap begin the second time (%s)\n",
430 snd_strerror(err));
431 exit(1);
432 }
433 dst = (int16_t *)my_areas[0].addr;
434
435 zeros = calloc(buffer_frames * channels, sizeof(zeros[0]));
436
437 if (memcmp(zeros, dst, n_bytes)) {
438 fprintf(stderr, "buffer is not all zeros\n");
439 free(zeros);
440 exit(1);
441 }
442 free(zeros);
443 printf("Buffer is filled with zeros\n");
444}
445
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800446int main(int argc, char *argv[])
447{
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800448 int c, drop_test = 0, move_test = 0, fill_test = 0;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800449 const char *short_opt = "hd:rm";
450 struct option long_opt[] =
451 {
452 {"help", no_argument, NULL, 'h'},
453 {"device", required_argument, NULL, 'd'},
454 {"drop", no_argument, NULL, 'r'},
455 {"move", no_argument, NULL, 'm'},
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800456 {"fill", no_argument, NULL, 'f'},
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800457 {NULL, 0, NULL, 0 }
458 };
459
460 while(1) {
461 c = getopt_long(argc, argv, short_opt, long_opt, NULL);
462 if (c == -1)
463 break;
464 switch(c) {
465 case 'd':
466 play_dev = optarg;
467 printf("Assign play_dev to %s\n", play_dev);
468 break;
469
470 case 'r':
471 drop_test = 1;
472 printf("Test snd_pcm_drop\n");
473 break;
474
475 case 'm':
476 move_test = 1;
477 printf("Test snd_pcm_forward\n");
478 break;
479
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800480 case 'f':
481 fill_test = 1;
482 printf("Test snd_pcm_mmap_begin and filling buffer.\n");
483 break;
484
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800485 case 'h':
486 printf("Usage: %s [OPTIONS]\n", argv[0]);
487 printf(" --device <Device> Device, default to hw:0,0\n");
488 printf(" -h, --help Print this help and exit\n");
489 printf(" --drop Test snd_pcm_drop\n");
490 printf(" --move Test snd_pcm_forward\n");
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800491 printf(" --fill Test snd_pcm_mmap_begin\n");
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800492 printf("\n");
493 return(0);
494 break;
495
496 case ':':
497 case '?':
498 fprintf(stderr, "Try `%s --help' for more information.\n",
499 argv[0]);
500 exit(EXIT_FAILURE);
501
502 default:
503 fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);
504 fprintf(stderr, "Try `%s --help' for more information.\n",
505 argv[0]);
506 exit(EXIT_FAILURE);
507 }
508 }
509
510 if (drop_test) {
511 alsa_drop_test();
512 exit(0);
513 }
514
515 if (move_test) {
Cheng-Yi Chiang5af2f5e2016-05-13 18:25:48 +0800516 /* Test rewind and forward.
517 * - Waiting 10 periods: appl_ptr is still ahead of hw_ptr, test
518 * snd_pcm_rewind call.
519 * - Waiting 1000 periods: hw_ptr is ahead of appl_ptr, test
520 * snd_pcm_forward call.
521 */
522 alsa_move_test(10);
523 alsa_move_test(1000);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800524 exit(0);
525 }
526
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800527 if (fill_test) {
528 alsa_fill_test();
529 exit(0);
530 }
531
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800532 exit(0);
533}