From 74bbf31c37b327be3c52c8043be72b96b8fee8f4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Jul 2004 17:03:11 +0000 Subject: implement alsa source split off alsa-util.c git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@76 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 14 +++- src/alsa-util.c | 73 ++++++++++++++++ src/alsa-util.h | 14 ++++ src/module-alsa-sink.c | 77 ++++------------- src/module-alsa-source.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++ src/polypaudio.run | 16 +++- src/todo | 5 +- 7 files changed, 344 insertions(+), 67 deletions(-) create mode 100644 src/alsa-util.c create mode 100644 src/alsa-util.h create mode 100644 src/module-alsa-source.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 53134792..09df6284 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ pkglib_LTLIBRARIES=libiochannel.la \ libpstream.la \ libpacket.la \ liboss-util.la \ + libalsa-util.la \ libioline.la \ libcli.la \ libprotocol-cli.la \ @@ -42,6 +43,7 @@ pkglib_LTLIBRARIES=libiochannel.la \ module-cli-protocol-unix.la \ module-pipe-sink.la \ module-alsa-sink.la \ + module-alsa-source.la \ module-oss.la \ module-oss-mmap.la \ module-simple-protocol-tcp.la \ @@ -128,6 +130,11 @@ libpacket_la_LDFLAGS = -avoid-version liboss_util_la_SOURCES = oss-util.c oss-util.h liboss_util_la_LDFLAGS = -avoid-version +libalsa_util_la_SOURCES = alsa-util.c alsa-util.h +libalsa_util_la_LDFLAGS = -avoid-version +libalsa_util_la_LIBADD = $(ASOUNDLIB_LIBS) +libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + libioline_la_SOURCES = ioline.c ioline.h libioline_la_LDFLAGS = -avoid-version libioline_la_LIBADD = libiochannel.la @@ -203,9 +210,14 @@ module_pipe_sink_la_LIBADD = libiochannel.la module_alsa_sink_la_SOURCES = module-alsa-sink.c module_alsa_sink_la_LDFLAGS = -module -avoid-version -module_alsa_sink_la_LIBADD = $(ASOUNDLIB_LIBS) +module_alsa_sink_la_LIBADD = $(ASOUNDLIB_LIBS) libalsa-util.la module_alsa_sink_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) +module_alsa_source_la_SOURCES = module-alsa-source.c +module_alsa_source_la_LDFLAGS = -module -avoid-version +module_alsa_source_la_LIBADD = $(ASOUNDLIB_LIBS) libalsa-util.la +module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + module_oss_la_SOURCES = module-oss.c module_oss_la_LDFLAGS = -module -avoid-version module_oss_la_LIBADD = libiochannel.la liboss-util.la diff --git a/src/alsa-util.c b/src/alsa-util.c new file mode 100644 index 00000000..edfd1613 --- /dev/null +++ b/src/alsa-util.c @@ -0,0 +1,73 @@ +#include + +#include "alsa-util.h" +#include "sample.h" + +int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, struct pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *buffer_size) { + int ret = 0; + snd_pcm_hw_params_t *hwparams = NULL; + static const snd_pcm_format_t format_trans[] = { + [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8, + [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW, + [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW, + [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE, + [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, + [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, + [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, + }; + + if (snd_pcm_hw_params_malloc(&hwparams) < 0 || + snd_pcm_hw_params_any(pcm_handle, hwparams) < 0 || + snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 || + snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[ss->format]) < 0 || + snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &ss->rate, NULL) < 0 || + snd_pcm_hw_params_set_channels(pcm_handle, hwparams, ss->channels) < 0 || + snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, periods, NULL) < 0 || + snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, buffer_size) < 0 || + snd_pcm_hw_params(pcm_handle, hwparams) < 0) { + ret = -1; + } + + if (hwparams) + snd_pcm_hw_params_free(hwparams); + return ret; +} + +int pa_create_io_sources(snd_pcm_t *pcm_handle, struct pa_mainloop_api* m, void ***io_sources, unsigned *n_io_sources, void (*cb)(struct pa_mainloop_api*a, void *id, int fd, enum pa_mainloop_api_io_events events, void *userdata), void *userdata) { + unsigned i; + struct pollfd *pfds, *ppfd; + void **ios; + assert(pcm_handle && m && io_sources && n_io_sources && cb); + + *n_io_sources = snd_pcm_poll_descriptors_count(pcm_handle); + + pfds = malloc(sizeof(struct pollfd) * *n_io_sources); + assert(pfds); + if (snd_pcm_poll_descriptors(pcm_handle, pfds, *n_io_sources) < 0) { + free(pfds); + return -1; + } + + *io_sources = malloc(sizeof(void*) * *n_io_sources); + assert(io_sources); + + for (i = 0, ios = *io_sources, ppfd = pfds; i < *n_io_sources; i++, ios++, ppfd++) { + *ios = m->source_io(m, ppfd->fd, + ((ppfd->events & POLLIN) ? PA_MAINLOOP_API_IO_EVENT_INPUT : 0) | + ((ppfd->events & POLLOUT) ? PA_MAINLOOP_API_IO_EVENT_OUTPUT : 0), cb, userdata); + assert(*ios); + } + + free(pfds); + return 0; +} + +void pa_free_io_sources(struct pa_mainloop_api* m, void **io_sources, unsigned n_io_sources) { + unsigned i; + void **ios; + assert(m && io_sources); + + for (ios = io_sources, i = 0; i < n_io_sources; i++, ios++) + m->cancel_io(m, *ios); + free(io_sources); +} diff --git a/src/alsa-util.h b/src/alsa-util.h new file mode 100644 index 00000000..4468c9d8 --- /dev/null +++ b/src/alsa-util.h @@ -0,0 +1,14 @@ +#ifndef fooalsautilhfoo +#define fooalsautilhfoo + +#include + +#include "sample.h" +#include "mainloop-api.h" + +int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, struct pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *buffer_size); + +int pa_create_io_sources(snd_pcm_t *pcm_handle, struct pa_mainloop_api *m, void ***io_sources, unsigned *n_io_sources, void (*cb)(struct pa_mainloop_api*a, void *id, int fd, enum pa_mainloop_api_io_events events, void *userdata), void *userdata); +void pa_free_io_sources(struct pa_mainloop_api* m, void **io_sources, unsigned n_io_sources); + +#endif diff --git a/src/module-alsa-sink.c b/src/module-alsa-sink.c index 16571a2c..a0b1e548 100644 --- a/src/module-alsa-sink.c +++ b/src/module-alsa-sink.c @@ -11,6 +11,7 @@ #include "modargs.h" #include "util.h" #include "sample-util.h" +#include "alsa-util.h" struct userdata { snd_pcm_t *pcm_handle; @@ -39,7 +40,7 @@ static const char* const valid_modargs[] = { static void xrun_recovery(struct userdata *u) { assert(u); - fprintf(stderr, "*** XRUN ***\n"); + fprintf(stderr, "*** ALSA-XRUN (playback) ***\n"); if (snd_pcm_prepare(u->pcm_handle) < 0) fprintf(stderr, "snd_pcm_prepare() failed\n"); @@ -63,13 +64,10 @@ static void do_write(struct userdata *u) { assert(memchunk->memblock && memchunk->memblock->data && memchunk->length && memchunk->memblock->length && (memchunk->length % u->frame_size) == 0); - assert(u->pcm_handle); - assert(memchunk->memblock); - assert(memchunk->memblock->data); - assert(memchunk->length); - assert(u->frame_size); - if ((frames = snd_pcm_writei(u->pcm_handle, memchunk->memblock->data + memchunk->index, memchunk->length / u->frame_size)) < 0) { + if (frames == -EAGAIN) + return; + if (frames == -EPIPE) { xrun_recovery(u); continue; @@ -80,8 +78,9 @@ static void do_write(struct userdata *u) { } if (memchunk == &u->memchunk) { - memchunk->index += frames * u->frame_size; - memchunk->length -= frames * u->frame_size; + size_t l = frames * u->frame_size; + memchunk->index += l; + memchunk->length -= l; if (memchunk->length == 0) { pa_memblock_unref(memchunk->memblock); @@ -125,23 +124,11 @@ int pa_module_init(struct pa_core *c, struct pa_module*m) { struct pa_modargs *ma = NULL; int ret = -1; struct userdata *u = NULL; - snd_pcm_hw_params_t *hwparams; const char *dev; - struct pollfd *pfds, *ppfd; struct pa_sample_spec ss; - unsigned i, periods, fragsize; + unsigned periods, fragsize; snd_pcm_uframes_t buffer_size; - void ** ios; size_t frame_size; - static const snd_pcm_format_t format_trans[] = { - [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8, - [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW, - [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW, - [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE, - [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, - [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, - [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, - }; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { fprintf(stderr, __FILE__": failed to parse module arguments\n"); @@ -169,20 +156,12 @@ int pa_module_init(struct pa_core *c, struct pa_module*m) { m->userdata = u; if (snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { - fprintf(stderr, "Error opening PCM device %s\n", dev); + fprintf(stderr, __FILE__": Error opening PCM device %s\n", dev); goto fail; } - snd_pcm_hw_params_alloca(&hwparams); - if (snd_pcm_hw_params_any(u->pcm_handle, hwparams) < 0 || - snd_pcm_hw_params_set_access(u->pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 || - snd_pcm_hw_params_set_format(u->pcm_handle, hwparams, format_trans[ss.format]) < 0 || - snd_pcm_hw_params_set_rate_near(u->pcm_handle, hwparams, &ss.rate, NULL) < 0 || - snd_pcm_hw_params_set_channels(u->pcm_handle, hwparams, ss.channels) < 0 || - snd_pcm_hw_params_set_periods_near(u->pcm_handle, hwparams, &periods, NULL) < 0 || - snd_pcm_hw_params_set_buffer_size_near(u->pcm_handle, hwparams, &buffer_size) < 0 || - snd_pcm_hw_params(u->pcm_handle, hwparams) < 0) { - fprintf(stderr, "Error setting HW params.\n"); + if (pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &buffer_size) < 0) { + fprintf(stderr, __FILE__": Failed to set hardware parameters\n"); goto fail; } @@ -194,28 +173,11 @@ int pa_module_init(struct pa_core *c, struct pa_module*m) { pa_sink_set_owner(u->sink, m); u->sink->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev); - u->n_io_sources = snd_pcm_poll_descriptors_count(u->pcm_handle); - - pfds = malloc(sizeof(struct pollfd) * u->n_io_sources); - assert(pfds); - if (snd_pcm_poll_descriptors(u->pcm_handle, pfds, u->n_io_sources) < 0) { - printf("Unable to obtain poll descriptors for playback.\n"); - free(pfds); + if (pa_create_io_sources(u->pcm_handle, c->mainloop, &u->io_sources, &u->n_io_sources, io_callback, u) < 0) { + fprintf(stderr, __FILE__": failed to obtain file descriptors\n"); goto fail; } - u->io_sources = malloc(sizeof(void*) * u->n_io_sources); - assert(u->io_sources); - - for (i = 0, ios = u->io_sources, ppfd = pfds; i < u->n_io_sources; i++, ios++, ppfd++) { - *ios = c->mainloop->source_io(c->mainloop, ppfd->fd, - ((ppfd->events & POLLIN) ? PA_MAINLOOP_API_IO_EVENT_INPUT : 0) | - ((ppfd->events & POLLOUT) ? PA_MAINLOOP_API_IO_EVENT_OUTPUT : 0), io_callback, u); - assert(*ios); - } - - free(pfds); - u->frame_size = frame_size; u->fragment_size = buffer_size*u->frame_size/periods; @@ -250,14 +212,11 @@ void pa_module_done(struct pa_core *c, struct pa_module*m) { assert(c && m); if ((u = m->userdata)) { - unsigned i; - void **ios; - - pa_sink_free(u->sink); + if (u->sink) + pa_sink_free(u->sink); - for (ios = u->io_sources, i = 0; i < u->n_io_sources; i++, ios++) - c->mainloop->cancel_io(c->mainloop, *ios); - free(u->io_sources); + if (u->io_sources) + pa_free_io_sources(c->mainloop, u->io_sources, u->n_io_sources); if (u->pcm_handle) { snd_pcm_drop(u->pcm_handle); diff --git a/src/module-alsa-source.c b/src/module-alsa-source.c new file mode 100644 index 00000000..cc45254c --- /dev/null +++ b/src/module-alsa-source.c @@ -0,0 +1,212 @@ +#include +#include +#include + +#include + +#include "module.h" +#include "core.h" +#include "memchunk.h" +#include "sink.h" +#include "modargs.h" +#include "util.h" +#include "sample-util.h" +#include "alsa-util.h" + +struct userdata { + snd_pcm_t *pcm_handle; + struct pa_source *source; + void **io_sources; + unsigned n_io_sources; + + size_t frame_size, fragment_size; + struct pa_memchunk memchunk; +}; + +static const char* const valid_modargs[] = { + "device", + "source_name", + "format", + "channels", + "rate", + "fragments", + "fragment_size", + NULL +}; + +#define DEFAULT_SOURCE_NAME "alsa_input" +#define DEFAULT_DEVICE "hw:0,0" + +static void xrun_recovery(struct userdata *u) { + assert(u); + + fprintf(stderr, "*** ALSA-XRUN (capture) ***\n"); + + if (snd_pcm_prepare(u->pcm_handle) < 0) + fprintf(stderr, "snd_pcm_prepare() failed\n"); +} + +static void do_read(struct userdata *u) { + assert(u); + + for (;;) { + struct pa_memchunk post_memchunk; + snd_pcm_sframes_t frames; + size_t l; + + if (!u->memchunk.memblock) { + u->memchunk.memblock = pa_memblock_new(u->memchunk.length = u->fragment_size); + u->memchunk.index = 0; + } + + assert(u->memchunk.memblock && u->memchunk.memblock->data && u->memchunk.length && u->memchunk.memblock->length && (u->memchunk.length % u->frame_size) == 0); + + if ((frames = snd_pcm_readi(u->pcm_handle, u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) { + if (frames == -EAGAIN) + return; + + if (frames == -EPIPE) { + xrun_recovery(u); + continue; + } + + fprintf(stderr, "snd_pcm_readi() failed: %s\n", strerror(-frames)); + return; + } + + l = frames * u->frame_size; + + post_memchunk = u->memchunk; + post_memchunk.length = l; + + pa_source_post(u->source, &post_memchunk); + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length == 0) { + pa_memblock_unref(u->memchunk.memblock); + u->memchunk.memblock = NULL; + u->memchunk.index = u->memchunk.length = 0; + } + + break; + } +} + +static void io_callback(struct pa_mainloop_api*a, void *id, int fd, enum pa_mainloop_api_io_events events, void *userdata) { + struct userdata *u = userdata; + assert(u && a && id); + + if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN) + xrun_recovery(u); + + do_read(u); +} + +int pa_module_init(struct pa_core *c, struct pa_module*m) { + struct pa_modargs *ma = NULL; + int ret = -1; + struct userdata *u = NULL; + const char *dev; + struct pa_sample_spec ss; + unsigned periods, fragsize; + snd_pcm_uframes_t buffer_size; + size_t frame_size; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + fprintf(stderr, __FILE__": failed to parse module arguments\n"); + goto fail; + } + + ss = c->default_sample_spec; + if (pa_modargs_get_sample_spec(ma, &ss) < 0) { + fprintf(stderr, __FILE__": failed to parse sample specification\n"); + goto fail; + } + frame_size = pa_sample_size(&ss); + + periods = 12; + fragsize = 1024; + if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) { + fprintf(stderr, __FILE__": failed to parse buffer metrics\n"); + goto fail; + } + buffer_size = fragsize/frame_size*periods; + + u = malloc(sizeof(struct userdata)); + assert(u); + memset(u, 0, sizeof(struct userdata)); + m->userdata = u; + + if (snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0) { + fprintf(stderr, __FILE__": Error opening PCM device %s\n", dev); + goto fail; + } + + if (pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &buffer_size) < 0) { + fprintf(stderr, __FILE__": Failed to set hardware parameters\n"); + goto fail; + } + + u->source = pa_source_new(c, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss); + assert(u->source); + + u->source->userdata = u; + pa_source_set_owner(u->source, m); + u->source->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev); + + if (pa_create_io_sources(u->pcm_handle, c->mainloop, &u->io_sources, &u->n_io_sources, io_callback, u) < 0) { + fprintf(stderr, __FILE__": failed to obtain file descriptors\n"); + goto fail; + } + + u->frame_size = frame_size; + u->fragment_size = buffer_size*u->frame_size/periods; + + fprintf(stderr, __FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size); + + u->memchunk.memblock = NULL; + u->memchunk.index = u->memchunk.length = 0; + + snd_pcm_start(u->pcm_handle); + + ret = 0; + +finish: + if (ma) + pa_modargs_free(ma); + + return ret; + +fail: + + if (u) + pa_module_done(c, m); + + goto finish; +} + +void pa_module_done(struct pa_core *c, struct pa_module*m) { + struct userdata *u; + assert(c && m); + + if ((u = m->userdata)) { + if (u->source) + pa_source_free(u->source); + + if (u->io_sources) + pa_free_io_sources(c->mainloop, u->io_sources, u->n_io_sources); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + free(u); + } +} + diff --git a/src/polypaudio.run b/src/polypaudio.run index 48c3f7ea..2698c538 100755 --- a/src/polypaudio.run +++ b/src/polypaudio.run @@ -1,12 +1,20 @@ #!./polypaudio -F -# Load the CLI module -load module-cli +# Load audio drivers +load module-alsa-sink +load module-alsa-source device=plughw:1,0 +#load module-oss-mmap device="/dev/dsp" + +# Make some devices default +sink_default alsa_output +source_default alsa_input +# Load several protocols load module-esound-protocol-tcp load module-simple-protocol-tcp load module-native-protocol-unix load module-cli-protocol-unix -load module-alsa-sink -#load module-oss-mmap device="/dev/dsp" +# Load the CLI module +load module-cli + diff --git a/src/todo b/src/todo index e6685b2e..8a97ca6a 100644 --- a/src/todo +++ b/src/todo @@ -1,5 +1,4 @@ -- alsa source - +- make the sample spec configurable for oss devices - prefix modules/libraries with pa_ - rename files - svn-id and license in every file @@ -33,4 +32,4 @@ drivers: - python modules: -- http +- http? -- cgit