]> git.alsa-project.org Git - alsa-lib.git/commitdiff
Implemented snd_pcm_sw_params_(set|get)_period_event for interrupt wakeup like behaviour
authorJaroslav Kysela <perex@perex.cz>
Fri, 9 May 2008 14:02:02 +0000 (16:02 +0200)
committerJaroslav Kysela <perex@perex.cz>
Fri, 9 May 2008 14:02:02 +0000 (16:02 +0200)
Actually, PCM timer is used as source for poll(). It might be optimized
in the kernel code later.

include/pcm.h
include/sound/asound.h
src/pcm/pcm.c
src/pcm/pcm_hw.c
src/pcm/pcm_local.h
test/pcm.c

index 4eb5035d936a69e493037d2b88f1f47fef4d84d4..e655aa675dce30673508e33177954ab29aa881e4 100644 (file)
@@ -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);
index d86a457bff9afc460c9a194ad02c1ebd259a8601..e5fbea436802c2ce875662701547b187dd3fb08d 100644 (file)
@@ -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 {
index 88d151523c8e93d7df2bdb895a7be439775409fd..3f4c033a58051fd4eda126c509ef74f0c5be2437 100644 (file)
@@ -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
index 5acad3dd9b150b2cd3cb4e29d1d64795e9c3cdf5..9d2d38a55ace6c5041951a16ee6eedb04dc45c35 100644 (file)
@@ -39,6 +39,7 @@
 #include <sys/shm.h>
 #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(&params);
+               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<<SND_TIMER_EVENT_MSUSPEND;
+               resume = 1<<SND_TIMER_EVENT_MRESUME;
+               /*
+                * hacks for older kernel drivers
+                */
+               {
+                       int ver = 0;
+                       ioctl(hw->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<<SND_TIMER_EVENT_MPAUSE;
+                               resume = 1<<SND_TIMER_EVENT_MCONTINUE;
+                       }
+               }
+               snd_timer_params_set_auto_start(params, 1);
+               snd_timer_params_set_ticks(params, 1);
+               snd_timer_params_set_filter(params, (1<<SND_TIMER_EVENT_TICK) |
+                                           suspend | resume);
+               err = snd_timer_params(hw->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
index 0f1dcad006dc5864d4202e5463d30705c888497b..72dd437aac05acb019e1fe1083e4cd2ac13c6be6 100644 (file)
@@ -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 */
index fb38d499ce30a7c6bbac4074a13a4fd1dfec84e6..cd29259f03f185ed4304177cfc9d9e0c87104951 100644 (file)
@@ -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;
                }
        }