1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <alsa/asoundlib.h>
7 #include <alsa/pcm_external.h>
8 #include <cras_client.h>
9 #include <sys/socket.h>
10
11 /* Holds configuration for the alsa plugin.
12 * io - ALSA ioplug object.
13 * fd - Wakes users with polled io.
14 * stream_playing - Indicates if the stream is playing/capturing.
15 * hw_ptr - Current read or write position.
16 * channels - Number of channels.
17 * stream_id - CRAS ID of the playing/capturing stream.
18 * bytes_per_frame - number of bytes in an audio frame.
19 * direction - input or output.
20 * areas - ALSA areas used to read from/write to.
21 * client - CRAS client object.
22 * capture_sample_index - The sample tracked for capture latency calculation.
23 * playback_sample_index - The sample tracked for playback latency calculation.
24 * capture_sample_time - The time when capture_sample_index was captured.
25 * playback_sample_time - The time when playback_sample_index was captured.
26 */
27 struct snd_pcm_cras {
28 snd_pcm_ioplug_t io;
29 int fd;
30 int stream_playing;
31 unsigned int hw_ptr;
32 unsigned int channels;
33 cras_stream_id_t stream_id;
34 size_t bytes_per_frame;
35 enum CRAS_STREAM_DIRECTION direction;
36 snd_pcm_channel_area_t *areas;
37 struct cras_client *client;
38 int capture_sample_index;
39 int playback_sample_index;
40 struct timespec capture_sample_time;
41 struct timespec playback_sample_time;
42 };
43
44 /* Frees all resources allocated during use. */
snd_pcm_cras_free(struct snd_pcm_cras * pcm_cras)45 static void snd_pcm_cras_free(struct snd_pcm_cras *pcm_cras)
46 {
47 if (pcm_cras == NULL)
48 return;
49 assert(!pcm_cras->stream_playing);
50 if (pcm_cras->fd >= 0)
51 close(pcm_cras->fd);
52 if (pcm_cras->io.poll_fd >= 0)
53 close(pcm_cras->io.poll_fd);
54 cras_client_destroy(pcm_cras->client);
55 free(pcm_cras->areas);
56 free(pcm_cras);
57 }
58
59 /* Stops a playing or capturing CRAS plugin. */
snd_pcm_cras_stop(snd_pcm_ioplug_t * io)60 static int snd_pcm_cras_stop(snd_pcm_ioplug_t *io)
61 {
62 struct snd_pcm_cras *pcm_cras = io->private_data;
63
64 if (pcm_cras->stream_playing) {
65 cras_client_rm_stream(pcm_cras->client, pcm_cras->stream_id);
66 cras_client_stop(pcm_cras->client);
67 pcm_cras->stream_playing = 0;
68 }
69 return 0;
70 }
71
72 /* Close a CRAS plugin opened with snd_pcm_cras_open. */
snd_pcm_cras_close(snd_pcm_ioplug_t * io)73 static int snd_pcm_cras_close(snd_pcm_ioplug_t *io)
74 {
75 struct snd_pcm_cras *pcm_cras = io->private_data;
76
77 if (pcm_cras->stream_playing)
78 snd_pcm_cras_stop(io);
79 snd_pcm_cras_free(pcm_cras);
80 return 0;
81 }
82
83 /* Poll callback used to wait for data ready (playback) or space available
84 * (capture). */
snd_pcm_cras_poll_revents(snd_pcm_ioplug_t * io,struct pollfd * pfds,unsigned int nfds,unsigned short * revents)85 static int snd_pcm_cras_poll_revents(snd_pcm_ioplug_t *io,
86 struct pollfd *pfds,
87 unsigned int nfds,
88 unsigned short *revents)
89 {
90 static char buf[1];
91 int rc;
92
93 if (pfds == NULL || nfds != 1 || revents == NULL)
94 return -EINVAL;
95 rc = read(pfds[0].fd, buf, 1);
96 if (rc < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
97 fprintf(stderr, "%s read failed %d\n", __func__, errno);
98 return errno;
99 }
100 *revents = pfds[0].revents & ~(POLLIN | POLLOUT);
101 if (pfds[0].revents & POLLIN)
102 *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT
103 : POLLIN;
104 return 0;
105 }
106
107 /* Callback to return the location of the write (playback) or read (capture)
108 * pointer. */
snd_pcm_cras_pointer(snd_pcm_ioplug_t * io)109 static snd_pcm_sframes_t snd_pcm_cras_pointer(snd_pcm_ioplug_t *io)
110 {
111 struct snd_pcm_cras *pcm_cras = io->private_data;
112 return pcm_cras->hw_ptr;
113 }
114
115 /* Main callback for processing audio. This is called by CRAS when more samples
116 * are needed (playback) or ready (capture). Copies bytes between ALSA and CRAS
117 * buffers. */
pcm_cras_process_cb(struct cras_client * client,cras_stream_id_t stream_id,uint8_t * capture_samples,uint8_t * playback_samples,unsigned int nframes,const struct timespec * capture_ts,const struct timespec * playback_ts,void * arg)118 static int pcm_cras_process_cb(struct cras_client *client,
119 cras_stream_id_t stream_id,
120 uint8_t *capture_samples,
121 uint8_t *playback_samples,
122 unsigned int nframes,
123 const struct timespec *capture_ts,
124 const struct timespec *playback_ts,
125 void *arg)
126 {
127 snd_pcm_ioplug_t *io;
128 struct snd_pcm_cras *pcm_cras;
129 const snd_pcm_channel_area_t *areas;
130 snd_pcm_uframes_t copied_frames;
131 char dummy_byte;
132 size_t chan, frame_bytes, sample_bytes;
133 int rc;
134 uint8_t *samples;
135 const struct timespec *sample_time;
136
137 samples = capture_samples ? : playback_samples;
138 sample_time = capture_ts ? : playback_ts;
139
140 io = (snd_pcm_ioplug_t *)arg;
141 pcm_cras = (struct snd_pcm_cras *)io->private_data;
142 frame_bytes = pcm_cras->bytes_per_frame;
143 sample_bytes = snd_pcm_format_physical_width(io->format) / 8;
144
145 if (io->stream == SND_PCM_STREAM_PLAYBACK) {
146 if (io->state != SND_PCM_STATE_RUNNING &&
147 io->state != SND_PCM_STATE_DRAINING) {
148 memset(samples, 0, nframes * frame_bytes);
149 return nframes;
150 }
151 /* Only take one period of data at a time. */
152 if (nframes > io->period_size)
153 nframes = io->period_size;
154
155 /* Keep track of the first transmitted sample index and the time
156 * it will be played. */
157 pcm_cras->playback_sample_index = io->hw_ptr;
158 pcm_cras->playback_sample_time = *sample_time;
159 } else {
160 /* Keep track of the first read sample index and the time it
161 * was captured. */
162 pcm_cras->capture_sample_index = io->hw_ptr;
163 pcm_cras->capture_sample_time = *sample_time;
164 }
165
166 /* CRAS always takes interleaved samples. */
167 for (chan = 0; chan < io->channels; chan++) {
168 pcm_cras->areas[chan].addr = samples + chan * sample_bytes;
169 pcm_cras->areas[chan].first = 0;
170 pcm_cras->areas[chan].step =
171 snd_pcm_format_physical_width(io->format) *
172 io->channels;
173 }
174
175 areas = snd_pcm_ioplug_mmap_areas(io);
176
177 copied_frames = 0;
178 while (copied_frames < nframes) {
179 snd_pcm_uframes_t frames = nframes - copied_frames;
180 snd_pcm_uframes_t remain = io->buffer_size - pcm_cras->hw_ptr;
181
182 if (frames > remain)
183 frames = remain;
184
185 for (chan = 0; chan < io->channels; chan++)
186 if (io->stream == SND_PCM_STREAM_PLAYBACK)
187 snd_pcm_area_copy(&pcm_cras->areas[chan],
188 copied_frames,
189 &areas[chan],
190 pcm_cras->hw_ptr,
191 frames,
192 io->format);
193 else
194 snd_pcm_area_copy(&areas[chan],
195 pcm_cras->hw_ptr,
196 &pcm_cras->areas[chan],
197 copied_frames,
198 frames,
199 io->format);
200
201 pcm_cras->hw_ptr += frames;
202 pcm_cras->hw_ptr %= io->buffer_size;
203 copied_frames += frames;
204 }
205
206 rc = write(pcm_cras->fd, &dummy_byte, 1); /* Wake up polling clients. */
207 if (rc < 0 && errno != EWOULDBLOCK && errno != EAGAIN)
208 fprintf(stderr, "%s write failed %d\n", __func__, errno);
209
210 return nframes;
211 }
212
213 /* Callback from CRAS for stream errors. */
pcm_cras_error_cb(struct cras_client * client,cras_stream_id_t stream_id,int err,void * arg)214 static int pcm_cras_error_cb(struct cras_client *client,
215 cras_stream_id_t stream_id,
216 int err,
217 void *arg)
218 {
219 fprintf(stderr, "Stream error %d\n", err);
220 return 0;
221 }
222
223 /* ALSA calls this automatically when the stream enters the
224 * SND_PCM_STATE_PREPARED state. */
snd_pcm_cras_prepare(snd_pcm_ioplug_t * io)225 static int snd_pcm_cras_prepare(snd_pcm_ioplug_t *io)
226 {
227 struct snd_pcm_cras *pcm_cras = io->private_data;
228
229 return cras_client_connect(pcm_cras->client);
230 }
231
232 /* Called when an ALSA stream is started. */
snd_pcm_cras_start(snd_pcm_ioplug_t * io)233 static int snd_pcm_cras_start(snd_pcm_ioplug_t *io)
234 {
235 struct snd_pcm_cras *pcm_cras = io->private_data;
236 struct cras_stream_params *params;
237 struct cras_audio_format *audio_format;
238 int rc;
239
240 audio_format = cras_audio_format_create(io->format, io->rate,
241 io->channels);
242 if (audio_format == NULL)
243 return -ENOMEM;
244
245 params = cras_client_unified_params_create(
246 pcm_cras->direction,
247 io->period_size,
248 0,
249 0,
250 io,
251 pcm_cras_process_cb,
252 pcm_cras_error_cb,
253 audio_format);
254 if (params == NULL) {
255 rc = -ENOMEM;
256 goto error_out;
257 }
258
259 rc = cras_client_run_thread(pcm_cras->client);
260 if (rc < 0)
261 goto error_out;
262
263 pcm_cras->bytes_per_frame =
264 cras_client_format_bytes_per_frame(audio_format);
265
266 rc = cras_client_add_stream(pcm_cras->client,
267 &pcm_cras->stream_id,
268 params);
269 if (rc < 0) {
270 fprintf(stderr, "CRAS add failed\n");
271 goto error_out;
272 }
273 pcm_cras->stream_playing = 1;
274
275 error_out:
276 cras_audio_format_destroy(audio_format);
277 cras_client_stream_params_destroy(params);
278 return rc;
279 }
280
281 static snd_pcm_ioplug_callback_t cras_pcm_callback = {
282 .close = snd_pcm_cras_close,
283 .start = snd_pcm_cras_start,
284 .stop = snd_pcm_cras_stop,
285 .pointer = snd_pcm_cras_pointer,
286 .prepare = snd_pcm_cras_prepare,
287 .poll_revents = snd_pcm_cras_poll_revents,
288 };
289
290 /* Set constraints for hw_params. This lists the handled formats, sample rates,
291 * access patters, and buffer/period sizes. These are enforce in
292 * snd_pcm_set_params(). */
set_hw_constraints(struct snd_pcm_cras * pcm_cras)293 static int set_hw_constraints(struct snd_pcm_cras *pcm_cras)
294 {
295 static const unsigned int access_list[] = {
296 SND_PCM_ACCESS_MMAP_INTERLEAVED,
297 SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
298 SND_PCM_ACCESS_RW_INTERLEAVED,
299 SND_PCM_ACCESS_RW_NONINTERLEAVED
300 };
301 static const unsigned int format_list[] = {
302 SND_PCM_FORMAT_U8,
303 SND_PCM_FORMAT_S16_LE,
304 SND_PCM_FORMAT_S24_LE,
305 SND_PCM_FORMAT_S32_LE,
306 SND_PCM_FORMAT_S24_3LE,
307 };
308 int rc;
309
310 rc = snd_pcm_ioplug_set_param_list(&pcm_cras->io,
311 SND_PCM_IOPLUG_HW_ACCESS,
312 ARRAY_SIZE(access_list),
313 access_list);
314 if (rc < 0)
315 return rc;
316 rc = snd_pcm_ioplug_set_param_list(&pcm_cras->io,
317 SND_PCM_IOPLUG_HW_FORMAT,
318 ARRAY_SIZE(format_list),
319 format_list);
320 if (rc < 0)
321 return rc;
322 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
323 SND_PCM_IOPLUG_HW_CHANNELS,
324 1,
325 pcm_cras->channels);
326 if (rc < 0)
327 return rc;
328 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
329 SND_PCM_IOPLUG_HW_RATE,
330 8000,
331 48000);
332 if (rc < 0)
333 return rc;
334 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
335 SND_PCM_IOPLUG_HW_BUFFER_BYTES,
336 64,
337 2 * 1024 * 1024);
338 if (rc < 0)
339 return rc;
340 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
341 SND_PCM_IOPLUG_HW_PERIOD_BYTES,
342 64,
343 2 * 1024 * 1024);
344 if (rc < 0)
345 return rc;
346 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
347 SND_PCM_IOPLUG_HW_PERIODS,
348 1,
349 2048);
350 return rc;
351 }
352
353 /* Called by snd_pcm_open(). Creates a CRAS client and an ioplug plugin. */
snd_pcm_cras_open(snd_pcm_t ** pcmp,const char * name,snd_pcm_stream_t stream,int mode)354 static int snd_pcm_cras_open(snd_pcm_t **pcmp, const char *name,
355 snd_pcm_stream_t stream, int mode)
356 {
357 struct snd_pcm_cras *pcm_cras;
358 int rc;
359 int fd[2];
360
361 assert(pcmp);
362 pcm_cras = calloc(1, sizeof(*pcm_cras));
363 if (!pcm_cras)
364 return -ENOMEM;
365
366 pcm_cras->fd = -1;
367 pcm_cras->io.poll_fd = -1;
368 pcm_cras->channels = 2;
369 pcm_cras->direction = (stream == SND_PCM_STREAM_PLAYBACK)
370 ? CRAS_STREAM_OUTPUT : CRAS_STREAM_INPUT;
371
372 rc = cras_client_create(&pcm_cras->client);
373 if (rc != 0 || pcm_cras->client == NULL) {
374 fprintf(stderr, "Couldn't create CRAS client\n");
375 free(pcm_cras);
376 return rc;
377 }
378
379 pcm_cras->areas = calloc(pcm_cras->channels,
380 sizeof(snd_pcm_channel_area_t));
381 if (pcm_cras->areas == NULL) {
382 snd_pcm_cras_free(pcm_cras);
383 return -ENOMEM;
384 }
385
386 socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
387
388 cras_make_fd_nonblocking(fd[0]);
389 cras_make_fd_nonblocking(fd[1]);
390
391 pcm_cras->fd = fd[0];
392
393 pcm_cras->io.version = SND_PCM_IOPLUG_VERSION;
394 pcm_cras->io.name = "ALSA to CRAS Plugin";
395 pcm_cras->io.callback = &cras_pcm_callback;
396 pcm_cras->io.private_data = pcm_cras;
397 pcm_cras->io.poll_fd = fd[1];
398 pcm_cras->io.poll_events = POLLIN;
399 pcm_cras->io.mmap_rw = 1;
400
401 rc = snd_pcm_ioplug_create(&pcm_cras->io, name, stream, mode);
402 if (rc < 0) {
403 snd_pcm_cras_free(pcm_cras);
404 return rc;
405 }
406
407 rc = set_hw_constraints(pcm_cras);
408 if (rc < 0) {
409 snd_pcm_ioplug_delete(&pcm_cras->io);
410 return rc;
411 }
412
413 *pcmp = pcm_cras->io.pcm;
414
415 return 0;
416 }
417
418
SND_PCM_PLUGIN_DEFINE_FUNC(cras)419 SND_PCM_PLUGIN_DEFINE_FUNC(cras)
420 {
421 return snd_pcm_cras_open(pcmp, name, stream, mode);
422 }
423
424 SND_PCM_PLUGIN_SYMBOL(cras);
425