Implement mmap support with STORE and RETRIEVE notifications
[ossp.git] / ossp-alsap.c
blob72f3bd540da8f95cfc66a013127a55c6e9c9a4ea
1 /*
2 * ossp-alsap - ossp DSP slave which forwards to alsa
4 * Copyright (C) 2009 Maarten Lankhorst <m.b.lankhorst@gmail.com>
6 * This file is released under the GPLv2.
8 * Why an alsa plugin as well? Just to show how much
9 * the alsa userspace api sucks ;-)
12 #include <assert.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <getopt.h>
16 #include <libgen.h>
17 #include <limits.h>
18 #include <poll.h>
19 #include <pthread.h>
20 #include <pwd.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <unistd.h>
28 #include <alsa/asoundlib.h>
29 #include <sys/soundcard.h>
31 #include "ossp-slave.h"
33 enum {
34 AFMT_FLOAT = 0x00004000,
35 AFMT_S32_LE = 0x00001000,
36 AFMT_S32_BE = 0x00002000,
39 static size_t page_size;
41 /* alsa structures */
42 static snd_pcm_t *pcm[2];
43 static snd_pcm_hw_params_t *hw_params;
44 static snd_pcm_sw_params_t *sw_params;
45 static int block;
47 static unsigned int byte_counter[2];
48 static snd_pcm_uframes_t mmap_pos[2];
49 static int stream_corked[2];
50 static int stream_notify;
52 static struct format {
53 snd_pcm_format_t format;
54 snd_pcm_sframes_t rate;
55 int channels;
56 } hw_format = { SND_PCM_FORMAT_U8, 8000, 1 };
58 #if 0
59 /* future mmap stuff */
60 static size_t mmap_raw_size, mmap_size;
61 static int mmap_fd[2] = { -1, -1 };
62 static void *mmap_map[2];
63 static uint64_t mmap_idx[2]; /* mmap pointer */
64 static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */
65 static struct ring_buf mmap_stg[2]; /* staging ring buffer */
66 static size_t mmap_lead[2]; /* lead bytes */
67 static int mmap_sync[2]; /* sync with backend stream */
68 #endif
70 static snd_pcm_format_t fmt_oss_to_alsa(int fmt)
72 switch (fmt) {
73 case AFMT_U8: return SND_PCM_FORMAT_U8;
74 case AFMT_A_LAW: return SND_PCM_FORMAT_A_LAW;
75 case AFMT_MU_LAW: return SND_PCM_FORMAT_MU_LAW;
76 case AFMT_S16_LE: return SND_PCM_FORMAT_S16_LE;
77 case AFMT_S16_BE: return SND_PCM_FORMAT_S16_BE;
78 case AFMT_FLOAT: return SND_PCM_FORMAT_FLOAT;
79 case AFMT_S32_LE: return SND_PCM_FORMAT_S32_LE;
80 case AFMT_S32_BE: return SND_PCM_FORMAT_S32_BE;
81 default: return SND_PCM_FORMAT_U8;
85 static int fmt_alsa_to_oss(snd_pcm_format_t fmt)
87 switch (fmt) {
88 case SND_PCM_FORMAT_U8: return AFMT_U8;
89 case SND_PCM_FORMAT_A_LAW: return AFMT_A_LAW;
90 case SND_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW;
91 case SND_PCM_FORMAT_S16_LE: return AFMT_S16_LE;
92 case SND_PCM_FORMAT_S16_BE: return AFMT_S16_BE;
93 case SND_PCM_FORMAT_FLOAT: return AFMT_FLOAT;
94 case SND_PCM_FORMAT_S32_LE: return AFMT_S32_LE;
95 case SND_PCM_FORMAT_S32_BE: return AFMT_S32_BE;
96 default: return AFMT_U8;
100 static void flush_streams(int drain)
102 /* FIXME: snd_pcm_drain appears to be able to deadlock,
103 * always drop or check state? */
104 if (drain) {
105 if (pcm[PLAY])
106 snd_pcm_drain(pcm[PLAY]);
107 if (pcm[REC])
108 snd_pcm_drain(pcm[REC]);
109 } else {
110 if (pcm[PLAY])
111 snd_pcm_drop(pcm[PLAY]);
112 if (pcm[REC])
113 snd_pcm_drop(pcm[REC]);
116 /* XXX: Really needed? */
117 #if 0
118 if (pcm[PLAY]) {
119 snd_pcm_close(pcm[PLAY]);
120 snd_pcm_open(&pcm[PLAY], "default",
121 SND_PCM_STREAM_PLAYBACK, block);
123 if (pcm[REC]) {
124 snd_pcm_close(pcm[REC]);
125 snd_pcm_open(&pcm[REC], "default",
126 SND_PCM_STREAM_CAPTURE, block);
128 #endif
131 static void kill_streams(void)
133 flush_streams(0);
136 static int trigger_streams(int play, int rec)
138 int ret = 0;
140 if (pcm[PLAY] && play >= 0) {
141 ret = snd_pcm_sw_params_set_start_threshold(pcm[PLAY], sw_params,
142 play ? 1 : -1);
143 if (ret >= 0)
144 snd_pcm_sw_params(pcm[PLAY], sw_params);
146 if (ret >= 0 && pcm[REC] && rec >= 0) {
147 ret = snd_pcm_sw_params_set_start_threshold(pcm[REC], sw_params,
148 rec ? 1 : -1);
149 if (ret >= 0)
150 snd_pcm_sw_params(pcm[REC], sw_params);
153 return ret;
156 static ssize_t alsap_mixer(enum ossp_opcode opcode,
157 void *carg, void *din, size_t din_sz,
158 void *rarg, void *dout, size_t *dout_szp, int tfd)
160 return -EBUSY;
163 static int set_hw_params(snd_pcm_t *pcm)
165 int ret;
166 unsigned rate;
168 ret = snd_pcm_hw_params_any(pcm, hw_params);
169 if (ret >= 0)
170 ret = snd_pcm_hw_params_set_access(pcm, hw_params,
171 SND_PCM_ACCESS_RW_INTERLEAVED);
172 rate = hw_format.rate;
173 if (ret >= 0)
174 ret = snd_pcm_hw_params_set_rate_minmax(pcm, hw_params,
175 &rate, NULL,
176 &rate, NULL);
177 if (ret >= 0)
178 ret = snd_pcm_hw_params_set_format(pcm, hw_params, hw_format.format);
179 if (ret >= 0)
180 ret = snd_pcm_hw_params_set_channels(pcm, hw_params,
181 hw_format.channels);
182 if (ret >= 0)
183 ret = snd_pcm_hw_params(pcm, hw_params);
184 if (ret >= 0)
185 ret = snd_pcm_sw_params_current(pcm, sw_params);
186 if (ret >= 0)
187 ret = snd_pcm_sw_params(pcm, sw_params);
188 return ret;
191 static ssize_t alsap_open(enum ossp_opcode opcode,
192 void *carg, void *din, size_t din_sz,
193 void *rarg, void *dout, size_t *dout_szp, int tfd)
195 struct ossp_dsp_open_arg *arg = carg;
196 int ret;
197 block = arg->flags & O_NONBLOCK ? SND_PCM_NONBLOCK : 0;
198 int access;
199 // block |= SND_PCM_ASYNC;
200 /* Woop dee dooo.. I love handling things in SIGIO (PAIN!!)
201 * Probably needed for MMAP
204 if (!hw_params)
205 ret = snd_pcm_hw_params_malloc(&hw_params);
206 if (ret < 0)
207 return ret;
209 if (!sw_params)
210 ret = snd_pcm_sw_params_malloc(&sw_params);
211 if (ret < 0)
212 return ret;
214 if (pcm[PLAY])
215 snd_pcm_close(pcm[PLAY]);
216 if (pcm[REC])
217 snd_pcm_close(pcm[REC]);
218 pcm[REC] = pcm[PLAY] = NULL;
220 access = arg->flags & O_ACCMODE;
221 if (access == O_WRONLY || access == O_RDWR) {
222 ret = snd_pcm_open(&pcm[PLAY], "default",
223 SND_PCM_STREAM_PLAYBACK, block);
224 if (ret >= 0)
225 ret = set_hw_params(pcm[PLAY]);
228 if (ret >= 0 && (access == O_RDONLY || access == O_RDWR)) {
229 ret = snd_pcm_open(&pcm[REC], "default",
230 SND_PCM_STREAM_CAPTURE, block);
231 if (ret >= 0)
232 ret = set_hw_params(pcm[REC]);
235 if (ret < 0) {
236 if (pcm[PLAY])
237 snd_pcm_close(pcm[PLAY]);
238 if (pcm[REC])
239 snd_pcm_close(pcm[REC]);
240 pcm[REC] = pcm[PLAY] = NULL;
241 return ret;
243 return 0;
246 static ssize_t alsap_write(enum ossp_opcode opcode,
247 void *carg, void *din, size_t din_sz,
248 void *rarg, void *dout, size_t *dout_szp, int tfd)
250 // struct ossp_dsp_rw_arg *arg = carg;
251 int ret, insize;
253 insize = snd_pcm_bytes_to_frames(pcm[PLAY], din_sz);
255 if (snd_pcm_state(pcm[PLAY]) == SND_PCM_STATE_SETUP)
256 snd_pcm_prepare(pcm[PLAY]);
258 // snd_pcm_start(pcm[PLAY]);
259 ret = snd_pcm_writei(pcm[PLAY], din, insize);
260 if (ret < 0)
261 ret = snd_pcm_recover(pcm[PLAY], ret, 1);
263 if (ret >= 0)
264 return snd_pcm_frames_to_bytes(pcm[PLAY], ret);
265 else
266 return ret;
269 static ssize_t alsap_read(enum ossp_opcode opcode,
270 void *carg, void *din, size_t din_sz,
271 void *rarg, void *dout, size_t *dout_szp, int tfd)
273 // struct ossp_dsp_rw_arg *arg = carg;
274 int ret, outsize;
276 outsize = snd_pcm_bytes_to_frames(pcm[REC], *dout_szp);
278 if (snd_pcm_state(pcm[REC]) == SND_PCM_STATE_SETUP)
279 snd_pcm_prepare(pcm[REC]);
281 ret = snd_pcm_readi(pcm[REC], dout, outsize);
282 if (ret < 0)
283 ret = snd_pcm_recover(pcm[REC], ret, 1);
284 if (ret >= 0)
285 *dout_szp = ret = snd_pcm_frames_to_bytes(pcm[REC], ret);
286 else
287 *dout_szp = 0;
289 return ret;
292 static ssize_t alsap_poll(enum ossp_opcode opcode,
293 void *carg, void *din, size_t din_sz,
294 void *rarg, void *dout, size_t *dout_szp, int tfd)
296 unsigned revents = 0;
298 stream_notify |= *(int *)carg;
300 if (pcm[PLAY])
301 revents |= POLLOUT;
302 if (pcm[REC])
303 revents |= POLLIN;
305 *(unsigned *)rarg = revents;
306 return 0;
310 static ssize_t alsap_flush(enum ossp_opcode opcode,
311 void *carg, void *din, size_t din_sz,
312 void *rarg, void *dout, size_t *dout_szp, int tfd)
314 flush_streams(opcode == OSSP_DSP_SYNC);
315 return 0;
318 static ssize_t alsap_post(enum ossp_opcode opcode,
319 void *carg, void *din, size_t din_sz,
320 void *rarg, void *dout, size_t *dout_szp, int tfd)
322 int ret;
324 ret = trigger_streams(1, 1);
325 if (ret >= 0 && pcm[PLAY])
326 ret = snd_pcm_start(pcm[PLAY]);
327 if (pcm[REC])
328 ret = snd_pcm_start(pcm[REC]);
329 return ret;
332 static ssize_t alsap_get_param(enum ossp_opcode opcode,
333 void *carg, void *din, size_t din_sz,
334 void *rarg, void *dout, size_t *dout_szp,
335 int tfd)
337 int v = 0;
339 switch (opcode) {
340 case OSSP_DSP_GET_RATE:
341 return hw_format.rate;
343 case OSSP_DSP_GET_CHANNELS:
344 return hw_format.channels;
346 case OSSP_DSP_GET_FORMAT: {
347 v = fmt_alsa_to_oss(hw_format.format);
348 break;
351 case OSSP_DSP_GET_BLKSIZE: {
352 snd_pcm_uframes_t psize;
353 snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
354 v = psize;
355 break;
358 case OSSP_DSP_GET_FORMATS:
359 v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE |
360 AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE;
361 break;
363 case OSSP_DSP_GET_TRIGGER:
364 if (!stream_corked[PLAY])
365 v |= PCM_ENABLE_OUTPUT;
366 if (!stream_corked[REC])
367 v |= PCM_ENABLE_INPUT;
368 break;
370 default:
371 assert(0);
374 *(int *)rarg = v;
376 return 0;
379 static ssize_t alsap_set_param(enum ossp_opcode opcode,
380 void *carg, void *din, size_t din_sz,
381 void *rarg, void *dout, size_t *dout_szp,
382 int tfd)
384 int v = *(int *)carg;
385 int ret = 0;
387 /* kill the streams before changing parameters */
388 kill_streams();
390 switch (opcode) {
391 case OSSP_DSP_SET_RATE: {
392 hw_format.rate = v;
393 break;
396 case OSSP_DSP_SET_CHANNELS: {
397 hw_format.channels = v;
398 break;
401 case OSSP_DSP_SET_FORMAT: {
402 snd_pcm_format_t format = fmt_oss_to_alsa(v);
403 hw_format.format = format;
404 break;
407 case OSSP_DSP_SET_SUBDIVISION:
408 if (!v)
409 v = 1;
410 #if 0
411 if (!v) {
412 v = user_subdivision ?: 1;
413 break;
415 user_frag_size = 0;
416 user_subdivision = v;
417 break;
419 case OSSP_DSP_SET_FRAGMENT:
420 user_subdivision = 0;
421 user_frag_size = 1 << (v & 0xffff);
422 user_max_frags = (v >> 16) & 0xffff;
423 if (user_frag_size < 4)
424 user_frag_size = 4;
425 if (user_max_frags < 2)
426 user_max_frags = 2;
427 #else
428 case OSSP_DSP_SET_FRAGMENT:
429 #endif
430 break;
431 default:
432 assert(0);
435 if (pcm[PLAY])
436 ret = set_hw_params(pcm[PLAY]);
437 if (ret >= 0 && pcm[REC])
438 ret = set_hw_params(pcm[REC]);
440 if (rarg)
441 *(int *)rarg = v;
442 return 0;
445 static ssize_t alsap_set_trigger(enum ossp_opcode opcode,
446 void *carg, void *din, size_t din_sz,
447 void *rarg, void *dout, size_t *dout_szp,
448 int fd)
450 int enable = *(int *)carg;
452 stream_corked[PLAY] = !!(enable & PCM_ENABLE_OUTPUT);
453 stream_corked[REC] = !!(enable & PCM_ENABLE_INPUT);
455 return trigger_streams(enable & PCM_ENABLE_OUTPUT,
456 enable & PCM_ENABLE_INPUT);
459 static ssize_t alsap_get_space(enum ossp_opcode opcode,
460 void *carg, void *din, size_t din_sz,
461 void *rarg, void *dout, size_t *dout_szp, int tfd)
463 int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC;
464 int underrun = 0;
465 struct audio_buf_info info = { };
466 unsigned long bufsize;
467 snd_pcm_uframes_t avail, fragsize;
468 snd_pcm_state_t state;
470 if (!pcm[dir])
471 return -EINVAL;
473 state = snd_pcm_state(pcm[dir]);
474 if (state == SND_PCM_STATE_XRUN) {
475 snd_pcm_recover(pcm[dir], -EPIPE, 0);
476 underrun = 1;
477 } else if (state == SND_PCM_STATE_SUSPENDED) {
478 snd_pcm_recover(pcm[dir], -ESTRPIPE, 0);
479 underrun = 1;
482 snd_pcm_hw_params_current(pcm[dir], hw_params);
483 snd_pcm_hw_params_get_period_size(hw_params, &fragsize, NULL);
484 snd_pcm_hw_params_get_buffer_size(hw_params, &bufsize);
485 info.fragsize = snd_pcm_frames_to_bytes(pcm[dir], fragsize);
486 info.fragstotal = bufsize / fragsize;
487 if (!underrun) {
488 avail = snd_pcm_avail_update(pcm[dir]);
489 info.fragments = avail / fragsize;
490 } else
491 info.fragments = info.fragstotal;
493 info.bytes = info.fragsize * info.fragments;
495 *(struct audio_buf_info *)rarg = info;
496 return 0;
499 static ssize_t alsap_get_ptr(enum ossp_opcode opcode,
500 void *carg, void *din, size_t din_sz,
501 void *rarg, void *dout, size_t *dout_szp, int tfd)
503 int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC;
504 struct count_info info = { };
506 if (!pcm[dir])
507 return -EIO;
509 snd_pcm_hw_params_current(pcm[dir], hw_params);
510 info.bytes = byte_counter[dir];
511 snd_pcm_hw_params_get_periods(hw_params, (unsigned int *)&info.blocks, NULL);
512 info.ptr = mmap_pos[dir];
514 *(struct count_info *)rarg = info;
515 return 0;
518 static ssize_t alsap_get_odelay(enum ossp_opcode opcode,
519 void *carg, void *din, size_t din_sz,
520 void *rarg, void *dout, size_t *dout_szp,
521 int fd)
523 snd_pcm_sframes_t delay;
525 if (!pcm[PLAY])
526 return -EIO;
528 if (snd_pcm_delay(pcm[PLAY], &delay) < 0)
529 return -EIO;
531 *(int *)rarg = snd_pcm_frames_to_bytes(pcm[PLAY], delay);
532 return 0;
535 static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = {
536 [OSSP_MIXER] = alsap_mixer,
537 [OSSP_DSP_OPEN] = alsap_open,
538 [OSSP_DSP_READ] = alsap_read,
539 [OSSP_DSP_WRITE] = alsap_write,
540 [OSSP_DSP_POLL] = alsap_poll,
541 #if 0
542 [OSSP_DSP_MMAP] = alsap_mmap,
543 [OSSP_DSP_MUNMAP] = alsap_munmap,
544 #endif
545 [OSSP_DSP_RESET] = alsap_flush,
546 [OSSP_DSP_SYNC] = alsap_flush,
547 [OSSP_DSP_POST] = alsap_post,
548 [OSSP_DSP_GET_RATE] = alsap_get_param,
549 [OSSP_DSP_GET_CHANNELS] = alsap_get_param,
550 [OSSP_DSP_GET_FORMAT] = alsap_get_param,
551 [OSSP_DSP_GET_BLKSIZE] = alsap_get_param,
552 [OSSP_DSP_GET_FORMATS] = alsap_get_param,
553 [OSSP_DSP_SET_RATE] = alsap_set_param,
554 [OSSP_DSP_SET_CHANNELS] = alsap_set_param,
555 [OSSP_DSP_SET_FORMAT] = alsap_set_param,
556 [OSSP_DSP_SET_SUBDIVISION] = alsap_set_param,
557 [OSSP_DSP_SET_FRAGMENT] = alsap_set_param,
558 [OSSP_DSP_GET_TRIGGER] = alsap_get_param,
559 [OSSP_DSP_SET_TRIGGER] = alsap_set_trigger,
560 [OSSP_DSP_GET_OSPACE] = alsap_get_space,
561 [OSSP_DSP_GET_ISPACE] = alsap_get_space,
562 [OSSP_DSP_GET_OPTR] = alsap_get_ptr,
563 [OSSP_DSP_GET_IPTR] = alsap_get_ptr,
564 [OSSP_DSP_GET_ODELAY] = alsap_get_odelay,
567 static int action_pre(void)
569 return 0;
572 static void action_post(void)
576 int main(int argc, char **argv)
578 int rc;
580 ossp_slave_init(argc, argv);
582 page_size = sysconf(_SC_PAGE_SIZE);
584 /* Okay, now we're open for business */
585 rc = 0;
586 do {
587 rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl,
588 action_pre, action_post);
589 } while (rc > 0);
591 return rc ? 1 : 0;