blob: 7f562abcb829ea064d1e23474029c9377c1bf8a2 [file] [log] [blame]
/*
* SALSA-Lib - PCM Interface
*
* Copyright (c) 2007-2012 by Takashi Iwai <tiwai@suse.de>
*
* 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 <string.h>
#include <malloc.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <ctype.h>
#include "pcm.h"
#include "control.h"
#include "local.h"
/*
*/
static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm);
static void snd_pcm_hw_munmap_status(snd_pcm_t *pcm);
/*
* open/close
*/
static int get_pcm_subdev(int fd)
{
snd_pcm_info_t info;
memzero_valgrind(&info, sizeof(info));
if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0)
return -1;
return info.subdevice;
}
#if SALSA_CHECK_ABI
int _snd_pcm_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_stream_t stream, int mode, unsigned int magic)
#else
int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_stream_t stream, int mode)
#endif
{
char filename[sizeof(SALSA_DEVPATH) + 24];
int card, dev, subdev;
int fd, err, fmode, ver;
snd_pcm_t *pcm = NULL;
check_incompatible_abi(magic, SALSA_PCM_MAGIC);
*pcmp = NULL;
err = _snd_dev_get_device(name, &card, &dev, &subdev);
if (err < 0)
return err;
snprintf(filename, sizeof(filename), "%s/pcmC%dD%d%c",
SALSA_DEVPATH, card, dev,
(stream == SND_PCM_STREAM_PLAYBACK ? 'p' : 'c'));
fmode = O_RDWR | O_NONBLOCK;
if (mode & SND_PCM_ASYNC)
fmode |= O_ASYNC;
if (subdev >= 0) {
fd = _snd_open_subdev(filename, fmode, card, subdev,
SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE);
if (fd < 0)
return fd;
if (get_pcm_subdev(fd) != subdev) {
close(fd);
return -EBUSY;
}
} else {
fd = open(filename, fmode);
if (fd < 0)
return -errno;
subdev = get_pcm_subdev(fd);
if (subdev < 0) {
close(fd);
return -EBUSY;
}
}
if (!(mode & SND_PCM_NONBLOCK)) {
fmode &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, fmode);
}
if (ioctl(fd, SNDRV_PCM_IOCTL_PVERSION, &ver) < 0) {
err = -errno;
goto error;
}
pcm = calloc(1, sizeof(*pcm));
if (!pcm) {
err = -ENOMEM;
goto error;
}
pcm->card = card;
pcm->stream = stream;
pcm->device = dev;
pcm->subdevice = subdev;
pcm->protocol = ver;
pcm->fd = fd;
pcm->pollfd.fd = fd;
pcm->pollfd.events =
(stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN)
| POLLERR | POLLNVAL;
err = snd_pcm_hw_mmap_status(pcm);
if (err < 0)
goto error;
*pcmp = pcm;
return 0;
error:
free(pcm);
close(fd);
return err;
}
int snd_pcm_close(snd_pcm_t *pcm)
{
if (pcm->setup) {
snd_pcm_drop(pcm);
snd_pcm_hw_free(pcm);
}
_snd_pcm_munmap(pcm);
snd_pcm_hw_munmap_status(pcm);
#if SALSA_HAS_ASYNC_SUPPORT
if (pcm->async)
snd_async_del_handler(pcm->async);
#endif
close(pcm->fd);
free(pcm);
return 0;
}
/*
* READ/WRITE
*/
static int correct_pcm_error(snd_pcm_t *pcm, int err)
{
switch (snd_pcm_state(pcm)) {
case SND_PCM_STATE_XRUN:
return -EPIPE;
case SND_PCM_STATE_SUSPENDED:
return -ESTRPIPE;
case SND_PCM_STATE_DISCONNECTED:
return -ENODEV;
default:
return err;
}
}
static inline int snd_pcm_check_error(snd_pcm_t *pcm, int err)
{
if (err == -EINTR)
return correct_pcm_error(pcm, err);
return err;
}
snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer,
snd_pcm_uframes_t size)
{
struct snd_xferi xferi;
#ifdef DELIGHT_VALGRIND
xferi.result = 0;
#endif
xferi.buf = (char*) buffer;
xferi.frames = size;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi) < 0)
return snd_pcm_check_error(pcm, -errno);
_snd_pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL);
return xferi.result;
}
snd_pcm_sframes_t snd_pcm_writen(snd_pcm_t *pcm, void **bufs,
snd_pcm_uframes_t size)
{
struct snd_xfern xfern;
#ifdef DELIGHT_VALGRIND
xfern.result = 0;
#endif
xfern.bufs = bufs;
xfern.frames = size;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEN_FRAMES, &xfern) < 0)
return snd_pcm_check_error(pcm, -errno);
_snd_pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL);
return xfern.result;
}
snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer,
snd_pcm_uframes_t size)
{
struct snd_xferi xferi;
#ifdef DELIGHT_VALGRIND
xferi.result = 0;
#endif
xferi.buf = buffer;
xferi.frames = size;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &xferi) < 0)
return snd_pcm_check_error(pcm, -errno);
_snd_pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL);
return xferi.result;
}
snd_pcm_sframes_t snd_pcm_readn(snd_pcm_t *pcm, void **bufs,
snd_pcm_uframes_t size)
{
struct snd_xfern xfern;
#ifdef DELIGHT_VALGRIND
xfern.result = 0;
#endif
xfern.bufs = bufs;
xfern.frames = size;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READN_FRAMES, &xfern) < 0)
return snd_pcm_check_error(pcm, -errno);
_snd_pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL);
return xfern.result;
}
/*
* STRINGS, DUMP
*/
#define PCMTYPE(v) [SND_PCM_TYPE_##v] = #v
#define STATE(v) [SND_PCM_STATE_##v] = #v
#define STREAM(v) [SND_PCM_STREAM_##v] = #v
#define READY(v) [SND_PCM_READY_##v] = #v
#define XRUN(v) [SND_PCM_XRUN_##v] = #v
#define SILENCE(v) [SND_PCM_SILENCE_##v] = #v
#define TSTAMP(v) [SND_PCM_TSTAMP_##v] = #v
#define ACCESS(v) [SND_PCM_ACCESS_##v] = #v
#define START(v) [SND_PCM_START_##v] = #v
#define HW_PARAM(v) [SND_PCM_HW_PARAM_##v] = #v
#define SW_PARAM(v) [SND_PCM_SW_PARAM_##v] = #v
#define FORMAT(v) [SND_PCM_FORMAT_##v] = #v
#define SUBFORMAT(v) [SND_PCM_SUBFORMAT_##v] = #v
#define FORMATD(v, d) [SND_PCM_FORMAT_##v] = d
#define SUBFORMATD(v, d) [SND_PCM_SUBFORMAT_##v] = d
const char * const _snd_pcm_stream_names[SND_PCM_STREAM_LAST + 1] = {
STREAM(PLAYBACK),
STREAM(CAPTURE),
};
const char * const _snd_pcm_state_names[SND_PCM_STATE_LAST + 1] = {
STATE(OPEN),
STATE(SETUP),
STATE(PREPARED),
STATE(RUNNING),
STATE(XRUN),
STATE(DRAINING),
STATE(PAUSED),
STATE(SUSPENDED),
STATE(DISCONNECTED),
};
const char * const _snd_pcm_access_names[SND_PCM_ACCESS_LAST + 1] = {
ACCESS(MMAP_INTERLEAVED),
ACCESS(MMAP_NONINTERLEAVED),
ACCESS(MMAP_COMPLEX),
ACCESS(RW_INTERLEAVED),
ACCESS(RW_NONINTERLEAVED),
};
const char * const _snd_pcm_format_names[SND_PCM_FORMAT_LAST + 1] = {
FORMAT(S8),
FORMAT(U8),
FORMAT(S16_LE),
FORMAT(S16_BE),
FORMAT(U16_LE),
FORMAT(U16_BE),
FORMAT(S24_LE),
FORMAT(S24_BE),
FORMAT(U24_LE),
FORMAT(U24_BE),
FORMAT(S32_LE),
FORMAT(S32_BE),
FORMAT(U32_LE),
FORMAT(U32_BE),
FORMAT(FLOAT_LE),
FORMAT(FLOAT_BE),
FORMAT(FLOAT64_LE),
FORMAT(FLOAT64_BE),
FORMAT(IEC958_SUBFRAME_LE),
FORMAT(IEC958_SUBFRAME_BE),
FORMAT(MU_LAW),
FORMAT(A_LAW),
FORMAT(IMA_ADPCM),
FORMAT(MPEG),
FORMAT(GSM),
FORMAT(SPECIAL),
FORMAT(S24_3LE),
FORMAT(S24_3BE),
FORMAT(U24_3LE),
FORMAT(U24_3BE),
FORMAT(S20_3LE),
FORMAT(S20_3BE),
FORMAT(U20_3LE),
FORMAT(U20_3BE),
FORMAT(S18_3LE),
FORMAT(S18_3BE),
FORMAT(U18_3LE),
FORMAT(U18_3BE),
};
static const char * const _snd_pcm_format_aliases[SND_PCM_FORMAT_LAST + 1] = {
FORMAT(S16),
FORMAT(U16),
FORMAT(S24),
FORMAT(U24),
FORMAT(S32),
FORMAT(U32),
FORMAT(FLOAT),
FORMAT(FLOAT64),
FORMAT(IEC958_SUBFRAME),
};
const char * const _snd_pcm_format_descriptions[SND_PCM_FORMAT_LAST + 1] = {
FORMATD(S8, "Signed 8 bit"),
FORMATD(U8, "Unsigned 8 bit"),
FORMATD(S16_LE, "Signed 16 bit Little Endian"),
FORMATD(S16_BE, "Signed 16 bit Big Endian"),
FORMATD(U16_LE, "Unsigned 16 bit Little Endian"),
FORMATD(U16_BE, "Unsigned 16 bit Big Endian"),
FORMATD(S24_LE, "Signed 24 bit Little Endian"),
FORMATD(S24_BE, "Signed 24 bit Big Endian"),
FORMATD(U24_LE, "Unsigned 24 bit Little Endian"),
FORMATD(U24_BE, "Unsigned 24 bit Big Endian"),
FORMATD(S32_LE, "Signed 32 bit Little Endian"),
FORMATD(S32_BE, "Signed 32 bit Big Endian"),
FORMATD(U32_LE, "Unsigned 32 bit Little Endian"),
FORMATD(U32_BE, "Unsigned 32 bit Big Endian"),
FORMATD(FLOAT_LE, "Float 32 bit Little Endian"),
FORMATD(FLOAT_BE, "Float 32 bit Big Endian"),
FORMATD(FLOAT64_LE, "Float 64 bit Little Endian"),
FORMATD(FLOAT64_BE, "Float 64 bit Big Endian"),
FORMATD(IEC958_SUBFRAME_LE, "IEC-958 Little Endian"),
FORMATD(IEC958_SUBFRAME_BE, "IEC-958 Big Endian"),
FORMATD(MU_LAW, "Mu-Law"),
FORMATD(A_LAW, "A-Law"),
FORMATD(IMA_ADPCM, "Ima-ADPCM"),
FORMATD(MPEG, "MPEG"),
FORMATD(GSM, "GSM"),
FORMATD(SPECIAL, "Special"),
FORMATD(S24_3LE, "Signed 24 bit Little Endian in 3bytes"),
FORMATD(S24_3BE, "Signed 24 bit Big Endian in 3bytes"),
FORMATD(U24_3LE, "Unsigned 24 bit Little Endian in 3bytes"),
FORMATD(U24_3BE, "Unsigned 24 bit Big Endian in 3bytes"),
FORMATD(S20_3LE, "Signed 20 bit Little Endian in 3bytes"),
FORMATD(S20_3BE, "Signed 20 bit Big Endian in 3bytes"),
FORMATD(U20_3LE, "Unsigned 20 bit Little Endian in 3bytes"),
FORMATD(U20_3BE, "Unsigned 20 bit Big Endian in 3bytes"),
FORMATD(S18_3LE, "Signed 18 bit Little Endian in 3bytes"),
FORMATD(S18_3BE, "Signed 18 bit Big Endian in 3bytes"),
FORMATD(U18_3LE, "Unsigned 18 bit Little Endian in 3bytes"),
FORMATD(U18_3BE, "Unsigned 18 bit Big Endian in 3bytes"),
};
const char * const _snd_pcm_type_names[] = {
PCMTYPE(HW),
PCMTYPE(HOOKS),
PCMTYPE(MULTI),
PCMTYPE(FILE),
PCMTYPE(NULL),
PCMTYPE(SHM),
PCMTYPE(INET),
PCMTYPE(COPY),
PCMTYPE(LINEAR),
PCMTYPE(ALAW),
PCMTYPE(MULAW),
PCMTYPE(ADPCM),
PCMTYPE(RATE),
PCMTYPE(ROUTE),
PCMTYPE(PLUG),
PCMTYPE(SHARE),
PCMTYPE(METER),
PCMTYPE(MIX),
PCMTYPE(DROUTE),
PCMTYPE(LBSERVER),
PCMTYPE(LINEAR_FLOAT),
PCMTYPE(LADSPA),
PCMTYPE(DMIX),
PCMTYPE(JACK),
PCMTYPE(DSNOOP),
PCMTYPE(IEC958),
PCMTYPE(SOFTVOL),
PCMTYPE(IOPLUG),
PCMTYPE(EXTPLUG),
};
const char * const _snd_pcm_subformat_names[SND_PCM_SUBFORMAT_LAST + 1] = {
SUBFORMAT(STD),
};
const char * const _snd_pcm_subformat_descriptions[SND_PCM_SUBFORMAT_LAST + 1] = {
SUBFORMATD(STD, "Standard"),
};
const char * const _snd_pcm_tstamp_mode_names[SND_PCM_TSTAMP_LAST + 1] = {
TSTAMP(NONE),
TSTAMP(MMAP),
};
snd_pcm_format_t snd_pcm_format_value(const char* name)
{
snd_pcm_format_t format;
for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
if (_snd_pcm_format_names[format] &&
strcasecmp(name, _snd_pcm_format_names[format]) == 0) {
return format;
}
if (_snd_pcm_format_aliases[format] &&
strcasecmp(name, _snd_pcm_format_aliases[format]) == 0) {
return format;
}
}
for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
if (_snd_pcm_format_descriptions[format] &&
strcasecmp(name, _snd_pcm_format_descriptions[format]) == 0) {
return format;
}
}
return SND_PCM_FORMAT_UNKNOWN;
}
int snd_pcm_dump_hw_setup(snd_pcm_t *pcm, snd_output_t *out)
{
snd_output_printf(out, " stream : %s\n",
snd_pcm_stream_name(pcm->stream));
snd_output_printf(out, " access : %s\n",
snd_pcm_access_name(pcm->_access));
snd_output_printf(out, " format : %s\n",
snd_pcm_format_name(pcm->format));
snd_output_printf(out, " subformat : %s\n",
snd_pcm_subformat_name(pcm->subformat));
snd_output_printf(out, " channels : %u\n", pcm->channels);
snd_output_printf(out, " rate : %u\n", pcm->rate);
snd_output_printf(out, " exact rate : %g (%u/%u)\n",
(pcm->hw_params.rate_den ?
((double) pcm->hw_params.rate_num / pcm->hw_params.rate_den) : 0.0),
pcm->hw_params.rate_num, pcm->hw_params.rate_den);
snd_output_printf(out, " msbits : %u\n", pcm->hw_params.msbits);
snd_output_printf(out, " buffer_size : %lu\n", pcm->buffer_size);
snd_output_printf(out, " period_size : %lu\n", pcm->period_size);
snd_output_printf(out, " period_time : %u\n", pcm->period_time);
#if 0 /* deprecated */
snd_output_printf(out, " tick_time : %u\n", pcm->tick_time);
#endif
return 0;
}
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->sw_params.tstamp_mode));
snd_output_printf(out, " period_step : %d\n",
pcm->sw_params.period_step);
#if 0 /* deprecated */
snd_output_printf(out, " sleep_min : %d\n",
pcm->sw_params.sleep_min);
#endif
snd_output_printf(out, " avail_min : %ld\n",
pcm->sw_params.avail_min);
#if 0 /* deprecated */
snd_output_printf(out, " xfer_align : %ld\n",
pcm->sw_params.xfer_align);
#endif
snd_output_printf(out, " start_threshold : %ld\n",
pcm->sw_params.start_threshold);
snd_output_printf(out, " stop_threshold : %ld\n",
pcm->sw_params.stop_threshold);
snd_output_printf(out, " silence_threshold: %ld\n",
pcm->sw_params.silence_threshold);
snd_output_printf(out, " silence_size : %ld\n",
pcm->sw_params.silence_size);
snd_output_printf(out, " boundary : %ld\n",
pcm->sw_params.boundary);
return 0;
}
int snd_pcm_dump_setup(snd_pcm_t *pcm, snd_output_t *out)
{
snd_pcm_dump_hw_setup(pcm, out);
snd_pcm_dump_sw_setup(pcm, out);
return 0;
}
int snd_pcm_status_dump(snd_pcm_status_t *status, snd_output_t *out)
{
snd_output_printf(out, " state : %s\n",
snd_pcm_state_name((snd_pcm_state_t) status->state));
snd_output_printf(out, " trigger_time: %ld.%06ld\n",
status->trigger_tstamp.tv_sec, status->trigger_tstamp.tv_nsec);
snd_output_printf(out, " tstamp : %ld.%06ld\n",
status->tstamp.tv_sec, status->tstamp.tv_nsec);
snd_output_printf(out, " delay : %ld\n", (long)status->delay);
snd_output_printf(out, " avail : %ld\n", (long)status->avail);
snd_output_printf(out, " avail_max : %ld\n", (long)status->avail_max);
return 0;
}
int snd_pcm_dump(snd_pcm_t *pcm, snd_output_t *out)
{
char *name;
int err = snd_card_get_name(pcm->card, &name);
if (err < 0)
return err;
snd_output_printf(out,
"Hardware PCM card %d '%s' device %d subdevice %d\n",
pcm->card, name, pcm->device, pcm->subdevice);
if (pcm->setup) {
snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
}
free(name);
return 0;
}
/*
* SILENCE AND COPY AREAS
*/
static inline
void *snd_pcm_channel_area_addr(const snd_pcm_channel_area_t *area,
snd_pcm_uframes_t offset)
{
unsigned int bitofs = area->first + area->step * offset;
return (char *) area->addr + bitofs / 8;
}
#if SALSA_SUPPORT_4BIT_PCM
static int area_silence_4bit(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
unsigned int samples, snd_pcm_format_t format)
{
char *dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
int dst_step = dst_area->step / 8;
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
while (samples-- > 0) {
if (dstbit)
*dst &= 0xf0;
else
*dst &= 0x0f;
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
return 0;
}
#else
static int area_silence_4bit(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
unsigned int samples, snd_pcm_format_t format)
{
return -EINVAL;
}
#endif /* SALSA_SUPPORT_4BIT_PCM */
int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
unsigned int samples, snd_pcm_format_t format)
{
char *dst;
unsigned int dst_step;
int width;
if (!dst_area->addr)
return 0;
width = snd_pcm_format_physical_width(format);
if (width < 8)
return area_silence_4bit(dst_area, dst_offset, samples, format);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
if (dst_area->step == (unsigned int) width) {
snd_pcm_format_set_silence(format, dst, samples);
return 0;
}
dst_step = dst_area->step / 8;
while (samples-- > 0) {
snd_pcm_format_set_silence(format, dst, 1);
dst += dst_step;
}
return 0;
}
int snd_pcm_areas_silence(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset,
unsigned int channels, snd_pcm_uframes_t frames,
snd_pcm_format_t format)
{
while (channels > 0) {
snd_pcm_area_silence(dst_areas, dst_offset, frames, format);
dst_areas++;
channels--;
}
return 0;
}
#if SALSA_SUPPORT_4BIT_PCM
static int area_copy_4bit(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_area,
snd_pcm_uframes_t src_offset,
unsigned int samples, snd_pcm_format_t format)
{
int srcbit = src_area->first % 8;
int srcbit_step = src_area->step % 8;
int dstbit = dst_area->first % 8;
int dstbit_step = dst_area->step % 8;
const unsigned char *src = snd_pcm_channel_area_addr(src_area,
src_offset);
unsigned char *dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
int src_step = src_area->step / 8;
int dst_step = dst_area->step / 8;
while (samples-- > 0) {
unsigned char srcval;
if (srcbit)
srcval = *src & 0x0f;
else
srcval = *src & 0xf0;
if (dstbit)
*dst &= 0xf0;
else
*dst &= 0x0f;
*dst |= srcval;
src += src_step;
srcbit += srcbit_step;
if (srcbit == 8) {
src++;
srcbit = 0;
}
dst += dst_step;
dstbit += dstbit_step;
if (dstbit == 8) {
dst++;
dstbit = 0;
}
}
return 0;
}
#else
static int area_copy_4bit(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_area,
snd_pcm_uframes_t src_offset,
unsigned int samples, snd_pcm_format_t format)
{
return -EINVAL;
}
#endif /* SALSA_SUPPORT_4BIT_PCM */
int snd_pcm_area_copy(const snd_pcm_channel_area_t *dst_area,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_area,
snd_pcm_uframes_t src_offset,
unsigned int samples, snd_pcm_format_t format)
{
const char *src;
char *dst;
int width;
int src_step, dst_step;
if (!dst_area->addr)
return 0;
if (dst_area == src_area && dst_offset == src_offset)
return 0;
if (!src_area->addr)
return snd_pcm_area_silence(dst_area, dst_offset, samples,
format);
width = snd_pcm_format_physical_width(format);
if (width < 8)
return area_copy_4bit(dst_area, dst_offset,
src_area, src_offset,
samples, format);
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
if (src_area->step == (unsigned int) width &&
dst_area->step == (unsigned int) width) {
memcpy(dst, src, samples * width / 8);
return 0;
}
src_step = src_area->step / 8;
dst_step = dst_area->step / 8;
width /= 8;
while (samples-- > 0) {
memcpy(dst, src, width);
src += src_step;
dst += dst_step;
}
return 0;
}
int snd_pcm_areas_copy(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
unsigned int channels, snd_pcm_uframes_t frames,
snd_pcm_format_t format)
{
if (!channels)
return -EINVAL;
if (!frames)
return -EINVAL;
while (channels > 0) {
snd_pcm_area_copy(dst_areas, dst_offset,
src_areas, src_offset,
frames, format);
src_areas++;
dst_areas++;
channels--;
}
return 0;
}
/*
* MMAP
*/
static size_t page_align(size_t size)
{
size_t r;
long psz = sysconf(_SC_PAGE_SIZE);
r = size % psz;
if (r)
return size + psz - r;
return size;
}
int _snd_pcm_mmap(snd_pcm_t *pcm)
{
unsigned int c;
/* clear first */
_snd_pcm_munmap(pcm);
pcm->mmap_channels = calloc(pcm->channels, sizeof(*pcm->mmap_channels));
if (!pcm->mmap_channels)
return -ENOMEM;
pcm->running_areas = calloc(pcm->channels, sizeof(*pcm->running_areas));
if (!pcm->running_areas) {
free(pcm->mmap_channels);
pcm->mmap_channels = NULL;
return -ENOMEM;
}
for (c = 0; c < pcm->channels; ++c) {
snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
i->info.channel = c;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_CHANNEL_INFO, &i->info) < 0)
return -errno;
}
for (c = 0; c < pcm->channels; ++c) {
snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
snd_pcm_channel_area_t *a = &pcm->running_areas[c];
char *ptr;
size_t size;
unsigned int c1;
if (i->addr)
goto copy;
size = i->info.first + i->info.step * (pcm->buffer_size - 1) +
pcm->sample_bits;
for (c1 = c + 1; c1 < pcm->channels; ++c1) {
snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
size_t s;
if (i1->info.offset != i->info.offset)
continue;
s = i1->info.first + i1->info.step *
(pcm->buffer_size - 1) + pcm->sample_bits;
if (s > size)
size = s;
}
size = (size + 7) / 8;
size = page_align(size);
ptr = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_FILE|MAP_SHARED, pcm->fd, i->info.offset);
if (ptr == MAP_FAILED)
return -errno;
i->addr = ptr;
for (c1 = c + 1; c1 < pcm->channels; ++c1) {
snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
if (i1->info.offset != i->info.offset)
continue;
i1->addr = i->addr;
}
copy:
a->addr = i->addr;
a->first = i->info.first;
a->step = i->info.step;
}
return 0;
}
int _snd_pcm_munmap(snd_pcm_t *pcm)
{
int err;
unsigned int c;
if (!pcm->mmap_channels)
return 0;
for (c = 0; c < pcm->channels; ++c) {
snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
unsigned int c1;
size_t size;
if (!i->addr)
continue;
size = i->info.first + i->info.step *
(pcm->buffer_size - 1) + pcm->sample_bits;
for (c1 = c + 1; c1 < pcm->channels; ++c1) {
snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
size_t s;
if (i1->addr != i->addr)
continue;
i1->addr = NULL;
s = i1->info.first + i1->info.step *
(pcm->buffer_size - 1) + pcm->sample_bits;
if (s > size)
size = s;
}
size = (size + 7) / 8;
size = page_align(size);
err = munmap(i->addr, size);
if (err < 0)
return -errno;
i->addr = NULL;
}
free(pcm->mmap_channels);
free(pcm->running_areas);
pcm->mmap_channels = NULL;
pcm->running_areas = NULL;
return 0;
}
static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm)
{
if (pcm->sync_ptr)
return 0;
pcm->mmap_status =
mmap(NULL, page_align(sizeof(struct snd_pcm_mmap_status)),
PROT_READ, MAP_FILE|MAP_SHARED,
pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
if (pcm->mmap_status == MAP_FAILED)
pcm->mmap_status = NULL;
if (!pcm->mmap_status)
goto no_mmap;
pcm->mmap_control =
mmap(NULL, page_align(sizeof(struct snd_pcm_mmap_control)),
PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED,
pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
if (pcm->mmap_control == MAP_FAILED)
pcm->mmap_control = NULL;
if (!pcm->mmap_control) {
munmap(pcm->mmap_status,
page_align(sizeof(struct snd_pcm_mmap_status)));
pcm->mmap_status = NULL;
goto no_mmap;
}
pcm->mmap_control->avail_min = 1;
return 0;
no_mmap:
pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));
if (!pcm->sync_ptr)
return -ENOMEM;
pcm->mmap_status = &pcm->sync_ptr->s.status;
pcm->mmap_control = &pcm->sync_ptr->c.control;
pcm->mmap_control->avail_min = 1;
_snd_pcm_sync_ptr(pcm, 0);
return 0;
}
static void snd_pcm_hw_munmap_status(snd_pcm_t *pcm)
{
if (pcm->sync_ptr) {
free(pcm->sync_ptr);
pcm->sync_ptr = NULL;
} else {
munmap(pcm->mmap_status,
page_align(sizeof(struct snd_pcm_mmap_status)));
munmap(pcm->mmap_control,
page_align(sizeof(struct snd_pcm_mmap_control)));
}
pcm->mmap_status = NULL;
pcm->mmap_control = NULL;
}
#define get_hw_ptr(pcm) ((pcm)->mmap_status->hw_ptr)
#define get_appl_ptr(pcm) ((pcm)->mmap_control->appl_ptr)
static snd_pcm_uframes_t snd_pcm_mmap_playback_avail(snd_pcm_t *pcm)
{
snd_pcm_sframes_t avail;
avail = get_hw_ptr(pcm) + pcm->buffer_size - get_appl_ptr(pcm);
if (avail < 0)
avail += pcm->boundary;
else if ((snd_pcm_uframes_t) avail >= pcm->boundary)
avail -= pcm->boundary;
return avail;
}
static snd_pcm_uframes_t snd_pcm_mmap_capture_avail(snd_pcm_t *pcm)
{
snd_pcm_sframes_t avail;
avail = get_hw_ptr(pcm) - get_appl_ptr(pcm);
if (avail < 0)
avail += pcm->boundary;
return avail;
}
static snd_pcm_uframes_t snd_pcm_mmap_avail(snd_pcm_t *pcm)
{
if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
return snd_pcm_mmap_playback_avail(pcm);
else
return snd_pcm_mmap_capture_avail(pcm);
}
snd_pcm_sframes_t snd_pcm_hw_forwardable(snd_pcm_t *pcm)
__attribute__ ((alias("snd_pcm_mmap_avail")));
snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm)
{
snd_pcm_uframes_t avail;
_snd_pcm_sync_ptr(pcm, 0);
avail = snd_pcm_mmap_avail(pcm);
switch (snd_pcm_state(pcm)) {
case SND_PCM_STATE_RUNNING:
if (avail >= pcm->sw_params.stop_threshold) {
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_XRUN) < 0)
return -errno;
/* everything is ok,
* state == SND_PCM_STATE_XRUN at the moment
*/
return -EPIPE;
}
break;
case SND_PCM_STATE_XRUN:
return -EPIPE;
default:
break;
}
return avail;
}
int snd_pcm_avail_delay(snd_pcm_t *pcm,
snd_pcm_sframes_t *availp,
snd_pcm_sframes_t *delayp)
{
int err;
snd_pcm_sframes_t val;
err = snd_pcm_delay(pcm, delayp);
if (err < 0)
return err;
val = snd_pcm_avail_update(pcm);
if (val < 0)
return (int)val;
*availp = val;
return 0;
}
int snd_pcm_mmap_begin(snd_pcm_t *pcm,
const snd_pcm_channel_area_t **areas,
snd_pcm_uframes_t *offset,
snd_pcm_uframes_t *frames)
{
snd_pcm_uframes_t cont;
snd_pcm_uframes_t f;
snd_pcm_uframes_t avail;
*areas = pcm->running_areas;
*offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;
avail = snd_pcm_mmap_avail(pcm);
if (avail > pcm->buffer_size)
avail = pcm->buffer_size;
cont = pcm->buffer_size - *offset;
f = *frames;
if (f > avail)
f = avail;
if (f > cont)
f = cont;
*frames = f;
return 0;
}
snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm,
snd_pcm_uframes_t offset,
snd_pcm_uframes_t frames)
{
snd_pcm_uframes_t appl_ptr = pcm->mmap_control->appl_ptr;
appl_ptr += frames;
if (appl_ptr >= pcm->boundary)
appl_ptr -= pcm->boundary;
pcm->mmap_control->appl_ptr = appl_ptr;
_snd_pcm_sync_ptr(pcm, 0);
return frames;
}
#if SALSA_HAS_ASYNC_SUPPORT
/*
* async handler
*/
int snd_async_add_pcm_handler(snd_async_handler_t **handlerp, snd_pcm_t *pcm,
snd_async_callback_t callback,
void *private_data)
{
int err;
if (pcm->async)
return -EBUSY;
err = snd_async_add_handler(&pcm->async, pcm->fd,
callback, private_data);
if (err < 0)
return err;
pcm->async->rec = pcm;
pcm->async->pointer = &pcm->async;
return 0;
}
#endif /* SALSA_HAS_ASYNC_SUPPORT */
/*
* HELPERS
*/
int snd_pcm_wait(snd_pcm_t *pcm, int timeout)
{
struct pollfd pfd;
int err;
#if 0 /* FIXME: NEEDED? */
_snd_pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL);
if (snd_pcm_mmap_avail(pcm) >= pcm->sw_params.avail_min)
return correct_pcm_error(pcm, 1);
#endif
pfd = pcm->pollfd;
for (;;) {
err = poll(&pfd, 1, timeout);
if (err < 0) {
if (errno == EINTR)
continue;
return -errno;
}
if (!err)
return 0;
if (pfd.revents & (POLLERR | POLLNVAL))
return correct_pcm_error(pcm, -EIO);
if (pfd.revents & (POLLIN | POLLOUT))
return 1;
}
}
int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
{
if (err > 0)
err = -err;
switch (err) {
case -EINTR:
return 0;
case -EPIPE:
return snd_pcm_prepare(pcm);
case -ESTRPIPE:
while ((err = snd_pcm_resume(pcm)) == -EAGAIN)
sleep(1);
if (err < 0)
return snd_pcm_prepare(pcm);
return 0;
}
return err;
}
#if SALSA_HAS_CHMAP_SUPPORT
/*
* channel mapping API
*/
static int do_ctl_chmap(int card, int dev, int subdev, int stream,
int (*func)(snd_ctl_t *, snd_ctl_elem_id_t *, void *),
void *arg)
{
char ctlname[32];
snd_ctl_t *ctl;
snd_ctl_elem_id_t id = {
.iface = SND_CTL_ELEM_IFACE_PCM,
.device = dev,
.index = subdev,
};
int ret;
sprintf(ctlname, "hw:%d", card);
ret = snd_ctl_open(&ctl, ctlname, 0);
if (ret < 0)
return ret;
if (stream == SND_PCM_STREAM_PLAYBACK)
strcpy((char *)id.name, "Playback Channel Map");
else
strcpy((char *)id.name, "Capture Channel Map");
ret = func(ctl, &id, arg);
snd_ctl_close(ctl);
if (ret < 0)
return ret;
return 0;
}
#define TLV_SIZE 256
static int do_read_tlv(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *tlv)
{
return snd_ctl_elem_tlv_read(ctl, id, tlv, TLV_SIZE * 4);
}
static inline int is_chmap_type(int type)
{
return (type >= SND_CTL_TLVT_CHMAP_FIXED &&
type <= SND_CTL_TLVT_CHMAP_PAIRED);
}
snd_pcm_chmap_query_t **
snd_pcm_query_chmaps_from_hw(int card, int dev, int subdev,
snd_pcm_stream_t stream)
{
unsigned int tlv[TLV_SIZE], *start;
snd_pcm_chmap_query_t **map;
int i, nums;
if (do_ctl_chmap(card, dev, subdev, stream, do_read_tlv, tlv) < 0)
return NULL;
if (tlv[0] != SND_CTL_TLVT_CONTAINER) {
if (!is_chmap_type(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]))
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;
}
void snd_pcm_free_chmaps(snd_pcm_chmap_query_t **maps)
{
snd_pcm_chmap_query_t **p = maps;
if (!maps)
return;
for (p = maps; *p; p++)
free(*p);
free(maps);
}
static int do_get_chmap(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *arg)
{
snd_ctl_elem_value_t val = { .id = *id };
snd_pcm_chmap_t *map = arg;
int i, ret;
ret = snd_ctl_elem_read(ctl, &val);
if (ret < 0)
return ret;
for (i = 0; i < map->channels; i++)
map->pos[i] = snd_ctl_elem_value_get_integer(&val, i);
return 0;
}
snd_pcm_chmap_t *snd_pcm_get_chmap(snd_pcm_t *pcm)
{
snd_pcm_chmap_t *map;
map = malloc(pcm->channels * sizeof(map->pos[0]) + sizeof(*map));
if (!map)
return NULL;
map->channels = pcm->channels;
if (do_ctl_chmap(pcm->card, pcm->device, pcm->subdevice, pcm->stream,
do_get_chmap, map) < 0) {
free(map);
return NULL;
}
return map;
}
static int do_set_chmap(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *arg)
{
snd_ctl_elem_value_t val = { .id = *id };
snd_pcm_chmap_t *map = arg;
int i;
for (i = 0; i < map->channels; i++)
snd_ctl_elem_value_set_integer(&val, i, map->pos[i]);
return snd_ctl_elem_write(ctl, &val);
}
int snd_pcm_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map)
{
int ret;
if (map->channels > 128)
return -EINVAL;
ret = do_ctl_chmap(pcm->card, pcm->device, pcm->subdevice, pcm->stream,
do_set_chmap, (void *)map);
if (ret == -ENOENT || ret == -EPERM )
ret = -ENXIO;
return ret;
}
#define _NAME(n) [SND_CHMAP_TYPE_##n] = #n
const char *_snd_chmap_type_names[SND_CHMAP_TYPE_LAST + 1] = {
_NAME(NONE), _NAME(FIXED), _NAME(VAR), _NAME(PAIRED),
};
#undef _NAME
#define _NAME(n) [SND_CHMAP_##n] = #n
const char *_snd_chmap_names[SND_CHMAP_LAST + 1] = {
_NAME(UNKNOWN), _NAME(NA), _NAME(MONO),
_NAME(FL), _NAME(FR),
_NAME(RL), _NAME(RR),
_NAME(FC), _NAME(LFE),
_NAME(SL), _NAME(SR),
_NAME(RC), _NAME(FLC), _NAME(FRC), _NAME(RLC), _NAME(RRC),
_NAME(FLW), _NAME(FRW),
_NAME(FLH), _NAME(FCH), _NAME(FRH), _NAME(TC),
_NAME(TFL), _NAME(TFR), _NAME(TFC),
_NAME(TRL), _NAME(TRR), _NAME(TRC),
_NAME(TFLC), _NAME(TFRC), _NAME(TSL), _NAME(TSR),
_NAME(LLFE), _NAME(RLFE),
_NAME(BC), _NAME(BLC), _NAME(BRC),
};
#undef _NAME
const char *_snd_chmap_long_names[SND_CHMAP_LAST + 1] = {
[SND_CHMAP_UNKNOWN] = "Unknown",
[SND_CHMAP_NA] = "Unused",
[SND_CHMAP_MONO] = "Mono",
[SND_CHMAP_FL] = "Front Left",
[SND_CHMAP_FR] = "Front Right",
[SND_CHMAP_RL] = "Rear Left",
[SND_CHMAP_RR] = "Rear Right",
[SND_CHMAP_FC] = "Front Center",
[SND_CHMAP_LFE] = "LFE",
[SND_CHMAP_SL] = "Side Left",
[SND_CHMAP_SR] = "Side Right",
[SND_CHMAP_RC] = "Rear Center",
[SND_CHMAP_FLC] = "Front Left Center",
[SND_CHMAP_FRC] = "Front Right Center",
[SND_CHMAP_RLC] = "Rear Left Center",
[SND_CHMAP_RRC] = "Rear Right Center",
[SND_CHMAP_FLW] = "Front Left Wide",
[SND_CHMAP_FRW] = "Front Right Wide",
[SND_CHMAP_FLH] = "Front Left High",
[SND_CHMAP_FCH] = "Front Center High",
[SND_CHMAP_FRH] = "Front Right High",
[SND_CHMAP_TC] = "Top Center",
[SND_CHMAP_TFL] = "Top Front Left",
[SND_CHMAP_TFR] = "Top Front Right",
[SND_CHMAP_TFC] = "Top Front Center",
[SND_CHMAP_TRL] = "Top Rear Left",
[SND_CHMAP_TRR] = "Top Rear Right",
[SND_CHMAP_TRC] = "Top Rear Center",
[SND_CHMAP_TFLC] = "Top Front Left Center",
[SND_CHMAP_TFRC] = "Top Front Right Center",
[SND_CHMAP_TSL] = "Top Side Left",
[SND_CHMAP_TSR] = "Top Side Right",
[SND_CHMAP_LLFE] = "Left LFE",
[SND_CHMAP_RLFE] = "Right LFE",
[SND_CHMAP_BC] = "Bottom Center",
[SND_CHMAP_BLC] = "Bottom Left Center",
[SND_CHMAP_BRC] = "Bottom Right Center",
};
int snd_pcm_chmap_print(const snd_pcm_chmap_t *map, size_t maxlen, char *buf)
{
unsigned int i, len = 0;
for (i = 0; i < map->channels; i++) {
unsigned int p = map->pos[i] & SND_CHMAP_POSITION_MASK;
if (i > 0) {
len += snprintf(buf + len, maxlen - len, " ");
if (len >= maxlen)
return -ENOMEM;
}
if (map->pos[i] & SND_CHMAP_DRIVER_SPEC)
len += snprintf(buf + len, maxlen, "%d", p);
else {
const char *name = _snd_chmap_names[p];
if (name)
len += snprintf(buf + len, maxlen - len,
"%s", name);
else
len += snprintf(buf + len, maxlen - len,
"Ch%d", p);
}
if (len >= maxlen)
return -ENOMEM;
if (map->pos[i] & SND_CHMAP_PHASE_INVERSE) {
len += snprintf(buf + len, maxlen - len, "[INV]");
if (len >= maxlen)
return -ENOMEM;
}
}
return len;
}
static int str_to_chmap(const char *str, int len)
{
int val;
unsigned long v;
char *p;
if (isdigit(*str)) {
v = strtoul(str, &p, 0);
if (v == (unsigned long)-1)
return -1;
val = v;
val |= SND_CHMAP_DRIVER_SPEC;
str = p;
} else if (!strncasecmp(str, "ch", 2)) {
v = strtoul(str + 2, &p, 0);
if (v == (unsigned long)-1)
return -1;
val = v;
str = p;
} else {
for (val = 0; val <= SND_CHMAP_LAST; val++) {
int slen;
slen = strlen(_snd_chmap_names[val]);
if (slen > len)
continue;
if (!strncasecmp(str, _snd_chmap_names[val], slen) &&
!isalpha(str[slen])) {
str += slen;
break;
}
}
if (val > SND_CHMAP_LAST)
return -1;
}
if (str && !strncasecmp(str, "[INV]", 5))
val |= SND_CHMAP_PHASE_INVERSE;
return val;
}
unsigned int snd_pcm_chmap_from_string(const char *str)
{
return str_to_chmap(str, strlen(str));
}
snd_pcm_chmap_t *snd_pcm_chmap_parse_string(const char *str)
{
int i, ch = 0;
int tmp_map[64];
snd_pcm_chmap_t *map;
for (;;) {
const char *p;
int len, val;
if (ch >= (int)(sizeof(tmp_map) / sizeof(tmp_map[0])))
return NULL;
for (p = str; *p && isalnum(*p); p++)
;
len = p - str;
if (!len)
return NULL;
val = str_to_chmap(str, len);
if (val < 0)
return NULL;
str += len;
if (*str == '[') {
if (!strncmp(str, "[INV]", 5)) {
val |= SND_CHMAP_PHASE_INVERSE;
str += 5;
}
}
tmp_map[ch] = val;
ch++;
for (; *str && !isalnum(*str); str++)
;
if (!*str)
break;
}
map = malloc(sizeof(*map) + ch * sizeof(int));
if (!map)
return NULL;
map->channels = ch;
for (i = 0; i < ch; i++)
map->pos[i] = tmp_map[i];
return map;
}
#endif /* SALSA_HAS_CHMAP_SUPPORT */