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 ;-)
25 #include <sys/types.h>
28 #include <alsa/asoundlib.h>
29 #include <sys/soundcard.h>
31 #include "ossp-slave.h"
34 AFMT_FLOAT
= 0x00004000,
35 AFMT_S32_LE
= 0x00001000,
36 AFMT_S32_BE
= 0x00002000,
39 static size_t page_size
;
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
;
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
;
56 } hw_format
= { SND_PCM_FORMAT_U8
, 8000, 1 };
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 */
70 static snd_pcm_format_t
fmt_oss_to_alsa(int 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
)
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? */
106 snd_pcm_drain(pcm
[PLAY
]);
108 snd_pcm_drain(pcm
[REC
]);
111 snd_pcm_drop(pcm
[PLAY
]);
113 snd_pcm_drop(pcm
[REC
]);
116 /* XXX: Really needed? */
119 snd_pcm_close(pcm
[PLAY
]);
120 snd_pcm_open(&pcm
[PLAY
], "default",
121 SND_PCM_STREAM_PLAYBACK
, block
);
124 snd_pcm_close(pcm
[REC
]);
125 snd_pcm_open(&pcm
[REC
], "default",
126 SND_PCM_STREAM_CAPTURE
, block
);
131 static void kill_streams(void)
136 static int trigger_streams(int play
, int rec
)
140 if (pcm
[PLAY
] && play
>= 0) {
141 ret
= snd_pcm_sw_params_set_start_threshold(pcm
[PLAY
], sw_params
,
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
,
150 snd_pcm_sw_params(pcm
[REC
], sw_params
);
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
)
163 static int set_hw_params(snd_pcm_t
*pcm
)
168 ret
= snd_pcm_hw_params_any(pcm
, hw_params
);
170 ret
= snd_pcm_hw_params_set_access(pcm
, hw_params
,
171 SND_PCM_ACCESS_RW_INTERLEAVED
);
172 rate
= hw_format
.rate
;
174 ret
= snd_pcm_hw_params_set_rate_minmax(pcm
, hw_params
,
178 ret
= snd_pcm_hw_params_set_format(pcm
, hw_params
, hw_format
.format
);
180 ret
= snd_pcm_hw_params_set_channels(pcm
, hw_params
,
183 ret
= snd_pcm_hw_params(pcm
, hw_params
);
185 ret
= snd_pcm_sw_params_current(pcm
, sw_params
);
187 ret
= snd_pcm_sw_params(pcm
, sw_params
);
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
;
197 block
= arg
->flags
& O_NONBLOCK
? SND_PCM_NONBLOCK
: 0;
199 // block |= SND_PCM_ASYNC;
200 /* Woop dee dooo.. I love handling things in SIGIO (PAIN!!)
201 * Probably needed for MMAP
205 ret
= snd_pcm_hw_params_malloc(&hw_params
);
210 ret
= snd_pcm_sw_params_malloc(&sw_params
);
215 snd_pcm_close(pcm
[PLAY
]);
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
);
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
);
232 ret
= set_hw_params(pcm
[REC
]);
237 snd_pcm_close(pcm
[PLAY
]);
239 snd_pcm_close(pcm
[REC
]);
240 pcm
[REC
] = pcm
[PLAY
] = NULL
;
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;
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
);
261 ret
= snd_pcm_recover(pcm
[PLAY
], ret
, 1);
264 return snd_pcm_frames_to_bytes(pcm
[PLAY
], 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;
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
);
283 ret
= snd_pcm_recover(pcm
[REC
], ret
, 1);
285 *dout_szp
= ret
= snd_pcm_frames_to_bytes(pcm
[REC
], 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
;
305 *(unsigned *)rarg
= revents
;
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
);
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
)
324 ret
= trigger_streams(1, 1);
325 if (ret
>= 0 && pcm
[PLAY
])
326 ret
= snd_pcm_start(pcm
[PLAY
]);
328 ret
= snd_pcm_start(pcm
[REC
]);
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
,
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
);
351 case OSSP_DSP_GET_BLKSIZE
: {
352 snd_pcm_uframes_t psize
;
353 snd_pcm_hw_params_get_period_size(hw_params
, &psize
, NULL
);
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
;
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
;
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
,
384 int v
= *(int *)carg
;
387 /* kill the streams before changing parameters */
391 case OSSP_DSP_SET_RATE
: {
396 case OSSP_DSP_SET_CHANNELS
: {
397 hw_format
.channels
= v
;
401 case OSSP_DSP_SET_FORMAT
: {
402 snd_pcm_format_t format
= fmt_oss_to_alsa(v
);
403 hw_format
.format
= format
;
407 case OSSP_DSP_SET_SUBDIVISION
:
412 v
= user_subdivision
?: 1;
416 user_subdivision
= v
;
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)
425 if (user_max_frags
< 2)
428 case OSSP_DSP_SET_FRAGMENT
:
436 ret
= set_hw_params(pcm
[PLAY
]);
437 if (ret
>= 0 && pcm
[REC
])
438 ret
= set_hw_params(pcm
[REC
]);
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
,
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
;
465 struct audio_buf_info info
= { };
466 unsigned long bufsize
;
467 snd_pcm_uframes_t avail
, fragsize
;
468 snd_pcm_state_t state
;
473 state
= snd_pcm_state(pcm
[dir
]);
474 if (state
== SND_PCM_STATE_XRUN
) {
475 snd_pcm_recover(pcm
[dir
], -EPIPE
, 0);
477 } else if (state
== SND_PCM_STATE_SUSPENDED
) {
478 snd_pcm_recover(pcm
[dir
], -ESTRPIPE
, 0);
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
;
488 avail
= snd_pcm_avail_update(pcm
[dir
]);
489 info
.fragments
= avail
/ fragsize
;
491 info
.fragments
= info
.fragstotal
;
493 info
.bytes
= info
.fragsize
* info
.fragments
;
495 *(struct audio_buf_info
*)rarg
= info
;
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
= { };
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
;
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
,
523 snd_pcm_sframes_t delay
;
528 if (snd_pcm_delay(pcm
[PLAY
], &delay
) < 0)
531 *(int *)rarg
= snd_pcm_frames_to_bytes(pcm
[PLAY
], delay
);
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
,
542 [OSSP_DSP_MMAP
] = alsap_mmap
,
543 [OSSP_DSP_MUNMAP
] = alsap_munmap
,
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)
572 static void action_post(void)
576 int main(int argc
, char **argv
)
580 ossp_slave_init(argc
, argv
);
582 page_size
= sysconf(_SC_PAGE_SIZE
);
584 /* Okay, now we're open for business */
587 rc
= ossp_slave_process_command(ossp_cmd_fd
, action_fn_tbl
,
588 action_pre
, action_post
);