mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-29 05:40:25 -04:00
761 lines
20 KiB
C
761 lines
20 KiB
C
/*
|
|
* PCM - Plug
|
|
* 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 Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 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 Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include "pcm_local.h"
|
|
#include "pcm_plugin.h"
|
|
|
|
|
|
typedef struct {
|
|
snd_pcm_t *req_slave;
|
|
int close_slave;
|
|
snd_pcm_t *slave;
|
|
ttable_entry_t *ttable;
|
|
unsigned int tt_ssize, tt_cused, tt_sused;
|
|
} snd_pcm_plug_t;
|
|
|
|
|
|
unsigned int snd_pcm_plug_formats(unsigned int formats)
|
|
{
|
|
int fmts = (SND_PCM_LINEAR_FORMATS | SND_PCM_FMT_MU_LAW |
|
|
SND_PCM_FMT_A_LAW | SND_PCM_FMT_IMA_ADPCM);
|
|
if (formats & fmts)
|
|
formats |= fmts;
|
|
return formats;
|
|
}
|
|
|
|
static int preferred_formats[] = {
|
|
SND_PCM_SFMT_S16_LE,
|
|
SND_PCM_SFMT_S16_BE,
|
|
SND_PCM_SFMT_U16_LE,
|
|
SND_PCM_SFMT_U16_BE,
|
|
SND_PCM_SFMT_S24_LE,
|
|
SND_PCM_SFMT_S24_BE,
|
|
SND_PCM_SFMT_U24_LE,
|
|
SND_PCM_SFMT_U24_BE,
|
|
SND_PCM_SFMT_S32_LE,
|
|
SND_PCM_SFMT_S32_BE,
|
|
SND_PCM_SFMT_U32_LE,
|
|
SND_PCM_SFMT_U32_BE,
|
|
SND_PCM_SFMT_S8,
|
|
SND_PCM_SFMT_U8
|
|
};
|
|
|
|
static int snd_pcm_plug_slave_fmt(int format,
|
|
snd_pcm_params_info_t *slave_info)
|
|
{
|
|
if ((snd_pcm_plug_formats(slave_info->formats) & (1 << format)) == 0)
|
|
return -EINVAL;
|
|
if (snd_pcm_format_linear(format)) {
|
|
int width = snd_pcm_format_width(format);
|
|
int unsignd = snd_pcm_format_unsigned(format);
|
|
int big = snd_pcm_format_big_endian(format);
|
|
int format1;
|
|
int wid, width1=width;
|
|
int dwidth1 = 8;
|
|
for (wid = 0; wid < 4; ++wid) {
|
|
int end, big1 = big;
|
|
for (end = 0; end < 2; ++end) {
|
|
int sgn, unsignd1 = unsignd;
|
|
for (sgn = 0; sgn < 2; ++sgn) {
|
|
format1 = snd_pcm_build_linear_format(width1, unsignd1, big1);
|
|
if (format1 >= 0 &&
|
|
slave_info->formats & (1 << format1))
|
|
goto _found;
|
|
unsignd1 = !unsignd1;
|
|
}
|
|
big1 = !big1;
|
|
}
|
|
if (width1 == 32) {
|
|
dwidth1 = -dwidth1;
|
|
width1 = width;
|
|
}
|
|
width1 += dwidth1;
|
|
}
|
|
return -EINVAL;
|
|
_found:
|
|
return format1;
|
|
} else {
|
|
unsigned int i;
|
|
switch (format) {
|
|
case SND_PCM_SFMT_MU_LAW:
|
|
case SND_PCM_SFMT_A_LAW:
|
|
case SND_PCM_SFMT_IMA_ADPCM:
|
|
for (i = 0; i < sizeof(preferred_formats) / sizeof(preferred_formats[0]); ++i) {
|
|
int format1 = preferred_formats[i];
|
|
if (slave_info->formats & (1 << format1))
|
|
return format1;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct {
|
|
unsigned int rate;
|
|
unsigned int flag;
|
|
} snd_pcm_rates[] = {
|
|
{ 8000, SND_PCM_RATE_8000 },
|
|
{ 11025, SND_PCM_RATE_11025 },
|
|
{ 16000, SND_PCM_RATE_16000 },
|
|
{ 22050, SND_PCM_RATE_22050 },
|
|
{ 32000, SND_PCM_RATE_32000 },
|
|
{ 44100, SND_PCM_RATE_44100 },
|
|
{ 48000, SND_PCM_RATE_48000 },
|
|
{ 88200, SND_PCM_RATE_88200 },
|
|
{ 96000, SND_PCM_RATE_96000 },
|
|
{ 176400, SND_PCM_RATE_176400 },
|
|
{ 192000, SND_PCM_RATE_192000 }
|
|
};
|
|
|
|
static int snd_pcm_plug_slave_rate(unsigned int rate,
|
|
snd_pcm_params_info_t *slave_info)
|
|
{
|
|
if (rate <= slave_info->min_rate)
|
|
return slave_info->min_rate;
|
|
else if (rate >= slave_info->max_rate)
|
|
return slave_info->max_rate;
|
|
else if (!(slave_info->rates & (SND_PCM_RATE_CONTINUOUS |
|
|
SND_PCM_RATE_KNOT))) {
|
|
unsigned int k;
|
|
unsigned int rate1 = 0, rate2 = 0;
|
|
int delta1, delta2;
|
|
for (k = 0; k < sizeof(snd_pcm_rates) /
|
|
sizeof(snd_pcm_rates[0]); ++k) {
|
|
if (!(snd_pcm_rates[k].flag & slave_info->rates))
|
|
continue;
|
|
if (snd_pcm_rates[k].rate < rate) {
|
|
rate1 = snd_pcm_rates[k].rate;
|
|
} else if (snd_pcm_rates[k].rate >= rate) {
|
|
rate2 = snd_pcm_rates[k].rate;
|
|
break;
|
|
}
|
|
}
|
|
if (rate1 == 0)
|
|
return rate2;
|
|
if (rate2 == 0)
|
|
return rate1;
|
|
delta1 = rate - rate1;
|
|
delta2 = rate2 - rate;
|
|
if (delta1 < delta2)
|
|
return rate1;
|
|
else
|
|
return rate2;
|
|
}
|
|
return rate;
|
|
}
|
|
|
|
static int snd_pcm_plug_close(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
int err, result = 0;
|
|
if (plug->ttable)
|
|
free(plug->ttable);
|
|
if (plug->slave != plug->req_slave) {
|
|
err = snd_pcm_close(plug->slave);
|
|
if (err < 0)
|
|
result = err;
|
|
}
|
|
if (plug->close_slave) {
|
|
err = snd_pcm_close(plug->req_slave);
|
|
if (err < 0)
|
|
result = err;
|
|
}
|
|
free(plug);
|
|
return result;
|
|
}
|
|
|
|
static int snd_pcm_plug_nonblock(snd_pcm_t *pcm, int nonblock)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_nonblock(plug->slave, nonblock);
|
|
}
|
|
|
|
static int snd_pcm_plug_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
snd_pcm_t *slave = plug->req_slave;
|
|
int err;
|
|
|
|
if ((err = snd_pcm_info(slave, info)) < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_plug_params_info(snd_pcm_t *pcm, snd_pcm_params_info_t *info)
|
|
{
|
|
int err;
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
snd_pcm_t *slave = plug->req_slave;
|
|
snd_pcm_params_info_t slave_info;
|
|
int sformat, srate;
|
|
unsigned int schannels;
|
|
int crate;
|
|
|
|
info->req.fail_reason = 0;
|
|
info->req.fail_mask = 0;
|
|
|
|
if (info->req_mask & SND_PCM_PARAMS_RATE) {
|
|
info->min_rate = info->req.format.rate;
|
|
info->max_rate = info->req.format.rate;
|
|
} else {
|
|
info->min_rate = 4000;
|
|
info->max_rate = 192000;
|
|
}
|
|
info->rates = SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_192000;
|
|
|
|
if (info->req_mask & SND_PCM_PARAMS_CHANNELS) {
|
|
info->min_channels = info->req.format.channels;
|
|
info->max_channels = info->req.format.channels;
|
|
} else {
|
|
info->min_channels = 1;
|
|
info->max_channels = 32;
|
|
}
|
|
|
|
memset(&slave_info, 0, sizeof(slave_info));
|
|
if ((err = snd_pcm_params_info(slave, &slave_info)) < 0)
|
|
return err;
|
|
|
|
info->flags = slave_info.flags;
|
|
info->flags |= SND_PCM_INFO_INTERLEAVED | SND_PCM_INFO_NONINTERLEAVED;
|
|
|
|
info->min_fragments = slave_info.min_fragments;
|
|
info->max_fragments = slave_info.max_fragments;
|
|
|
|
if (info->req_mask & SND_PCM_PARAMS_SFMT)
|
|
info->formats = 1 << info->req.format.sfmt;
|
|
else {
|
|
info->formats = snd_pcm_plug_formats(slave_info.formats);
|
|
return 0;
|
|
}
|
|
|
|
sformat = snd_pcm_plug_slave_fmt(info->req.format.sfmt, &slave_info);
|
|
if (sformat < 0) {
|
|
info->req.fail_mask = SND_PCM_PARAMS_SFMT;
|
|
info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(info->req_mask & SND_PCM_PARAMS_RATE))
|
|
return 0;
|
|
crate = info->req.format.rate;
|
|
srate = snd_pcm_plug_slave_rate(crate, &slave_info);
|
|
if (srate < 0) {
|
|
info->req.fail_mask = SND_PCM_PARAMS_RATE;
|
|
info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(info->req_mask & SND_PCM_PARAMS_CHANNELS))
|
|
return 0;
|
|
schannels = info->req.format.channels;
|
|
if (schannels < info->min_channels)
|
|
schannels = info->min_channels;
|
|
else if (schannels > info->max_channels)
|
|
schannels = info->max_channels;
|
|
|
|
slave_info.req_mask = (SND_PCM_PARAMS_SFMT |
|
|
SND_PCM_PARAMS_CHANNELS |
|
|
SND_PCM_PARAMS_RATE);
|
|
slave_info.req.format.sfmt = sformat;
|
|
slave_info.req.format.channels = schannels;
|
|
slave_info.req.format.rate = srate;
|
|
if ((err = snd_pcm_params_info(slave, &slave_info)) < 0) {
|
|
info->req.fail_mask = slave_info.req.fail_mask;
|
|
info->req.fail_reason = slave_info.req.fail_reason;
|
|
return err;
|
|
}
|
|
info->buffer_size = muldiv64(slave_info.buffer_size, crate, srate);
|
|
info->min_fragment_size = muldiv64(slave_info.min_fragment_size, crate, srate);
|
|
info->max_fragment_size = muldiv64(slave_info.max_fragment_size, crate, srate);
|
|
info->fragment_align = muldiv64(slave_info.fragment_align, crate, srate);
|
|
if (sformat != info->req.format.sfmt ||
|
|
(unsigned int) srate != info->req.format.rate ||
|
|
schannels != info->req.format.channels)
|
|
info->flags &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
|
|
return 0;
|
|
}
|
|
|
|
static void snd_pcm_plug_clear(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
snd_pcm_t *slave = plug->req_slave;
|
|
/* Clear old plugins */
|
|
if (plug->slave != slave) {
|
|
snd_pcm_close(plug->slave);
|
|
plug->slave = slave;
|
|
pcm->fast_ops = slave->fast_ops;
|
|
pcm->fast_op_arg = slave->fast_op_arg;
|
|
}
|
|
}
|
|
|
|
static int snd_pcm_plug_change_rate(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_format_t *clt, snd_pcm_format_t *slv)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
int err;
|
|
assert(snd_pcm_format_linear(slv->sfmt));
|
|
if (clt->rate == slv->rate)
|
|
return 0;
|
|
err = snd_pcm_rate_open(new, slv->sfmt, slv->rate, plug->slave, plug->slave != plug->req_slave);
|
|
if (err < 0)
|
|
return err;
|
|
slv->rate = clt->rate;
|
|
if (snd_pcm_format_linear(clt->sfmt))
|
|
slv->sfmt = clt->sfmt;
|
|
return 1;
|
|
}
|
|
|
|
static int snd_pcm_plug_change_channels(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_format_t *clt, snd_pcm_format_t *slv)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
unsigned int tt_ssize, tt_cused, tt_sused;
|
|
ttable_entry_t *ttable;
|
|
int err;
|
|
assert(snd_pcm_format_linear(slv->sfmt));
|
|
if (clt->channels == slv->channels)
|
|
return 0;
|
|
if (clt->rate != slv->rate &&
|
|
clt->channels > slv->channels)
|
|
return 0;
|
|
|
|
ttable = plug->ttable;
|
|
if (ttable) {
|
|
tt_ssize = plug->tt_ssize;
|
|
tt_cused = plug->tt_cused;
|
|
tt_sused = plug->tt_sused;
|
|
} else {
|
|
unsigned int k;
|
|
unsigned int c = 0, s = 0;
|
|
int n;
|
|
tt_ssize = slv->channels;
|
|
tt_cused = clt->channels;
|
|
tt_sused = slv->channels;
|
|
ttable = alloca(tt_cused * tt_sused * sizeof(*ttable));
|
|
for (k = 0; k < tt_cused * tt_sused; ++k)
|
|
ttable[k] = 0;
|
|
if (clt->channels > slv->channels) {
|
|
n = clt->channels;
|
|
} else {
|
|
n = slv->channels;
|
|
}
|
|
while (n-- > 0) {
|
|
ttable_entry_t v = FULL;
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
|
|
clt->channels > slv->channels) {
|
|
int srcs = clt->channels / slv->channels;
|
|
if (s < clt->channels % slv->channels)
|
|
srcs++;
|
|
v /= srcs;
|
|
} else if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
|
|
slv->channels > clt->channels) {
|
|
int srcs = slv->channels / clt->channels;
|
|
if (s < slv->channels % clt->channels)
|
|
srcs++;
|
|
v /= srcs;
|
|
}
|
|
ttable[c * tt_ssize + s] = v;
|
|
if (++c == clt->channels)
|
|
c = 0;
|
|
if (++s == slv->channels)
|
|
s = 0;
|
|
}
|
|
}
|
|
err = snd_pcm_route_open(new, slv->sfmt, slv->channels, ttable, tt_ssize, tt_cused, tt_sused, plug->slave, plug->slave != plug->req_slave);
|
|
if (err < 0)
|
|
return err;
|
|
slv->channels = clt->channels;
|
|
if (snd_pcm_format_linear(clt->sfmt))
|
|
slv->sfmt = clt->sfmt;
|
|
return 1;
|
|
}
|
|
|
|
static int snd_pcm_plug_change_format(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_format_t *clt, snd_pcm_format_t *slv)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
int err, cfmt;
|
|
int (*f)(snd_pcm_t **handle, int sformat, snd_pcm_t *slave, int close_slave);
|
|
if (snd_pcm_format_linear(slv->sfmt)) {
|
|
/* Conversion is done in another plugin */
|
|
if (clt->sfmt == slv->sfmt ||
|
|
clt->rate != slv->rate ||
|
|
clt->channels != slv->channels)
|
|
return 0;
|
|
} else {
|
|
/* No conversion is needed */
|
|
if (clt->sfmt == slv->sfmt &&
|
|
clt->rate == slv->rate &&
|
|
clt->channels == clt->channels)
|
|
return 0;
|
|
}
|
|
if (snd_pcm_format_linear(slv->sfmt)) {
|
|
cfmt = clt->sfmt;
|
|
switch (clt->sfmt) {
|
|
case SND_PCM_SFMT_MU_LAW:
|
|
f = snd_pcm_mulaw_open;
|
|
break;
|
|
case SND_PCM_SFMT_A_LAW:
|
|
f = snd_pcm_alaw_open;
|
|
break;
|
|
case SND_PCM_SFMT_IMA_ADPCM:
|
|
f = snd_pcm_adpcm_open;
|
|
break;
|
|
default:
|
|
assert(snd_pcm_format_linear(clt->sfmt));
|
|
f = snd_pcm_linear_open;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (slv->sfmt) {
|
|
case SND_PCM_SFMT_MU_LAW:
|
|
f = snd_pcm_mulaw_open;
|
|
break;
|
|
case SND_PCM_SFMT_A_LAW:
|
|
f = snd_pcm_alaw_open;
|
|
break;
|
|
case SND_PCM_SFMT_IMA_ADPCM:
|
|
f = snd_pcm_adpcm_open;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
return -EINVAL;
|
|
}
|
|
if (snd_pcm_format_linear(clt->sfmt))
|
|
cfmt = clt->sfmt;
|
|
else
|
|
cfmt = SND_PCM_SFMT_S16;
|
|
}
|
|
err = f(new, slv->sfmt, plug->slave, plug->slave != plug->req_slave);
|
|
if (err < 0)
|
|
return err;
|
|
slv->sfmt = cfmt;
|
|
return 1;
|
|
}
|
|
|
|
static int snd_pcm_plug_insert_plugins(snd_pcm_t *pcm,
|
|
snd_pcm_format_t *client_fmt,
|
|
snd_pcm_format_t *slave_fmt)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
int (*funcs[])(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_format_t *s, snd_pcm_format_t *d) = {
|
|
snd_pcm_plug_change_format,
|
|
snd_pcm_plug_change_channels,
|
|
snd_pcm_plug_change_rate,
|
|
snd_pcm_plug_change_channels,
|
|
snd_pcm_plug_change_format
|
|
};
|
|
snd_pcm_format_t sfmt = *slave_fmt;
|
|
unsigned int k = 0;
|
|
while (1) {
|
|
snd_pcm_t *new;
|
|
int err;
|
|
if (client_fmt->sfmt == sfmt.sfmt &&
|
|
client_fmt->channels == sfmt.channels &&
|
|
client_fmt->rate == sfmt.rate)
|
|
return 0;
|
|
assert(k < sizeof(funcs)/sizeof(*funcs));
|
|
err = funcs[k](pcm, &new, client_fmt, &sfmt);
|
|
if (err < 0) {
|
|
snd_pcm_plug_clear(pcm);
|
|
return err;
|
|
}
|
|
if (err) {
|
|
plug->slave = new;
|
|
pcm->fast_ops = new->fast_ops;
|
|
pcm->fast_op_arg = new->fast_op_arg;
|
|
}
|
|
k++;
|
|
}
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_plug_params(snd_pcm_t *pcm, snd_pcm_params_t *params)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
snd_pcm_t *slave = plug->req_slave;
|
|
snd_pcm_format_t *slave_format, *format;
|
|
snd_pcm_params_info_t slave_info;
|
|
int srate;
|
|
int err;
|
|
|
|
memset(&slave_info, 0, sizeof(slave_info));
|
|
err = snd_pcm_params_info(slave, &slave_info);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
slave_info.req = *params;
|
|
format = ¶ms->format;
|
|
slave_format = &slave_info.req.format;
|
|
|
|
if ((slave_info.formats & (1 << format->sfmt)) == 0) {
|
|
int slave_fmt = snd_pcm_plug_slave_fmt(format->sfmt, &slave_info);
|
|
if (slave_fmt < 0) {
|
|
params->fail_mask = SND_PCM_PARAMS_SFMT;
|
|
params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return slave_fmt;
|
|
}
|
|
slave_format->sfmt = slave_fmt;
|
|
}
|
|
|
|
if (format->channels < slave_info.min_channels)
|
|
slave_format->channels = slave_info.min_channels;
|
|
else if (format->channels > slave_info.max_channels)
|
|
slave_format->channels = slave_info.max_channels;
|
|
|
|
srate = snd_pcm_plug_slave_rate(format->rate, &slave_info);
|
|
if (srate < 0) {
|
|
params->fail_mask = SND_PCM_PARAMS_RATE;
|
|
params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return srate;
|
|
}
|
|
slave_format->rate = srate;
|
|
|
|
slave_info.req_mask = ~0;
|
|
err = snd_pcm_params_info(slave, &slave_info);
|
|
if (err < 0) {
|
|
params->fail_mask = slave_info.req.fail_mask;
|
|
params->fail_reason = slave_info.req.fail_reason;
|
|
return err;
|
|
}
|
|
|
|
if (slave_format->rate - slave_info.min_rate < slave_info.max_rate - slave_format->rate)
|
|
slave_format->rate = slave_info.min_rate;
|
|
else
|
|
slave_format->rate = slave_info.max_rate;
|
|
|
|
err = snd_pcm_plug_insert_plugins(pcm, format, slave_format);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = snd_pcm_params(plug->slave, params);
|
|
if (err < 0)
|
|
snd_pcm_plug_clear(pcm);
|
|
|
|
assert(plug->req_slave->setup.format.sfmt == slave_format->sfmt);
|
|
assert(plug->req_slave->setup.format.channels == slave_format->channels);
|
|
assert(plug->req_slave->setup.format.rate == slave_format->rate);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_plug_setup(snd_pcm_t *pcm, snd_pcm_setup_t *setup)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_setup(plug->slave, setup);
|
|
}
|
|
|
|
static int snd_pcm_plug_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_channel_info(plug->slave, info);
|
|
}
|
|
|
|
static int snd_pcm_plug_channel_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_channel_params(plug->slave, params);
|
|
}
|
|
|
|
static int snd_pcm_plug_channel_setup(snd_pcm_t *pcm, snd_pcm_channel_setup_t *setup)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_channel_setup(plug->slave, setup);
|
|
}
|
|
|
|
static int snd_pcm_plug_mmap_status(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_mmap_status(plug->slave, NULL);
|
|
}
|
|
|
|
static int snd_pcm_plug_mmap_control(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_mmap_control(plug->slave, NULL);
|
|
}
|
|
|
|
static int snd_pcm_plug_mmap_data(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_mmap_data(plug->slave, NULL);
|
|
}
|
|
|
|
static int snd_pcm_plug_munmap_status(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_munmap_status(plug->slave);
|
|
}
|
|
|
|
static int snd_pcm_plug_munmap_control(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_munmap_control(plug->slave);
|
|
}
|
|
|
|
static int snd_pcm_plug_munmap_data(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
return snd_pcm_munmap_data(plug->slave);
|
|
}
|
|
|
|
static void snd_pcm_plug_dump(snd_pcm_t *pcm, FILE *fp)
|
|
{
|
|
snd_pcm_plug_t *plug = pcm->private;
|
|
fprintf(fp, "Plug PCM: ");
|
|
snd_pcm_dump(plug->slave, fp);
|
|
}
|
|
|
|
struct snd_pcm_ops snd_pcm_plug_ops = {
|
|
close: snd_pcm_plug_close,
|
|
info: snd_pcm_plug_info,
|
|
params_info: snd_pcm_plug_params_info,
|
|
params: snd_pcm_plug_params,
|
|
setup: snd_pcm_plug_setup,
|
|
channel_info: snd_pcm_plug_channel_info,
|
|
channel_params: snd_pcm_plug_channel_params,
|
|
channel_setup: snd_pcm_plug_channel_setup,
|
|
dump: snd_pcm_plug_dump,
|
|
nonblock: snd_pcm_plug_nonblock,
|
|
mmap_status: snd_pcm_plug_mmap_status,
|
|
mmap_control: snd_pcm_plug_mmap_control,
|
|
mmap_data: snd_pcm_plug_mmap_data,
|
|
munmap_status: snd_pcm_plug_munmap_status,
|
|
munmap_control: snd_pcm_plug_munmap_control,
|
|
munmap_data: snd_pcm_plug_munmap_data,
|
|
};
|
|
|
|
int snd_pcm_plug_open(snd_pcm_t **handlep,
|
|
ttable_entry_t *ttable,
|
|
unsigned int tt_ssize,
|
|
unsigned int tt_cused, unsigned int tt_sused,
|
|
snd_pcm_t *slave, int close_slave)
|
|
{
|
|
snd_pcm_t *handle;
|
|
snd_pcm_plug_t *plug;
|
|
int err;
|
|
assert(handlep && slave);
|
|
plug = calloc(1, sizeof(snd_pcm_plug_t));
|
|
if (!plug)
|
|
return -ENOMEM;
|
|
plug->slave = plug->req_slave = slave;
|
|
plug->close_slave = close_slave;
|
|
plug->ttable = ttable;
|
|
plug->tt_ssize = tt_ssize;
|
|
plug->tt_cused = tt_cused;
|
|
plug->tt_sused = tt_sused;
|
|
|
|
handle = calloc(1, sizeof(snd_pcm_t));
|
|
if (!handle) {
|
|
free(plug);
|
|
return -ENOMEM;
|
|
}
|
|
handle->type = SND_PCM_TYPE_PLUG;
|
|
handle->stream = slave->stream;
|
|
handle->ops = &snd_pcm_plug_ops;
|
|
handle->op_arg = handle;
|
|
handle->fast_ops = slave->fast_ops;
|
|
handle->fast_op_arg = slave->fast_op_arg;
|
|
handle->mode = slave->mode;
|
|
handle->private = plug;
|
|
err = snd_pcm_init(handle);
|
|
if (err < 0) {
|
|
snd_pcm_close(handle);
|
|
return err;
|
|
}
|
|
*handlep = handle;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_pcm_plug_open_subdevice(snd_pcm_t **handlep, int card, int device, int subdevice, int stream, int mode)
|
|
{
|
|
snd_pcm_t *slave;
|
|
int err;
|
|
err = snd_pcm_hw_open_subdevice(&slave, card, device, subdevice, stream, mode);
|
|
if (err < 0)
|
|
return err;
|
|
return snd_pcm_plug_open(handlep, 0, 0, 0, 0, slave, 1);
|
|
}
|
|
|
|
int snd_pcm_plug_open_card(snd_pcm_t **handlep, int card, int device, int stream, int mode)
|
|
{
|
|
return snd_pcm_plug_open_subdevice(handlep, card, device, -1, stream, mode);
|
|
}
|
|
|
|
#define MAX_CHANNELS 32
|
|
|
|
int _snd_pcm_plug_open(snd_pcm_t **pcmp, char *name,
|
|
snd_config_t *conf,
|
|
int stream, int mode)
|
|
{
|
|
snd_config_iterator_t i;
|
|
char *sname = NULL;
|
|
int err;
|
|
snd_pcm_t *spcm;
|
|
snd_config_t *tt = NULL;
|
|
ttable_entry_t *ttable = NULL;
|
|
unsigned int cused, sused;
|
|
snd_config_foreach(i, conf) {
|
|
snd_config_t *n = snd_config_entry(i);
|
|
if (strcmp(n->id, "comment") == 0)
|
|
continue;
|
|
if (strcmp(n->id, "type") == 0)
|
|
continue;
|
|
if (strcmp(n->id, "stream") == 0)
|
|
continue;
|
|
if (strcmp(n->id, "sname") == 0) {
|
|
err = snd_config_string_get(n, &sname);
|
|
if (err < 0)
|
|
return -EINVAL;
|
|
continue;
|
|
}
|
|
if (strcmp(n->id, "ttable") == 0) {
|
|
if (snd_config_type(n) != SND_CONFIG_TYPE_COMPOUND)
|
|
return -EINVAL;
|
|
tt = n;
|
|
continue;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
if (!sname)
|
|
return -EINVAL;
|
|
if (tt) {
|
|
ttable = malloc(MAX_CHANNELS * MAX_CHANNELS * sizeof(*ttable));
|
|
err = snd_pcm_route_load_ttable(tt, ttable, MAX_CHANNELS, MAX_CHANNELS,
|
|
&cused, &sused, -1);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
/* This is needed cause snd_config_update may destroy config */
|
|
sname = strdup(sname);
|
|
if (!sname)
|
|
return -ENOMEM;
|
|
err = snd_pcm_open(&spcm, sname, stream, mode);
|
|
free(sname);
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_pcm_plug_open(pcmp, ttable, MAX_CHANNELS, cused, sused, spcm, 1);
|
|
if (err < 0)
|
|
snd_pcm_close(spcm);
|
|
return err;
|
|
}
|
|
|