835 lines
20 KiB
C
835 lines
20 KiB
C
|
/*
|
||
|
* Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <sndio.h>
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <pulse/util.h>
|
||
|
|
||
|
#include <pulsecore/core-error.h>
|
||
|
#include <pulsecore/thread.h>
|
||
|
#include <pulsecore/sink.h>
|
||
|
#include <pulsecore/source.h>
|
||
|
#include <pulsecore/module.h>
|
||
|
#include <pulsecore/sample-util.h>
|
||
|
#include <pulsecore/core-util.h>
|
||
|
#include <pulsecore/modargs.h>
|
||
|
#include <pulsecore/log.h>
|
||
|
#include <pulsecore/macro.h>
|
||
|
#include <pulsecore/thread-mq.h>
|
||
|
#include <pulsecore/rtpoll.h>
|
||
|
#include <pulsecore/poll.h>
|
||
|
|
||
|
#include "module-sndio-sysex.h"
|
||
|
|
||
|
/*
|
||
|
* TODO
|
||
|
*
|
||
|
* - handle latency correctly
|
||
|
* - make recording work correctly with playback
|
||
|
*/
|
||
|
|
||
|
PA_MODULE_AUTHOR("Eric Faurot");
|
||
|
PA_MODULE_DESCRIPTION("OpenBSD sndio sink/source");
|
||
|
PA_MODULE_VERSION(PACKAGE_VERSION);
|
||
|
PA_MODULE_LOAD_ONCE(false);
|
||
|
PA_MODULE_USAGE(
|
||
|
"sink_name=<name for the sink> "
|
||
|
"sink_properties=<properties for the sink> "
|
||
|
"source_name=<name for the source> "
|
||
|
"source_properties=<properties for the source> "
|
||
|
"device=<sndio device> "
|
||
|
"record=<enable source?> "
|
||
|
"playback=<enable sink?> "
|
||
|
"format=<sample format> "
|
||
|
"rate=<sample rate> "
|
||
|
"channels=<number of channels> "
|
||
|
"channel_map=<channel map> ");
|
||
|
|
||
|
static const char* const modargs[] = {
|
||
|
"sink_name",
|
||
|
"sink_properties",
|
||
|
"source_name",
|
||
|
"source_properties",
|
||
|
"device",
|
||
|
"record",
|
||
|
"playback",
|
||
|
"format",
|
||
|
"rate",
|
||
|
"channels",
|
||
|
"channel_map",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
struct userdata {
|
||
|
pa_core *core;
|
||
|
pa_module *module;
|
||
|
pa_sink *sink;
|
||
|
pa_source *source;
|
||
|
|
||
|
pa_thread *thread;
|
||
|
pa_thread_mq thread_mq;
|
||
|
pa_rtpoll *rtpoll;
|
||
|
pa_rtpoll_item *rtpoll_item;
|
||
|
|
||
|
pa_memchunk memchunk;
|
||
|
|
||
|
struct sio_hdl *hdl;
|
||
|
struct sio_par par;
|
||
|
size_t bufsz;
|
||
|
|
||
|
int sink_running;
|
||
|
unsigned int volume;
|
||
|
|
||
|
pa_rtpoll_item *rtpoll_item_mio;
|
||
|
struct mio_hdl *mio;
|
||
|
int set_master; /* master we're writing */
|
||
|
int last_master; /* last master we wrote */
|
||
|
int feedback_master; /* actual master */
|
||
|
int mst;
|
||
|
int midx;
|
||
|
int mlen;
|
||
|
int mready;
|
||
|
#define MSGMAX 0x100
|
||
|
uint8_t mmsg[MSGMAX];
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
sndio_midi_message(struct userdata *u, const char *msg, size_t len)
|
||
|
{
|
||
|
struct sysex *x = (struct sysex *)msg;
|
||
|
|
||
|
if (len == SYSEX_SIZE(master) &&
|
||
|
x->start == SYSEX_START &&
|
||
|
x->type == SYSEX_TYPE_RT &&
|
||
|
x->id0 == SYSEX_CONTROL &&
|
||
|
x->id1 == SYSEX_MASTER) {
|
||
|
u->feedback_master = x->u.master.coarse;
|
||
|
pa_log_debug("MIDI master level is %i", u->feedback_master);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (len == SYSEX_SIZE(empty) &&
|
||
|
x->start == SYSEX_START &&
|
||
|
x->type == SYSEX_TYPE_EDU &&
|
||
|
x->id0 == SYSEX_AUCAT &&
|
||
|
x->id1 == SYSEX_AUCAT_DUMPEND) {
|
||
|
pa_log_debug("MIDI config done");
|
||
|
u->mready = 1;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sndio_midi_input(struct userdata *u, const unsigned char *buf, unsigned int len)
|
||
|
{
|
||
|
static unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
|
||
|
static unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
|
||
|
unsigned int c;
|
||
|
|
||
|
for (; len > 0; len--) {
|
||
|
c = *buf;
|
||
|
buf++;
|
||
|
|
||
|
if (c >= 0xf8) {
|
||
|
/* clock events not used yet */
|
||
|
} else if (c >= 0xf0) {
|
||
|
if (u->mst == SYSEX_START &&
|
||
|
c == SYSEX_END &&
|
||
|
u->midx < MSGMAX) {
|
||
|
u->mmsg[u->midx++] = c;
|
||
|
sndio_midi_message(u, u->mmsg, u->midx);
|
||
|
continue;
|
||
|
}
|
||
|
u->mmsg[0] = c;
|
||
|
u->mlen = common_len[c & 7];
|
||
|
u->mst = c;
|
||
|
u->midx = 1;
|
||
|
} else if (c >= 0x80) {
|
||
|
u->mmsg[0] = c;
|
||
|
u->mlen = voice_len[(c >> 4) & 7];
|
||
|
u->mst = c;
|
||
|
u->midx = 1;
|
||
|
} else if (u->mst) {
|
||
|
if (u->midx == MSGMAX)
|
||
|
continue;
|
||
|
if (u->midx == 0)
|
||
|
u->mmsg[u->midx++] = u->mst;
|
||
|
u->mmsg[u->midx++] = c;
|
||
|
if (u->midx == u->mlen) {
|
||
|
sndio_midi_message(u, u->mmsg, u->midx);
|
||
|
u->midx = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sndio_midi_setup(struct userdata *u)
|
||
|
{
|
||
|
static const unsigned char dumpreq[] = {
|
||
|
SYSEX_START,
|
||
|
SYSEX_TYPE_EDU,
|
||
|
0,
|
||
|
SYSEX_AUCAT,
|
||
|
SYSEX_AUCAT_DUMPREQ,
|
||
|
SYSEX_END
|
||
|
};
|
||
|
size_t s;
|
||
|
int n;
|
||
|
unsigned char buf[MSGMAX];
|
||
|
const char *midi_port;
|
||
|
|
||
|
midi_port = getenv("AUDIODEVICE");
|
||
|
if (midi_port == NULL)
|
||
|
midi_port = "snd/0";
|
||
|
|
||
|
u->mio = mio_open(midi_port, MIO_IN | MIO_OUT, 0);
|
||
|
if (u->mio == NULL) {
|
||
|
pa_log("mio_open failed");
|
||
|
return (-1);
|
||
|
}
|
||
|
n = mio_nfds(u->mio);
|
||
|
u->rtpoll_item_mio = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n);
|
||
|
if (u->rtpoll_item_mio == NULL) {
|
||
|
pa_log("could not allocate mio poll item");
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
s = mio_write(u->mio, dumpreq, sizeof(dumpreq));
|
||
|
pa_log_debug("mio_write: %zu / %zu", s, sizeof(dumpreq));
|
||
|
while (!u->mready) {
|
||
|
s = mio_read(u->mio, buf, sizeof buf);
|
||
|
pa_log_debug("mio_read: %zu", s);
|
||
|
if (s == 0) {
|
||
|
pa_log("mio_read()");
|
||
|
return (-1);
|
||
|
}
|
||
|
sndio_midi_input(u, buf, s);
|
||
|
}
|
||
|
u->set_master = u->last_master = u->feedback_master;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sndio_on_volume(void *arg, unsigned int vol)
|
||
|
{
|
||
|
struct userdata *u = arg;
|
||
|
|
||
|
u->volume = vol;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sndio_get_volume(pa_sink *s)
|
||
|
{
|
||
|
struct userdata *u = s->userdata;
|
||
|
int i;
|
||
|
uint32_t v;
|
||
|
|
||
|
if (u->feedback_master >= SIO_MAXVOL)
|
||
|
v = PA_VOLUME_NORM;
|
||
|
else
|
||
|
v = PA_CLAMP_VOLUME((u->volume * PA_VOLUME_NORM) / SIO_MAXVOL);
|
||
|
|
||
|
for (i = 0; i < s->real_volume.channels; i++)
|
||
|
s->real_volume.values[i] = v;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sndio_set_volume(pa_sink *s)
|
||
|
{
|
||
|
struct userdata *u = s->userdata;
|
||
|
|
||
|
if (s->real_volume.values[0] >= PA_VOLUME_NORM)
|
||
|
u->set_master = SIO_MAXVOL;
|
||
|
else
|
||
|
u->set_master = (s->real_volume.values[0] * SIO_MAXVOL) / PA_VOLUME_NORM;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sndio_sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
|
||
|
pa_suspend_cause_t new_suspend_cause) {
|
||
|
struct userdata *u = s->userdata;
|
||
|
int r;
|
||
|
|
||
|
switch (new_state) {
|
||
|
case PA_SINK_SUSPENDED:
|
||
|
if (!u->sink_running)
|
||
|
break;
|
||
|
r = sio_stop(u->hdl);
|
||
|
pa_log_debug("sio_stop() = %d", r);
|
||
|
u->sink_running = 0;
|
||
|
break;
|
||
|
case PA_SINK_IDLE:
|
||
|
case PA_SINK_RUNNING:
|
||
|
if (u->sink_running)
|
||
|
break;
|
||
|
r = sio_start(u->hdl);
|
||
|
pa_log_debug("sio_start() = %d", r);
|
||
|
u->sink_running = 1;
|
||
|
break;
|
||
|
|
||
|
case PA_SINK_INVALID_STATE:
|
||
|
case PA_SINK_UNLINKED:
|
||
|
case PA_SINK_INIT:
|
||
|
default:
|
||
|
pa_log_debug("%s new_state=%d", __func__, new_state);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sndio_sink_message(pa_msgobject *o, int code, void *data, int64_t offset,
|
||
|
pa_memchunk *chunk)
|
||
|
{
|
||
|
struct userdata *u = PA_SINK(o)->userdata;
|
||
|
|
||
|
pa_log_debug("%s: obj=%p code=%i data=%p offset=%lli chunk=%p",
|
||
|
__func__, o, code, data, offset, chunk);
|
||
|
switch (code) {
|
||
|
case PA_SINK_MESSAGE_GET_LATENCY:
|
||
|
*(int64_t*)data = pa_bytes_to_usec(u->par.bufsz, &u->sink->sample_spec);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sndio_source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_state,
|
||
|
pa_suspend_cause_t new_suspend_cause) {
|
||
|
struct userdata *u = s->userdata;
|
||
|
int r;
|
||
|
|
||
|
switch (new_state) {
|
||
|
case PA_SOURCE_SUSPENDED:
|
||
|
r = sio_stop(u->hdl);
|
||
|
pa_log_debug("sio_stop() = %d", r);
|
||
|
break;
|
||
|
case PA_SOURCE_IDLE:
|
||
|
case PA_SOURCE_RUNNING:
|
||
|
r = sio_start(u->hdl);
|
||
|
pa_log_debug("sio_start() = %d", r);
|
||
|
break;
|
||
|
|
||
|
case PA_SOURCE_INVALID_STATE:
|
||
|
case PA_SOURCE_UNLINKED:
|
||
|
case PA_SOURCE_INIT:
|
||
|
default:
|
||
|
pa_log_debug("%s new_state=%d", __func__, new_state);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sndio_source_message(pa_msgobject *o, int code, void *data, int64_t offset,
|
||
|
pa_memchunk *chunk)
|
||
|
{
|
||
|
struct userdata *u = PA_SOURCE(o)->userdata;
|
||
|
|
||
|
pa_log_debug("%s: obj=%p code=%i data=%p offset=%lli chunk=%p",
|
||
|
__func__, o, code, data, offset, chunk);
|
||
|
switch (code) {
|
||
|
case PA_SOURCE_MESSAGE_GET_LATENCY:
|
||
|
*(int64_t*)data = pa_bytes_to_usec(u->bufsz, &u->source->sample_spec);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
return pa_source_process_msg(o, code, data, offset, chunk);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sndio_thread(void *arg)
|
||
|
{
|
||
|
struct userdata *u = arg;
|
||
|
int ret;
|
||
|
short revents, events;
|
||
|
struct pollfd *fds_sio, *fds_mio;
|
||
|
size_t w, r, l;
|
||
|
char *p;
|
||
|
char buf[256];
|
||
|
struct pa_memchunk memchunk;
|
||
|
struct sysex msg;
|
||
|
|
||
|
pa_log_debug("sndio thread starting up");
|
||
|
|
||
|
pa_thread_mq_install(&u->thread_mq);
|
||
|
|
||
|
fds_sio = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||
|
fds_mio = pa_rtpoll_item_get_pollfd(u->rtpoll_item_mio, NULL);
|
||
|
|
||
|
revents = 0;
|
||
|
for (;;) {
|
||
|
pa_log_debug("sndio_thread: loop");
|
||
|
|
||
|
/* ??? oss does that. */
|
||
|
if (u->sink
|
||
|
&& PA_SINK_IS_OPENED(u->sink->thread_info.state)
|
||
|
&& u->sink->thread_info.rewind_requested)
|
||
|
pa_sink_process_rewind(u->sink, 0);
|
||
|
|
||
|
if (u->sink &&
|
||
|
PA_SINK_IS_OPENED(u->sink->thread_info.state)
|
||
|
&& (revents & POLLOUT)) {
|
||
|
if (u->memchunk.length <= 0)
|
||
|
pa_sink_render(u->sink, u->bufsz, &u->memchunk);
|
||
|
p = pa_memblock_acquire(u->memchunk.memblock);
|
||
|
w = sio_write(u->hdl, p + u->memchunk.index,
|
||
|
u->memchunk.length);
|
||
|
pa_memblock_release(u->memchunk.memblock);
|
||
|
pa_log_debug("wrote %zu bytes of %zu", w,
|
||
|
u->memchunk.length);
|
||
|
u->memchunk.index += w;
|
||
|
u->memchunk.length -= w;
|
||
|
if (u->memchunk.length <= 0) {
|
||
|
pa_memblock_unref(u->memchunk.memblock);
|
||
|
pa_memchunk_reset(&u->memchunk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (u->source &&
|
||
|
PA_SOURCE_IS_OPENED(u->source->thread_info.state)
|
||
|
&& (revents & POLLIN)) {
|
||
|
memchunk.memblock = pa_memblock_new(u->core->mempool,
|
||
|
(size_t) -1);
|
||
|
l = pa_memblock_get_length(memchunk.memblock);
|
||
|
if (l > u->bufsz)
|
||
|
l = u->bufsz;
|
||
|
p = pa_memblock_acquire(memchunk.memblock);
|
||
|
r = sio_read(u->hdl, p, l);
|
||
|
pa_memblock_release(memchunk.memblock);
|
||
|
pa_log_debug("read %zu bytes of %zu", r, l);
|
||
|
memchunk.index = 0;
|
||
|
memchunk.length = r;
|
||
|
pa_source_post(u->source, &memchunk);
|
||
|
pa_memblock_unref(memchunk.memblock);
|
||
|
}
|
||
|
|
||
|
events = 0;
|
||
|
if (u->source &&
|
||
|
PA_SOURCE_IS_OPENED(u->source->thread_info.state))
|
||
|
events |= POLLIN;
|
||
|
if (u->sink &&
|
||
|
PA_SINK_IS_OPENED(u->sink->thread_info.state))
|
||
|
events |= POLLOUT;
|
||
|
|
||
|
/*
|
||
|
* XXX: {sio,mio}_pollfd() return the number
|
||
|
* of descriptors to poll(). It's not correct
|
||
|
* to assume only 1 descriptor is used
|
||
|
*/
|
||
|
|
||
|
sio_pollfd(u->hdl, fds_sio, events);
|
||
|
|
||
|
mio_pollfd(u->mio, fds_mio, POLLIN);
|
||
|
|
||
|
pa_log_debug("sndio_thread: POLLING sio events=%x", events);
|
||
|
|
||
|
if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
|
||
|
goto fail;
|
||
|
if (ret == 0)
|
||
|
goto finish;
|
||
|
|
||
|
revents = mio_revents(u->mio, fds_mio);
|
||
|
|
||
|
pa_log_debug("sndio_thread: mio_revents()=%x", revents);
|
||
|
if (revents & POLLHUP) {
|
||
|
pa_log("mio POLLHUP!");
|
||
|
break;
|
||
|
}
|
||
|
if (revents & POLLIN) {
|
||
|
r = mio_read(u->mio, buf, sizeof buf);
|
||
|
if (mio_eof(u->mio)) {
|
||
|
pa_log("mio error");
|
||
|
break;
|
||
|
}
|
||
|
if (r)
|
||
|
sndio_midi_input(u, buf, r);
|
||
|
}
|
||
|
if (u->set_master != u->last_master) {
|
||
|
u->last_master = u->set_master;
|
||
|
msg.start = SYSEX_START;
|
||
|
msg.type = SYSEX_TYPE_RT;
|
||
|
msg.dev = 0;
|
||
|
msg.id0 = SYSEX_CONTROL;
|
||
|
msg.id1 = SYSEX_MASTER;
|
||
|
msg.u.master.fine = 0;
|
||
|
msg.u.master.coarse = u->set_master;
|
||
|
msg.u.master.end = SYSEX_END;
|
||
|
if (mio_write(u->mio, &msg, SYSEX_SIZE(master)) !=
|
||
|
SYSEX_SIZE(master))
|
||
|
pa_log("set master: couldn't write message");
|
||
|
}
|
||
|
|
||
|
revents = 0;
|
||
|
if (!u->sink_running)
|
||
|
continue;
|
||
|
|
||
|
revents = sio_revents(u->hdl, fds_sio);
|
||
|
pa_log_debug("sndio_thread: sio_revents()=%x", revents);
|
||
|
|
||
|
if (revents & POLLHUP) {
|
||
|
pa_log("POLLHUP!");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fail:
|
||
|
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core),
|
||
|
PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||
|
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||
|
finish:
|
||
|
pa_log_debug("sndio thread shutting down");
|
||
|
}
|
||
|
|
||
|
int
|
||
|
pa__init(pa_module *m)
|
||
|
{
|
||
|
bool record = false, playback = true;
|
||
|
pa_modargs *ma = NULL;
|
||
|
pa_sample_spec ss;
|
||
|
pa_channel_map map;
|
||
|
pa_sink_new_data sink;
|
||
|
pa_source_new_data source;
|
||
|
|
||
|
struct sio_par par;
|
||
|
unsigned int mode = 0;
|
||
|
char buf[256];
|
||
|
const char *name, *dev;
|
||
|
struct userdata *u = NULL;
|
||
|
int nfds;
|
||
|
struct pollfd;
|
||
|
|
||
|
if ((u = calloc(1, sizeof(struct userdata))) == NULL) {
|
||
|
pa_log("Failed to allocate userdata");
|
||
|
goto fail;
|
||
|
}
|
||
|
m->userdata = u;
|
||
|
u->core = m->core;
|
||
|
u->module = m;
|
||
|
u->rtpoll = pa_rtpoll_new();
|
||
|
|
||
|
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
|
||
|
pa_log("pa_thread_mq_init() failed.");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
|
||
|
|
||
|
if (!(ma = pa_modargs_new(m->argument, modargs))) {
|
||
|
pa_log("Failed to parse module arguments.");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 ||
|
||
|
pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
|
||
|
pa_log("record= and playback= expect boolean argument");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (playback)
|
||
|
mode |= SIO_PLAY;
|
||
|
if (record)
|
||
|
mode |= SIO_REC;
|
||
|
|
||
|
if (!mode) {
|
||
|
pa_log("Neither playback nor record enabled for device");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
dev = pa_modargs_get_value(ma, "device", NULL);
|
||
|
if ((u->hdl = sio_open(dev, mode, 1)) == NULL) {
|
||
|
pa_log("Cannot open sndio device.");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
ss = m->core->default_sample_spec;
|
||
|
map = m->core->default_channel_map;
|
||
|
|
||
|
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map,
|
||
|
PA_CHANNEL_MAP_OSS) < 0) {
|
||
|
pa_log("Failed to parse sample specification or channel map");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
sio_initpar(&par);
|
||
|
par.rate = ss.rate;
|
||
|
par.pchan = (mode & SIO_PLAY) ? ss.channels : 0;
|
||
|
par.rchan = (mode & SIO_REC) ? ss.channels : 0;
|
||
|
par.sig = 1;
|
||
|
|
||
|
switch (ss.format) {
|
||
|
case PA_SAMPLE_U8:
|
||
|
par.bits = 8;
|
||
|
par.bps = 1;
|
||
|
par.sig = 0;
|
||
|
break;
|
||
|
case PA_SAMPLE_S16LE:
|
||
|
case PA_SAMPLE_S16BE:
|
||
|
par.bits = 16;
|
||
|
par.bps = 2;
|
||
|
par.le = (ss.format == PA_SAMPLE_S16LE) ? 1 : 0;
|
||
|
break;
|
||
|
case PA_SAMPLE_S32LE:
|
||
|
case PA_SAMPLE_S32BE:
|
||
|
par.bits = 32;
|
||
|
par.bps = 4;
|
||
|
par.le = (ss.format == PA_SAMPLE_S32LE) ? 1 : 0;
|
||
|
break;
|
||
|
case PA_SAMPLE_S24LE:
|
||
|
case PA_SAMPLE_S24BE:
|
||
|
par.bits = 24;
|
||
|
par.bps = 3;
|
||
|
par.le = (ss.format == PA_SAMPLE_S24LE) ? 1 : 0;
|
||
|
break;
|
||
|
case PA_SAMPLE_S24_32LE:
|
||
|
case PA_SAMPLE_S24_32BE:
|
||
|
par.bits = 24;
|
||
|
par.bps = 4;
|
||
|
par.le = (ss.format == PA_SAMPLE_S24_32LE) ? 1 : 0;
|
||
|
par.msb = 0; /* XXX check this */
|
||
|
break;
|
||
|
case PA_SAMPLE_ALAW:
|
||
|
case PA_SAMPLE_ULAW:
|
||
|
case PA_SAMPLE_FLOAT32LE:
|
||
|
case PA_SAMPLE_FLOAT32BE:
|
||
|
default:
|
||
|
pa_log("Unsupported sample format");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* XXX what to do with channel map? */
|
||
|
|
||
|
if (sio_setpar(u->hdl, &par) == -1) {
|
||
|
pa_log("Could not set requested parameters");
|
||
|
goto fail;
|
||
|
}
|
||
|
if (sio_getpar(u->hdl, &u->par) == -1) {
|
||
|
pa_log("Could not retreive parameters");
|
||
|
goto fail;
|
||
|
}
|
||
|
if (u->par.rate != par.rate)
|
||
|
pa_log_warn("rate changed: %u -> %u", par.rate, u->par.rate);
|
||
|
if (u->par.pchan != par.pchan)
|
||
|
pa_log_warn("playback channels changed: %u -> %u",
|
||
|
par.rchan, u->par.rchan);
|
||
|
if (u->par.rchan != par.rchan)
|
||
|
pa_log_warn("record channels changed: %u -> %u",
|
||
|
par.rchan, u->par.rchan);
|
||
|
/* XXX check sample format */
|
||
|
|
||
|
ss.rate = u->par.rate;
|
||
|
ss.channels = (mode & SIO_PLAY) ? u->par.pchan : u->par.rchan;
|
||
|
/* XXX what to do with map? */
|
||
|
|
||
|
u->bufsz = u->par.bufsz * u->par.bps * u->par.pchan;
|
||
|
|
||
|
nfds = sio_nfds(u->hdl);
|
||
|
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, nfds);
|
||
|
if (u->rtpoll_item == NULL) {
|
||
|
pa_log("could not allocate poll item");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (mode & SIO_PLAY) {
|
||
|
|
||
|
pa_sink_new_data_init(&sink);
|
||
|
sink.driver = __FILE__;
|
||
|
sink.module = m;
|
||
|
sink.namereg_fail = true;
|
||
|
name = pa_modargs_get_value(ma, "sink_name", NULL);
|
||
|
if (name == NULL) {
|
||
|
sink.namereg_fail = false;
|
||
|
snprintf(buf, sizeof (buf), "sndio-sink");
|
||
|
name = buf;
|
||
|
}
|
||
|
pa_sink_new_data_set_name(&sink, name);
|
||
|
pa_sink_new_data_set_sample_spec(&sink, &ss);
|
||
|
pa_sink_new_data_set_channel_map(&sink, &map);
|
||
|
pa_proplist_sets(sink.proplist,
|
||
|
PA_PROP_DEVICE_STRING, dev ? dev : "default");
|
||
|
pa_proplist_sets(sink.proplist,
|
||
|
PA_PROP_DEVICE_API, "sndio");
|
||
|
pa_proplist_sets(sink.proplist,
|
||
|
PA_PROP_DEVICE_DESCRIPTION, dev ? dev : "default");
|
||
|
pa_proplist_sets(sink.proplist,
|
||
|
PA_PROP_DEVICE_ACCESS_MODE, "serial");
|
||
|
/*
|
||
|
pa_proplist_setf(sink.proplist,
|
||
|
PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%u",
|
||
|
u->par.bufsz * u->par.bps * u->par.pchan);
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
pa_proplist_setf(sink.proplist,
|
||
|
PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu",
|
||
|
(unsigned long) (u->out_fragment_size));
|
||
|
*/
|
||
|
if (pa_modargs_get_proplist(ma, "sink_properties",
|
||
|
sink.proplist, PA_UPDATE_REPLACE) < 0) {
|
||
|
pa_log("Invalid sink properties");
|
||
|
pa_sink_new_data_done(&sink);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
u->sink = pa_sink_new(m->core, &sink, PA_SINK_LATENCY);
|
||
|
pa_sink_new_data_done(&sink);
|
||
|
if (u->sink == NULL) {
|
||
|
pa_log("Failed to create sync");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
u->sink->userdata = u;
|
||
|
u->sink->parent.process_msg = sndio_sink_message;
|
||
|
u->sink->set_state_in_io_thread = sndio_sink_set_state_in_io_thread_cb;
|
||
|
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
||
|
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
||
|
pa_sink_set_fixed_latency(u->sink,
|
||
|
pa_bytes_to_usec(u->bufsz, &u->sink->sample_spec));
|
||
|
|
||
|
sio_onvol(u->hdl, sndio_on_volume, u);
|
||
|
pa_sink_set_get_volume_callback(u->sink, sndio_get_volume);
|
||
|
pa_sink_set_set_volume_callback(u->sink, sndio_set_volume);
|
||
|
u->sink->n_volume_steps = SIO_MAXVOL + 1;
|
||
|
}
|
||
|
|
||
|
if (mode & SIO_REC) {
|
||
|
pa_source_new_data_init(&source);
|
||
|
source.driver = __FILE__;
|
||
|
source.module = m;
|
||
|
source.namereg_fail = true;
|
||
|
name = pa_modargs_get_value(ma, "source_name", NULL);
|
||
|
if (name == NULL) {
|
||
|
source.namereg_fail = false;
|
||
|
snprintf(buf, sizeof (buf), "sndio-source");
|
||
|
name = buf;
|
||
|
}
|
||
|
pa_source_new_data_set_name(&source, name);
|
||
|
pa_source_new_data_set_sample_spec(&source, &ss);
|
||
|
pa_source_new_data_set_channel_map(&source, &map);
|
||
|
pa_proplist_sets(source.proplist,
|
||
|
PA_PROP_DEVICE_STRING, dev ? dev : "default");
|
||
|
pa_proplist_sets(source.proplist,
|
||
|
PA_PROP_DEVICE_API, "sndio");
|
||
|
pa_proplist_sets(source.proplist,
|
||
|
PA_PROP_DEVICE_DESCRIPTION, dev ? dev : "default");
|
||
|
pa_proplist_sets(source.proplist,
|
||
|
PA_PROP_DEVICE_ACCESS_MODE, "serial");
|
||
|
pa_proplist_setf(source.proplist,
|
||
|
PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%u",
|
||
|
u->par.bufsz * u->par.bps * u->par.rchan);
|
||
|
/*
|
||
|
pa_proplist_setf(source.proplist,
|
||
|
PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu",
|
||
|
(unsigned long) (u->in_fragment_size));
|
||
|
*/
|
||
|
if (pa_modargs_get_proplist(ma, "source_properties",
|
||
|
source.proplist, PA_UPDATE_REPLACE) < 0) {
|
||
|
pa_log("Invalid source properties");
|
||
|
pa_source_new_data_done(&source);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
u->source = pa_source_new(m->core, &source, PA_SOURCE_LATENCY);
|
||
|
pa_source_new_data_done(&source);
|
||
|
if (u->source == NULL) {
|
||
|
pa_log("Failed to create source");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
u->source->userdata = u;
|
||
|
u->source->parent.process_msg = sndio_source_message;
|
||
|
u->source->set_state_in_io_thread = sndio_source_set_state_in_io_thread_cb;
|
||
|
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
|
||
|
pa_source_set_rtpoll(u->source, u->rtpoll);
|
||
|
/*
|
||
|
pa_source_set_fixed_latency(u->source,
|
||
|
pa_bytes_to_usec(u->in_hwbuf_size, &u->source->sample_spec));
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
pa_log_debug("buffer: frame=%u bytes=%zu msec=%u", u->par.bufsz,
|
||
|
u->bufsz, (unsigned int) pa_bytes_to_usec(u->bufsz, &u->sink->sample_spec));
|
||
|
|
||
|
pa_memchunk_reset(&u->memchunk);
|
||
|
|
||
|
if (sndio_midi_setup(u) == -1)
|
||
|
goto fail;
|
||
|
|
||
|
if ((u->thread = pa_thread_new("sndio", sndio_thread, u)) == NULL) {
|
||
|
pa_log("Failed to create sndio thread.");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (u->sink)
|
||
|
pa_sink_put(u->sink);
|
||
|
if (u->source)
|
||
|
pa_source_put(u->source);
|
||
|
|
||
|
pa_modargs_free(ma);
|
||
|
|
||
|
return (0);
|
||
|
fail:
|
||
|
if (u)
|
||
|
pa__done(m);
|
||
|
if (ma)
|
||
|
pa_modargs_free(ma);
|
||
|
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
pa__done(pa_module *m)
|
||
|
{
|
||
|
struct userdata *u;
|
||
|
|
||
|
if (!(u = m->userdata))
|
||
|
return;
|
||
|
|
||
|
if (u->sink)
|
||
|
pa_sink_unlink(u->sink);
|
||
|
if (u->source)
|
||
|
pa_source_unlink(u->source);
|
||
|
if (u->thread) {
|
||
|
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN,
|
||
|
NULL, 0, NULL);
|
||
|
pa_thread_free(u->thread);
|
||
|
}
|
||
|
pa_thread_mq_done(&u->thread_mq);
|
||
|
|
||
|
if (u->sink)
|
||
|
pa_sink_unref(u->sink);
|
||
|
if (u->source)
|
||
|
pa_source_unref(u->source);
|
||
|
if (u->memchunk.memblock)
|
||
|
pa_memblock_unref(u->memchunk.memblock);
|
||
|
if (u->rtpoll_item)
|
||
|
pa_rtpoll_item_free(u->rtpoll_item);
|
||
|
if (u->rtpoll_item_mio)
|
||
|
pa_rtpoll_item_free(u->rtpoll_item_mio);
|
||
|
if (u->rtpoll)
|
||
|
pa_rtpoll_free(u->rtpoll);
|
||
|
if (u->hdl)
|
||
|
sio_close(u->hdl);
|
||
|
if (u->mio)
|
||
|
mio_close(u->mio);
|
||
|
free(u);
|
||
|
}
|