From 8aaccc9484a2f2f33f3fd514975ca590b2d691fd Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 9 May 2008 16:02:02 +0200 Subject: [PATCH] Implemented snd_pcm_sw_params_(set|get)_period_event for interrupt wakeup like behaviour Actually, PCM timer is used as source for poll(). It might be optimized in the kernel code later. --- include/pcm.h | 2 + include/sound/asound.h | 3 +- src/pcm/pcm.c | 31 ++++++++ src/pcm/pcm_hw.c | 173 ++++++++++++++++++++++++++++++++++++++++- src/pcm/pcm_local.h | 5 +- test/pcm.c | 24 +++++- 6 files changed, 231 insertions(+), 7 deletions(-) diff --git a/include/pcm.h b/include/pcm.h index 4eb5035d..e655aa67 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -717,6 +717,8 @@ int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *param int snd_pcm_sw_params_get_tstamp_mode(const snd_pcm_sw_params_t *params, snd_pcm_tstamp_t *val); int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); int snd_pcm_sw_params_get_avail_min(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val); +int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val); +int snd_pcm_sw_params_get_period_event(const snd_pcm_sw_params_t *params, int *val); int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); int snd_pcm_sw_params_get_start_threshold(const snd_pcm_sw_params_t *paramsm, snd_pcm_uframes_t *val); int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); diff --git a/include/sound/asound.h b/include/sound/asound.h index d86a457b..e5fbea43 100644 --- a/include/sound/asound.h +++ b/include/sound/asound.h @@ -396,7 +396,8 @@ struct sndrv_pcm_sw_params { sndrv_pcm_uframes_t silence_threshold; /* min distance from noise for silence filling */ sndrv_pcm_uframes_t silence_size; /* silence block size */ sndrv_pcm_uframes_t boundary; /* pointers wrap point */ - unsigned char reserved[64]; /* reserved for future */ + unsigned char reserved[60]; /* reserved for future */ + unsigned int period_event; /* for alsa-lib implementation */ }; struct sndrv_pcm_channel_info { diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index 88d15152..3f4c033a 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -879,6 +879,7 @@ int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) pcm->tstamp_mode = params->tstamp_mode; pcm->period_step = params->period_step; pcm->avail_min = params->avail_min; + pcm->period_event = params->period_event; pcm->start_threshold = params->start_threshold; pcm->stop_threshold = params->stop_threshold; pcm->silence_threshold = params->silence_threshold; @@ -1847,6 +1848,7 @@ int snd_pcm_dump_sw_setup(snd_pcm_t *pcm, snd_output_t *out) snd_output_printf(out, " tstamp_mode : %s\n", snd_pcm_tstamp_mode_name(pcm->tstamp_mode)); snd_output_printf(out, " period_step : %d\n", pcm->period_step); snd_output_printf(out, " avail_min : %ld\n", pcm->avail_min); + snd_output_printf(out, " period_event : %i\n", pcm->period_event); snd_output_printf(out, " start_threshold : %ld\n", pcm->start_threshold); snd_output_printf(out, " stop_threshold : %ld\n", pcm->stop_threshold); snd_output_printf(out, " silence_threshold: %ld\n", pcm->silence_threshold); @@ -5364,6 +5366,7 @@ int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) params->period_step = pcm->period_step; params->sleep_min = 0; params->avail_min = pcm->avail_min; + params->period_event = pcm->period_event; params->xfer_align = 1; params->start_threshold = pcm->start_threshold; params->stop_threshold = pcm->stop_threshold; @@ -5659,6 +5662,34 @@ int snd_pcm_sw_params_get_avail_min(const snd_pcm_sw_params_t *params, snd_pcm_u return 0; } +/** + * \brief Set period event inside a software configuration container + * \param pcm PCM handle + * \param params Software configuration container + * \param val 0 = disable period event, 1 = enable period event + * \return 0 otherwise a negative error code + * + * An poll (select) wakeup event is raised if enabled. + */ +int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val) +{ + assert(pcm && params); + params->period_event = val; + return 0; +} + +/** + * \brief Get period event from a software configuration container + * \param params Software configuration container + * \param val returned period event state + * \return 0 otherwise a negative error code + */ +int snd_pcm_sw_params_get_period_event(const snd_pcm_sw_params_t *params, int *val) +{ + assert(params && val); + *val = params->period_event; + return 0; +} /** * \brief (DEPRECATED) Set xfer align inside a software configuration container diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c index 5acad3dd..9d2d38a5 100644 --- a/src/pcm/pcm_hw.c +++ b/src/pcm/pcm_hw.c @@ -39,6 +39,7 @@ #include #include "pcm_local.h" #include "../control/control_local.h" +#include "../timer/timer_local.h" //#define DEBUG_RW /* use to debug readi/writei/readn/writen */ //#define DEBUG_MMAP /* debug mmap_commit */ @@ -79,6 +80,8 @@ struct sndrv_pcm_hw_params_old { static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params); static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm); +static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops; +static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer; /* * @@ -94,6 +97,10 @@ typedef struct { struct sndrv_pcm_sync_ptr *sync_ptr; snd_pcm_uframes_t hw_ptr; snd_pcm_uframes_t appl_ptr; + int period_event; + snd_timer_t *period_timer; + struct pollfd period_timer_pfd; + int period_timer_need_poll; /* restricted parameters */ snd_pcm_format_t format; int rate; @@ -139,6 +146,53 @@ static inline int sync_ptr(snd_pcm_hw_t *hw, unsigned int flags) return hw->sync_ptr ? sync_ptr1(hw, flags) : 0; } +static int snd_pcm_hw_clear_timer_queue(snd_pcm_hw_t *hw) +{ + if (hw->period_timer_need_poll) { + while (poll(&hw->period_timer_pfd, 1, 0) > 0) { + snd_timer_tread_t rbuf[4]; + snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf)); + } + } else { + snd_timer_tread_t rbuf[4]; + snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf)); + } +} + +static int snd_pcm_hw_poll_descriptors_count(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 2; +} + +static int snd_pcm_hw_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space) +{ + snd_pcm_hw_t *hw = pcm->private_data; + + if (space < 2) + return -ENOMEM; + pfds[0].fd = hw->fd; + pfds[0].events = pcm->poll_events | POLLERR | POLLNVAL; + pfds[1].fd = hw->period_timer_pfd.fd; + pfds[1].events = POLLIN | POLLERR | POLLNVAL; + return 2; +} + +static int snd_pcm_hw_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned nfds, unsigned short *revents) +{ + snd_pcm_hw_t *hw = pcm->private_data; + unsigned int events; + + if (nfds != 2 || pfds[0].fd != hw->fd || pfds[1].fd != hw->period_timer_pfd.fd) + return -EINVAL; + events = pfds[0].revents; + if (pfds[1].revents & POLLIN) { + snd_pcm_hw_clear_timer_queue(hw); + events |= pcm->poll_events & ~(POLLERR|POLLNVAL); + } + *revents = events; + return 0; +} + static int snd_pcm_hw_nonblock(snd_pcm_t *pcm, int nonblock) { long flags; @@ -293,16 +347,94 @@ static int snd_pcm_hw_hw_free(snd_pcm_t *pcm) return 0; } +static void snd_pcm_hw_close_timer(snd_pcm_hw_t *hw) +{ + if (hw->period_timer) { + snd_timer_close(hw->period_timer); + hw->period_timer = NULL; + } +} + +static int snd_pcm_hw_change_timer(snd_pcm_t *pcm, int enable) +{ + snd_pcm_hw_t *hw = pcm->private_data; + snd_timer_params_t *params; + unsigned int suspend, resume; + int err; + + if (enable) { + snd_timer_params_alloca(¶ms); + err = snd_timer_hw_open(&hw->period_timer, "hw-pcm-period-event", SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, hw->card, hw->device, hw->subdevice, SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD); + if (err < 0) { + err = snd_timer_hw_open(&hw->period_timer, "hw-pcm-period-event", SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, hw->card, hw->device, hw->subdevice, SND_TIMER_OPEN_NONBLOCK); + return err; + } + if (snd_timer_poll_descriptors_count(hw->period_timer) != 1) { + snd_pcm_hw_close_timer(hw); + return -EINVAL; + } + hw->period_timer_pfd.events = POLLIN; + hw->period_timer_pfd.revents = 0; + snd_timer_poll_descriptors(hw->period_timer, &hw->period_timer_pfd, 1); + hw->period_timer_need_poll = 0; + suspend = 1<period_timer_pfd.fd, SNDRV_TIMER_IOCTL_PVERSION, &ver); + /* In older versions, check via poll before read() is needed + * because of the confliction between TIMER_START and + * FIONBIO ioctls. + */ + if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 4)) + hw->period_timer_need_poll = 1; + /* + * In older versions, timer uses pause events instead + * suspend/resume events. + */ + if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 5)) { + suspend = 1<period_timer, params); + if (err < 0) { + snd_pcm_hw_close_timer(hw); + return err; + } + err = snd_timer_start(hw->period_timer); + if (err < 0) { + snd_pcm_hw_close_timer(hw); + return err; + } + pcm->fast_ops = &snd_pcm_hw_fast_ops_timer; + } else { + snd_pcm_hw_close_timer(hw); + pcm->fast_ops = &snd_pcm_hw_fast_ops; + } + return 0; +} + static int snd_pcm_hw_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) { snd_pcm_hw_t *hw = pcm->private_data; int fd = hw->fd, err; + int old_period_event = params->period_event; + params->period_event = 0; if ((snd_pcm_tstamp_t) params->tstamp_mode == pcm->tstamp_mode && params->period_step == pcm->period_step && params->start_threshold == pcm->start_threshold && params->stop_threshold == pcm->stop_threshold && params->silence_threshold == pcm->silence_threshold && - params->silence_size == pcm->silence_size) { + params->silence_size == pcm->silence_size && + old_period_event == hw->period_event) { hw->mmap_control->avail_min = params->avail_min; return sync_ptr(hw, 0); } @@ -311,7 +443,14 @@ static int snd_pcm_hw_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed"); return err; } + params->period_event = old_period_event; hw->mmap_control->avail_min = params->avail_min; + if (hw->period_event != old_period_event) { + err = snd_pcm_hw_change_timer(pcm, old_period_event); + if (err < 0) + return err; + hw->period_event = old_period_event; + } return 0; } @@ -480,6 +619,7 @@ static int snd_pcm_hw_drop(snd_pcm_t *pcm) err = -errno; SYSMSG("SNDRV_PCM_IOCTL_DROP failed"); return err; + } else { } return 0; } @@ -945,6 +1085,37 @@ static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = { .poll_revents = NULL, }; +static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer = { + .status = snd_pcm_hw_status, + .state = snd_pcm_hw_state, + .hwsync = snd_pcm_hw_hwsync, + .delay = snd_pcm_hw_delay, + .prepare = snd_pcm_hw_prepare, + .reset = snd_pcm_hw_reset, + .start = snd_pcm_hw_start, + .drop = snd_pcm_hw_drop, + .drain = snd_pcm_hw_drain, + .pause = snd_pcm_hw_pause, + .rewindable = snd_pcm_hw_rewindable, + .rewind = snd_pcm_hw_rewind, + .forwardable = snd_pcm_hw_forwardable, + .forward = snd_pcm_hw_forward, + .resume = snd_pcm_hw_resume, + .link = snd_pcm_hw_link, + .link_slaves = snd_pcm_hw_link_slaves, + .unlink = snd_pcm_hw_unlink, + .writei = snd_pcm_hw_writei, + .writen = snd_pcm_hw_writen, + .readi = snd_pcm_hw_readi, + .readn = snd_pcm_hw_readn, + .avail_update = snd_pcm_hw_avail_update, + .mmap_commit = snd_pcm_hw_mmap_commit, + .htimestamp = snd_pcm_hw_htimestamp, + .poll_descriptors = snd_pcm_hw_poll_descriptors, + .poll_descriptors_count = snd_pcm_hw_poll_descriptors_count, + .poll_revents = snd_pcm_hw_poll_revents, +}; + /** * \brief Creates a new hw PCM * \param pcmp Returns created PCM handle diff --git a/src/pcm/pcm_local.h b/src/pcm/pcm_local.h index 0f1dcad0..72dd437a 100644 --- a/src/pcm/pcm_local.h +++ b/src/pcm/pcm_local.h @@ -195,8 +195,9 @@ struct _snd_pcm { snd_pcm_tstamp_t tstamp_mode; /* timestamp mode */ unsigned int period_step; snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */ - snd_pcm_uframes_t start_threshold; - snd_pcm_uframes_t stop_threshold; + int period_event; + snd_pcm_uframes_t start_threshold; + snd_pcm_uframes_t stop_threshold; snd_pcm_uframes_t silence_threshold; /* Silence filling happens when noise is nearest than this */ snd_pcm_uframes_t silence_size; /* Silence filling size */ diff --git a/test/pcm.c b/test/pcm.c index fb38d499..cd29259f 100644 --- a/test/pcm.c +++ b/test/pcm.c @@ -19,8 +19,9 @@ static unsigned int channels = 1; /* count of channels */ static unsigned int buffer_time = 500000; /* ring buffer length in us */ static unsigned int period_time = 100000; /* period time in us */ static double freq = 440; /* sinusoidal wave frequency in Hz */ -static int verbose = 0; /* verbose flag */ +static int verbose = 0; /* verbose flag */ static int resample = 1; /* enable alsa-lib resampling */ +static int period_event = 0; /* produce poll event after each period */ static snd_pcm_sframes_t buffer_size; static snd_pcm_sframes_t period_size; @@ -172,11 +173,20 @@ static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) return err; } /* allow the transfer when at least period_size samples can be processed */ - err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); + /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */ + err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size); if (err < 0) { printf("Unable to set avail min for playback: %s\n", snd_strerror(err)); return err; } + /* enable period events when requested */ + if (period_event) { + err = snd_pcm_sw_params_set_period_event(handle, swparams, 1); + if (err < 0) { + printf("Unable to set period event: %s\n", snd_strerror(err)); + return err; + } + } /* write the parameters to the playback device */ err = snd_pcm_sw_params(handle, swparams); if (err < 0) { @@ -192,6 +202,8 @@ static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) static int xrun_recovery(snd_pcm_t *handle, int err) { + if (verbose) + printf("stream recovery\n"); if (err == -EPIPE) { /* under-run */ err = snd_pcm_prepare(handle); if (err < 0) @@ -711,6 +723,8 @@ static void help(void) "-m,--method transfer method\n" "-o,--format sample format\n" "-v,--verbose show the PCM setup parameters\n" +"-n,--noresample do not resample\n" +"-e,--pevent enable poll event after each period\n" "\n"); printf("Recognized sample formats are:"); for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) { @@ -740,6 +754,7 @@ int main(int argc, char *argv[]) {"format", 1, NULL, 'o'}, {"verbose", 1, NULL, 'v'}, {"noresample", 1, NULL, 'n'}, + {"pevent", 1, NULL, 'e'}, {NULL, 0, NULL, 0}, }; snd_pcm_t *handle; @@ -757,7 +772,7 @@ int main(int argc, char *argv[]) morehelp = 0; while (1) { int c; - if ((c = getopt_long(argc, argv, "hD:r:c:f:b:p:m:o:vn", long_option, NULL)) < 0) + if ((c = getopt_long(argc, argv, "hD:r:c:f:b:p:m:o:vne", long_option, NULL)) < 0) break; switch (c) { case 'h': @@ -814,6 +829,9 @@ int main(int argc, char *argv[]) case 'n': resample = 0; break; + case 'e': + period_event = 1; + break; } } -- 2.47.1