summaryrefslogtreecommitdiff
path: root/src/pcm/pcm_hw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pcm/pcm_hw.c')
-rw-r--r--src/pcm/pcm_hw.c210
1 files changed, 202 insertions, 8 deletions
diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c
index 8495f71..95bb2ac 100644
--- a/src/pcm/pcm_hw.c
+++ b/src/pcm/pcm_hw.c
@@ -36,9 +36,9 @@
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
-#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 +79,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 const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops;
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer;
/*
*
@@ -94,6 +96,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 +145,54 @@ 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));
+ }
+ return 0;
+}
+
+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;
@@ -245,6 +299,11 @@ static int snd_pcm_hw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
// SYSMSG("SNDRV_PCM_IOCTL_HW_REFINE failed");
return err;
}
+
+ if (params->info != ~0U) {
+ params->info &= ~0xf0000000;
+ params->info |= (pcm->monotonic ? SND_PCM_INFO_MONOTONIC : 0);
+ }
return 0;
}
@@ -288,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);
}
@@ -306,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;
}
@@ -475,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;
}
@@ -503,6 +648,11 @@ static int snd_pcm_hw_pause(snd_pcm_t *pcm, int enable)
return 0;
}
+static snd_pcm_sframes_t snd_pcm_hw_rewindable(snd_pcm_t *pcm)
+{
+ return snd_pcm_mmap_hw_avail(pcm);
+}
+
static snd_pcm_sframes_t snd_pcm_hw_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
snd_pcm_hw_t *hw = pcm->private_data;
@@ -518,6 +668,11 @@ static snd_pcm_sframes_t snd_pcm_hw_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t fra
return frames;
}
+static snd_pcm_sframes_t snd_pcm_hw_forwardable(snd_pcm_t *pcm)
+{
+ return snd_pcm_mmap_avail(pcm);
+}
+
static snd_pcm_sframes_t snd_pcm_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
snd_pcm_hw_t *hw = pcm->private_data;
@@ -884,7 +1039,7 @@ static void snd_pcm_hw_dump(snd_pcm_t *pcm, snd_output_t *out)
}
}
-static snd_pcm_ops_t snd_pcm_hw_ops = {
+static const snd_pcm_ops_t snd_pcm_hw_ops = {
.close = snd_pcm_hw_close,
.info = snd_pcm_hw_info,
.hw_refine = snd_pcm_hw_hw_refine,
@@ -899,7 +1054,7 @@ static snd_pcm_ops_t snd_pcm_hw_ops = {
.munmap = snd_pcm_hw_munmap,
};
-static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
.status = snd_pcm_hw_status,
.state = snd_pcm_hw_state,
.hwsync = snd_pcm_hw_hwsync,
@@ -910,7 +1065,9 @@ static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
.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,
@@ -928,6 +1085,37 @@ static snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
.poll_revents = NULL,
};
+static const 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
@@ -994,7 +1182,7 @@ int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name,
if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_PCM_VERSION_MAX))
return -SND_ERROR_INCOMPATIBLE_VERSION;
-#ifdef HAVE_CLOCK_GETTIME
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
if (SNDRV_PROTOCOL_VERSION(2, 0, 9) <= ver) {
struct timespec timespec;
if (clock_gettime(CLOCK_MONOTONIC, &timespec) == 0) {
@@ -1006,9 +1194,9 @@ int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name,
}
monotonic = 1;
}
- }
+ } else
#endif
- else if (SNDRV_PROTOCOL_VERSION(2, 0, 5) <= ver) {
+ if (SNDRV_PROTOCOL_VERSION(2, 0, 5) <= ver) {
int on = 1;
if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) {
ret = -errno;
@@ -1318,7 +1506,13 @@ int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
/* revert to blocking mode for read/write access */
snd_pcm_hw_nonblock(*pcmp, 0);
(*pcmp)->mode = mode;
- }
+ } else
+ /* make sure the SND_PCM_NO_xxx flags don't get lost on the
+ * way */
+ (*pcmp)->mode |= mode & (SND_PCM_NO_AUTO_RESAMPLE|
+ SND_PCM_NO_AUTO_CHANNELS|
+ SND_PCM_NO_AUTO_FORMAT|
+ SND_PCM_NO_SOFTVOL);
hw = (*pcmp)->private_data;
if (format != SND_PCM_FORMAT_UNKNOWN)