blob: 55a47401846c07ab6978e87e57bc15386bb62bf2 [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. */
25static void pcm_fill_zeros(snd_pcm_t *handle, snd_pcm_uframes_t frames) {
26 short *play_buf;
27 int err;
28
29 play_buf = calloc(frames * channels, sizeof(play_buf[0]));
30 printf("Write %d into device\n", (int)frames);
31
32 if ((err = snd_pcm_writei(handle, play_buf, frames))
33 != frames) {
34 fprintf(stderr, "write to audio interface failed (%s)\n",
35 snd_strerror(err));
36 }
37
38 free(play_buf);
39}
40
41static void pcm_hw_param(snd_pcm_t *handle) {
42 int err;
43 snd_pcm_hw_params_t *hw_params;
44
45 if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
46 fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
47 snd_strerror(err));
48 exit(1);
49 }
50
51 if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
52 fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
53 snd_strerror(err));
54 exit(1);
55 }
56
57 if ((err = snd_pcm_hw_params_set_access(handle, hw_params,
58 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
59 fprintf(stderr, "cannot set access type (%s)\n",
60 snd_strerror(err));
61 exit(1);
62 }
63
64 if ((err = snd_pcm_hw_params_set_format(handle, hw_params,
65 format)) < 0) {
66 fprintf(stderr, "cannot set sample format (%s)\n",
67 snd_strerror(err));
68 exit(1);
69 }
70
71 if ((err = snd_pcm_hw_params_set_rate_near(
72 handle, hw_params, &rate, 0)) < 0) {
73 fprintf(stderr, "cannot set sample rate (%s)\n",
74 snd_strerror(err));
75 exit(1);
76 }
77
78 if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, 2)) < 0) {
79 fprintf(stderr, "cannot set channel count (%s)\n",
80 snd_strerror(err));
81 exit(1);
82 }
83
84 /* Makes sure buffer frames is even, or snd_pcm_hw_params will
85 * return invalid argument error. */
86 if ((err = snd_pcm_hw_params_get_buffer_size_max(
87 hw_params, &buffer_frames)) < 0) {
88 fprintf(stderr, "get buffer max (%s)\n", snd_strerror(err));
89 exit(1);
90 }
91
92 buffer_frames &= ~0x01;
93 if ((err = snd_pcm_hw_params_set_buffer_size_max(
94 handle, hw_params, &buffer_frames)) < 0) {
95 fprintf(stderr, "set_buffer_size_max (%s)\n", snd_strerror(err));
96 exit(1);
97 }
98
99 printf("buffer size set to %u\n", (unsigned int)buffer_frames);
100
101 if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) {
102 fprintf(stderr, "cannot set parameters (%s)\n",
103 snd_strerror(err));
104 exit(1);
105 }
106
107 snd_pcm_hw_params_free(hw_params);
108}
109
110static void pcm_sw_param(snd_pcm_t *handle) {
111 int err;
112 snd_pcm_sw_params_t *swparams;
113 snd_pcm_uframes_t boundary;
114
115 snd_pcm_sw_params_alloca(&swparams);
116
117 err = snd_pcm_sw_params_current(handle, swparams);
118 if (err < 0) {
119 fprintf(stderr, "sw_params_current: %s\n", snd_strerror(err));
120 exit(1);
121 }
122
123 err = snd_pcm_sw_params_get_boundary(swparams, &boundary);
124 if (err < 0) {
125 fprintf(stderr, "get_boundary: %s\n", snd_strerror(err));
126 exit(1);
127 }
128
129 err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, boundary);
130 if (err < 0) {
131 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
132 exit(1);
133 }
134
135 /* Don't auto start. */
136 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, boundary);
137 if (err < 0) {
138 fprintf(stderr, "set_stop_threshold: %s\n", snd_strerror(err));
139 exit(1);
140 }
141
142 /* Disable period events. */
143 err = snd_pcm_sw_params_set_period_event(handle, swparams, 0);
144 if (err < 0) {
145 fprintf(stderr, "set_period_event: %s\n", snd_strerror(err));
146 exit(1);
147 }
148
149 err = snd_pcm_sw_params(handle, swparams);
150 if (err < 0) {
151 fprintf(stderr, "sw_params: %s\n", snd_strerror(err));
152 exit(1);
153 }
154}
155
156static void pcm_init(snd_pcm_t *handle)
157{
158 int err;
159 pcm_hw_param(handle);
160 pcm_sw_param(handle);
161
162 if ((err = snd_pcm_prepare(handle)) < 0) {
163 fprintf(stderr, "cannot prepare audio interface (%s)\n",
164 snd_strerror(err));
165 exit(1);
166 }
167
168 if ((err = snd_pcm_start(handle)) < 0) {
169 fprintf(stderr, "cannot start audio interface (%s)\n",
170 snd_strerror(err));
171 exit(1);
172 }
173}
174
175/* Waits for target_periods periods and logs time stamp and snd_pcm_avail
176 * value in each period.
177 */
178static void wait_for_periods(snd_pcm_t *handle, unsigned int target_periods)
179{
180 unsigned int num_periods = 0;
181 unsigned int wake_period_us = period_size * 1E6 / rate;
182 struct timespec now;
183 snd_pcm_sframes_t avail_frames;
184 while (num_periods < target_periods) {
185 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
186 printf("time: %ld.%09ld", (long)now.tv_sec, (long)now.tv_nsec);
187 avail_frames = snd_pcm_avail(handle);
188 printf(" state: %d, avail frames: %d, hw_level: %d\n",
189 (int)snd_pcm_state(handle), (int)avail_frames,
190 (int)(buffer_frames - avail_frames));
191 num_periods++;
192 usleep(wake_period_us);
193 }
194}
195
196void check_hw_level_in_range(snd_pcm_sframes_t hw_level, int min, int max){
197 printf("Expected range: %d - %d\n", min, max);
198 if (hw_level <= max && hw_level >= min) {
199 printf("hw_level is in the expected range\n");
200 } else {
201 fprintf(stderr,
202 "hw_level is not in the expected range\n");
203 exit(1);
204 }
205}
206
207
208void alsa_move_test()
209{
210 int err;
211 snd_pcm_t *handle;
212 snd_pcm_sframes_t to_move, hw_level, avail_frames;
213 unsigned int wait_periods = 1000;
214 snd_pcm_sframes_t fuzz = 50;
215 int periods_after_move = 10;
216
217 if ((err = snd_pcm_open(&handle, play_dev,
218 SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
219 fprintf(stderr, "cannot open audio device %s (%s)\n",
220 play_dev, snd_strerror(err));
221 exit(1);
222 }
223
224 pcm_init(handle);
225
226 pcm_fill_zeros(handle, buffer_frames);
227
228 wait_for_periods(handle, wait_periods);
229
230 /* We want to move appl_ptr to hw_ptr plus fuzz such that hardware can
231 * play the new samples as quick as possible.
232 * avail = buffer_frames - appl_ptr + hw_ptr
233 * => hw_ptr - appl_ptr = avail - buffer_frames.
234 * The difference between hw_ptr and app can be inferred from snd_pcm_avail.
235 * So the amount of frames to forward appl_ptr is
236 * avail - buffer_frames + fuzz.
237 */
238 to_move = snd_pcm_avail(handle) - buffer_frames + fuzz;
239 printf("Frames to move appl_ptr forward: %d\n", (int)to_move);
240
241 /* Move appl_ptr forward so it becomes leading hw_ptr by fuzz. */
242 if ((err = snd_pcm_forward(handle, to_move)) < 0) {
243 fprintf(stderr, "cannot move appl ptr forward (%s)\n",
244 snd_strerror(err));
245 exit(1);
246 }
247
248 /* Checks the result after moving. The hw_level should be in the range
249 * 0 - fuzz. */
250 avail_frames = snd_pcm_avail(handle);
251 printf("Available frames after move: %d\n", (int)avail_frames);
252 hw_level = buffer_frames - avail_frames;
253 printf("hw_level after moving: %d\n", (int)hw_level);
254
255 check_hw_level_in_range(hw_level, 0, fuzz);
256
257 /* Fills some zeros after moving to make sure PCM still plays fine. */
258 pcm_fill_zeros(handle, period_size * periods_after_move);
259 hw_level = buffer_frames - snd_pcm_avail(handle);
260 printf("hw_level after filling %d period is %d\n",
261 periods_after_move, (int)hw_level);
262
263 wait_for_periods(handle, periods_after_move - 1);
264 hw_level = buffer_frames - snd_pcm_avail(handle);
265 printf("hw_level after playing %d period is %d\n",
266 periods_after_move, (int)hw_level);
267
268 /* After playing for periods_after_move - 1 periods, the hw_level
269 * should be less than one period. */
270 check_hw_level_in_range(hw_level, 0, period_size);
271}
272
273/* Checks if snd_pcm_drop resets the hw_ptr to 0. See bug crosbug.com/p/51882.
274 */
275void alsa_drop_test()
276{
277 int err;
278 snd_pcm_t *handle;
279 snd_pcm_sframes_t frames;
280 snd_pcm_sframes_t fuzz = 50;
281 unsigned int wait_periods = 50;
282
283 if ((err = snd_pcm_open(
284 &handle, play_dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
285 fprintf(stderr, "cannot open audio device %s (%s)\n",
286 play_dev, snd_strerror(err));
287 exit(1);
288 }
289
290 pcm_init(handle);
291
292 pcm_fill_zeros(handle, buffer_frames);
293
294 wait_for_periods(handle, wait_periods);
295
296 /* Drop the samples. */
297 if ((err = snd_pcm_drop(handle)) < 0) {
298 fprintf(stderr, "cannot drop audio interface (%s)\n",
299 snd_strerror(err));
300 exit(1);
301 }
302
303 /* Prepare and start playback again. */
304 if ((err = snd_pcm_prepare(handle)) < 0) {
305 fprintf(stderr, "cannot prepare audio interface (%s)\n",
306 snd_strerror(err));
307 exit(1);
308 }
309
310 if ((err = snd_pcm_start(handle)) < 0) {
311 fprintf(stderr, "cannot start for the second time (%s)\n",
312 snd_strerror(err));
313 exit(1);
314 }
315
316 /* The avail should be a reasonable value that is slightly larger than
317 * buffer level. avail = buffer - (appl_ptr - hw_ptr).
318 * The expected behavior:
319 * snd_pcm_drop: hw_ptr should be 0.
320 * snd_pcm_prepare: appl_ptr should be the same as hw_ptr, which is 0.
321 * snd_pcm_start: hw_ptr gets synced to hardware, should be a small number.
322 * So, the new avail should be slightly larger than buffer. */
323 frames = snd_pcm_avail(handle);
324
325 printf("Avail frames after drop, prepare, start: %d\n", (int)frames);
326
327 if ((err = snd_pcm_close(handle)) < 0) {
328 fprintf(stderr, "cannot close device (%s)\n", snd_strerror(err));
329 exit(1);
330 }
331
332 printf("Expected avail frames after drop, prepare, start is 0 - %d\n",
333 (int)(buffer_frames + fuzz));
334
335 if (frames < 0 || frames > buffer_frames + fuzz) {
336 fprintf(stderr, "Avail frames after drop, prepare, start is %d\n",
337 (int)frames);
338 exit(1);
339 }
340}
341
342int main(int argc, char *argv[])
343{
344 int c, drop_test = 0, move_test = 0;
345 const char *short_opt = "hd:rm";
346 struct option long_opt[] =
347 {
348 {"help", no_argument, NULL, 'h'},
349 {"device", required_argument, NULL, 'd'},
350 {"drop", no_argument, NULL, 'r'},
351 {"move", no_argument, NULL, 'm'},
352 {NULL, 0, NULL, 0 }
353 };
354
355 while(1) {
356 c = getopt_long(argc, argv, short_opt, long_opt, NULL);
357 if (c == -1)
358 break;
359 switch(c) {
360 case 'd':
361 play_dev = optarg;
362 printf("Assign play_dev to %s\n", play_dev);
363 break;
364
365 case 'r':
366 drop_test = 1;
367 printf("Test snd_pcm_drop\n");
368 break;
369
370 case 'm':
371 move_test = 1;
372 printf("Test snd_pcm_forward\n");
373 break;
374
375 case 'h':
376 printf("Usage: %s [OPTIONS]\n", argv[0]);
377 printf(" --device <Device> Device, default to hw:0,0\n");
378 printf(" -h, --help Print this help and exit\n");
379 printf(" --drop Test snd_pcm_drop\n");
380 printf(" --move Test snd_pcm_forward\n");
381 printf("\n");
382 return(0);
383 break;
384
385 case ':':
386 case '?':
387 fprintf(stderr, "Try `%s --help' for more information.\n",
388 argv[0]);
389 exit(EXIT_FAILURE);
390
391 default:
392 fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);
393 fprintf(stderr, "Try `%s --help' for more information.\n",
394 argv[0]);
395 exit(EXIT_FAILURE);
396 }
397 }
398
399 if (drop_test) {
400 alsa_drop_test();
401 exit(0);
402 }
403
404 if (move_test) {
405 alsa_move_test();
406 exit(0);
407 }
408
409 exit(0);
410}