blob: 2122de9b9906781a923636bc4d08a92ea0f7a203 [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 Chiang16c4c192016-05-03 12:28:42 +080034 printf("Write %d of value %d into device\n", (int)frames, (int)value);
35
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 }
132
133 err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, boundary);
134 if (err < 0) {
135 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
136 exit(1);
137 }
138
139 /* Don't auto start. */
140 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, boundary);
141 if (err < 0) {
142 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
143 exit(1);
144 }
145
146 /* Disable period events. */
147 err = snd_pcm_sw_params_set_period_event(handle, swparams, 0);
148 if (err < 0) {
149 fprintf(stderr, "set_period_event: %s\n", snd_strerror(err));
150 exit(1);
151 }
152
153 err = snd_pcm_sw_params(handle, swparams);
154 if (err < 0) {
155 fprintf(stderr, "sw_params: %s\n", snd_strerror(err));
156 exit(1);
157 }
158}
159
160static void pcm_init(snd_pcm_t *handle)
161{
162 int err;
163 pcm_hw_param(handle);
164 pcm_sw_param(handle);
165
166 if ((err = snd_pcm_prepare(handle)) < 0) {
167 fprintf(stderr, "cannot prepare audio interface (%s)\n",
168 snd_strerror(err));
169 exit(1);
170 }
171
172 if ((err = snd_pcm_start(handle)) < 0) {
173 fprintf(stderr, "cannot start audio interface (%s)\n",
174 snd_strerror(err));
175 exit(1);
176 }
177}
178
179/* Waits for target_periods periods and logs time stamp and snd_pcm_avail
180 * value in each period.
181 */
182static void wait_for_periods(snd_pcm_t *handle, unsigned int target_periods)
183{
184 unsigned int num_periods = 0;
185 unsigned int wake_period_us = period_size * 1E6 / rate;
186 struct timespec now;
187 snd_pcm_sframes_t avail_frames;
188 while (num_periods < target_periods) {
189 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
190 printf("time: %ld.%09ld", (long)now.tv_sec, (long)now.tv_nsec);
191 avail_frames = snd_pcm_avail(handle);
192 printf(" state: %d, avail frames: %d, hw_level: %d\n",
193 (int)snd_pcm_state(handle), (int)avail_frames,
194 (int)(buffer_frames - avail_frames));
195 num_periods++;
196 usleep(wake_period_us);
197 }
198}
199
200void check_hw_level_in_range(snd_pcm_sframes_t hw_level, int min, int max){
201 printf("Expected range: %d - %d\n", min, max);
202 if (hw_level <= max && hw_level >= min) {
203 printf("hw_level is in the expected range\n");
204 } else {
205 fprintf(stderr,
206 "hw_level is not in the expected range\n");
207 exit(1);
208 }
209}
210
211
212void alsa_move_test()
213{
214 int err;
215 snd_pcm_t *handle;
216 snd_pcm_sframes_t to_move, hw_level, avail_frames;
217 unsigned int wait_periods = 1000;
218 snd_pcm_sframes_t fuzz = 50;
219 int periods_after_move = 10;
220
221 if ((err = snd_pcm_open(&handle, play_dev,
222 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
223 fprintf(stderr, "cannot open audio device %s (%s)\n",
224 play_dev, snd_strerror(err));
225 exit(1);
226 }
227
228 pcm_init(handle);
229
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800230 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800231
232 wait_for_periods(handle, wait_periods);
233
234 /* We want to move appl_ptr to hw_ptr plus fuzz such that hardware can
235 * play the new samples as quick as possible.
236 * avail = buffer_frames - appl_ptr + hw_ptr
237 * => hw_ptr - appl_ptr = avail - buffer_frames.
238 * The difference between hw_ptr and app can be inferred from snd_pcm_avail.
239 * So the amount of frames to forward appl_ptr is
240 * avail - buffer_frames + fuzz.
241 */
242 to_move = snd_pcm_avail(handle) - buffer_frames + fuzz;
243 printf("Frames to move appl_ptr forward: %d\n", (int)to_move);
244
245 /* Move appl_ptr forward so it becomes leading hw_ptr by fuzz. */
246 if ((err = snd_pcm_forward(handle, to_move)) < 0) {
247 fprintf(stderr, "cannot move appl ptr forward (%s)\n",
248 snd_strerror(err));
249 exit(1);
250 }
251
252 /* Checks the result after moving. The hw_level should be in the range
253 * 0 - fuzz. */
254 avail_frames = snd_pcm_avail(handle);
255 printf("Available frames after move: %d\n", (int)avail_frames);
256 hw_level = buffer_frames - avail_frames;
257 printf("hw_level after moving: %d\n", (int)hw_level);
258
259 check_hw_level_in_range(hw_level, 0, fuzz);
260
261 /* Fills some zeros after moving to make sure PCM still plays fine. */
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800262 pcm_fill(handle, period_size * periods_after_move, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800263 hw_level = buffer_frames - snd_pcm_avail(handle);
264 printf("hw_level after filling %d period is %d\n",
265 periods_after_move, (int)hw_level);
266
267 wait_for_periods(handle, periods_after_move - 1);
268 hw_level = buffer_frames - snd_pcm_avail(handle);
269 printf("hw_level after playing %d period is %d\n",
270 periods_after_move, (int)hw_level);
271
272 /* After playing for periods_after_move - 1 periods, the hw_level
273 * should be less than one period. */
274 check_hw_level_in_range(hw_level, 0, period_size);
275}
276
277/* Checks if snd_pcm_drop resets the hw_ptr to 0. See bug crosbug.com/p/51882.
278 */
279void alsa_drop_test()
280{
281 int err;
282 snd_pcm_t *handle;
283 snd_pcm_sframes_t frames;
284 snd_pcm_sframes_t fuzz = 50;
285 unsigned int wait_periods = 50;
286
287 if ((err = snd_pcm_open(
288 &handle, play_dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
289 fprintf(stderr, "cannot open audio device %s (%s)\n",
290 play_dev, snd_strerror(err));
291 exit(1);
292 }
293
294 pcm_init(handle);
295
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800296 pcm_fill(handle, buffer_frames, 0);
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800297
298 wait_for_periods(handle, wait_periods);
299
300 /* Drop the samples. */
301 if ((err = snd_pcm_drop(handle)) < 0) {
302 fprintf(stderr, "cannot drop audio interface (%s)\n",
303 snd_strerror(err));
304 exit(1);
305 }
306
307 /* Prepare and start playback again. */
308 if ((err = snd_pcm_prepare(handle)) < 0) {
309 fprintf(stderr, "cannot prepare audio interface (%s)\n",
310 snd_strerror(err));
311 exit(1);
312 }
313
314 if ((err = snd_pcm_start(handle)) < 0) {
315 fprintf(stderr, "cannot start for the second time (%s)\n",
316 snd_strerror(err));
317 exit(1);
318 }
319
320 /* The avail should be a reasonable value that is slightly larger than
321 * buffer level. avail = buffer - (appl_ptr - hw_ptr).
322 * The expected behavior:
323 * snd_pcm_drop: hw_ptr should be 0.
324 * snd_pcm_prepare: appl_ptr should be the same as hw_ptr, which is 0.
325 * snd_pcm_start: hw_ptr gets synced to hardware, should be a small number.
326 * So, the new avail should be slightly larger than buffer. */
327 frames = snd_pcm_avail(handle);
328
329 printf("Avail frames after drop, prepare, start: %d\n", (int)frames);
330
331 if ((err = snd_pcm_close(handle)) < 0) {
332 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
333 exit(1);
334 }
335
336 printf("Expected avail frames after drop, prepare, start is 0 - %d\n",
337 (int)(buffer_frames + fuzz));
338
339 if (frames < 0 || frames > buffer_frames + fuzz) {
340 fprintf(stderr, "Avail frames after drop, prepare, start is %d\n",
341 (int)frames);
342 exit(1);
343 }
344}
345
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800346void alsa_fill_test()
347{
348 int err;
349 snd_pcm_t *handle;
350 unsigned int wait_periods = 10;
351 const snd_pcm_channel_area_t *my_areas;
352 snd_pcm_uframes_t offset, frames;
353 int16_t *dst, *zeros;
354 int n_bytes;
355
356 if ((err = snd_pcm_open(&handle, play_dev,
357 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
358 fprintf(stderr, "cannot open audio device %s (%s)\n",
359 play_dev, snd_strerror(err));
360 exit(1);
361 }
362
363 pcm_init(handle);
364
365 /* Write nonzero values into buffer. */
366 pcm_fill(handle, buffer_frames, 1);
367
368 /* Play for some periods. */
369 wait_for_periods(handle, wait_periods);
370
371 /* Get the mmap area. */
372 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
373 if (err < 0) {
374 fprintf(stderr, "cannot mmap begin (%s)\n", snd_strerror(err));
375 exit(1);
376 }
377
378 /* Fill whole buffer with zeros without committing it.
379 * The number of bytes is buffer_frames * channel * 2 (16 bit sample) */
380 n_bytes = buffer_frames * channels * 2;
381 memset((int8_t *)my_areas[0].addr, 0, n_bytes);
382 printf("Filled mmap buffer with zeros\n");
383
384 /* Play for some periods. */
385 wait_for_periods(handle, wait_periods);
386
387 /* Check the samples in buffer are all zeros. */
388 err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
389 if (err < 0) {
390 fprintf(stderr, "cannot mmap begin the second time (%s)\n",
391 snd_strerror(err));
392 exit(1);
393 }
394 dst = (int16_t *)my_areas[0].addr;
395
396 zeros = calloc(buffer_frames * channels, sizeof(zeros[0]));
397
398 if (memcmp(zeros, dst, n_bytes)) {
399 fprintf(stderr, "buffer is not all zeros\n");
400 free(zeros);
401 exit(1);
402 }
403 free(zeros);
404 printf("Buffer is filled with zeros\n");
405}
406
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800407int main(int argc, char *argv[])
408{
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800409 int c, drop_test = 0, move_test = 0, fill_test = 0;
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800410 const char *short_opt = "hd:rm";
411 struct option long_opt[] =
412 {
413 {"help", no_argument, NULL, 'h'},
414 {"device", required_argument, NULL, 'd'},
415 {"drop", no_argument, NULL, 'r'},
416 {"move", no_argument, NULL, 'm'},
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800417 {"fill", no_argument, NULL, 'f'},
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800418 {NULL, 0, NULL, 0 }
419 };
420
421 while(1) {
422 c = getopt_long(argc, argv, short_opt, long_opt, NULL);
423 if (c == -1)
424 break;
425 switch(c) {
426 case 'd':
427 play_dev = optarg;
428 printf("Assign play_dev to %s\n", play_dev);
429 break;
430
431 case 'r':
432 drop_test = 1;
433 printf("Test snd_pcm_drop\n");
434 break;
435
436 case 'm':
437 move_test = 1;
438 printf("Test snd_pcm_forward\n");
439 break;
440
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800441 case 'f':
442 fill_test = 1;
443 printf("Test snd_pcm_mmap_begin and filling buffer.\n");
444 break;
445
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800446 case 'h':
447 printf("Usage: %s [OPTIONS]\n", argv[0]);
448 printf(" --device <Device> Device, default to hw:0,0\n");
449 printf(" -h, --help Print this help and exit\n");
450 printf(" --drop Test snd_pcm_drop\n");
451 printf(" --move Test snd_pcm_forward\n");
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800452 printf(" --fill Test snd_pcm_mmap_begin\n");
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800453 printf("\n");
454 return(0);
455 break;
456
457 case ':':
458 case '?':
459 fprintf(stderr, "Try `%s --help' for more information.\n",
460 argv[0]);
461 exit(EXIT_FAILURE);
462
463 default:
464 fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);
465 fprintf(stderr, "Try `%s --help' for more information.\n",
466 argv[0]);
467 exit(EXIT_FAILURE);
468 }
469 }
470
471 if (drop_test) {
472 alsa_drop_test();
473 exit(0);
474 }
475
476 if (move_test) {
477 alsa_move_test();
478 exit(0);
479 }
480
Cheng-Yi Chiang16c4c192016-05-03 12:28:42 +0800481 if (fill_test) {
482 alsa_fill_test();
483 exit(0);
484 }
485
Cheng-Yi Chiangb8755a32016-04-13 10:17:56 +0800486 exit(0);
487}