#include <byteswap.h>
#include <limits.h>
#include <sys/shm.h>
+#include "../control/control_local.h"
#include "pcm_local.h"
#include "pcm_plugin.h"
unsigned int channels; /* count of channels (4 or 6) */
int pcms; /* count of PCM channels */
snd_pcm_t *pcm[3]; /* up to three PCM stereo streams */
+ int linked[3]; /* streams are linked */
} snd_pcm_surround_t;
+static int snd_pcm_surround_free(snd_pcm_surround_t *surr);
+
static int snd_pcm_surround_close(snd_pcm_t *pcm)
{
- int i;
snd_pcm_surround_t *surr = pcm->private_data;
- for (i = 0; i < surr->pcms; i++)
- snd_pcm_close(surr->pcm[i]);
- free(surr);
- return 0;
+ return snd_pcm_surround_free(surr);
}
static int snd_pcm_surround_nonblock(snd_pcm_t *pcm, int nonblock)
static int snd_pcm_surround_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info)
{
snd_pcm_surround_t *surr = pcm->private_data;
+ int err;
if (surr->pcms == 1 || info->channel == 0 || info->channel == 1)
return snd_pcm_channel_info(surr->pcm[0], info);
- if (surr->pcms > 1 && (info->channel == 2 || info->channel == 3))
- return snd_pcm_channel_info(surr->pcm[1], info);
- if (surr->pcms > 2 && (info->channel == 3 || info->channel == 4))
- return snd_pcm_channel_info(surr->pcm[2], info);
+ if (surr->pcms > 1 && (info->channel == 2 || info->channel == 3)) {
+ info->channel -= 2;
+ err = snd_pcm_channel_info(surr->pcm[1], info);
+ info->channel += 2;
+ return err;
+ }
+ if (surr->pcms > 2 && (info->channel == 3 || info->channel == 4)) {
+ info->channel -= 4;
+ err = snd_pcm_channel_info(surr->pcm[2], info);
+ info->channel += 4;
+ return err;
+ }
return -EINVAL;
}
if (interval->empty)
return -EINVAL;
if (interval->openmin) {
- if (!refine)
+ if (!refine) {
+ interval->empty = 1;
return -EINVAL;
+ }
interval->min = surr->channels;
interval->openmin = 0;
}
if (interval->openmax) {
- if (!refine)
+ if (!refine) {
+ interval->empty = 1;
return -EINVAL;
+ }
interval->max = surr->channels;
interval->openmax = 0;
}
if (refine && interval->min <= surr->channels && interval->max >= surr->channels)
interval->min = interval->max = surr->channels;
- if (interval->min != interval->max || interval->min != surr->channels)
+ if (interval->min != interval->max || interval->min != surr->channels) {
+ interval->empty = 1;
return -EINVAL;
+ }
if (surr->pcms != 1)
interval->min = interval->max = 2;
return 0;
}
+static void snd_pcm_surround_interval_channels_fixup(snd_pcm_surround_t *surr,
+ snd_pcm_hw_params_t *params)
+{
+ snd_interval_t *interval;
+ interval = ¶ms->intervals[SND_PCM_HW_PARAM_CHANNELS-SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
+ interval->min = interval->max = surr->channels;
+}
+
static int snd_pcm_surround_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
snd_pcm_surround_t *surr = pcm->private_data;
- int i, err = snd_pcm_surround_interval_channels(surr, params, 1);
+ int i, err;
+
+ err = snd_pcm_surround_interval_channels(surr, params, 1);
if (err < 0)
return err;
if (surr->pcms == 1)
return snd_pcm_hw_refine(surr->pcm[0], params);
for (i = 0; i < surr->pcms; i++) {
err = snd_pcm_hw_refine(surr->pcm[i], params);
- if (err < 0)
+ if (err < 0) {
+ snd_pcm_surround_interval_channels_fixup(surr, params);
return err;
+ }
}
+ snd_pcm_surround_interval_channels_fixup(surr, params);
return 0;
}
static int snd_pcm_surround_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
{
snd_pcm_surround_t *surr = pcm->private_data;
- int i, err = snd_pcm_surround_interval_channels(surr, params, 0);
+ int i, err;
+
+ err = snd_pcm_surround_interval_channels(surr, params, 0);
if (err < 0)
return err;
if (surr->pcms == 1)
return snd_pcm_hw_params(surr->pcm[0], params);
for (i = 0; i < surr->pcms; i++) {
err = snd_pcm_hw_params(surr->pcm[i], params);
- if (err < 0)
+ if (err < 0) {
+ snd_pcm_surround_interval_channels_fixup(surr, params);
+ return err;
+ }
+ }
+ snd_pcm_surround_interval_channels_fixup(surr, params);
+ surr->linked[0] = 0;
+ for (i = 1; i < surr->pcms; i++) {
+ err = snd_pcm_link(surr->pcm[0], surr->pcm[1]);
+ if ((surr->linked[1] = (err >= 0)) == 0)
return err;
}
return 0;
static int snd_pcm_surround_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
- int i, err;
+ int i, err, res = 0;
snd_pcm_surround_t *surr = pcm->private_data;
for (i = 0; i < surr->pcms; i++) {
err = snd_pcm_hw_free(surr->pcm[i]);
if (err < 0)
- return err;
+ res = err;
+ if (!surr->linked[i])
+ continue;
+ surr->linked[i] = 0;
+ err = snd_pcm_unlink(surr->pcm[i]);
+ if (err < 0)
+ res = err;
}
- return 0;
+ return res;
}
static int snd_pcm_surround_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
return 0;
}
-static int snd_pcm_surround_mmap(snd_pcm_t *pcm)
+static int snd_pcm_surround_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
- snd_pcm_surround_t *surr = pcm->private_data;
+ // snd_pcm_surround_t *surr = pcm->private_data;
return 0;
}
-static int snd_pcm_surround_munmap(snd_pcm_t *pcm)
+static int snd_pcm_surround_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
- snd_pcm_surround_t *surr = pcm->private_data;
+ // snd_pcm_surround_t *surr = pcm->private_data;
return 0;
}
mmap_commit: snd_pcm_surround_mmap_commit,
};
+static int snd_pcm_surround_free(snd_pcm_surround_t *surr)
+{
+ int i;
+
+ assert(surr);
+ for (i = 2; i >= 0; i--) {
+ if (surr->pcm[i] == NULL)
+ continue;
+ snd_pcm_close(surr->pcm[i]);
+ surr->pcm[i] = NULL;
+ }
+ free(surr);
+ return 0;
+}
+
+static int snd_pcm_surround_three_streams(snd_pcm_surround_t *surr,
+ snd_pcm_surround_type_t type,
+ int card,
+ int dev0, int subdev0,
+ int dev1, int subdev1,
+ int dev2, int subdev2,
+ int mode)
+{
+ int err;
+
+ if ((err = snd_pcm_hw_open(&surr->pcm[0], "Surround L/R", card, dev0,
+ subdev0, SND_PCM_STREAM_PLAYBACK, mode)) < 0)
+ return err;
+ surr->pcms++;
+ if ((err = snd_pcm_hw_open(&surr->pcm[1], "Surround Rear L/R", card, dev1,
+ subdev1, SND_PCM_STREAM_PLAYBACK, mode)) < 0)
+ return err;
+ surr->pcms++;
+ if (type == SND_PCM_SURROUND_51) {
+ if ((err = snd_pcm_hw_open(&surr->pcm[2], "Surround Center/LFE", card, dev2,
+ subdev2, SND_PCM_STREAM_PLAYBACK, mode)) < 0)
+ return err;
+ surr->pcms++;
+ }
+ return 0;
+}
+
int snd_pcm_surround_open(snd_pcm_t **pcmp, const char *name, int card, int dev,
snd_pcm_surround_type_t type,
snd_pcm_stream_t stream, int mode)
{
snd_pcm_t *pcm;
snd_pcm_surround_t *surr;
+ snd_ctl_t *ctl;
+ snd_ctl_card_info_t *info;
+ int err;
+
assert(pcmp);
if (stream == SND_PCM_STREAM_CAPTURE)
return -EINVAL; /* not supported at the time */
+ if (dev != 0)
+ return -EINVAL; /* not supported at the time */
surr = calloc(1, sizeof(snd_pcm_surround_t));
- if (!surr) {
+ if (!surr)
return -ENOMEM;
+ switch (type) {
+ case SND_PCM_SURROUND_40:
+ surr->channels = 4;
+ break;
+ case SND_PCM_SURROUND_51:
+ surr->channels = 6;
+ break;
+ default:
+ snd_pcm_surround_free(surr);
+ return -EINVAL;
}
-
+ if ((err = snd_ctl_hw_open(&ctl, "Surround", card, 0)) < 0) {
+ snd_pcm_surround_free(surr);
+ return err;
+ }
+ snd_ctl_card_info_alloca(&info);
+ if ((err = snd_ctl_card_info(ctl, info)) < 0) {
+ snd_ctl_close(ctl);
+ snd_pcm_surround_free(surr);
+ return err;
+ }
+ switch (snd_ctl_card_info_get_type(info)) {
+ case SND_CARD_TYPE_SI_7018:
+ if ((err = snd_pcm_surround_three_streams(surr, type, card,
+ 0, -1, 0, -1, 0, -1, mode)) < 0) {
+ snd_pcm_surround_free(surr);
+ return err;
+ }
+ break;
+ default:
+ snd_ctl_close(ctl);
+ snd_pcm_surround_free(surr);
+ return -ENODEV;
+ }
+ snd_ctl_close(ctl);
pcm = calloc(1, sizeof(snd_pcm_t));
if (!pcm) {
- free(surr);
+ snd_pcm_surround_free(surr);
return -ENOMEM;
}
if (name)
pcm->fast_ops = &snd_pcm_surround_fast_ops;
pcm->fast_op_arg = pcm;
pcm->private_data = surr;
- pcm->poll_fd = -1; /* FIXME */
- pcm->hw_ptr = NULL; /* FIXME */
- pcm->appl_ptr = NULL; /* FIXME */
+ pcm->poll_fd = surr->pcm[0]->poll_fd;
+ pcm->hw_ptr = surr->pcm[0]->hw_ptr;
+ pcm->appl_ptr = surr->pcm[0]->appl_ptr;
*pcmp = pcm;
return 0;
continue;
}
#endif
- if (strcmp(id, "type") == 0) {
+ if (strcmp(id, "stype") == 0) {
err = snd_config_get_string(n, &str);
if (strcmp(str, "40") == 0 || strcmp(str, "4.0") == 0)
type = SND_PCM_SURROUND_40;
else if (strcmp(str, "51") == 0 || strcmp(str, "5.1") == 0)
type = SND_PCM_SURROUND_51;
+ continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;