diff options
author | Liam Girdwood <lrg@ti.com> | 2011-10-13 12:59:51 -0700 |
---|---|---|
committer | Simon Wilson <simonwilson@google.com> | 2011-10-13 13:46:50 -0700 |
commit | 6be28f1b16d3f48340069b93b5db42fead60bfa2 (patch) | |
tree | f0e43999a8be7363aea8ca27a84dfb450a78fbdb | |
parent | fa0697e887b383a970d726513ae804bccb7630c0 (diff) |
pcm: add mmap playback and no periodic IRQ support.
Add mmap playback and no period IRQ support to pcm core.
-rw-r--r-- | include/tinyalsa/asoundlib.h | 25 | ||||
-rw-r--r-- | pcm.c | 343 |
2 files changed, 360 insertions, 8 deletions
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h index 2e8c3e0..3c86c84 100644 --- a/include/tinyalsa/asoundlib.h +++ b/include/tinyalsa/asoundlib.h @@ -43,6 +43,19 @@ struct pcm; #define PCM_OUT 0x00000000 #define PCM_IN 0x10000000 +#define PCM_MMAP 0x00000001 +#define PCM_NOIRQ 0x00000002 + +/* PCM runtime states */ +#define PCM_STATE_OPEN 0 +#define PCM_STATE_SETUP 1 +#define PCM_STATE_PREPARED 2 +#define PCM_STATE_RUNNING 3 +#define PCM_STATE_XRUN 4 +#define PCM_STATE_DRAINING 5 +#define PCM_STATE_PAUSED 6 +#define PCM_STATE_SUSPENDED 7 +#define PCM_STATE_DISCONNECTED 8 /* Bit formats */ enum pcm_format { @@ -99,10 +112,12 @@ int pcm_set_config(struct pcm *pcm, struct pcm_config *config); /* Returns a human readable reason for the last error */ const char *pcm_get_error(struct pcm *pcm); -/* Returns the buffer size (int bytes) that should be used for pcm_write. +/* Returns the buffer size (int frames) that should be used for pcm_write. * This will be 1/2 of the actual fifo size. */ unsigned int pcm_get_buffer_size(struct pcm *pcm); +unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames); +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int frames); /* Returns the pcm latency in ms */ unsigned int pcm_get_latency(struct pcm *pcm); @@ -123,6 +138,14 @@ int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, int pcm_write(struct pcm *pcm, void *data, unsigned int count); int pcm_read(struct pcm *pcm, void *data, unsigned int count); +/* + * mmap() support. + */ +int pcm_mmap_write(struct pcm *pcm, void *data, unsigned int count); +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, + unsigned int *frames); +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames); + /* Start and stop a PCM channel that doesn't transfer data */ int pcm_start(struct pcm *pcm); int pcm_stop(struct pcm *pcm); @@ -33,10 +33,12 @@ #include <string.h> #include <errno.h> #include <unistd.h> +#include <poll.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> +#include <limits.h> #include <linux/ioctl.h> #define __force @@ -47,6 +49,7 @@ #include <tinyalsa/asoundlib.h> #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL +#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) static inline int param_is_mask(int p) { @@ -108,6 +111,16 @@ static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) } } +static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) +{ + if (param_is_interval(n)) { + struct snd_interval *i = param_to_interval(p, n); + if (i->integer) + return i->max; + } + return 0; +} + static void param_init(struct snd_pcm_hw_params *p) { int n; @@ -135,11 +148,14 @@ struct pcm { int running:1; int underruns; unsigned int buffer_size; + unsigned int boundary; char error[PCM_ERROR_MAX]; struct pcm_config config; struct snd_pcm_mmap_status *mmap_status; struct snd_pcm_mmap_control *mmap_control; struct snd_pcm_sync_ptr *sync_ptr; + void *mmap_buffer; + unsigned int noirq_frames_per_msec; }; unsigned int pcm_get_buffer_size(struct pcm *pcm) @@ -190,6 +206,18 @@ static unsigned int pcm_format_to_bits(enum pcm_format format) }; } +unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes) +{ + return bytes / (pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3)); +} + +unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames) +{ + return frames * pcm->config.channels * + (pcm_format_to_bits(pcm->config.format) >> 3); +} + static int pcm_sync_ptr(struct pcm *pcm, int flags) { if (pcm->sync_ptr) { pcm->sync_ptr->flags = flags; @@ -253,6 +281,44 @@ static void pcm_hw_munmap_status(struct pcm *pcm) { pcm->mmap_control = NULL; } +static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, + const char *src, unsigned int src_offset, + unsigned int frames) +{ + int size_bytes = pcm_frames_to_bytes(pcm, frames); + int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset); + int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); + + /* interleaved only atm */ + memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes, + src + src_offset_bytes, size_bytes); + return 0; +} + +static int pcm_mmap_write_areas(struct pcm *pcm, char *src, + unsigned int offset, unsigned int size) +{ + void *pcm_areas; + int commit; + unsigned int pcm_offset, frames, count = 0; + + while (size > 0) { + frames = size; + pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); + pcm_areas_copy(pcm, pcm_offset, src, offset, frames); + commit = pcm_mmap_commit(pcm, pcm_offset, frames); + if (commit < 0) { + oops(pcm, commit, "failed to commit %d frames\n", frames); + return commit; + } + + offset += commit; + count += commit; + size -= commit; + } + return count; +} + int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp) { @@ -361,6 +427,11 @@ int pcm_close(struct pcm *pcm) pcm_hw_munmap_status(pcm); + if (pcm->flags & PCM_MMAP) { + pcm_stop(pcm); + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); + } + if (pcm->fd >= 0) close(pcm->fd); pcm->running = 0; @@ -398,12 +469,10 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { oops(pcm, errno, "cannot get info"); - goto fail; + goto fail_close; } param_init(¶ms); - param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, - SNDRV_PCM_ACCESS_RW_INTERLEAVED); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, pcm_format_to_alsa(config->format)); param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, @@ -418,30 +487,70 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate); + if (flags & PCM_NOIRQ) { + + if (!(flags & PCM_MMAP)) { + oops(pcm, -EINVAL, "noirq only currently supported with mmap()."); + goto fail; + } + + params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP; + pcm->noirq_frames_per_msec = config->rate / 1000; + } + + if (flags & PCM_MMAP) + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else + param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { oops(pcm, errno, "cannot set hw params"); - goto fail; + goto fail_close; } + /* get our refined hw_params */ + config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS); + pcm->buffer_size = config->period_count * config->period_size; + + if (flags & PCM_MMAP) { + pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size), + PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0); + if (pcm->mmap_buffer == MAP_FAILED) { + oops(pcm, -errno, "failed to mmap buffer %d bytes\n", + pcm_frames_to_bytes(pcm, pcm->buffer_size)); + goto fail_close; + } + } + + memset(&sparams, 0, sizeof(sparams)); sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE; sparams.period_step = 1; sparams.avail_min = 1; if (!config->start_threshold) - sparams.start_threshold = config->period_count * config->period_size; + pcm->config.start_threshold = sparams.start_threshold = + config->period_count * config->period_size / 2; else sparams.start_threshold = config->start_threshold; + /* pick a high stop threshold - todo: does this need further tuning */ if (!config->stop_threshold) - sparams.stop_threshold = config->period_count * config->period_size; + pcm->config.stop_threshold = sparams.stop_threshold = + config->period_count * config->period_size * 10; else sparams.stop_threshold = config->stop_threshold; sparams.xfer_align = config->period_size / 2; /* needed for old kernels */ sparams.silence_size = 0; sparams.silence_threshold = config->silence_threshold; + pcm->boundary = sparams.boundary = pcm->buffer_size; + while (pcm->boundary * 2 <= LONG_MAX - pcm->buffer_size) + pcm->boundary *= 2; if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { oops(pcm, errno, "cannot set sw params"); @@ -454,11 +563,13 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, goto fail; } - pcm->buffer_size = config->period_count * config->period_size; pcm->underruns = 0; return pcm; fail: + if (flags & PCM_MMAP) + munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); +fail_close: close(pcm->fd); pcm->fd = -1; return pcm; @@ -473,9 +584,14 @@ int pcm_start(struct pcm *pcm) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) return oops(pcm, errno, "cannot prepare channel"); + + if (pcm->flags & PCM_MMAP) + pcm_sync_ptr(pcm, 0); + if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) return oops(pcm, errno, "cannot start channel"); + pcm->running = 1; return 0; } @@ -484,6 +600,219 @@ int pcm_stop(struct pcm *pcm) if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) return oops(pcm, errno, "cannot stop channel"); + pcm->running = 0; + return 0; +} + +static inline int pcm_mmap_playback_avail(struct pcm *pcm) +{ + int avail; + + avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; + + if (avail < 0) + avail += pcm->boundary; + else if (avail > (int)pcm->boundary) + avail -= pcm->boundary; + + return avail; +} + +static inline int pcm_mmap_capture_avail(struct pcm *pcm) +{ + int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr; + if (avail < 0) + avail += pcm->boundary; + return avail; +} + +static inline int pcm_mmap_avail(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC); + if (pcm->flags & PCM_IN) + return pcm_mmap_capture_avail(pcm); + else + return pcm_mmap_playback_avail(pcm); +} + +static void pcm_mmap_appl_forward(struct pcm *pcm, int frames) +{ + unsigned int appl_ptr = pcm->mmap_control->appl_ptr; + appl_ptr += frames; + + /* check for boundary wrap */ + if (appl_ptr > pcm->boundary) + appl_ptr -= pcm->boundary; + pcm->mmap_control->appl_ptr = appl_ptr; +} + +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, + unsigned int *frames) +{ + unsigned int continuous, copy_frames, avail; + + /* return the mmap buffer */ + *areas = pcm->mmap_buffer; + + /* and the application offset in frames */ + *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size; + + avail = pcm_mmap_avail(pcm); + if (avail > pcm->buffer_size) + avail = pcm->buffer_size; + continuous = pcm->buffer_size - *offset; + + /* we can only copy frames if the are availabale and continuos */ + copy_frames = *frames; + if (copy_frames > avail) + copy_frames = avail; + if (copy_frames > continuous) + copy_frames = continuous; + *frames = copy_frames; + return 0; } +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames) +{ + /* update the application pointer in userspace and kernel */ + pcm_mmap_appl_forward(pcm, frames); + pcm_sync_ptr(pcm, 0); + + return frames; +} + +int pcm_avail_update(struct pcm *pcm) +{ + pcm_sync_ptr(pcm, 0); + return pcm_mmap_avail(pcm); +} + +int pcm_state(struct pcm *pcm) +{ + int err = pcm_sync_ptr(pcm, 0); + if (err < 0) + return err; + + return pcm->mmap_status->state; +} + +int pcm_wait(struct pcm *pcm, int timeout) +{ + struct pollfd pfd; + unsigned short revents = 0; + int err; + + pfd.fd = pcm->fd; + pfd.events = POLLOUT | POLLERR | POLLNVAL; + + do { + /* let's wait for avail or timeout */ + err = poll(&pfd, 1, timeout); + if (err < 0) + return -errno; + + /* timeout ? */ + if (err == 0) + return 0; + + /* have we been interrupted ? */ + if (errno == -EINTR) + continue; + + /* check for any errors */ + if (pfd.revents & (POLLERR | POLLNVAL)) { + switch (pcm_state(pcm)) { + case PCM_STATE_XRUN: + return -EPIPE; + case PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EIO; + } + } + /* poll again if fd not ready for IO */ + } while (!(pfd.revents & (POLLIN | POLLOUT))); + + return 1; +} + +int pcm_mmap_write(struct pcm *pcm, void *buffer, unsigned int bytes) +{ + int err = 0, frames, avail; + unsigned int offset = 0, count; + + if (bytes == 0) + return 0; + + count = pcm_bytes_to_frames(pcm, bytes); + + while (count > 0) { + + /* get the available space for writing new frames */ + avail = pcm_avail_update(pcm); + if (avail < 0) { + fprintf(stderr, "cannot determine available mmap frames"); + return err; + } + + /* start the audio if we reach the threshold */ + if (!pcm->running && + (pcm->buffer_size - avail) >= pcm->config.start_threshold) { + if (pcm_start(pcm) < 0) { + fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return -errno; + } + } + + /* sleep until we have space to write new frames */ + if (pcm->running && + (unsigned int)avail < pcm->mmap_control->avail_min) { + int time = -1; + + if (pcm->flags & PCM_NOIRQ) + time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min) + / pcm->noirq_frames_per_msec; + + err = pcm_wait(pcm, time); + if (err < 0) { + pcm->running = 0; + fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + pcm->mmap_control->appl_ptr = 0; + return err; + } + continue; + } + + frames = count; + if (frames > avail) + frames = avail; + + if (!frames) + break; + + /* copy frames from buffer */ + frames = pcm_mmap_write_areas(pcm, buffer, offset, frames); + if (frames < 0) { + fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n", + (unsigned int)pcm->mmap_status->hw_ptr, + (unsigned int)pcm->mmap_control->appl_ptr, + avail); + return frames; + } + + offset += frames; + count -= frames; + } + +_end: + return 0; +} |