• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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