mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-05 13:29:57 -05:00
implement a /dev/mixer interface
git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@956 fefdeb5f-60dc-0310-8127-8f9354f1896f
This commit is contained in:
parent
440b901a4d
commit
ca08e57470
1 changed files with 262 additions and 26 deletions
|
|
@ -79,6 +79,10 @@ struct fd_info {
|
||||||
|
|
||||||
int operation_success;
|
int operation_success;
|
||||||
|
|
||||||
|
pa_cvolume volume;
|
||||||
|
uint32_t sink_index;
|
||||||
|
int volume_modify_count;
|
||||||
|
|
||||||
PA_LLIST_FIELDS(fd_info);
|
PA_LLIST_FIELDS(fd_info);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,6 +158,21 @@ do { \
|
||||||
pthread_mutex_unlock(&func_mutex); \
|
pthread_mutex_unlock(&func_mutex); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
|
#define CONTEXT_CHECK_DEAD_GOTO(i, label) do { \
|
||||||
|
if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY) { \
|
||||||
|
debug(__FILE__": Not connected: %s", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \
|
||||||
|
goto label; \
|
||||||
|
} \
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
#define STREAM_CHECK_DEAD_GOTO(i, label) do { \
|
||||||
|
if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \
|
||||||
|
!(i)->stream || pa_stream_get_state((i)->stream) != PA_STREAM_READY) { \
|
||||||
|
debug(__FILE__": Not connected: %s", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \
|
||||||
|
goto label; \
|
||||||
|
} \
|
||||||
|
} while(0);
|
||||||
|
|
||||||
static void debug(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2);
|
static void debug(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2);
|
||||||
|
|
||||||
static void debug(const char *format, ...) {
|
static void debug(const char *format, ...) {
|
||||||
|
|
@ -375,6 +394,26 @@ static void install_atfork(void) {
|
||||||
pthread_atfork(atfork_prepare, atfork_parent, atfork_child);
|
pthread_atfork(atfork_prepare, atfork_parent, atfork_child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void stream_success_cb(pa_stream *s, int success, void *userdata) {
|
||||||
|
fd_info *i = userdata;
|
||||||
|
|
||||||
|
assert(s);
|
||||||
|
assert(i);
|
||||||
|
|
||||||
|
i->operation_success = success;
|
||||||
|
pa_threaded_mainloop_signal(i->mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void context_success_cb(pa_context *c, int success, void *userdata) {
|
||||||
|
fd_info *i = userdata;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
assert(i);
|
||||||
|
|
||||||
|
i->operation_success = success;
|
||||||
|
pa_threaded_mainloop_signal(i->mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static fd_info* fd_info_new(fd_info_type_t type, int *_errno) {
|
static fd_info* fd_info_new(fd_info_type_t type, int *_errno) {
|
||||||
fd_info *i;
|
fd_info *i;
|
||||||
int sfds[2] = { -1, -1 };
|
int sfds[2] = { -1, -1 };
|
||||||
|
|
@ -403,6 +442,9 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) {
|
||||||
i->ref = 1;
|
i->ref = 1;
|
||||||
i->buf = NULL;
|
i->buf = NULL;
|
||||||
i->unusable = 0;
|
i->unusable = 0;
|
||||||
|
pa_cvolume_reset(&i->volume, 2);
|
||||||
|
i->volume_modify_count = 0;
|
||||||
|
i->sink_index = (uint32_t) -1;
|
||||||
PA_LLIST_INIT(fd_info, i);
|
PA_LLIST_INIT(fd_info, i);
|
||||||
|
|
||||||
reset_params(i);
|
reset_params(i);
|
||||||
|
|
@ -760,15 +802,118 @@ fail:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mixer_open(int flags, int *_errno) {
|
static void sink_info_cb(pa_context *context, const pa_sink_info *si, int eol, void *userdata) {
|
||||||
/* fd_info *i; */
|
fd_info *i = userdata;
|
||||||
|
|
||||||
*_errno = ENOSYS;
|
if (!si && eol < 0) {
|
||||||
|
i->operation_success = 0;
|
||||||
|
pa_threaded_mainloop_signal(i->mainloop, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eol)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!pa_cvolume_equal(&i->volume, &si->volume))
|
||||||
|
i->volume_modify_count++;
|
||||||
|
|
||||||
|
i->volume = si->volume;
|
||||||
|
i->sink_index = si->index;
|
||||||
|
|
||||||
|
i->operation_success = 1;
|
||||||
|
pa_threaded_mainloop_signal(i->mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
|
||||||
|
fd_info *i = userdata;
|
||||||
|
pa_operation *o = NULL;
|
||||||
|
|
||||||
|
if (i->sink_index != idx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(o = pa_context_get_sink_info_by_index(i->context, i->sink_index, sink_info_cb, i))) {
|
||||||
|
debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_operation_unref(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mixer_open(int flags, int *_errno) {
|
||||||
|
fd_info *i;
|
||||||
|
pa_operation *o;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!(i = fd_info_new(FD_INFO_MIXER, _errno)))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* if (!(i = fd_info_new(FD_INFO_MIXER))) */
|
pa_threaded_mainloop_lock(i->mainloop);
|
||||||
/* return -1; */
|
|
||||||
|
|
||||||
|
pa_context_set_subscribe_callback(i->context, subscribe_cb, i);
|
||||||
|
|
||||||
|
if (!(o = pa_context_subscribe(i->context, PA_SUBSCRIPTION_MASK_SINK, context_success_cb, i))) {
|
||||||
|
debug(__FILE__": Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
*_errno = EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i->operation_success = 0;
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
||||||
|
pa_threaded_mainloop_wait(i->mainloop);
|
||||||
|
CONTEXT_CHECK_DEAD_GOTO(i, fail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i->operation_success) {
|
||||||
|
debug(__FILE__":Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
*_errno = EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get sink info */
|
||||||
|
|
||||||
|
pa_operation_unref(o);
|
||||||
|
if (!(o = pa_context_get_sink_info_by_name(i->context, NULL, sink_info_cb, i))) {
|
||||||
|
debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
*_errno = EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i->operation_success = 0;
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
||||||
|
pa_threaded_mainloop_wait(i->mainloop);
|
||||||
|
CONTEXT_CHECK_DEAD_GOTO(i, fail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i->operation_success) {
|
||||||
|
debug(__FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
*_errno = EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
|
||||||
|
debug(__FILE__": mixer_open() succeeded, fd=%i\n", i->app_fd);
|
||||||
|
|
||||||
|
fd_info_add_to_list(i);
|
||||||
|
ret = i->app_fd;
|
||||||
|
fd_info_unref(i);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
|
||||||
|
if (i)
|
||||||
|
fd_info_unref(i);
|
||||||
|
|
||||||
|
*_errno = EIO;
|
||||||
|
|
||||||
|
debug(__FILE__": mixer_open() failed\n");
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sndstat_open(int flags, int *_errno) {
|
static int sndstat_open(int flags, int *_errno) {
|
||||||
|
|
@ -879,8 +1024,109 @@ int open(const char *filename, int flags, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
|
static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
|
||||||
*_errno = ENOSYS;
|
int ret = -1;
|
||||||
return -1;
|
|
||||||
|
switch (request) {
|
||||||
|
case SOUND_MIXER_READ_DEVMASK :
|
||||||
|
debug(__FILE__": SOUND_MIXER_READ_DEVMASK\n");
|
||||||
|
|
||||||
|
*(int*) argp = SOUND_MASK_PCM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_READ_RECMASK :
|
||||||
|
debug(__FILE__": SOUND_MIXER_READ_RECMASK\n");
|
||||||
|
|
||||||
|
*(int*) argp = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_READ_STEREODEVS:
|
||||||
|
debug(__FILE__": SOUND_MIXER_READ_STEREODEVS\n");
|
||||||
|
|
||||||
|
pa_threaded_mainloop_lock(i->mainloop);
|
||||||
|
*(int*) argp = i->volume.channels > 1 ? SOUND_MASK_PCM : 0;
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_READ_RECSRC:
|
||||||
|
debug(__FILE__": SOUND_MIXER_READ_RECSRC\n");
|
||||||
|
|
||||||
|
*(int*) argp = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_CAPS:
|
||||||
|
debug(__FILE__": SOUND_MIXER_CAPS\n");
|
||||||
|
|
||||||
|
*(int*) argp = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_READ_PCM:
|
||||||
|
|
||||||
|
debug(__FILE__": SOUND_MIXER_READ_PCM\n");
|
||||||
|
|
||||||
|
pa_threaded_mainloop_lock(i->mainloop);
|
||||||
|
|
||||||
|
*(int*) argp =
|
||||||
|
((i->volume.values[0]*100/PA_VOLUME_NORM) << 8) |
|
||||||
|
((i->volume.values[i->volume.channels > 1 ? 1 : 0]*100/PA_VOLUME_NORM));
|
||||||
|
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOUND_MIXER_WRITE_PCM: {
|
||||||
|
pa_cvolume v;
|
||||||
|
|
||||||
|
debug(__FILE__": SOUND_MIXER_WRITE_PCM\n");
|
||||||
|
|
||||||
|
pa_threaded_mainloop_lock(i->mainloop);
|
||||||
|
|
||||||
|
v = i->volume;
|
||||||
|
|
||||||
|
i->volume.values[0] = ((*(int*) argp >> 8)*PA_VOLUME_NORM)/100;
|
||||||
|
i->volume.values[1] = ((*(int*) argp & 0xFF)*PA_VOLUME_NORM)/100;
|
||||||
|
|
||||||
|
if (!pa_cvolume_equal(&i->volume, &v)) {
|
||||||
|
pa_operation *o;
|
||||||
|
|
||||||
|
if (!(o = pa_context_set_sink_volume_by_index(i->context, i->sink_index, &i->volume, NULL, NULL)))
|
||||||
|
debug(__FILE__":Failed set volume: %s", pa_strerror(pa_context_errno(i->context)));
|
||||||
|
else
|
||||||
|
pa_operation_unref(o);
|
||||||
|
|
||||||
|
/* We don't wait for completion here */
|
||||||
|
i->volume_modify_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SOUND_MIXER_INFO: {
|
||||||
|
mixer_info *mi = argp;
|
||||||
|
|
||||||
|
memset(mi, 0, sizeof(mixer_info));
|
||||||
|
strncpy(mi->id, "POLYPAUDIO", sizeof(mi->id));
|
||||||
|
strncpy(mi->name, "Polypaudio Virtual OSS", sizeof(mi->name));
|
||||||
|
pa_threaded_mainloop_lock(i->mainloop);
|
||||||
|
mi->modify_counter = i->volume_modify_count;
|
||||||
|
pa_threaded_mainloop_unlock(i->mainloop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
debug(__FILE__": unknwon ioctl 0x%08lx\n", request);
|
||||||
|
|
||||||
|
*_errno = EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int map_format(int *fmt, pa_sample_spec *ss) {
|
static int map_format(int *fmt, pa_sample_spec *ss) {
|
||||||
|
|
@ -936,16 +1182,6 @@ static int map_format_back(pa_sample_format_t format) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void success_cb(pa_stream *s, int success, void *userdata) {
|
|
||||||
fd_info *i = userdata;
|
|
||||||
|
|
||||||
assert(s);
|
|
||||||
assert(i);
|
|
||||||
|
|
||||||
i->operation_success = success;
|
|
||||||
pa_threaded_mainloop_signal(i->mainloop, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dsp_flush_socket(fd_info *i) {
|
static int dsp_flush_socket(fd_info *i) {
|
||||||
int l;
|
int l;
|
||||||
|
|
||||||
|
|
@ -1015,15 +1251,14 @@ static int dsp_drain(fd_info *i) {
|
||||||
|
|
||||||
debug(__FILE__": Really draining.\n");
|
debug(__FILE__": Really draining.\n");
|
||||||
|
|
||||||
if (!(o = pa_stream_drain(i->stream, success_cb, i))) {
|
if (!(o = pa_stream_drain(i->stream, stream_success_cb, i))) {
|
||||||
debug(__FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context)));
|
debug(__FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context)));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
i->operation_success = 0;
|
i->operation_success = 0;
|
||||||
while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
||||||
if (!i->stream || pa_stream_get_state(i->stream) != PA_STREAM_READY)
|
STREAM_CHECK_DEAD_GOTO(i, fail);
|
||||||
goto fail;
|
|
||||||
|
|
||||||
pa_threaded_mainloop_wait(i->mainloop);
|
pa_threaded_mainloop_wait(i->mainloop);
|
||||||
}
|
}
|
||||||
|
|
@ -1059,15 +1294,14 @@ static int dsp_trigger(fd_info *i) {
|
||||||
|
|
||||||
debug(__FILE__": Triggering.\n");
|
debug(__FILE__": Triggering.\n");
|
||||||
|
|
||||||
if (!(o = pa_stream_trigger(i->stream, success_cb, i))) {
|
if (!(o = pa_stream_trigger(i->stream, stream_success_cb, i))) {
|
||||||
debug(__FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context)));
|
debug(__FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context)));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
i->operation_success = 0;
|
i->operation_success = 0;
|
||||||
while (!pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
while (!pa_operation_get_state(o) != PA_OPERATION_DONE) {
|
||||||
if (!i->stream || pa_stream_get_state(i->stream) != PA_STREAM_READY)
|
STREAM_CHECK_DEAD_GOTO(i, fail);
|
||||||
goto fail;
|
|
||||||
|
|
||||||
pa_threaded_mainloop_wait(i->mainloop);
|
pa_threaded_mainloop_wait(i->mainloop);
|
||||||
}
|
}
|
||||||
|
|
@ -1218,8 +1452,8 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno)
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
pa_usec_t usec;
|
pa_usec_t usec;
|
||||||
if (!i->stream || pa_stream_get_state(i->stream) != PA_STREAM_READY)
|
|
||||||
break;
|
STREAM_CHECK_DEAD_GOTO(i, exit_loop);
|
||||||
|
|
||||||
if (pa_stream_get_latency(i->stream, &usec, NULL) >= 0) {
|
if (pa_stream_get_latency(i->stream, &usec, NULL) >= 0) {
|
||||||
*(int*) argp = pa_usec_to_bytes(usec, &i->sample_spec);
|
*(int*) argp = pa_usec_to_bytes(usec, &i->sample_spec);
|
||||||
|
|
@ -1234,6 +1468,8 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno)
|
||||||
pa_threaded_mainloop_wait(i->mainloop);
|
pa_threaded_mainloop_wait(i->mainloop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit_loop:
|
||||||
|
|
||||||
if (ioctl(i->thread_fd, SIOCINQ, &l) < 0)
|
if (ioctl(i->thread_fd, SIOCINQ, &l) < 0)
|
||||||
debug(__FILE__": SIOCINQ failed: %s\n", strerror(errno));
|
debug(__FILE__": SIOCINQ failed: %s\n", strerror(errno));
|
||||||
else
|
else
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue