| /** |
| * \file pcm/pcm_hw.c |
| * \ingroup PCM_Plugins |
| * \brief PCM HW Plugin Interface |
| * \author Abramo Bagnara <abramo@alsa-project.org> |
| * \author Jaroslav Kysela <perex@perex.cz> |
| * \date 2000-2001 |
| */ |
| /* |
| * PCM - Hardware |
| * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> |
| * |
| * |
| * This library is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as |
| * published by the Free Software Foundation; either version 2.1 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <string.h> |
| #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 */ |
| |
| #ifndef PIC |
| /* entry for static linking */ |
| const char *_snd_module_pcm_hw = ""; |
| #endif |
| |
| #ifndef DOC_HIDDEN |
| |
| #ifndef F_SETSIG |
| #define F_SETSIG 10 |
| #endif |
| |
| /* |
| * Compatibility |
| */ |
| |
| struct sndrv_pcm_hw_params_old { |
| unsigned int flags; |
| unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - |
| SNDRV_PCM_HW_PARAM_ACCESS + 1]; |
| struct snd_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME - |
| SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1]; |
| unsigned int rmask; |
| unsigned int cmask; |
| unsigned int info; |
| unsigned int msbits; |
| unsigned int rate_num; |
| unsigned int rate_den; |
| sndrv_pcm_uframes_t fifo_size; |
| unsigned char reserved[64]; |
| }; |
| |
| #define SND_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old) |
| #define SND_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, 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; |
| |
| /* |
| * |
| */ |
| |
| typedef struct { |
| int version; |
| int fd; |
| int card, device, subdevice; |
| int sync_ptr_ioctl; |
| volatile struct snd_pcm_mmap_status * mmap_status; |
| struct snd_pcm_mmap_control *mmap_control; |
| struct snd_pcm_sync_ptr *sync_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; |
| int channels; |
| /* for chmap */ |
| unsigned int chmap_caps; |
| snd_pcm_chmap_query_t **chmap_override; |
| } snd_pcm_hw_t; |
| |
| #define SNDRV_FILE_PCM_STREAM_PLAYBACK ALSA_DEVICE_DIRECTORY "pcmC%iD%ip" |
| #define SNDRV_FILE_PCM_STREAM_CAPTURE ALSA_DEVICE_DIRECTORY "pcmC%iD%ic" |
| #define SNDRV_PCM_VERSION_MAX SNDRV_PROTOCOL_VERSION(2, 0, 9) |
| |
| /* update appl_ptr with driver */ |
| #define FAST_PCM_STATE(hw) \ |
| ((snd_pcm_state_t) (hw)->mmap_status->state) |
| #define FAST_PCM_TSTAMP(hw) \ |
| ((hw)->mmap_status->tstamp) |
| |
| struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm) |
| { |
| struct timespec res; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| res = FAST_PCM_TSTAMP(hw); |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version) |
| res.tv_nsec *= 1000L; |
| return res; |
| } |
| #endif /* DOC_HIDDEN */ |
| |
| static int sync_ptr1(snd_pcm_hw_t *hw, unsigned int flags) |
| { |
| int err; |
| hw->sync_ptr->flags = flags; |
| err = ioctl((hw)->fd, SNDRV_PCM_IOCTL_SYNC_PTR, (hw)->sync_ptr); |
| if (err < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_SYNC_PTR failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| 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; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| |
| if ((flags = fcntl(fd, F_GETFL)) < 0) { |
| err = -errno; |
| SYSMSG("F_GETFL failed (%i)", err); |
| return err; |
| } |
| if (nonblock) |
| flags |= O_NONBLOCK; |
| else |
| flags &= ~O_NONBLOCK; |
| if (fcntl(fd, F_SETFL, flags) < 0) { |
| err = -errno; |
| SYSMSG("F_SETFL for O_NONBLOCK failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_async(snd_pcm_t *pcm, int sig, pid_t pid) |
| { |
| long flags; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| |
| if ((flags = fcntl(fd, F_GETFL)) < 0) { |
| err = -errno; |
| SYSMSG("F_GETFL failed (%i)", err); |
| return err; |
| } |
| if (sig >= 0) |
| flags |= O_ASYNC; |
| else |
| flags &= ~O_ASYNC; |
| if (fcntl(fd, F_SETFL, flags) < 0) { |
| err = -errno; |
| SYSMSG("F_SETFL for O_ASYNC failed (%i)", err); |
| return err; |
| } |
| if (sig < 0) |
| return 0; |
| if (fcntl(fd, F_SETSIG, (long)sig) < 0) { |
| err = -errno; |
| SYSMSG("F_SETSIG failed (%i)", err); |
| return err; |
| } |
| if (fcntl(fd, F_SETOWN, (long)pid) < 0) { |
| err = -errno; |
| SYSMSG("F_SETOWN failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_info(snd_pcm_t *pcm, snd_pcm_info_t * info) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, info) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static inline int hw_refine_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params) |
| { |
| /* check for new hw_params structure; it's available from 2.0.2 version of PCM API */ |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version) |
| return ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_REFINE, params); |
| return use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_REFINE_OLD, params); |
| } |
| |
| static int snd_pcm_hw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| |
| if (hw->format != SND_PCM_FORMAT_UNKNOWN) { |
| err = _snd_pcm_hw_params_set_format(params, hw->format); |
| if (err < 0) |
| return err; |
| } |
| if (hw->channels > 0) { |
| err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, |
| hw->channels, 0); |
| if (err < 0) |
| return err; |
| } |
| if (hw->rate > 0) { |
| err = _snd_pcm_hw_param_set_minmax(params, SND_PCM_HW_PARAM_RATE, |
| hw->rate, 0, hw->rate + 1, -1); |
| if (err < 0) |
| return err; |
| } |
| |
| if (hw_refine_call(hw, params) < 0) { |
| err = -errno; |
| // SYSMSG("SNDRV_PCM_IOCTL_HW_REFINE failed"); |
| return err; |
| } |
| |
| if (params->info != ~0U) { |
| params->info &= ~0xf0000000; |
| if (pcm->tstamp_type != SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY) |
| params->info |= SND_PCM_INFO_MONOTONIC; |
| } |
| |
| return 0; |
| } |
| |
| static inline int hw_params_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params) |
| { |
| /* check for new hw_params structure; it's available from 2.0.2 version of PCM API */ |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version) |
| return ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_PARAMS, params); |
| return use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_PARAMS_OLD, params); |
| } |
| |
| static int snd_pcm_hw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (hw_params_call(hw, params) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_HW_PARAMS failed (%i)", err); |
| return err; |
| } |
| params->info &= ~0xf0000000; |
| if (pcm->tstamp_type != SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY) |
| params->info |= SND_PCM_INFO_MONOTONIC; |
| err = sync_ptr(hw, 0); |
| if (err < 0) |
| return err; |
| if (pcm->stream == SND_PCM_STREAM_CAPTURE) { |
| snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, |
| SNDRV_PCM_MMAP_OFFSET_CONTROL); |
| } |
| 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 << 1) | (pcm->stream & 1), 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 << 1) | (pcm->stream & 1), 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; |
| hw->period_event = 0; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_hw_free(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| snd_pcm_hw_change_timer(pcm, 0); |
| if (ioctl(fd, SNDRV_PCM_IOCTL_HW_FREE) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_HW_FREE failed (%i)", err); |
| return err; |
| } |
| 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 = sw_get_period_event(params); |
| sw_set_period_event(params, 0); |
| if ((snd_pcm_tstamp_t) params->tstamp_mode == pcm->tstamp_mode && |
| (snd_pcm_tstamp_type_t) params->tstamp_type == pcm->tstamp_type && |
| 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 && |
| old_period_event == hw->period_event) { |
| hw->mmap_control->avail_min = params->avail_min; |
| return sync_ptr(hw, 0); |
| } |
| if (params->tstamp_type == SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW && |
| hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 12)) { |
| SYSMSG("Kernel doesn't support SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW"); |
| return -EINVAL; |
| } |
| if (params->tstamp_type == SND_PCM_TSTAMP_TYPE_MONOTONIC && |
| hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 5)) { |
| SYSMSG("Kernel doesn't support SND_PCM_TSTAMP_TYPE_MONOTONIC"); |
| return -EINVAL; |
| } |
| if (ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, params) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed (%i)", err); |
| return err; |
| } |
| if ((snd_pcm_tstamp_type_t) params->tstamp_type != pcm->tstamp_type) { |
| if (hw->version < SNDRV_PROTOCOL_VERSION(2, 0, 12)) { |
| int on = (snd_pcm_tstamp_type_t) params->tstamp_type == |
| SND_PCM_TSTAMP_TYPE_MONOTONIC; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) { |
| err = -errno; |
| SNDMSG("TSTAMP failed\n"); |
| return err; |
| } |
| } |
| pcm->tstamp_type = params->tstamp_type; |
| } |
| sw_set_period_event(params, 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; |
| } |
| |
| static int snd_pcm_hw_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| struct snd_pcm_channel_info i; |
| int fd = hw->fd, err; |
| i.channel = info->channel; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_CHANNEL_INFO, &i) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_CHANNEL_INFO failed (%i)", err); |
| return err; |
| } |
| info->channel = i.channel; |
| info->addr = 0; |
| info->first = i.first; |
| info->step = i.step; |
| info->type = SND_PCM_AREA_MMAP; |
| info->u.mmap.fd = fd; |
| info->u.mmap.offset = i.offset; |
| return 0; |
| } |
| |
| static int snd_pcm_hw_status(snd_pcm_t *pcm, snd_pcm_status_t * status) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_STATUS, status) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_STATUS failed (%i)", err); |
| return err; |
| } |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version) { |
| status->tstamp.tv_nsec *= 1000L; |
| status->trigger_tstamp.tv_nsec *= 1000L; |
| } |
| return 0; |
| } |
| |
| static snd_pcm_state_t snd_pcm_hw_state(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err = sync_ptr(hw, 0); |
| if (err < 0) |
| return err; |
| return (snd_pcm_state_t) hw->mmap_status->state; |
| } |
| |
| static int snd_pcm_hw_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_DELAY, delayp) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_DELAY failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_hwsync(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 3) <= hw->version) { |
| if (hw->sync_ptr) { |
| err = sync_ptr1(hw, SNDRV_PCM_SYNC_PTR_HWSYNC); |
| if (err < 0) |
| return err; |
| } else { |
| if (ioctl(fd, SNDRV_PCM_IOCTL_HWSYNC) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_HWSYNC failed (%i)", err); |
| return err; |
| } |
| } |
| } else { |
| snd_pcm_sframes_t delay; |
| int err = snd_pcm_hw_delay(pcm, &delay); |
| if (err < 0) { |
| switch (FAST_PCM_STATE(hw)) { |
| case SND_PCM_STATE_PREPARED: |
| case SND_PCM_STATE_SUSPENDED: |
| return 0; |
| default: |
| return err; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_prepare(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_PREPARE failed (%i)", err); |
| return err; |
| } |
| return sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL); |
| } |
| |
| static int snd_pcm_hw_reset(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_RESET) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_RESET failed (%i)", err); |
| return err; |
| } |
| return sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL); |
| } |
| |
| static int snd_pcm_hw_start(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| #if 0 |
| assert(pcm->stream != SND_PCM_STREAM_PLAYBACK || |
| snd_pcm_mmap_playback_hw_avail(pcm) > 0); |
| #endif |
| sync_ptr(hw, 0); |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_START) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_START failed (%i)", err); |
| #if 0 |
| if (err == -EBADFD) |
| SNDERR("PCM state = %s", snd_pcm_state_name(snd_pcm_hw_state(pcm))); |
| #endif |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_drop(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DROP) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_DROP failed (%i)", err); |
| return err; |
| } else { |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_drain(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DRAIN) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_DRAIN failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_pause(snd_pcm_t *pcm, int enable) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_PAUSE, enable) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_PAUSE failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_rewindable(snd_pcm_t *pcm) |
| { |
| return snd_pcm_mmap_hw_rewindable(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; |
| int err; |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_REWIND, &frames) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_REWIND failed (%i)", err); |
| return err; |
| } |
| err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL); |
| if (err < 0) |
| return err; |
| 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; |
| int err; |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 4) <= hw->version) { |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_FORWARD, &frames) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_FORWARD failed (%i)", err); |
| return err; |
| } |
| err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL); |
| if (err < 0) |
| return err; |
| return frames; |
| } else { |
| snd_pcm_sframes_t avail; |
| |
| err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_HWSYNC); |
| if (err < 0) |
| return err; |
| switch (FAST_PCM_STATE(hw)) { |
| case SNDRV_PCM_STATE_RUNNING: |
| case SNDRV_PCM_STATE_DRAINING: |
| case SNDRV_PCM_STATE_PAUSED: |
| case SNDRV_PCM_STATE_PREPARED: |
| break; |
| case SNDRV_PCM_STATE_XRUN: |
| return -EPIPE; |
| default: |
| return -EBADFD; |
| } |
| avail = snd_pcm_mmap_avail(pcm); |
| if (avail < 0) |
| return 0; |
| if (frames > (snd_pcm_uframes_t)avail) |
| frames = avail; |
| snd_pcm_mmap_appl_forward(pcm, frames); |
| err = sync_ptr(hw, 0); |
| if (err < 0) |
| return err; |
| return frames; |
| } |
| } |
| |
| static int snd_pcm_hw_resume(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd, err; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_RESUME) < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_RESUME failed (%i)", err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) |
| { |
| snd_pcm_hw_t *hw1 = pcm1->private_data; |
| snd_pcm_hw_t *hw2 = pcm2->private_data; |
| if (ioctl(hw1->fd, SNDRV_PCM_IOCTL_LINK, hw2->fd) < 0) { |
| SYSMSG("SNDRV_PCM_IOCTL_LINK failed (%i)", -errno); |
| return -errno; |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master) |
| { |
| if (master->type != SND_PCM_TYPE_HW) { |
| SYSMSG("Invalid type for SNDRV_PCM_IOCTL_LINK (%i)", master->type); |
| return -EINVAL; |
| } |
| return hw_link(master, pcm); |
| } |
| |
| static int snd_pcm_hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) |
| { |
| if (pcm2->type != SND_PCM_TYPE_HW) { |
| if (pcm2->fast_ops->link_slaves) |
| return pcm2->fast_ops->link_slaves(pcm2, pcm1); |
| return -ENOSYS; |
| } |
| return hw_link(pcm1, pcm2); |
| } |
| |
| static int snd_pcm_hw_unlink(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_UNLINK) < 0) { |
| SYSMSG("SNDRV_PCM_IOCTL_UNLINK failed (%i)", -errno); |
| return -errno; |
| } |
| return 0; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) |
| { |
| int err; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd; |
| struct snd_xferi xferi; |
| xferi.buf = (char*) buffer; |
| xferi.frames = size; |
| xferi.result = 0; /* make valgrind happy */ |
| err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi); |
| err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno; |
| #ifdef DEBUG_RW |
| fprintf(stderr, "hw_writei: frames = %li, xferi.result = %li, err = %i\n", size, xferi.result, err); |
| #endif |
| if (err < 0) |
| return snd_pcm_check_error(pcm, err); |
| return xferi.result; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) |
| { |
| int err; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd; |
| struct snd_xfern xfern; |
| memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */ |
| xfern.bufs = bufs; |
| xfern.frames = size; |
| err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEN_FRAMES, &xfern); |
| err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno; |
| #ifdef DEBUG_RW |
| fprintf(stderr, "hw_writen: frames = %li, result = %li, err = %i\n", size, xfern.result, err); |
| #endif |
| if (err < 0) |
| return snd_pcm_check_error(pcm, err); |
| return xfern.result; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) |
| { |
| int err; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd; |
| struct snd_xferi xferi; |
| xferi.buf = buffer; |
| xferi.frames = size; |
| xferi.result = 0; /* make valgrind happy */ |
| err = ioctl(fd, SNDRV_PCM_IOCTL_READI_FRAMES, &xferi); |
| err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno; |
| #ifdef DEBUG_RW |
| fprintf(stderr, "hw_readi: frames = %li, result = %li, err = %i\n", size, xferi.result, err); |
| #endif |
| if (err < 0) |
| return snd_pcm_check_error(pcm, err); |
| return xferi.result; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) |
| { |
| int err; |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int fd = hw->fd; |
| struct snd_xfern xfern; |
| memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */ |
| xfern.bufs = bufs; |
| xfern.frames = size; |
| err = ioctl(fd, SNDRV_PCM_IOCTL_READN_FRAMES, &xfern); |
| err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno; |
| #ifdef DEBUG_RW |
| fprintf(stderr, "hw_readn: frames = %li, result = %li, err = %i\n", size, xfern.result, err); |
| #endif |
| if (err < 0) |
| return snd_pcm_check_error(pcm, err); |
| return xfern.result; |
| } |
| |
| static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| struct snd_pcm_sync_ptr sync_ptr; |
| void *ptr; |
| int err; |
| ptr = MAP_FAILED; |
| if (hw->sync_ptr_ioctl == 0) |
| ptr = mmap(NULL, page_align(sizeof(struct snd_pcm_mmap_status)), |
| PROT_READ, MAP_FILE|MAP_SHARED, |
| hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); |
| if (ptr == MAP_FAILED || ptr == NULL) { |
| memset(&sync_ptr, 0, sizeof(sync_ptr)); |
| sync_ptr.c.control.appl_ptr = 0; |
| sync_ptr.c.control.avail_min = 1; |
| err = ioctl(hw->fd, SNDRV_PCM_IOCTL_SYNC_PTR, &sync_ptr); |
| if (err < 0) { |
| err = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_SYNC_PTR failed (%i)", err); |
| return err; |
| } |
| hw->sync_ptr = calloc(1, sizeof(struct snd_pcm_sync_ptr)); |
| if (hw->sync_ptr == NULL) |
| return -ENOMEM; |
| hw->mmap_status = &hw->sync_ptr->s.status; |
| hw->mmap_control = &hw->sync_ptr->c.control; |
| hw->sync_ptr_ioctl = 1; |
| } else { |
| hw->mmap_status = ptr; |
| } |
| snd_pcm_set_hw_ptr(pcm, &hw->mmap_status->hw_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS + offsetof(struct snd_pcm_mmap_status, hw_ptr)); |
| return 0; |
| } |
| |
| static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| void *ptr; |
| int err; |
| if (hw->sync_ptr == NULL) { |
| ptr = mmap(NULL, page_align(sizeof(struct snd_pcm_mmap_control)), |
| PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, |
| hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); |
| if (ptr == MAP_FAILED || ptr == NULL) { |
| err = -errno; |
| SYSMSG("control mmap failed (%i)", err); |
| return err; |
| } |
| hw->mmap_control = ptr; |
| } else { |
| hw->mmap_control->avail_min = 1; |
| } |
| snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); |
| return 0; |
| } |
| |
| static int snd_pcm_hw_munmap_status(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (hw->sync_ptr_ioctl) { |
| free(hw->sync_ptr); |
| hw->sync_ptr = NULL; |
| } else { |
| if (munmap((void*)hw->mmap_status, page_align(sizeof(*hw->mmap_status))) < 0) { |
| err = -errno; |
| SYSMSG("status munmap failed (%i)", err); |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_munmap_control(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err; |
| if (hw->sync_ptr_ioctl) { |
| free(hw->sync_ptr); |
| hw->sync_ptr = NULL; |
| } else { |
| if (munmap(hw->mmap_control, page_align(sizeof(*hw->mmap_control))) < 0) { |
| err = -errno; |
| SYSMSG("control munmap failed (%i)", err); |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| static int snd_pcm_hw_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| static int snd_pcm_hw_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| static int snd_pcm_hw_close(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| int err = 0; |
| if (close(hw->fd)) { |
| err = -errno; |
| SYSMSG("close failed (%i)\n", err); |
| } |
| snd_pcm_hw_munmap_status(pcm); |
| snd_pcm_hw_munmap_control(pcm); |
| free(hw); |
| return err; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_mmap_commit(snd_pcm_t *pcm, |
| snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, |
| snd_pcm_uframes_t size) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| |
| snd_pcm_mmap_appl_forward(pcm, size); |
| sync_ptr(hw, 0); |
| #ifdef DEBUG_MMAP |
| fprintf(stderr, "appl_forward: hw_ptr = %li, appl_ptr = %li, size = %li\n", *pcm->hw.ptr, *pcm->appl.ptr, size); |
| #endif |
| return size; |
| } |
| |
| static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| snd_pcm_uframes_t avail; |
| |
| sync_ptr(hw, 0); |
| avail = snd_pcm_mmap_avail(pcm); |
| switch (FAST_PCM_STATE(hw)) { |
| case SNDRV_PCM_STATE_RUNNING: |
| if (avail >= pcm->stop_threshold) { |
| /* SNDRV_PCM_IOCTL_XRUN ioctl has been implemented since PCM kernel API 2.0.1 */ |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 1) <= hw->version) { |
| if (ioctl(hw->fd, SNDRV_PCM_IOCTL_XRUN) < 0) |
| return -errno; |
| } |
| /* everything is ok, state == SND_PCM_STATE_XRUN at the moment */ |
| return -EPIPE; |
| } |
| break; |
| case SNDRV_PCM_STATE_XRUN: |
| return -EPIPE; |
| default: |
| break; |
| } |
| return avail; |
| } |
| |
| static int snd_pcm_hw_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, |
| snd_htimestamp_t *tstamp) |
| { |
| snd_pcm_sframes_t avail1; |
| int ok = 0; |
| |
| /* unfortunately, loop is necessary to ensure valid timestamp */ |
| while (1) { |
| avail1 = snd_pcm_hw_avail_update(pcm); |
| if (avail1 < 0) |
| return avail1; |
| if (ok && (snd_pcm_uframes_t)avail1 == *avail) |
| break; |
| *avail = avail1; |
| *tstamp = snd_pcm_hw_fast_tstamp(pcm); |
| ok = 1; |
| } |
| return 0; |
| } |
| |
| static void __fill_chmap_ctl_id(snd_ctl_elem_id_t *id, int dev, int subdev, |
| int stream) |
| { |
| snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); |
| if (stream == SND_PCM_STREAM_PLAYBACK) |
| snd_ctl_elem_id_set_name(id, "Playback Channel Map"); |
| else |
| snd_ctl_elem_id_set_name(id, "Capture Channel Map"); |
| snd_ctl_elem_id_set_device(id, dev); |
| snd_ctl_elem_id_set_index(id, subdev); |
| } |
| |
| static void fill_chmap_ctl_id(snd_pcm_t *pcm, snd_ctl_elem_id_t *id) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| return __fill_chmap_ctl_id(id, hw->device, hw->subdevice, pcm->stream); |
| } |
| |
| static int is_chmap_type(int type) |
| { |
| return (type >= SND_CTL_TLVT_CHMAP_FIXED && |
| type <= SND_CTL_TLVT_CHMAP_PAIRED); |
| } |
| |
| /** |
| * \!brief Query the available channel maps |
| * \param card the card number |
| * \param dev the PCM device number |
| * \param subdev the PCM substream index |
| * \param stream the direction of PCM stream |
| * \return the NULL-terminated array of integer pointers, or NULL at error. |
| * |
| * This function works like snd_pcm_query_chmaps() but it takes the card, |
| * device, substream and stream numbers instead of the already opened |
| * snd_pcm_t instance, so that you can query available channel maps of |
| * a PCM before actually opening it. |
| * |
| * As the parameters stand, the query is performed only to the hw PCM |
| * devices, not the abstracted PCM object in alsa-lib. |
| */ |
| snd_pcm_chmap_query_t ** |
| snd_pcm_query_chmaps_from_hw(int card, int dev, int subdev, |
| snd_pcm_stream_t stream) |
| { |
| snd_ctl_t *ctl; |
| snd_ctl_elem_id_t *id; |
| unsigned int tlv[2048], *start; |
| snd_pcm_chmap_query_t **map; |
| int i, ret, nums; |
| |
| ret = snd_ctl_hw_open(&ctl, NULL, card, 0); |
| if (ret < 0) { |
| SYSMSG("Cannot open the associated CTL\n"); |
| return NULL; |
| } |
| |
| snd_ctl_elem_id_alloca(&id); |
| __fill_chmap_ctl_id(id, dev, subdev, stream); |
| ret = snd_ctl_elem_tlv_read(ctl, id, tlv, sizeof(tlv)); |
| snd_ctl_close(ctl); |
| if (ret < 0) { |
| SYSMSG("Cannot read Channel Map TLV\n"); |
| return NULL; |
| } |
| |
| #if 0 |
| for (i = 0; i < 32; i++) |
| fprintf(stderr, "%02x: %08x\n", i, tlv[i]); |
| #endif |
| /* FIXME: the parser below assumes that the TLV only contains |
| * chmap-related blocks |
| */ |
| if (tlv[0] != SND_CTL_TLVT_CONTAINER) { |
| if (!is_chmap_type(tlv[0])) { |
| SYSMSG("Invalid TLV type %d\n", tlv[0]); |
| return NULL; |
| } |
| start = tlv; |
| nums = 1; |
| } else { |
| unsigned int *p; |
| int size; |
| start = tlv + 2; |
| size = tlv[1]; |
| nums = 0; |
| for (p = start; size > 0; ) { |
| if (!is_chmap_type(p[0])) { |
| SYSMSG("Invalid TLV type %d\n", p[0]); |
| return NULL; |
| } |
| nums++; |
| size -= p[1] + 8; |
| p += p[1] / 4 + 2; |
| } |
| } |
| map = calloc(nums + 1, sizeof(int *)); |
| if (!map) |
| return NULL; |
| for (i = 0; i < nums; i++) { |
| map[i] = malloc(start[1] + 8); |
| if (!map[i]) { |
| snd_pcm_free_chmaps(map); |
| return NULL; |
| } |
| map[i]->type = start[0] - 0x100; |
| map[i]->map.channels = start[1] / 4; |
| memcpy(map[i]->map.pos, start + 2, start[1]); |
| start += start[1] / 4 + 2; |
| } |
| return map; |
| } |
| |
| enum { CHMAP_CTL_QUERY, CHMAP_CTL_GET, CHMAP_CTL_SET }; |
| |
| static int chmap_caps(snd_pcm_hw_t *hw, int type) |
| { |
| if (hw->chmap_caps & (1 << type)) |
| return 1; |
| if (hw->chmap_caps & (1 << (type + 8))) |
| return 0; |
| return 1; |
| } |
| |
| static void chmap_caps_set_ok(snd_pcm_hw_t *hw, int type) |
| { |
| hw->chmap_caps |= (1 << type); |
| } |
| |
| static void chmap_caps_set_error(snd_pcm_hw_t *hw, int type) |
| { |
| hw->chmap_caps |= (1 << (type + 8)); |
| } |
| |
| static snd_pcm_chmap_query_t **snd_pcm_hw_query_chmaps(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| snd_pcm_chmap_query_t **map; |
| |
| if (hw->chmap_override) |
| return _snd_pcm_copy_chmap_query(hw->chmap_override); |
| |
| if (!chmap_caps(hw, CHMAP_CTL_QUERY)) |
| return NULL; |
| |
| map = snd_pcm_query_chmaps_from_hw(hw->card, hw->device, |
| hw->subdevice, pcm->stream); |
| if (map) |
| chmap_caps_set_ok(hw, CHMAP_CTL_QUERY); |
| else |
| chmap_caps_set_error(hw, CHMAP_CTL_QUERY); |
| return map; |
| } |
| |
| static snd_pcm_chmap_t *snd_pcm_hw_get_chmap(snd_pcm_t *pcm) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| snd_pcm_chmap_t *map; |
| snd_ctl_t *ctl; |
| snd_ctl_elem_id_t *id; |
| snd_ctl_elem_value_t *val; |
| unsigned int i; |
| int ret; |
| |
| if (hw->chmap_override) |
| return _snd_pcm_choose_fixed_chmap(pcm, hw->chmap_override); |
| |
| if (!chmap_caps(hw, CHMAP_CTL_GET)) |
| return NULL; |
| |
| switch (FAST_PCM_STATE(hw)) { |
| case SNDRV_PCM_STATE_PREPARED: |
| case SNDRV_PCM_STATE_RUNNING: |
| case SNDRV_PCM_STATE_XRUN: |
| case SNDRV_PCM_STATE_DRAINING: |
| case SNDRV_PCM_STATE_PAUSED: |
| case SNDRV_PCM_STATE_SUSPENDED: |
| break; |
| default: |
| SYSMSG("Invalid PCM state for chmap_get: %s\n", |
| snd_pcm_state_name(FAST_PCM_STATE(hw))); |
| return NULL; |
| } |
| map = malloc(pcm->channels * sizeof(map->pos[0]) + sizeof(*map)); |
| if (!map) |
| return NULL; |
| map->channels = pcm->channels; |
| ret = snd_ctl_hw_open(&ctl, NULL, hw->card, 0); |
| if (ret < 0) { |
| free(map); |
| SYSMSG("Cannot open the associated CTL\n"); |
| chmap_caps_set_error(hw, CHMAP_CTL_GET); |
| return NULL; |
| } |
| snd_ctl_elem_value_alloca(&val); |
| snd_ctl_elem_id_alloca(&id); |
| fill_chmap_ctl_id(pcm, id); |
| snd_ctl_elem_value_set_id(val, id); |
| ret = snd_ctl_elem_read(ctl, val); |
| snd_ctl_close(ctl); |
| if (ret < 0) { |
| free(map); |
| SYSMSG("Cannot read Channel Map ctl\n"); |
| chmap_caps_set_error(hw, CHMAP_CTL_GET); |
| return NULL; |
| } |
| for (i = 0; i < pcm->channels; i++) |
| map->pos[i] = snd_ctl_elem_value_get_integer(val, i); |
| chmap_caps_set_ok(hw, CHMAP_CTL_GET); |
| return map; |
| } |
| |
| static int snd_pcm_hw_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| snd_ctl_t *ctl; |
| snd_ctl_elem_id_t *id; |
| snd_ctl_elem_value_t *val; |
| unsigned int i; |
| int ret; |
| |
| if (hw->chmap_override) |
| return -ENXIO; |
| |
| if (!chmap_caps(hw, CHMAP_CTL_SET)) |
| return -ENXIO; |
| |
| if (map->channels > 128) { |
| SYSMSG("Invalid number of channels %d\n", map->channels); |
| return -EINVAL; |
| } |
| if (FAST_PCM_STATE(hw) != SNDRV_PCM_STATE_PREPARED) { |
| SYSMSG("Invalid PCM state for chmap_set: %s\n", |
| snd_pcm_state_name(FAST_PCM_STATE(hw))); |
| return -EBADFD; |
| } |
| ret = snd_ctl_hw_open(&ctl, NULL, hw->card, 0); |
| if (ret < 0) { |
| SYSMSG("Cannot open the associated CTL\n"); |
| chmap_caps_set_error(hw, CHMAP_CTL_SET); |
| return ret; |
| } |
| snd_ctl_elem_id_alloca(&id); |
| snd_ctl_elem_value_alloca(&val); |
| fill_chmap_ctl_id(pcm, id); |
| snd_ctl_elem_value_set_id(val, id); |
| for (i = 0; i < map->channels; i++) |
| snd_ctl_elem_value_set_integer(val, i, map->pos[i]); |
| ret = snd_ctl_elem_write(ctl, val); |
| snd_ctl_close(ctl); |
| if (ret >= 0) |
| chmap_caps_set_ok(hw, CHMAP_CTL_SET); |
| else if (ret == -ENOENT || ret == -EPERM || ret == -ENXIO) { |
| chmap_caps_set_error(hw, CHMAP_CTL_SET); |
| ret = -ENXIO; |
| } |
| if (ret < 0) |
| SYSMSG("Cannot write Channel Map ctl\n"); |
| return ret; |
| } |
| |
| static void snd_pcm_hw_dump(snd_pcm_t *pcm, snd_output_t *out) |
| { |
| snd_pcm_hw_t *hw = pcm->private_data; |
| char *name; |
| int err = snd_card_get_name(hw->card, &name); |
| if (err < 0) { |
| SNDERR("cannot get card name"); |
| return; |
| } |
| snd_output_printf(out, "Hardware PCM card %d '%s' device %d subdevice %d\n", |
| hw->card, name, hw->device, hw->subdevice); |
| free(name); |
| if (pcm->setup) { |
| snd_output_printf(out, "Its setup is:\n"); |
| snd_pcm_dump_setup(pcm, out); |
| snd_output_printf(out, " appl_ptr : %li\n", hw->mmap_control->appl_ptr); |
| snd_output_printf(out, " hw_ptr : %li\n", hw->mmap_status->hw_ptr); |
| } |
| } |
| |
| 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, |
| .hw_params = snd_pcm_hw_hw_params, |
| .hw_free = snd_pcm_hw_hw_free, |
| .sw_params = snd_pcm_hw_sw_params, |
| .channel_info = snd_pcm_hw_channel_info, |
| .dump = snd_pcm_hw_dump, |
| .nonblock = snd_pcm_hw_nonblock, |
| .async = snd_pcm_hw_async, |
| .mmap = snd_pcm_hw_mmap, |
| .munmap = snd_pcm_hw_munmap, |
| .query_chmaps = snd_pcm_hw_query_chmaps, |
| .get_chmap = snd_pcm_hw_get_chmap, |
| .set_chmap = snd_pcm_hw_set_chmap, |
| }; |
| |
| 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, |
| .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 = NULL, |
| .poll_descriptors_count = NULL, |
| .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 |
| * \param name Name of PCM |
| * \param fd File descriptor |
| * \param mmap_emulation Obsoleted parameter |
| * \param sync_ptr_ioctl Boolean flag for sync_ptr ioctl |
| * \retval zero on success otherwise a negative error code |
| * \warning Using of this function might be dangerous in the sense |
| * of compatibility reasons. The prototype might be freely |
| * changed in future. |
| */ |
| int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, |
| int fd, int mmap_emulation ATTRIBUTE_UNUSED, |
| int sync_ptr_ioctl) |
| { |
| int ver, mode; |
| snd_pcm_tstamp_type_t tstamp_type = SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY; |
| long fmode; |
| snd_pcm_t *pcm = NULL; |
| snd_pcm_hw_t *hw = NULL; |
| snd_pcm_info_t info; |
| int ret; |
| |
| assert(pcmp); |
| |
| memset(&info, 0, sizeof(info)); |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) { |
| ret = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret); |
| close(fd); |
| return ret; |
| |
| } |
| |
| if ((fmode = fcntl(fd, F_GETFL)) < 0) { |
| ret = -errno; |
| close(fd); |
| return ret; |
| } |
| mode = 0; |
| if (fmode & O_NONBLOCK) |
| mode |= SND_PCM_NONBLOCK; |
| if (fmode & O_ASYNC) |
| mode |= SND_PCM_ASYNC; |
| |
| if (ioctl(fd, SNDRV_PCM_IOCTL_PVERSION, &ver) < 0) { |
| ret = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_PVERSION failed (%i)", ret); |
| close(fd); |
| return ret; |
| } |
| if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_PCM_VERSION_MAX)) |
| return -SND_ERROR_INCOMPATIBLE_VERSION; |
| |
| #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 9) <= ver) { |
| struct timespec timespec; |
| if (clock_gettime(CLOCK_MONOTONIC, ×pec) == 0) { |
| int on = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TTSTAMP, &on) < 0) { |
| ret = -errno; |
| SNDMSG("TTSTAMP failed\n"); |
| return ret; |
| } |
| tstamp_type = SND_PCM_TSTAMP_TYPE_MONOTONIC; |
| } |
| } else |
| #endif |
| if (SNDRV_PROTOCOL_VERSION(2, 0, 5) <= ver) { |
| int on = 1; |
| if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) { |
| ret = -errno; |
| SNDMSG("TSTAMP failed\n"); |
| return ret; |
| } |
| } |
| |
| hw = calloc(1, sizeof(snd_pcm_hw_t)); |
| if (!hw) { |
| close(fd); |
| return -ENOMEM; |
| } |
| |
| hw->version = ver; |
| hw->card = info.card; |
| hw->device = info.device; |
| hw->subdevice = info.subdevice; |
| hw->fd = fd; |
| hw->sync_ptr_ioctl = sync_ptr_ioctl; |
| /* no restriction */ |
| hw->format = SND_PCM_FORMAT_UNKNOWN; |
| hw->rate = 0; |
| hw->channels = 0; |
| |
| ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode); |
| if (ret < 0) { |
| free(hw); |
| close(fd); |
| return ret; |
| } |
| |
| pcm->ops = &snd_pcm_hw_ops; |
| pcm->fast_ops = &snd_pcm_hw_fast_ops; |
| pcm->private_data = hw; |
| pcm->poll_fd = fd; |
| pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; |
| pcm->tstamp_type = tstamp_type; |
| |
| ret = snd_pcm_hw_mmap_status(pcm); |
| if (ret < 0) { |
| snd_pcm_close(pcm); |
| return ret; |
| } |
| ret = snd_pcm_hw_mmap_control(pcm); |
| if (ret < 0) { |
| snd_pcm_close(pcm); |
| return ret; |
| } |
| |
| *pcmp = pcm; |
| return 0; |
| } |
| |
| /** |
| * \brief Creates a new hw PCM |
| * \param pcmp Returns created PCM handle |
| * \param name Name of PCM |
| * \param card Number of card |
| * \param device Number of device |
| * \param subdevice Number of subdevice |
| * \param stream PCM Stream |
| * \param mode PCM Mode |
| * \param mmap_emulation Obsoleted parameter |
| * \param sync_ptr_ioctl Use SYNC_PTR ioctl rather than mmap for control structures |
| * \retval zero on success otherwise a negative error code |
| * \warning Using of this function might be dangerous in the sense |
| * of compatibility reasons. The prototype might be freely |
| * changed in future. |
| */ |
| int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, |
| int card, int device, int subdevice, |
| snd_pcm_stream_t stream, int mode, |
| int mmap_emulation ATTRIBUTE_UNUSED, |
| int sync_ptr_ioctl) |
| { |
| char filename[sizeof(SNDRV_FILE_PCM_STREAM_PLAYBACK) + 20]; |
| const char *filefmt; |
| int ret = 0, fd = -1; |
| int attempt = 0; |
| snd_pcm_info_t info; |
| int fmode; |
| snd_ctl_t *ctl; |
| |
| assert(pcmp); |
| |
| if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0) |
| return ret; |
| |
| switch (stream) { |
| case SND_PCM_STREAM_PLAYBACK: |
| filefmt = SNDRV_FILE_PCM_STREAM_PLAYBACK; |
| break; |
| case SND_PCM_STREAM_CAPTURE: |
| filefmt = SNDRV_FILE_PCM_STREAM_CAPTURE; |
| break; |
| default: |
| SNDERR("invalid stream %d", stream); |
| return -EINVAL; |
| } |
| sprintf(filename, filefmt, card, device); |
| |
| __again: |
| if (attempt++ > 3) { |
| ret = -EBUSY; |
| goto _err; |
| } |
| ret = snd_ctl_pcm_prefer_subdevice(ctl, subdevice); |
| if (ret < 0) |
| goto _err; |
| fmode = O_RDWR; |
| if (mode & SND_PCM_NONBLOCK) |
| fmode |= O_NONBLOCK; |
| if (mode & SND_PCM_ASYNC) |
| fmode |= O_ASYNC; |
| if (mode & SND_PCM_APPEND) |
| fmode |= O_APPEND; |
| fd = snd_open_device(filename, fmode); |
| if (fd < 0) { |
| ret = -errno; |
| SYSMSG("open '%s' failed (%i)", filename, ret); |
| goto _err; |
| } |
| if (subdevice >= 0) { |
| memset(&info, 0, sizeof(info)); |
| if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) { |
| ret = -errno; |
| SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret); |
| goto _err; |
| } |
| if (info.subdevice != (unsigned int) subdevice) { |
| close(fd); |
| goto __again; |
| } |
| } |
| snd_ctl_close(ctl); |
| return snd_pcm_hw_open_fd(pcmp, name, fd, 0, sync_ptr_ioctl); |
| _err: |
| snd_ctl_close(ctl); |
| return ret; |
| } |
| |
| /*! \page pcm_plugins |
| |
| \section pcm_plugins_hw Plugin: hw |
| |
| This plugin communicates directly with the ALSA kernel driver. It is a raw |
| communication without any conversions. The emulation of mmap access can be |
| optionally enabled, but expect worse latency in the case. |
| |
| The nonblock option specifies whether the device is opened in a non-blocking |
| manner. Note that the blocking behavior for read/write access won't be |
| changed by this option. This influences only on the blocking behavior at |
| opening the device. If you would like to keep the compatibility with the |
| older ALSA stuff, turn this option off. |
| |
| \code |
| pcm.name { |
| type hw # Kernel PCM |
| card INT/STR # Card name (string) or number (integer) |
| [device INT] # Device number (default 0) |
| [subdevice INT] # Subdevice number (default -1: first available) |
| [sync_ptr_ioctl BOOL] # Use SYNC_PTR ioctl rather than the direct mmap access for control structures |
| [nonblock BOOL] # Force non-blocking open mode |
| [format STR] # Restrict only to the given format |
| [channels INT] # Restrict only to the given channels |
| [rate INT] # Restrict only to the given rate |
| [chmap MAP] # Override channel maps; MAP is a string array |
| } |
| \endcode |
| |
| \subsection pcm_plugins_hw_funcref Function reference |
| |
| <UL> |
| <LI>snd_pcm_hw_open() |
| <LI>_snd_pcm_hw_open() |
| </UL> |
| |
| */ |
| |
| /** |
| * \brief Creates a new hw PCM |
| * \param pcmp Returns created PCM handle |
| * \param name Name of PCM |
| * \param root Root configuration node |
| * \param conf Configuration node with hw PCM description |
| * \param stream PCM Stream |
| * \param mode PCM Mode |
| * \warning Using of this function might be dangerous in the sense |
| * of compatibility reasons. The prototype might be freely |
| * changed in future. |
| */ |
| int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, |
| snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, |
| snd_pcm_stream_t stream, int mode) |
| { |
| snd_config_iterator_t i, next; |
| long card = -1, device = 0, subdevice = -1; |
| const char *str; |
| int err, sync_ptr_ioctl = 0; |
| int rate = 0, channels = 0; |
| snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; |
| snd_config_t *n; |
| int nonblock = 1; /* non-block per default */ |
| snd_pcm_chmap_query_t **chmap = NULL; |
| snd_pcm_hw_t *hw; |
| |
| /* look for defaults.pcm.nonblock definition */ |
| if (snd_config_search(root, "defaults.pcm.nonblock", &n) >= 0) { |
| err = snd_config_get_bool(n); |
| if (err >= 0) |
| nonblock = err; |
| } |
| snd_config_for_each(i, next, conf) { |
| const char *id; |
| n = snd_config_iterator_entry(i); |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (snd_pcm_conf_generic_id(id)) |
| continue; |
| if (strcmp(id, "card") == 0) { |
| err = snd_config_get_integer(n, &card); |
| if (err < 0) { |
| err = snd_config_get_string(n, &str); |
| if (err < 0) { |
| SNDERR("Invalid type for %s", id); |
| return -EINVAL; |
| } |
| card = snd_card_get_index(str); |
| if (card < 0) { |
| SNDERR("Invalid value for %s", id); |
| return card; |
| } |
| } |
| continue; |
| } |
| if (strcmp(id, "device") == 0) { |
| err = snd_config_get_integer(n, &device); |
| if (err < 0) { |
| SNDERR("Invalid type for %s", id); |
| return err; |
| } |
| continue; |
| } |
| if (strcmp(id, "subdevice") == 0) { |
| err = snd_config_get_integer(n, &subdevice); |
| if (err < 0) { |
| SNDERR("Invalid type for %s", id); |
| return err; |
| } |
| continue; |
| } |
| if (strcmp(id, "sync_ptr_ioctl") == 0) { |
| err = snd_config_get_bool(n); |
| if (err < 0) |
| continue; |
| sync_ptr_ioctl = err; |
| continue; |
| } |
| if (strcmp(id, "nonblock") == 0) { |
| err = snd_config_get_bool(n); |
| if (err < 0) |
| continue; |
| nonblock = err; |
| continue; |
| } |
| if (strcmp(id, "rate") == 0) { |
| long val; |
| err = snd_config_get_integer(n, &val); |
| if (err < 0) { |
| SNDERR("Invalid type for %s", id); |
| return err; |
| } |
| rate = val; |
| continue; |
| } |
| if (strcmp(id, "format") == 0) { |
| err = snd_config_get_string(n, &str); |
| if (err < 0) { |
| SNDERR("invalid type for %s", id); |
| return err; |
| } |
| format = snd_pcm_format_value(str); |
| continue; |
| } |
| if (strcmp(id, "channels") == 0) { |
| long val; |
| err = snd_config_get_integer(n, &val); |
| if (err < 0) { |
| SNDERR("Invalid type for %s", id); |
| return err; |
| } |
| channels = val; |
| continue; |
| } |
| if (strcmp(id, "chmap") == 0) { |
| snd_pcm_free_chmaps(chmap); |
| chmap = _snd_pcm_parse_config_chmaps(n); |
| if (!chmap) { |
| SNDERR("Invalid channel map for %s", id); |
| return -EINVAL; |
| } |
| continue; |
| } |
| SNDERR("Unknown field %s", id); |
| snd_pcm_free_chmaps(chmap); |
| return -EINVAL; |
| } |
| if (card < 0) { |
| SNDERR("card is not defined"); |
| snd_pcm_free_chmaps(chmap); |
| return -EINVAL; |
| } |
| err = snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream, |
| mode | (nonblock ? SND_PCM_NONBLOCK : 0), |
| 0, sync_ptr_ioctl); |
| if (err < 0) { |
| snd_pcm_free_chmaps(chmap); |
| return err; |
| } |
| if (nonblock && ! (mode & SND_PCM_NONBLOCK)) { |
| /* 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) |
| hw->format = format; |
| if (channels > 0) |
| hw->channels = channels; |
| if (rate > 0) |
| hw->rate = rate; |
| if (chmap) |
| hw->chmap_override = chmap; |
| |
| return 0; |
| } |
| |
| #ifndef DOC_HIDDEN |
| SND_DLSYM_BUILD_VERSION(_snd_pcm_hw_open, SND_PCM_DLSYM_VERSION); |
| #endif |
| |
| /* |
| * To be removed helpers, but keep binary compatibility at the time |
| */ |
| |
| #ifndef DOC_HIDDEN |
| #define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5)) |
| #define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5)) |
| #endif |
| |
| static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, |
| struct sndrv_pcm_hw_params_old *oparams) |
| { |
| unsigned int i; |
| |
| memset(params, 0, sizeof(*params)); |
| params->flags = oparams->flags; |
| for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++) |
| params->masks[i].bits[0] = oparams->masks[i]; |
| memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals)); |
| params->rmask = __OLD_TO_NEW_MASK(oparams->rmask); |
| params->cmask = __OLD_TO_NEW_MASK(oparams->cmask); |
| params->info = oparams->info; |
| params->msbits = oparams->msbits; |
| params->rate_num = oparams->rate_num; |
| params->rate_den = oparams->rate_den; |
| params->fifo_size = oparams->fifo_size; |
| } |
| |
| static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams, |
| snd_pcm_hw_params_t *params, |
| unsigned int *cmask) |
| { |
| unsigned int i, j; |
| |
| memset(oparams, 0, sizeof(*oparams)); |
| oparams->flags = params->flags; |
| for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++) { |
| oparams->masks[i] = params->masks[i].bits[0]; |
| for (j = 1; j < sizeof(params->masks[i].bits) / sizeof(unsigned int); j++) |
| if (params->masks[i].bits[j]) { |
| *cmask |= 1 << i; |
| break; |
| } |
| } |
| memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals)); |
| oparams->rmask = __NEW_TO_OLD_MASK(params->rmask); |
| oparams->cmask = __NEW_TO_OLD_MASK(params->cmask); |
| oparams->info = params->info; |
| oparams->msbits = params->msbits; |
| oparams->rate_num = params->rate_num; |
| oparams->rate_den = params->rate_den; |
| oparams->fifo_size = params->fifo_size; |
| } |
| |
| static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params) |
| { |
| struct sndrv_pcm_hw_params_old oparams; |
| unsigned int cmask = 0; |
| int res; |
| |
| snd_pcm_hw_convert_to_old_params(&oparams, params, &cmask); |
| res = ioctl(fd, cmd, &oparams); |
| snd_pcm_hw_convert_from_old_params(params, &oparams); |
| params->cmask |= cmask; |
| return res; |
| } |