pcm: route: Select slave chmap based on ttable information

It means we need to initialize this order:

 1) Read the ttable to figure out which channels are present
 2) Open slave pcm and find a matching chmap
 3) Determine size of ttable (this can now depend on the chmap)
 4) Read ttable coefficients
 5) At prepare time, select the matching chmap

Signed-off-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
David Henningsson 2014-02-28 08:57:06 +01:00 committed by Takashi Iwai
parent 8ad8d22216
commit 5c4cd46810

View file

@ -103,6 +103,7 @@ typedef struct {
snd_pcm_format_t sformat; snd_pcm_format_t sformat;
int schannels; int schannels;
snd_pcm_route_params_t params; snd_pcm_route_params_t params;
snd_pcm_chmap_t *chmap;
} snd_pcm_route_t; } snd_pcm_route_t;
#endif /* DOC_HIDDEN */ #endif /* DOC_HIDDEN */
@ -518,6 +519,7 @@ static int snd_pcm_route_close(snd_pcm_t *pcm)
} }
free(params->dsts); free(params->dsts);
} }
free(route->chmap);
return snd_pcm_generic_close(pcm); return snd_pcm_generic_close(pcm);
} }
@ -789,21 +791,187 @@ static void snd_pcm_route_dump(snd_pcm_t *pcm, snd_output_t *out)
snd_pcm_dump(route->plug.gen.slave, out); snd_pcm_dump(route->plug.gen.slave, out);
} }
static int strtochannel(const char *id, long *channel) /*
* Converts a string to an array of channel indices:
* - Given a number, the result is an array with one element,
* containing that number
* - Given a channel name (e g "FL") and a chmap,
* it will look this up in the chmap and return all matches
* - Given a channel name and no chmap, the result is an array with one element,
containing alsa standard channel map. Note that this might be a negative
number in case of "UNKNOWN", "NA" or "MONO".
* Return value is number of matches written.
*/
static int strtochannel(const char *id, snd_pcm_chmap_t *chmap,
long *channel, int channel_size)
{ {
int err;
int ch; int ch;
err = safe_strtol(id, channel); if (safe_strtol(id, channel) >= 0)
if (err >= 0) return 1;
return err;
ch = (int) snd_pcm_chmap_from_string(id); ch = (int) snd_pcm_chmap_from_string(id);
if (ch == -1) if (ch == -1)
return -EINVAL; return -EINVAL;
/* For now, assume standard channel mapping */ if (chmap) {
int i, r = 0;
/* Start with highest channel to simplify implementation of
determine ttable size */
for (i = chmap->channels - 1; i >= 0; i--) {
if ((int) chmap->pos[i] != ch)
continue;
if (r >= channel_size)
continue;
channel[r++] = i;
}
return r;
}
else {
/* Assume ALSA standard channel mapping */
*channel = ch - SND_CHMAP_FL; *channel = ch - SND_CHMAP_FL;
return 1;
}
}
#define MAX_CHMAP_CHANNELS 256
static int determine_chmap(snd_config_t *tt, snd_pcm_chmap_t **tt_chmap)
{
snd_config_iterator_t i, inext;
snd_pcm_chmap_t *chmap;
assert(tt && tt_chmap);
chmap = malloc(sizeof(snd_pcm_chmap_t) +
MAX_CHMAP_CHANNELS * sizeof(unsigned int));
chmap->channels = 0;
snd_config_for_each(i, inext, tt) {
const char *id;
snd_config_iterator_t j, jnext;
snd_config_t *in = snd_config_iterator_entry(i);
if (!snd_config_get_id(in, &id) < 0)
continue;
if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
goto err;
snd_config_for_each(j, jnext, in) {
int ch, k, found;
long schannel;
snd_config_t *jnode = snd_config_iterator_entry(j);
if (snd_config_get_id(jnode, &id) < 0)
continue;
if (safe_strtol(id, &schannel) >= 0)
continue;
ch = (int) snd_pcm_chmap_from_string(id);
if (ch == -1)
goto err;
found = 0;
for (k = 0; k < (int) chmap->channels; k++)
if (ch == (int) chmap->pos[k]) {
found = 1;
break;
}
if (found)
continue;
if (chmap->channels >= MAX_CHMAP_CHANNELS) {
SNDERR("Too many channels in ttable chmap");
goto err;
}
chmap->pos[chmap->channels++] = ch;
}
}
*tt_chmap = chmap;
return 0; return 0;
err:
*tt_chmap = NULL;
free(chmap);
return -EINVAL;
}
static int find_matching_chmap(snd_pcm_t *spcm, snd_pcm_chmap_t *tt_chmap,
snd_pcm_chmap_t **found_chmap, int *schannels)
{
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(spcm);
int i;
*found_chmap = NULL;
if (chmaps == NULL)
return 0; /* chmap API not supported for this slave */
for (i = 0; chmaps[i]; i++) {
unsigned int j, k;
int match = 1;
snd_pcm_chmap_t *c = &chmaps[i]->map;
if (*schannels >= 0 && (int) c->channels != *schannels)
continue;
for (j = 0; j < tt_chmap->channels; j++) {
int found = 0;
unsigned int ch = tt_chmap->pos[j];
for (k = 0; k < c->channels; k++)
if (c->pos[k] == ch) {
found = 1;
break;
}
if (!found) {
match = 0;
break;
}
}
if (match) {
int size = sizeof(snd_pcm_chmap_t) + c->channels * sizeof(unsigned int);
*found_chmap = malloc(size);
if (!*found_chmap) {
snd_pcm_free_chmaps(chmaps);
return -ENOMEM;
}
memcpy(*found_chmap, c, size);
*schannels = c->channels;
break;
}
}
snd_pcm_free_chmaps(chmaps);
if (*found_chmap == NULL) {
SNDERR("Found no matching channel map");
return -EINVAL;
}
return 0;
}
static int route_chmap_init(snd_pcm_t *pcm)
{
int set_map = 0;
snd_pcm_chmap_t *current;
snd_pcm_route_t *route = pcm->private_data;
if (!route->chmap)
return 0;
if (snd_pcm_state(pcm) != SND_PCM_STATE_PREPARED)
return 0;
/* Check if we really need to set the chmap or not.
This is important in case set_chmap is not implemented. */
current = snd_pcm_get_chmap(route->plug.gen.slave);
if (!current)
return -ENOSYS;
if (current->channels != route->chmap->channels)
set_map = 1;
else
set_map = memcmp(current->pos, route->chmap->pos,
current->channels);
free(current);
if (!set_map)
return 0;
return snd_pcm_set_chmap(route->plug.gen.slave, route->chmap);
} }
@ -939,6 +1107,7 @@ int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
route->plug.undo_write = snd_pcm_plugin_undo_write_generic; route->plug.undo_write = snd_pcm_plugin_undo_write_generic;
route->plug.gen.slave = slave; route->plug.gen.slave = slave;
route->plug.gen.close_slave = close_slave; route->plug.gen.close_slave = close_slave;
route->plug.init = route_chmap_init;
err = snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode); err = snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode);
if (err < 0) { if (err < 0) {
@ -963,16 +1132,10 @@ int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
return 0; return 0;
} }
/** static int _snd_pcm_route_determine_ttable(snd_config_t *tt,
* \brief Determine route matrix sizes
* \param tt Configuration root describing route matrix
* \param tt_csize Returned client size in elements
* \param tt_ssize Returned slave size in elements
* \retval zero on success otherwise a negative error code
*/
int snd_pcm_route_determine_ttable(snd_config_t *tt,
unsigned int *tt_csize, unsigned int *tt_csize,
unsigned int *tt_ssize) unsigned int *tt_ssize,
snd_pcm_chmap_t *chmap)
{ {
snd_config_iterator_t i, inext; snd_config_iterator_t i, inext;
long csize = 0, ssize = 0; long csize = 0, ssize = 0;
@ -1001,7 +1164,7 @@ int snd_pcm_route_determine_ttable(snd_config_t *tt,
const char *id; const char *id;
if (snd_config_get_id(jnode, &id) < 0) if (snd_config_get_id(jnode, &id) < 0)
continue; continue;
err = strtochannel(id, &schannel); err = strtochannel(id, chmap, &schannel, 1);
if (err < 0) { if (err < 0) {
SNDERR("Invalid slave channel: %s", id); SNDERR("Invalid slave channel: %s", id);
return -EINVAL; return -EINVAL;
@ -1019,6 +1182,20 @@ int snd_pcm_route_determine_ttable(snd_config_t *tt,
return 0; return 0;
} }
/**
* \brief Determine route matrix sizes
* \param tt Configuration root describing route matrix
* \param tt_csize Returned client size in elements
* \param tt_ssize Returned slave size in elements
* \retval zero on success otherwise a negative error code
*/
int snd_pcm_route_determine_ttable(snd_config_t *tt,
unsigned int *tt_csize,
unsigned int *tt_ssize)
{
return _snd_pcm_route_determine_ttable(tt, tt_csize, tt_ssize, NULL);
}
/** /**
* \brief Load route matrix * \brief Load route matrix
* \param tt Configuration root describing route matrix * \param tt Configuration root describing route matrix
@ -1030,10 +1207,10 @@ int snd_pcm_route_determine_ttable(snd_config_t *tt,
* \param schannels Slave channels * \param schannels Slave channels
* \retval zero on success otherwise a negative error code * \retval zero on success otherwise a negative error code
*/ */
int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable, static int _snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_csize, unsigned int tt_ssize, unsigned int tt_csize, unsigned int tt_ssize,
unsigned int *tt_cused, unsigned int *tt_sused, unsigned int *tt_cused, unsigned int *tt_sused,
int schannels) int schannels, snd_pcm_chmap_t *chmap)
{ {
int cused = -1; int cused = -1;
int sused = -1; int sused = -1;
@ -1060,17 +1237,18 @@ int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *tt
snd_config_for_each(j, jnext, in) { snd_config_for_each(j, jnext, in) {
snd_config_t *jnode = snd_config_iterator_entry(j); snd_config_t *jnode = snd_config_iterator_entry(j);
double value; double value;
long schannel; int ss;
long *scha = alloca(tt_ssize * sizeof(long));
const char *id; const char *id;
if (snd_config_get_id(jnode, &id) < 0) if (snd_config_get_id(jnode, &id) < 0)
continue; continue;
err = strtochannel(id, &schannel);
if (err < 0 || ss = strtochannel(id, chmap, scha, tt_ssize);
schannel < 0 || (unsigned int) schannel > tt_ssize || if (ss < 0) {
(schannels > 0 && schannel >= schannels)) {
SNDERR("Invalid slave channel: %s", id); SNDERR("Invalid slave channel: %s", id);
return -EINVAL; return -EINVAL;
} }
err = snd_config_get_real(jnode, &value); err = snd_config_get_real(jnode, &value);
if (err < 0) { if (err < 0) {
long v; long v;
@ -1081,10 +1259,19 @@ int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *tt
} }
value = v; value = v;
} }
for (k = 0; (int) k < ss; k++) {
long schannel = scha[k];
if (schannel < 0 || (unsigned int) schannel > tt_ssize ||
(schannels > 0 && schannel >= schannels)) {
SNDERR("Invalid slave channel: %s", id);
return -EINVAL;
}
ttable[cchannel * tt_ssize + schannel] = value; ttable[cchannel * tt_ssize + schannel] = value;
if (schannel > sused) if (schannel > sused)
sused = schannel; sused = schannel;
} }
}
if (cchannel > cused) if (cchannel > cused)
cused = cchannel; cused = cchannel;
} }
@ -1093,6 +1280,26 @@ int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *tt
return 0; return 0;
} }
/**
* \brief Load route matrix
* \param tt Configuration root describing route matrix
* \param ttable Returned route matrix
* \param tt_csize Client size in elements
* \param tt_ssize Slave size in elements
* \param tt_cused Used client elements
* \param tt_sused Used slave elements
* \param schannels Slave channels
* \retval zero on success otherwise a negative error code
*/
int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_csize, unsigned int tt_ssize,
unsigned int *tt_cused, unsigned int *tt_sused,
int schannels)
{
return _snd_pcm_route_load_ttable(tt, ttable, tt_csize, tt_ssize,
tt_cused, tt_sused, schannels, NULL);
}
/*! \page pcm_plugins /*! \page pcm_plugins
\section pcm_plugins_route Plugin: Route & Volume \section pcm_plugins_route Plugin: Route & Volume
@ -1100,6 +1307,9 @@ int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *tt
This plugin converts channels and applies volume during the conversion. This plugin converts channels and applies volume during the conversion.
The format and rate must match for both of them. The format and rate must match for both of them.
SCHANNEL can be a channel name instead of a number (e g FL, LFE).
If so, a matching channel map will be selected for the slave.
\code \code
pcm.name { pcm.name {
type route # Route & Volume conversion PCM type route # Route & Volume conversion PCM
@ -1150,6 +1360,7 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
int err; int err;
snd_pcm_t *spcm; snd_pcm_t *spcm;
snd_config_t *slave = NULL, *sconf; snd_config_t *slave = NULL, *sconf;
snd_pcm_chmap_t *tt_chmap, *chmap;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN; snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
int schannels = -1; int schannels = -1;
snd_config_t *tt = NULL; snd_config_t *tt = NULL;
@ -1198,37 +1409,59 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
return -EINVAL; return -EINVAL;
} }
err = snd_pcm_route_determine_ttable(tt, &csize, &ssize); err = determine_chmap(tt, &tt_chmap);
if (err < 0) {
snd_config_delete(sconf);
return err;
}
ttable = malloc(csize * ssize * sizeof(snd_pcm_route_ttable_entry_t));
if (ttable == NULL) {
snd_config_delete(sconf);
return -ENOMEM;
}
err = snd_pcm_route_load_ttable(tt, ttable, csize, ssize,
&cused, &sused, schannels);
if (err < 0) { if (err < 0) {
free(ttable); free(ttable);
snd_config_delete(sconf);
return err; return err;
} }
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf); err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_config_delete(sconf); snd_config_delete(sconf);
if (err < 0) { if (err < 0) {
free(tt_chmap);
free(ttable); free(ttable);
return err; return err;
} }
if (tt_chmap) {
err = find_matching_chmap(spcm, tt_chmap, &chmap, &schannels);
free(tt_chmap);
if (err < 0)
return err;
}
err = _snd_pcm_route_determine_ttable(tt, &csize, &ssize, chmap);
if (err < 0) {
free(chmap);
snd_pcm_close(spcm);
return err;
}
ttable = malloc(csize * ssize * sizeof(snd_pcm_route_ttable_entry_t));
if (ttable == NULL) {
free(chmap);
snd_pcm_close(spcm);
return -ENOMEM;
}
err = _snd_pcm_route_load_ttable(tt, ttable, csize, ssize,
&cused, &sused, schannels, chmap);
if (err < 0) {
free(chmap);
free(ttable);
snd_pcm_close(spcm);
return err;
}
err = snd_pcm_route_open(pcmp, name, sformat, schannels, err = snd_pcm_route_open(pcmp, name, sformat, schannels,
ttable, ssize, ttable, ssize,
cused, sused, cused, sused,
spcm, 1); spcm, 1);
free(ttable); free(ttable);
if (err < 0) if (err < 0) {
free(chmap);
snd_pcm_close(spcm); snd_pcm_close(spcm);
}
((snd_pcm_route_t*) (*pcmp)->private_data)->chmap = chmap;
return err; return err;
} }
#ifndef DOC_HIDDEN #ifndef DOC_HIDDEN