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, struct pollfd *pfds,
86 unsigned int nfds, unsigned short *revents)
87 {
88 static char buf[1];
89 int rc;
90
91 if (pfds == NULL || nfds != 1 || revents == NULL)
92 return -EINVAL;
93 rc = read(pfds[0].fd, buf, 1);
94 if (rc < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
95 fprintf(stderr, "%s read failed %d\n", __func__, errno);
96 return errno;
97 }
98 *revents = pfds[0].revents & ~(POLLIN | POLLOUT);
99 if (pfds[0].revents & POLLIN)
100 *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT :
101 POLLIN;
102 return 0;
103 }
104
105 /* Callback to return the location of the write (playback) or read (capture)
106 * pointer. */
snd_pcm_cras_pointer(snd_pcm_ioplug_t * io)107 static snd_pcm_sframes_t snd_pcm_cras_pointer(snd_pcm_ioplug_t *io)
108 {
109 struct snd_pcm_cras *pcm_cras = io->private_data;
110 return pcm_cras->hw_ptr;
111 }
112
113 /* Main callback for processing audio. This is called by CRAS when more samples
114 * are needed (playback) or ready (capture). Copies bytes between ALSA and CRAS
115 * 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)116 static int pcm_cras_process_cb(struct cras_client *client,
117 cras_stream_id_t stream_id,
118 uint8_t *capture_samples,
119 uint8_t *playback_samples, unsigned int nframes,
120 const struct timespec *capture_ts,
121 const struct timespec *playback_ts, void *arg)
122 {
123 snd_pcm_ioplug_t *io;
124 struct snd_pcm_cras *pcm_cras;
125 const snd_pcm_channel_area_t *areas;
126 snd_pcm_uframes_t copied_frames;
127 char dummy_byte;
128 size_t chan, frame_bytes, sample_bytes;
129 int rc;
130 uint8_t *samples;
131 const struct timespec *sample_time;
132
133 samples = capture_samples ?: playback_samples;
134 sample_time = capture_ts ?: playback_ts;
135
136 io = (snd_pcm_ioplug_t *)arg;
137 pcm_cras = (struct snd_pcm_cras *)io->private_data;
138 frame_bytes = pcm_cras->bytes_per_frame;
139 sample_bytes = snd_pcm_format_physical_width(io->format) / 8;
140
141 if (io->stream == SND_PCM_STREAM_PLAYBACK) {
142 if (io->state != SND_PCM_STATE_RUNNING &&
143 io->state != SND_PCM_STATE_DRAINING) {
144 memset(samples, 0, nframes * frame_bytes);
145 return nframes;
146 }
147 /* Only take one period of data at a time. */
148 if (nframes > io->period_size)
149 nframes = io->period_size;
150
151 /* Keep track of the first transmitted sample index and the time
152 * it will be played. */
153 pcm_cras->playback_sample_index = io->hw_ptr;
154 pcm_cras->playback_sample_time = *sample_time;
155 } else {
156 /* Keep track of the first read sample index and the time it
157 * was captured. */
158 pcm_cras->capture_sample_index = io->hw_ptr;
159 pcm_cras->capture_sample_time = *sample_time;
160 }
161
162 /* CRAS always takes interleaved samples. */
163 for (chan = 0; chan < io->channels; chan++) {
164 pcm_cras->areas[chan].addr = samples + chan * sample_bytes;
165 pcm_cras->areas[chan].first = 0;
166 pcm_cras->areas[chan].step =
167 snd_pcm_format_physical_width(io->format) *
168 io->channels;
169 }
170
171 areas = snd_pcm_ioplug_mmap_areas(io);
172
173 copied_frames = 0;
174 while (copied_frames < nframes) {
175 snd_pcm_uframes_t frames = nframes - copied_frames;
176 snd_pcm_uframes_t remain = io->buffer_size - pcm_cras->hw_ptr;
177
178 if (frames > remain)
179 frames = remain;
180
181 for (chan = 0; chan < io->channels; chan++)
182 if (io->stream == SND_PCM_STREAM_PLAYBACK)
183 snd_pcm_area_copy(&pcm_cras->areas[chan],
184 copied_frames, &areas[chan],
185 pcm_cras->hw_ptr, frames,
186 io->format);
187 else
188 snd_pcm_area_copy(&areas[chan],
189 pcm_cras->hw_ptr,
190 &pcm_cras->areas[chan],
191 copied_frames, frames,
192 io->format);
193
194 pcm_cras->hw_ptr += frames;
195 pcm_cras->hw_ptr %= io->buffer_size;
196 copied_frames += frames;
197 }
198
199 rc = write(pcm_cras->fd, &dummy_byte, 1); /* Wake up polling clients. */
200 if (rc < 0 && errno != EWOULDBLOCK && errno != EAGAIN)
201 fprintf(stderr, "%s write failed %d\n", __func__, errno);
202
203 return nframes;
204 }
205
206 /* Callback from CRAS for stream errors. */
pcm_cras_error_cb(struct cras_client * client,cras_stream_id_t stream_id,int err,void * arg)207 static int pcm_cras_error_cb(struct cras_client *client,
208 cras_stream_id_t stream_id, int err, void *arg)
209 {
210 fprintf(stderr, "Stream error %d\n", err);
211 return 0;
212 }
213
214 /* ALSA calls this automatically when the stream enters the
215 * SND_PCM_STATE_PREPARED state. */
snd_pcm_cras_prepare(snd_pcm_ioplug_t * io)216 static int snd_pcm_cras_prepare(snd_pcm_ioplug_t *io)
217 {
218 struct snd_pcm_cras *pcm_cras = io->private_data;
219
220 return cras_client_connect(pcm_cras->client);
221 }
222
223 /* Called when an ALSA stream is started. */
snd_pcm_cras_start(snd_pcm_ioplug_t * io)224 static int snd_pcm_cras_start(snd_pcm_ioplug_t *io)
225 {
226 struct snd_pcm_cras *pcm_cras = io->private_data;
227 struct cras_stream_params *params;
228 struct cras_audio_format *audio_format;
229 int rc;
230
231 audio_format =
232 cras_audio_format_create(io->format, io->rate, io->channels);
233 if (audio_format == NULL)
234 return -ENOMEM;
235
236 params = cras_client_unified_params_create(
237 pcm_cras->direction, io->period_size, 0, 0, io,
238 pcm_cras_process_cb, pcm_cras_error_cb, audio_format);
239 if (params == NULL) {
240 rc = -ENOMEM;
241 goto error_out;
242 }
243
244 cras_client_stream_params_set_client_type(params, CRAS_CLIENT_TYPE_PCM);
245
246 rc = cras_client_run_thread(pcm_cras->client);
247 if (rc < 0)
248 goto error_out;
249
250 pcm_cras->bytes_per_frame =
251 cras_client_format_bytes_per_frame(audio_format);
252
253 rc = cras_client_add_stream(pcm_cras->client, &pcm_cras->stream_id,
254 params);
255 if (rc < 0) {
256 fprintf(stderr, "CRAS add failed\n");
257 goto error_out;
258 }
259 pcm_cras->stream_playing = 1;
260
261 error_out:
262 cras_audio_format_destroy(audio_format);
263 cras_client_stream_params_destroy(params);
264 return rc;
265 }
266
267 static snd_pcm_ioplug_callback_t cras_pcm_callback = {
268 .close = snd_pcm_cras_close,
269 .start = snd_pcm_cras_start,
270 .stop = snd_pcm_cras_stop,
271 .pointer = snd_pcm_cras_pointer,
272 .prepare = snd_pcm_cras_prepare,
273 .poll_revents = snd_pcm_cras_poll_revents,
274 };
275
276 /* Set constraints for hw_params. This lists the handled formats, sample rates,
277 * access patters, and buffer/period sizes. These are enforce in
278 * snd_pcm_set_params(). */
set_hw_constraints(struct snd_pcm_cras * pcm_cras)279 static int set_hw_constraints(struct snd_pcm_cras *pcm_cras)
280 {
281 // clang-format off
282 static const unsigned int access_list[] = {
283 SND_PCM_ACCESS_MMAP_INTERLEAVED,
284 SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
285 SND_PCM_ACCESS_RW_INTERLEAVED,
286 SND_PCM_ACCESS_RW_NONINTERLEAVED
287 };
288 static const unsigned int format_list[] = {
289 SND_PCM_FORMAT_U8,
290 SND_PCM_FORMAT_S16_LE,
291 SND_PCM_FORMAT_S24_LE,
292 SND_PCM_FORMAT_S32_LE,
293 SND_PCM_FORMAT_S24_3LE,
294 };
295 // clang-format on
296 int rc;
297
298 rc = snd_pcm_ioplug_set_param_list(&pcm_cras->io,
299 SND_PCM_IOPLUG_HW_ACCESS,
300 ARRAY_SIZE(access_list),
301 access_list);
302 if (rc < 0)
303 return rc;
304 rc = snd_pcm_ioplug_set_param_list(&pcm_cras->io,
305 SND_PCM_IOPLUG_HW_FORMAT,
306 ARRAY_SIZE(format_list),
307 format_list);
308 if (rc < 0)
309 return rc;
310 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
311 SND_PCM_IOPLUG_HW_CHANNELS, 1,
312 pcm_cras->channels);
313 if (rc < 0)
314 return rc;
315 rc = snd_pcm_ioplug_set_param_minmax(
316 &pcm_cras->io, SND_PCM_IOPLUG_HW_RATE, 8000, 48000);
317 if (rc < 0)
318 return rc;
319 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
320 SND_PCM_IOPLUG_HW_BUFFER_BYTES, 64,
321 2 * 1024 * 1024);
322 if (rc < 0)
323 return rc;
324 rc = snd_pcm_ioplug_set_param_minmax(&pcm_cras->io,
325 SND_PCM_IOPLUG_HW_PERIOD_BYTES, 64,
326 2 * 1024 * 1024);
327 if (rc < 0)
328 return rc;
329 rc = snd_pcm_ioplug_set_param_minmax(
330 &pcm_cras->io, SND_PCM_IOPLUG_HW_PERIODS, 1, 2048);
331 return rc;
332 }
333
334 /* 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)335 static int snd_pcm_cras_open(snd_pcm_t **pcmp, const char *name,
336 snd_pcm_stream_t stream, int mode)
337 {
338 struct snd_pcm_cras *pcm_cras;
339 int rc;
340 int fd[2];
341
342 assert(pcmp);
343 pcm_cras = calloc(1, sizeof(*pcm_cras));
344 if (!pcm_cras)
345 return -ENOMEM;
346
347 pcm_cras->fd = -1;
348 pcm_cras->io.poll_fd = -1;
349 pcm_cras->channels = 2;
350 pcm_cras->direction = (stream == SND_PCM_STREAM_PLAYBACK) ?
351 CRAS_STREAM_OUTPUT :
352 CRAS_STREAM_INPUT;
353
354 rc = cras_client_create(&pcm_cras->client);
355 if (rc != 0 || pcm_cras->client == NULL) {
356 fprintf(stderr, "Couldn't create CRAS client\n");
357 free(pcm_cras);
358 return rc;
359 }
360
361 pcm_cras->areas =
362 calloc(pcm_cras->channels, sizeof(snd_pcm_channel_area_t));
363 if (pcm_cras->areas == NULL) {
364 snd_pcm_cras_free(pcm_cras);
365 return -ENOMEM;
366 }
367
368 socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
369
370 cras_make_fd_nonblocking(fd[0]);
371 cras_make_fd_nonblocking(fd[1]);
372
373 pcm_cras->fd = fd[0];
374
375 pcm_cras->io.version = SND_PCM_IOPLUG_VERSION;
376 pcm_cras->io.name = "ALSA to CRAS Plugin";
377 pcm_cras->io.callback = &cras_pcm_callback;
378 pcm_cras->io.private_data = pcm_cras;
379 pcm_cras->io.poll_fd = fd[1];
380 pcm_cras->io.poll_events = POLLIN;
381 pcm_cras->io.mmap_rw = 1;
382
383 rc = snd_pcm_ioplug_create(&pcm_cras->io, name, stream, mode);
384 if (rc < 0) {
385 snd_pcm_cras_free(pcm_cras);
386 return rc;
387 }
388
389 rc = set_hw_constraints(pcm_cras);
390 if (rc < 0) {
391 snd_pcm_ioplug_delete(&pcm_cras->io);
392 return rc;
393 }
394
395 *pcmp = pcm_cras->io.pcm;
396
397 return 0;
398 }
399
SND_PCM_PLUGIN_DEFINE_FUNC(cras)400 SND_PCM_PLUGIN_DEFINE_FUNC(cras)
401 {
402 return snd_pcm_cras_open(pcmp, name, stream, mode);
403 }
404
405 SND_PCM_PLUGIN_SYMBOL(cras);
406