mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-31 22:25:35 -04:00
1226 lines
32 KiB
C
1226 lines
32 KiB
C
/*
|
|
* PCM - Share
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/shm.h>
|
|
#include <pthread.h>
|
|
#include "pcm_local.h"
|
|
#include "list.h"
|
|
|
|
static LIST_HEAD(slaves);
|
|
static pthread_mutex_t slaves_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
typedef struct {
|
|
struct list_head clients;
|
|
struct list_head list;
|
|
snd_pcm_t *pcm;
|
|
size_t channels_count;
|
|
size_t open_count;
|
|
size_t setup_count;
|
|
size_t mmap_count;
|
|
size_t prepared_count;
|
|
size_t running_count;
|
|
size_t safety_threshold;
|
|
pthread_t thread;
|
|
pthread_mutex_t mutex;
|
|
} snd_pcm_share_slave_t;
|
|
|
|
typedef struct {
|
|
struct list_head list;
|
|
snd_pcm_t *pcm;
|
|
snd_pcm_share_slave_t *slave;
|
|
size_t channels_count;
|
|
int *slave_channels;
|
|
int xfer_mode;
|
|
int xrun_mode;
|
|
int async_sig;
|
|
pid_t async_pid;
|
|
struct timeval trigger_time;
|
|
size_t draining_silence;
|
|
int state;
|
|
size_t hw_ptr;
|
|
size_t appl_ptr;
|
|
int ready;
|
|
int client_socket;
|
|
int slave_socket;
|
|
void *stopped_data;
|
|
} snd_pcm_share_t;
|
|
|
|
|
|
static void snd_pcm_share_interrupt(snd_pcm_share_slave_t *slave)
|
|
{
|
|
struct list_head *i;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
/* Update poll status */
|
|
for (i = slave->clients.next; i != &slave->clients; i = i->next) {
|
|
snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list);
|
|
snd_pcm_t *pcm = share->pcm;
|
|
int ready;
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_DRAINING:
|
|
if (pcm->stream == SND_PCM_STREAM_CAPTURE)
|
|
ready = 1;
|
|
else {
|
|
if (pcm->mode & SND_PCM_ASYNC)
|
|
kill(share->async_pid, share->async_sig);
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
ready = 0;
|
|
}
|
|
break;
|
|
case SND_PCM_STATE_RUNNING:
|
|
if (pcm->mode & SND_PCM_ASYNC)
|
|
kill(share->async_pid, share->async_sig);
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
ready = (snd_pcm_mmap_avail(pcm) >= pcm->setup.avail_min);
|
|
break;
|
|
default:
|
|
ready = 1;
|
|
}
|
|
if (ready != share->ready) {
|
|
char buf[1];
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
if (ready)
|
|
read(share->slave_socket, buf, 1);
|
|
else
|
|
write(share->client_socket, buf, 1);
|
|
} else {
|
|
if (ready)
|
|
write(share->slave_socket, buf, 1);
|
|
else
|
|
read(share->client_socket, buf, 1);
|
|
}
|
|
share->ready = ready;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
}
|
|
|
|
|
|
void sigio_handler(int sig ATTRIBUTE_UNUSED)
|
|
{
|
|
}
|
|
|
|
void *snd_pcm_share_slave_thread(void *data)
|
|
{
|
|
snd_pcm_share_slave_t *slave = data;
|
|
int err;
|
|
struct sigaction act;
|
|
err = snd_pcm_async(slave->pcm, SIGIO, 0);
|
|
assert(err == 0);
|
|
act.sa_handler = sigio_handler;
|
|
sigemptyset(&act.sa_mask);
|
|
sigaddset(&act.sa_mask, SIGIO);
|
|
act.sa_flags = 0;
|
|
err = sigaction(SIGIO, &act, NULL);
|
|
assert(err == 0);
|
|
while (1) {
|
|
int sig;
|
|
sigwait(&act.sa_mask, &sig);
|
|
snd_pcm_share_interrupt(slave);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Warning: take the mutex before to call this */
|
|
static void snd_pcm_share_stop(snd_pcm_t *pcm, int state)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
share->state = state;
|
|
gettimeofday(&share->trigger_time, 0);
|
|
slave->prepared_count--;
|
|
slave->running_count--;
|
|
if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
|
|
snd_pcm_avail_update(slave->pcm);
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
snd_pcm_areas_copy(pcm->running_areas, 0,
|
|
pcm->stopped_areas, 0,
|
|
pcm->setup.format.channels, pcm->setup.buffer_size,
|
|
pcm->setup.format.sfmt);
|
|
}
|
|
if (slave->running_count == 0) {
|
|
int err = snd_pcm_drop(slave->pcm);
|
|
assert(err >= 0);
|
|
}
|
|
}
|
|
|
|
/* Warning: take the mutex before to call this */
|
|
static void snd_pcm_share_slave_forward(snd_pcm_share_slave_t *slave)
|
|
{
|
|
struct list_head *i;
|
|
size_t buffer_size, boundary;
|
|
size_t slave_appl_ptr;
|
|
ssize_t frames, safety_frames;
|
|
size_t min_frames, max_frames;
|
|
ssize_t avail;
|
|
int err;
|
|
avail = snd_pcm_avail_update(slave->pcm);
|
|
if (avail == 0)
|
|
return;
|
|
assert(avail > 0);
|
|
boundary = slave->pcm->setup.boundary;
|
|
buffer_size = slave->pcm->setup.buffer_size;
|
|
min_frames = buffer_size;
|
|
max_frames = 0;
|
|
slave_appl_ptr = *slave->pcm->appl_ptr;
|
|
for (i = slave->clients.next; i != &slave->clients; i = i->next) {
|
|
snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list);
|
|
snd_pcm_t *pcm = share->pcm;
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_RUNNING:
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
break;
|
|
case SND_PCM_STATE_DRAINING:
|
|
{
|
|
size_t a, offset;
|
|
if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
|
|
continue;
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
a = snd_pcm_mmap_avail(pcm);
|
|
frames = a - share->draining_silence;
|
|
offset = snd_pcm_mmap_offset(pcm);
|
|
offset += share->draining_silence;
|
|
if (offset >= buffer_size)
|
|
offset -= buffer_size;
|
|
while (frames > 0) {
|
|
size_t f = buffer_size - offset;
|
|
if (f > (size_t) frames)
|
|
f = frames;
|
|
snd_pcm_areas_silence(pcm->running_areas, offset,
|
|
pcm->setup.format.channels,
|
|
f, pcm->setup.format.sfmt);
|
|
offset += f;
|
|
if (offset == buffer_size)
|
|
offset = 0;
|
|
frames -= f;
|
|
}
|
|
share->draining_silence = a;
|
|
if (a == buffer_size)
|
|
snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP);
|
|
break;
|
|
}
|
|
default:
|
|
continue;
|
|
}
|
|
frames = share->appl_ptr - slave_appl_ptr;
|
|
if (frames > (ssize_t)buffer_size)
|
|
frames -= pcm->setup.boundary;
|
|
else if (frames < -(ssize_t)pcm->setup.buffer_size)
|
|
frames += pcm->setup.boundary;
|
|
if (frames < 0) {
|
|
if (snd_pcm_mmap_hw_avail(pcm) <= 0 &&
|
|
pcm->setup.xrun_mode != SND_PCM_XRUN_NONE)
|
|
snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN);
|
|
continue;
|
|
}
|
|
if ((size_t)frames < min_frames)
|
|
min_frames = frames;
|
|
if ((size_t)frames > max_frames)
|
|
max_frames = frames;
|
|
}
|
|
if (max_frames == 0)
|
|
return;
|
|
frames = min_frames;
|
|
if (frames > avail)
|
|
frames = avail;
|
|
/* Slave xrun prevention */
|
|
safety_frames = slave->safety_threshold - snd_pcm_mmap_hw_avail(slave->pcm);
|
|
if (safety_frames > 0 &&
|
|
frames < (ssize_t)safety_frames) {
|
|
/* Avoid to pass over the last */
|
|
if (max_frames < (size_t)safety_frames)
|
|
frames = max_frames;
|
|
else
|
|
frames = safety_frames;
|
|
}
|
|
if (frames > 0) {
|
|
err = snd_pcm_mmap_forward(slave->pcm, frames);
|
|
assert(err == frames);
|
|
}
|
|
}
|
|
|
|
static int snd_pcm_share_close(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
if (share->state == SND_PCM_STATE_RUNNING) {
|
|
if (pcm->mode & SND_PCM_NONBLOCK)
|
|
snd_pcm_drop(pcm);
|
|
else
|
|
snd_pcm_drain(pcm);
|
|
}
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (pcm->valid_setup)
|
|
slave->setup_count--;
|
|
slave->open_count--;
|
|
if (slave->open_count == 0) {
|
|
err = pthread_cancel(slave->thread);
|
|
assert(err == 0);
|
|
err = pthread_join(slave->thread, 0);
|
|
assert(err == 0);
|
|
err = snd_pcm_close(slave->pcm);
|
|
list_del(&slave->list);
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
pthread_mutex_destroy(&slave->mutex);
|
|
free(slave);
|
|
list_del(&share->list);
|
|
} else {
|
|
list_del(&share->list);
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
}
|
|
close(share->client_socket);
|
|
close(share->slave_socket);
|
|
free(share->slave_channels);
|
|
free(share);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_share_async(snd_pcm_t *pcm, int sig, pid_t pid)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
if (sig)
|
|
share->async_sig = sig;
|
|
else
|
|
share->async_sig = SIGIO;
|
|
if (pid)
|
|
share->async_pid = pid;
|
|
else
|
|
share->async_pid = getpid();
|
|
return -ENOSYS;
|
|
}
|
|
|
|
static int snd_pcm_share_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
return snd_pcm_info(share->slave->pcm, info);
|
|
}
|
|
|
|
static int snd_pcm_share_params_info(snd_pcm_t *pcm, snd_pcm_params_info_t *info)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
unsigned int req_mask = info->req_mask;
|
|
unsigned int channels = info->req.format.channels;
|
|
if ((req_mask & SND_PCM_PARAMS_CHANNELS) &&
|
|
channels != share->channels_count) {
|
|
info->req.fail_mask |= SND_PCM_PARAMS_CHANNELS;
|
|
info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return -EINVAL;
|
|
}
|
|
info->req_mask |= SND_PCM_PARAMS_CHANNELS;
|
|
info->req.format.channels = slave->channels_count;
|
|
err = snd_pcm_params_info(slave->pcm, info);
|
|
info->req.format.channels = channels;
|
|
info->req_mask = req_mask;
|
|
if (slave->setup_count > 1 ||
|
|
(slave->setup_count == 1 && !pcm->valid_setup)) {
|
|
snd_pcm_setup_t *s = &slave->pcm->setup;
|
|
if ((req_mask & SND_PCM_PARAMS_SFMT) &&
|
|
info->req.format.sfmt != s->format.sfmt) {
|
|
info->req.fail_mask |= SND_PCM_PARAMS_SFMT;
|
|
info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
return -EINVAL;
|
|
}
|
|
info->formats = 1 << s->format.sfmt;
|
|
info->rates = SND_PCM_RATE_CONTINUOUS;
|
|
info->min_rate = info->max_rate = s->format.rate;
|
|
info->buffer_size = s->buffer_size;
|
|
info->min_fragment_size = info->max_fragment_size = s->frag_size;
|
|
info->min_fragments = info->max_fragments = s->frags;
|
|
info->fragment_align = s->frag_size;
|
|
info->req.fail_mask = 0;
|
|
}
|
|
|
|
info->min_channels = info->max_channels = share->channels_count;
|
|
if (info->flags & SND_PCM_INFO_INTERLEAVED) {
|
|
info->flags &= ~SND_PCM_INFO_INTERLEAVED;
|
|
info->flags |= SND_PCM_INFO_COMPLEX;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_mmap(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
snd_pcm_mmap_info_t *i;
|
|
size_t count;
|
|
int err;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (slave->mmap_count == 0) {
|
|
err = snd_pcm_mmap(slave->pcm);
|
|
if (err < 0) {
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
if (slave->pcm->stream == SND_PCM_STREAM_PLAYBACK)
|
|
snd_pcm_areas_silence(slave->pcm->running_areas, 0, slave->pcm->setup.format.channels, slave->pcm->setup.buffer_size, slave->pcm->setup.format.sfmt);
|
|
slave->mmap_count++;
|
|
}
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
count = slave->pcm->mmap_info_count;
|
|
i = malloc((count + 1) * sizeof(*i));
|
|
if (!i)
|
|
return -ENOMEM;
|
|
i->type = SND_PCM_MMAP_USER;
|
|
i->size = snd_pcm_frames_to_bytes(pcm, pcm->setup.buffer_size);
|
|
i->u.user.shmid = shmget(IPC_PRIVATE, i->size, 0666);
|
|
if (i->u.user.shmid < 0) {
|
|
SYSERR("shmget failed");
|
|
free(i);
|
|
return -errno;
|
|
}
|
|
i->addr = shmat(i->u.user.shmid, 0, 0);
|
|
if (i->addr == (void*) -1) {
|
|
SYSERR("shmat failed");
|
|
free(i);
|
|
return -errno;
|
|
}
|
|
share->stopped_data = i->addr;
|
|
memcpy(i + 1, slave->pcm->mmap_info, count * sizeof(*pcm->mmap_info));
|
|
pcm->mmap_info_count = count + 1;
|
|
pcm->mmap_info = i;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_share_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
slave->mmap_count--;
|
|
if (slave->mmap_count == 0) {
|
|
err = snd_pcm_munmap(slave->pcm);
|
|
if (err < 0) {
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
if (shmdt(pcm->mmap_info->addr) < 0) {
|
|
SYSERR("shmdt failed");
|
|
return -errno;
|
|
}
|
|
if (shmctl(pcm->mmap_info->u.user.shmid, IPC_RMID, 0) < 0) {
|
|
SYSERR("shmctl IPC_RMID failed");
|
|
return -errno;
|
|
}
|
|
pcm->mmap_info_count = 0;
|
|
free(pcm->mmap_info);
|
|
pcm->mmap_info = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_share_params(snd_pcm_t *pcm, snd_pcm_params_t *params)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int channels = params->format.channels;
|
|
int err = 0;
|
|
if (channels != share->channels_count) {
|
|
params->fail_mask = SND_PCM_PARAMS_CHANNELS;
|
|
params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
ERR("channels requested (%d) differs from configuration (%ld)", channels, (long)share->channels_count);
|
|
return -EINVAL;
|
|
}
|
|
share->xfer_mode = params->xfer_mode;
|
|
share->xrun_mode = params->xrun_mode;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (slave->setup_count > 1 ||
|
|
(slave->setup_count == 1 && !pcm->valid_setup)) {
|
|
snd_pcm_setup_t *s = &slave->pcm->setup;
|
|
if (params->format.sfmt != s->format.sfmt) {
|
|
ERR("slave is already running with different format");
|
|
params->fail_mask |= SND_PCM_PARAMS_SFMT;
|
|
}
|
|
if (params->fail_mask) {
|
|
params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL;
|
|
err = -EINVAL;
|
|
goto _end;
|
|
}
|
|
} else {
|
|
snd_pcm_params_t sp = *params;
|
|
sp.xfer_mode = SND_PCM_XFER_UNSPECIFIED;
|
|
sp.xrun_mode = SND_PCM_XRUN_NONE;
|
|
sp.format.channels = slave->channels_count;
|
|
err = snd_pcm_params(slave->pcm, &sp);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
slave->setup_count++;
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_setup(snd_pcm_t *pcm, snd_pcm_setup_t *setup)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err;
|
|
err = snd_pcm_setup(slave->pcm, setup);
|
|
if (err < 0)
|
|
return err;
|
|
setup->xrun_mode = share->xrun_mode;
|
|
setup->format.channels = share->channels_count;
|
|
if (share->xfer_mode == SND_PCM_XFER_UNSPECIFIED)
|
|
setup->xfer_mode = SND_PCM_XFER_NONINTERLEAVED;
|
|
else
|
|
setup->xfer_mode = share->xfer_mode;
|
|
if (setup->mmap_shape != SND_PCM_MMAP_INTERLEAVED)
|
|
setup->mmap_shape = SND_PCM_MMAP_COMPLEX;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_pcm_share_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
ssize_t sd = 0, d = 0;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
status->avail = snd_pcm_mmap_playback_avail(pcm);
|
|
if (share->state != SND_PCM_STATE_RUNNING &&
|
|
share->state != SND_PCM_STATE_DRAINING)
|
|
goto _notrunning;
|
|
d = pcm->setup.buffer_size - status->avail;
|
|
} else {
|
|
status->avail = snd_pcm_mmap_capture_avail(pcm);
|
|
if (share->state != SND_PCM_STATE_RUNNING)
|
|
goto _notrunning;
|
|
d = status->avail;
|
|
}
|
|
err = snd_pcm_delay(slave->pcm, &sd);
|
|
if (err < 0)
|
|
goto _end;
|
|
_notrunning:
|
|
status->delay = sd + d;
|
|
status->state = share->state;
|
|
status->trigger_time = share->trigger_time;
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_state(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
return share->state;
|
|
}
|
|
|
|
static int snd_pcm_share_delay(snd_pcm_t *pcm, ssize_t *delayp)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
ssize_t sd;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_XRUN:
|
|
err = -EPIPE;
|
|
goto _end;
|
|
case SND_PCM_STATE_RUNNING:
|
|
break;
|
|
case SND_PCM_STATE_DRAINING:
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
|
|
break;
|
|
/* Fall through */
|
|
default:
|
|
err = -EBADFD;
|
|
goto _end;
|
|
}
|
|
err = snd_pcm_delay(slave->pcm, &sd);
|
|
if (err < 0)
|
|
goto _end;
|
|
*delayp = sd + snd_pcm_mmap_delay(pcm);
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t snd_pcm_share_avail_update(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
ssize_t ret = 0;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
ret = snd_pcm_avail_update(slave->pcm);
|
|
if (share->state == SND_PCM_STATE_RUNNING)
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
if (ret == -EPIPE) {
|
|
struct list_head *i;
|
|
for (i = slave->clients.next; i != &slave->clients; i = i->next) {
|
|
snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list);
|
|
snd_pcm_t *pcm = share->pcm;
|
|
if (share->state == SND_PCM_STATE_RUNNING &&
|
|
pcm->setup.xrun_mode != SND_PCM_XRUN_NONE)
|
|
snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
if (ret >= 0) {
|
|
ret = snd_pcm_mmap_avail(pcm);
|
|
if ((size_t)ret > pcm->setup.buffer_size) {
|
|
if (share->state == SND_PCM_STATE_RUNNING &&
|
|
pcm->setup.xrun_mode != SND_PCM_XRUN_NONE)
|
|
snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN);
|
|
return -EPIPE;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Call it with mutex held */
|
|
static ssize_t _snd_pcm_share_mmap_forward(snd_pcm_t *pcm, size_t size)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
ssize_t ret = 0;
|
|
ssize_t frames;
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
|
|
share->state == SND_PCM_STATE_RUNNING) {
|
|
frames = *slave->pcm->appl_ptr - share->appl_ptr;
|
|
if (frames > (ssize_t)pcm->setup.buffer_size)
|
|
frames -= pcm->setup.boundary;
|
|
else if (frames < -(ssize_t)pcm->setup.buffer_size)
|
|
frames += pcm->setup.boundary;
|
|
if (frames > 0) {
|
|
/* Latecomer PCM */
|
|
ret = snd_pcm_rewind(slave->pcm, frames);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
snd_pcm_mmap_appl_forward(pcm, size);
|
|
if (share->state == SND_PCM_STATE_RUNNING)
|
|
snd_pcm_share_slave_forward(share->slave);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t snd_pcm_share_mmap_forward(snd_pcm_t *pcm, size_t size)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
ssize_t ret;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
ret = _snd_pcm_share_mmap_forward(pcm, size);
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int snd_pcm_share_prepare(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (slave->prepared_count == 0) {
|
|
err = snd_pcm_prepare(slave->pcm);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
slave->prepared_count++;
|
|
share->hw_ptr = 0;
|
|
share->appl_ptr = 0;
|
|
share->state = SND_PCM_STATE_PREPARED;
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_start(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
if (share->state != SND_PCM_STATE_PREPARED)
|
|
return -EBADFD;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
share->state = SND_PCM_STATE_RUNNING;
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
size_t hw_avail = snd_pcm_mmap_playback_hw_avail(pcm);
|
|
size_t xfer = 0;
|
|
if (hw_avail == 0) {
|
|
err = -EPIPE;
|
|
goto _end;
|
|
}
|
|
if (slave->running_count) {
|
|
ssize_t sd;
|
|
err = snd_pcm_delay(slave->pcm, &sd);
|
|
if (err < 0)
|
|
goto _end;
|
|
err = snd_pcm_rewind(slave->pcm, sd);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
assert(share->hw_ptr == 0);
|
|
share->hw_ptr = *slave->pcm->hw_ptr;
|
|
share->appl_ptr = *slave->pcm->appl_ptr;
|
|
while (xfer < hw_avail) {
|
|
size_t frames = hw_avail - xfer;
|
|
size_t offset = snd_pcm_mmap_offset(pcm);
|
|
size_t cont = pcm->setup.buffer_size - offset;
|
|
if (cont < frames)
|
|
frames = cont;
|
|
snd_pcm_areas_copy(pcm->stopped_areas, xfer,
|
|
pcm->running_areas, offset,
|
|
pcm->setup.format.channels, frames,
|
|
pcm->setup.format.sfmt);
|
|
xfer += frames;
|
|
}
|
|
_snd_pcm_share_mmap_forward(pcm, hw_avail);
|
|
}
|
|
if (slave->running_count == 0) {
|
|
err = snd_pcm_start(slave->pcm);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
slave->running_count++;
|
|
gettimeofday(&share->trigger_time, 0);
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_drop(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_OPEN:
|
|
err = -EBADFD;
|
|
goto _end;
|
|
case SND_PCM_STATE_SETUP:
|
|
goto _end;
|
|
case SND_PCM_STATE_DRAINING:
|
|
if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
case SND_PCM_STATE_RUNNING:
|
|
snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP);
|
|
break;
|
|
case SND_PCM_STATE_PREPARED:
|
|
case SND_PCM_STATE_XRUN:
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
break;
|
|
}
|
|
|
|
if (slave->running_count > 0 &&
|
|
pcm->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
ssize_t delay;
|
|
err = snd_pcm_delay(pcm, &delay);
|
|
if (err < 0)
|
|
goto _end;
|
|
if (delay > 0) {
|
|
err = snd_pcm_rewind(pcm, delay);
|
|
if (err < 0)
|
|
goto _end;
|
|
}
|
|
snd_pcm_areas_silence(pcm->running_areas, 0, pcm->setup.format.channels,
|
|
pcm->setup.buffer_size, pcm->setup.format.sfmt);
|
|
snd_pcm_mmap_forward(pcm, pcm->setup.buffer_size);
|
|
}
|
|
share->appl_ptr = share->hw_ptr = 0;
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_drain(snd_pcm_t *pcm)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int err = 0;
|
|
pthread_mutex_lock(&slave->mutex);
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_OPEN:
|
|
err = -EBADFD;
|
|
goto _end;
|
|
case SND_PCM_STATE_PREPARED:
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
break;
|
|
case SND_PCM_STATE_SETUP:
|
|
case SND_PCM_STATE_DRAINING:
|
|
goto _end;
|
|
case SND_PCM_STATE_XRUN:
|
|
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
case SND_PCM_STATE_RUNNING:
|
|
if (snd_pcm_mmap_avail(pcm) <= 0) {
|
|
share->state = SND_PCM_STATE_SETUP;
|
|
break;
|
|
}
|
|
share->draining_silence = 0;
|
|
share->state = SND_PCM_STATE_DRAINING;
|
|
break;
|
|
}
|
|
_end:
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
static int snd_pcm_share_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int channel = info->channel;
|
|
int c = share->slave_channels[channel];
|
|
int err;
|
|
info->channel = c;
|
|
err = snd_pcm_channel_info(slave->pcm, info);
|
|
info->channel = channel;
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_channel_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int channel = params->channel;
|
|
int c = share->slave_channels[channel];
|
|
int err;
|
|
params->channel = c;
|
|
err = snd_pcm_channel_params(slave->pcm, params);
|
|
params->channel = channel;
|
|
return err;
|
|
}
|
|
|
|
static int snd_pcm_share_channel_setup(snd_pcm_t *pcm, snd_pcm_channel_setup_t *setup)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int channel = setup->channel;
|
|
int c = share->slave_channels[channel];
|
|
int err;
|
|
setup->channel = c;
|
|
err = snd_pcm_channel_setup(slave->pcm, setup);
|
|
setup->channel = channel;
|
|
if (err < 0)
|
|
return err;
|
|
if (!pcm->mmap_info)
|
|
return 0;
|
|
switch (pcm->setup.mmap_shape) {
|
|
case SND_PCM_MMAP_INTERLEAVED:
|
|
case SND_PCM_MMAP_COMPLEX:
|
|
setup->stopped_area.addr = share->stopped_data;
|
|
setup->stopped_area.first = channel * pcm->bits_per_sample;
|
|
setup->stopped_area.step = pcm->bits_per_frame;
|
|
break;
|
|
case SND_PCM_MMAP_NONINTERLEAVED:
|
|
setup->stopped_area.addr = share->stopped_data + c * pcm->setup.buffer_size * pcm->bits_per_sample / 8;
|
|
setup->stopped_area.first = 0;
|
|
setup->stopped_area.step = pcm->bits_per_sample;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t snd_pcm_share_rewind(snd_pcm_t *pcm, size_t frames)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
int ret = -EBADFD;
|
|
ssize_t n;
|
|
switch (share->state) {
|
|
case SND_PCM_STATE_RUNNING:
|
|
break;
|
|
case SND_PCM_STATE_PREPARED:
|
|
if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
|
|
return -EBADFD;
|
|
break;
|
|
case SND_PCM_STATE_DRAINING:
|
|
if (pcm->stream != SND_PCM_STREAM_CAPTURE)
|
|
return -EBADFD;
|
|
break;
|
|
case SND_PCM_STATE_XRUN:
|
|
return -EPIPE;
|
|
default:
|
|
goto _err;
|
|
}
|
|
n = snd_pcm_mmap_hw_avail(pcm);
|
|
assert(n >= 0);
|
|
if (n > 0) {
|
|
if ((size_t)n > frames)
|
|
n = frames;
|
|
frames -= n;
|
|
}
|
|
if (share->state == SND_PCM_STATE_RUNNING &&
|
|
frames > 0) {
|
|
pthread_mutex_lock(&slave->mutex);
|
|
ret = snd_pcm_rewind(slave->pcm, frames);
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
if (ret < 0) {
|
|
if (n <= 0)
|
|
return ret;
|
|
goto _end;
|
|
}
|
|
n += ret;
|
|
}
|
|
_end:
|
|
snd_pcm_mmap_appl_backward(pcm, n);
|
|
ret = n;
|
|
_err:
|
|
return ret;
|
|
}
|
|
|
|
static int snd_pcm_share_channels_mask(snd_pcm_t *pcm, bitset_t *cmask)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int i;
|
|
bitset_t m[bitset_size(slave->channels_count)];
|
|
int err = snd_pcm_channels_mask(slave->pcm, m);
|
|
if (err < 0)
|
|
return err;
|
|
for (i = 0; i < share->channels_count; ++i) {
|
|
if (!bitset_get(m, share->slave_channels[i]))
|
|
bitset_reset(cmask, i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void snd_pcm_share_dump(snd_pcm_t *pcm, FILE *fp)
|
|
{
|
|
snd_pcm_share_t *share = pcm->private;
|
|
snd_pcm_share_slave_t *slave = share->slave;
|
|
unsigned int k;
|
|
fprintf(fp, "Share PCM\n");
|
|
fprintf(fp, "\nChannel bindings:\n");
|
|
for (k = 0; k < share->channels_count; ++k)
|
|
fprintf(fp, "%d: %d\n", k, share->slave_channels[k]);
|
|
if (pcm->valid_setup) {
|
|
fprintf(fp, "\nIts setup is:\n");
|
|
snd_pcm_dump_setup(pcm, fp);
|
|
}
|
|
fprintf(fp, "Slave: ");
|
|
snd_pcm_dump(slave->pcm, fp);
|
|
}
|
|
|
|
snd_pcm_ops_t snd_pcm_share_ops = {
|
|
close: snd_pcm_share_close,
|
|
info: snd_pcm_share_info,
|
|
params_info: snd_pcm_share_params_info,
|
|
params: snd_pcm_share_params,
|
|
setup: snd_pcm_share_setup,
|
|
channel_info: snd_pcm_share_channel_info,
|
|
channel_params: snd_pcm_share_channel_params,
|
|
channel_setup: snd_pcm_share_channel_setup,
|
|
dump: snd_pcm_share_dump,
|
|
nonblock: snd_pcm_share_nonblock,
|
|
async: snd_pcm_share_async,
|
|
mmap: snd_pcm_share_mmap,
|
|
munmap: snd_pcm_share_munmap,
|
|
};
|
|
|
|
snd_pcm_fast_ops_t snd_pcm_share_fast_ops = {
|
|
status: snd_pcm_share_status,
|
|
state: snd_pcm_share_state,
|
|
delay: snd_pcm_share_delay,
|
|
prepare: snd_pcm_share_prepare,
|
|
start: snd_pcm_share_start,
|
|
drop: snd_pcm_share_drop,
|
|
drain: snd_pcm_share_drain,
|
|
pause: snd_pcm_share_pause,
|
|
writei: snd_pcm_mmap_writei,
|
|
writen: snd_pcm_mmap_writen,
|
|
readi: snd_pcm_mmap_readi,
|
|
readn: snd_pcm_mmap_readn,
|
|
rewind: snd_pcm_share_rewind,
|
|
channels_mask: snd_pcm_share_channels_mask,
|
|
avail_update: snd_pcm_share_avail_update,
|
|
mmap_forward: snd_pcm_share_mmap_forward,
|
|
};
|
|
|
|
int snd_pcm_share_open(snd_pcm_t **pcmp, char *name, char *sname,
|
|
size_t schannels_count,
|
|
size_t channels_count, int *channels_map,
|
|
int stream, int mode)
|
|
{
|
|
snd_pcm_t *pcm;
|
|
snd_pcm_share_t *share;
|
|
int err;
|
|
struct list_head *i;
|
|
char slave_map[32] = { 0 };
|
|
unsigned int k;
|
|
snd_pcm_share_slave_t *slave = NULL;
|
|
int sd[2];
|
|
|
|
assert(pcmp);
|
|
assert(channels_count > 0 && sname && channels_map);
|
|
|
|
for (k = 0; k < channels_count; ++k) {
|
|
if (channels_map[k] < 0 || channels_map[k] > 31) {
|
|
ERR("Invalid slave channel (%d) in binding", channels_map[k]);
|
|
return -EINVAL;
|
|
}
|
|
if (slave_map[channels_map[k]]) {
|
|
ERR("Repeated slave channel (%d) in binding", channels_map[k]);
|
|
return -EINVAL;
|
|
}
|
|
slave_map[channels_map[k]] = 1;
|
|
assert((unsigned)channels_map[k] < schannels_count);
|
|
}
|
|
|
|
share = calloc(1, sizeof(snd_pcm_share_t));
|
|
if (!share)
|
|
return -ENOMEM;
|
|
|
|
share->channels_count = channels_count;
|
|
share->slave_channels = calloc(channels_count, sizeof(*share->slave_channels));
|
|
if (!share->slave_channels) {
|
|
free(share);
|
|
return -ENOMEM;
|
|
}
|
|
memcpy(share->slave_channels, channels_map, channels_count * sizeof(*share->slave_channels));
|
|
|
|
pcm = calloc(1, sizeof(snd_pcm_t));
|
|
if (!pcm) {
|
|
free(share->slave_channels);
|
|
free(share);
|
|
return -ENOMEM;
|
|
}
|
|
err = socketpair(AF_LOCAL, SOCK_STREAM, 0, sd);
|
|
if (err >= 0 && stream == SND_PCM_STREAM_PLAYBACK) {
|
|
int bufsize = 1;
|
|
err = setsockopt(sd[0], SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
|
|
if (err >= 0) {
|
|
struct pollfd pfd;
|
|
pfd.fd = sd[0];
|
|
pfd.events = POLLOUT;
|
|
while ((err = poll(&pfd, 1, 0)) == 1) {
|
|
char buf[1];
|
|
err = write(sd[0], buf, 1);
|
|
assert(err != 0);
|
|
if (err != 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (err < 0) {
|
|
err = -errno;
|
|
free(pcm);
|
|
free(share->slave_channels);
|
|
free(share);
|
|
return err;
|
|
}
|
|
|
|
pthread_mutex_lock(&slaves_mutex);
|
|
for (i = slaves.next; i != &slaves; i = i->next) {
|
|
snd_pcm_share_slave_t *s = list_entry(i, snd_pcm_share_slave_t, list);
|
|
if (s->pcm->name && strcmp(s->pcm->name, sname)) {
|
|
slave = s;
|
|
break;
|
|
}
|
|
}
|
|
if (!slave) {
|
|
snd_pcm_t *spcm;
|
|
err = snd_pcm_open(&spcm, sname, stream, mode);
|
|
if (err < 0) {
|
|
pthread_mutex_unlock(&slaves_mutex);
|
|
close(sd[0]);
|
|
close(sd[1]);
|
|
free(pcm);
|
|
free(share->slave_channels);
|
|
free(share);
|
|
return err;
|
|
}
|
|
slave = calloc(1, sizeof(*slave));
|
|
if (!slave) {
|
|
pthread_mutex_unlock(&slaves_mutex);
|
|
snd_pcm_close(spcm);
|
|
close(sd[0]);
|
|
close(sd[1]);
|
|
free(pcm);
|
|
free(share->slave_channels);
|
|
free(share);
|
|
return err;
|
|
}
|
|
INIT_LIST_HEAD(&slave->clients);
|
|
slave->pcm = spcm;
|
|
slave->channels_count = schannels_count;
|
|
pthread_mutex_init(&slave->mutex, NULL);
|
|
list_add_tail(&slave->list, &slaves);
|
|
}
|
|
pthread_mutex_unlock(&slaves_mutex);
|
|
|
|
pthread_mutex_lock(&slave->mutex);
|
|
if (slave->open_count == 0) {
|
|
err = pthread_create(&slave->thread, NULL, snd_pcm_share_slave_thread, slave);
|
|
assert(err == 0);
|
|
}
|
|
slave->open_count++;
|
|
list_add_tail(&share->list, &slave->clients);
|
|
pthread_mutex_unlock(&slave->mutex);
|
|
|
|
share->slave = slave;
|
|
share->pcm = pcm;
|
|
share->client_socket = sd[0];
|
|
share->slave_socket = sd[1];
|
|
share->async_sig = SIGIO;
|
|
share->async_pid = getpid();
|
|
|
|
if (name)
|
|
pcm->name = strdup(name);
|
|
pcm->type = SND_PCM_TYPE_SHARE;
|
|
pcm->stream = stream;
|
|
pcm->mode = mode;
|
|
pcm->mmap_auto = 1;
|
|
pcm->ops = &snd_pcm_share_ops;
|
|
pcm->op_arg = pcm;
|
|
pcm->fast_ops = &snd_pcm_share_fast_ops;
|
|
pcm->fast_op_arg = pcm;
|
|
pcm->private = share;
|
|
pcm->poll_fd = share->client_socket;
|
|
pcm->hw_ptr = &share->hw_ptr;
|
|
pcm->appl_ptr = &share->appl_ptr;
|
|
*pcmp = pcm;
|
|
return 0;
|
|
}
|
|
|
|
int _snd_pcm_share_open(snd_pcm_t **pcmp, char *name, snd_config_t *conf,
|
|
int stream, int mode)
|
|
{
|
|
snd_config_iterator_t i;
|
|
char *sname = NULL;
|
|
snd_config_t *binding = NULL;
|
|
int err;
|
|
unsigned int idx;
|
|
int *channels_map;
|
|
size_t channels_count = 0;
|
|
long schannels_count = -1;
|
|
size_t schannel_max = 0;
|
|
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) {
|
|
ERR("Invalid type for sname");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(n->id, "schannels") == 0) {
|
|
err = snd_config_integer_get(n, &schannels_count);
|
|
if (err < 0) {
|
|
ERR("Invalid type for schannels");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(n->id, "binding") == 0) {
|
|
if (snd_config_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
|
ERR("Invalid type for binding");
|
|
return -EINVAL;
|
|
}
|
|
binding = n;
|
|
continue;
|
|
}
|
|
ERR("Unknown field: %s", n->id);
|
|
return -EINVAL;
|
|
}
|
|
if (!sname) {
|
|
ERR("sname is not defined");
|
|
return -EINVAL;
|
|
}
|
|
if (!binding) {
|
|
ERR("binding is not defined");
|
|
return -EINVAL;
|
|
}
|
|
snd_config_foreach(i, binding) {
|
|
int cchannel = -1;
|
|
char *p;
|
|
snd_config_t *n = snd_config_entry(i);
|
|
errno = 0;
|
|
cchannel = strtol(n->id, &p, 10);
|
|
if (errno || *p || cchannel < 0) {
|
|
ERR("Invalid client channel in binding: %s", n->id);
|
|
return -EINVAL;
|
|
}
|
|
if ((unsigned)cchannel >= channels_count)
|
|
channels_count = cchannel + 1;
|
|
}
|
|
if (channels_count == 0) {
|
|
ERR("No bindings defined");
|
|
return -EINVAL;
|
|
}
|
|
channels_map = calloc(channels_count, sizeof(*channels_map));
|
|
for (idx = 0; idx < channels_count; ++idx)
|
|
channels_map[idx] = -1;
|
|
|
|
snd_config_foreach(i, binding) {
|
|
snd_config_t *n = snd_config_entry(i);
|
|
long cchannel;
|
|
long schannel = -1;
|
|
cchannel = strtol(n->id, 0, 10);
|
|
err = snd_config_integer_get(n, &schannel);
|
|
if (err < 0)
|
|
goto _free;
|
|
assert(schannels_count <= 0 || schannel < schannels_count);
|
|
channels_map[cchannel] = schannel;
|
|
if ((unsigned)schannel > schannel_max)
|
|
schannel_max = schannel;
|
|
}
|
|
if (schannels_count <= 0)
|
|
schannels_count = schannel_max + 1;
|
|
err = snd_pcm_share_open(pcmp, name, sname, schannels_count,
|
|
channels_count, channels_map, stream, mode);
|
|
_free:
|
|
free(channels_map);
|
|
return err;
|
|
}
|