set install locations
[osspd.git] / ossp-alsap.c
1 /*
2  * ossp-alsap - ossp DSP slave which forwards to alsa
3  *
4  * Copyright (C)      2009 Maarten Lankhorst <m.b.lankhorst@gmail.com>
5  *
6  * This file is released under the GPLv2.
7  *
8  * Why an alsa plugin as well? Just to show how much
9  * the alsa userspace api sucks ;-)
10  */
11
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>
27
28 #include <alsa/asoundlib.h>
29 #include <sys/soundcard.h>
30
31 #include "ossp-slave.h"
32
33 enum {
34         AFMT_FLOAT              = 0x00004000,
35         AFMT_S32_LE             = 0x00001000,
36         AFMT_S32_BE             = 0x00002000,
37 };
38
39 static size_t page_size;
40
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;
46
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;
51
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 };
57
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
69
70 static snd_pcm_format_t fmt_oss_to_alsa(int fmt)
71 {
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;
82         }
83 }
84
85 static int fmt_alsa_to_oss(snd_pcm_format_t fmt)
86 {
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;
97         }
98 }
99
100 static void flush_streams(int drain)
101 {
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]);
114         }
115
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);
122         }
123         if (pcm[REC]) {
124                 snd_pcm_close(pcm[REC]);
125                 snd_pcm_open(&pcm[REC], "default",
126                              SND_PCM_STREAM_CAPTURE, block);
127         }
128 #endif
129 }
130
131 static void kill_streams(void)
132 {
133         flush_streams(0);
134 }
135
136 static int trigger_streams(int play, int rec)
137 {
138         int ret = 0;
139
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);
145         }
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);
151         }
152
153         return ret;
154 }
155
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)
159 {
160 return -EBUSY;
161 }
162
163 static int set_hw_params(snd_pcm_t *pcm)
164 {
165         int ret;
166         unsigned rate;
167
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;
189 }
190
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)
194 {
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
202          */
203
204         if (!hw_params)
205                 ret = snd_pcm_hw_params_malloc(&hw_params);
206         if (ret < 0)
207                 return ret;
208
209         if (!sw_params)
210                 ret = snd_pcm_sw_params_malloc(&sw_params);
211         if (ret < 0)
212                 return ret;
213
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;
219
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]);
226         }
227
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]);
233         }
234
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;
242         }
243         return 0;
244 }
245
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)
249 {
250 //      struct ossp_dsp_rw_arg *arg = carg;
251         int ret, insize;
252
253         insize = snd_pcm_bytes_to_frames(pcm[PLAY], din_sz);
254
255         if (snd_pcm_state(pcm[PLAY]) == SND_PCM_STATE_SETUP)
256                 snd_pcm_prepare(pcm[PLAY]);
257
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);
262
263         if (ret >= 0)
264                 return snd_pcm_frames_to_bytes(pcm[PLAY], ret);
265         else
266                 return ret;
267 }
268
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)
272 {
273 //      struct ossp_dsp_rw_arg *arg = carg;
274         int ret, outsize;
275
276         outsize = snd_pcm_bytes_to_frames(pcm[REC], *dout_szp);
277
278         if (snd_pcm_state(pcm[REC]) == SND_PCM_STATE_SETUP)
279                 snd_pcm_prepare(pcm[REC]);
280
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;
288
289         return ret;
290 }
291
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)
295 {
296         unsigned revents = 0;
297
298         stream_notify |= *(int *)carg;
299
300         if (pcm[PLAY])
301                 revents |= POLLOUT;
302         if (pcm[REC])
303                 revents |= POLLIN;
304
305         *(unsigned *)rarg = revents;
306         return 0;
307 }
308
309
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)
313 {
314         flush_streams(opcode == OSSP_DSP_SYNC);
315         return 0;
316 }
317
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)
321 {
322         int ret;
323
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;
330 }
331
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)
336 {
337         int v = 0;
338
339         switch (opcode) {
340         case OSSP_DSP_GET_RATE:
341                 return hw_format.rate;
342
343         case OSSP_DSP_GET_CHANNELS:
344                 return hw_format.channels;
345
346         case OSSP_DSP_GET_FORMAT: {
347                 v = fmt_alsa_to_oss(hw_format.format);
348                 break;
349         }
350
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;
356         }
357
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;
362
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;
369
370         default:
371                 assert(0);
372         }
373
374         *(int *)rarg = v;
375
376         return 0;
377 }
378
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)
383 {
384         int v = *(int *)carg;
385         int ret = 0;
386
387         /* kill the streams before changing parameters */
388         kill_streams();
389
390         switch (opcode) {
391         case OSSP_DSP_SET_RATE: {
392                 hw_format.rate = v;
393                 break;
394         }
395
396         case OSSP_DSP_SET_CHANNELS: {
397                 hw_format.channels = v;
398                 break;
399         }
400
401         case OSSP_DSP_SET_FORMAT: {
402                 snd_pcm_format_t format = fmt_oss_to_alsa(v);
403                 hw_format.format = format;
404                 break;
405         }
406
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;
414                 }
415                 user_frag_size = 0;
416                 user_subdivision = v;
417                 break;
418
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);
433         }
434
435         if (pcm[PLAY])
436                 ret = set_hw_params(pcm[PLAY]);
437         if (ret >= 0 && pcm[REC])
438                 ret = set_hw_params(pcm[REC]);
439
440         if (rarg)
441                 *(int *)rarg = v;
442         return 0;
443 }
444
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)
449 {
450         int enable = *(int *)carg;
451
452         stream_corked[PLAY] = !!(enable & PCM_ENABLE_OUTPUT);
453         stream_corked[REC] = !!(enable & PCM_ENABLE_INPUT);
454
455         return trigger_streams(enable & PCM_ENABLE_OUTPUT,
456                                enable & PCM_ENABLE_INPUT);
457 }
458
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)
462 {
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;
469
470         if (!pcm[dir])
471                 return -EINVAL;
472
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;
480         }
481
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;
492
493         info.bytes = info.fragsize * info.fragments;
494
495         *(struct audio_buf_info *)rarg = info;
496         return 0;
497 }
498
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)
502 {
503         int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC;
504         struct count_info info = { };
505
506         if (!pcm[dir])
507                 return -EIO;
508
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];
513
514         *(struct count_info *)rarg = info;
515         return 0;
516 }
517
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)
522 {
523         snd_pcm_sframes_t delay;
524
525         if (!pcm[PLAY])
526                 return -EIO;
527
528         if (snd_pcm_delay(pcm[PLAY], &delay) < 0)
529                 return -EIO;
530
531         *(int *)rarg = snd_pcm_frames_to_bytes(pcm[PLAY], delay);
532         return 0;
533 }
534
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,
565 };
566
567 static int action_pre(void)
568 {
569         return 0;
570 }
571
572 static void action_post(void)
573 {
574 }
575
576 int main(int argc, char **argv)
577 {
578         int rc;
579
580         ossp_slave_init(argc, argv);
581
582         page_size = sysconf(_SC_PAGE_SIZE);
583
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);
590
591         return rc ? 1 : 0;
592 }