+++ /dev/null
-/*
- * ossp-padsp - ossp DSP slave which forwards to pulseaduio
- *
- * Copyright (C) 2008-2010 SUSE Linux Products GmbH
- * Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
- *
- * This file is released under the GPLv2.
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <getopt.h>
-#include <libgen.h>
-#include <limits.h>
-#include <poll.h>
-#include <pthread.h>
-#include <pwd.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <pulse/pulseaudio.h>
-#include <sys/soundcard.h>
-
-#include "ossp-slave.h"
-
-enum {
- AFMT_FLOAT = 0x00004000,
- AFMT_S32_LE = 0x00001000,
- AFMT_S32_BE = 0x00002000,
-};
-
-/* everything is in millisecs */
-struct stream_params {
- size_t min_process;
- size_t min_latency;
- size_t dfl_process;
- size_t dfl_latency;
- size_t mmap_process;
- size_t mmap_latency;
- size_t mmap_lead;
- size_t mmap_staging;
-};
-
-/* TODO: make this configurable */
-static struct stream_params stream_params[] = {
- [ PLAY ] = { .min_process = 25, .min_latency = 100,
- .dfl_process = 50, .dfl_latency = 200,
- .mmap_process = 25, .mmap_latency = 50,
- .mmap_lead = 25, .mmap_staging = 100 },
- [ REC ] = { .min_process = 25, .min_latency = 200,
- .dfl_process = 50, .dfl_latency = 400,
- .mmap_process = 25, .mmap_latency = 50,
- .mmap_lead = 25, .mmap_staging = 1000 },
-};
-
-static size_t page_size;
-static pa_context *context;
-static pa_threaded_mainloop *mainloop;
-static pa_mainloop_api *mainloop_api;
-static char stream_name[128];
-static int stream_enabled[2];
-static int stream_corked[2];
-static int stream_waiting;
-static int stream_notify;
-static pa_channel_map channel_map_stor;
-static pa_channel_map *channel_map;
-static pa_stream *stream[2];
-static pa_usec_t stream_ptr_timestamp[2];
-static struct ring_buf rec_buf;
-static int stored_oss_vol[2][2] = { { -1, -1 }, { -1, -1 } };
-static int fail_code;
-
-static pa_sample_spec sample_spec = {
- .format = PA_SAMPLE_U8,
- .rate = 8000,
- .channels = 1,
-};
-static size_t sample_bps = 8000;
-static size_t frame_size = 1;
-
-/* user visible stream parameters */
-static size_t user_frag_size;
-static size_t user_subdivision; /* alternative way to determine frag_size */
-static size_t user_max_frags; /* maximum number of fragments */
-static size_t user_max_length;
-
-/* actual stream parameters */
-static size_t frag_size;
-static size_t target_length;
-static size_t max_length;
-static size_t prebuf_size;
-
-/* mmap stuff */
-static size_t mmap_raw_size, mmap_size;
-static void *mmap_map[2];
-static uint64_t mmap_idx[2]; /* mmap pointer */
-static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */
-static struct ring_buf mmap_stg[2]; /* staging ring buffer */
-static size_t mmap_lead[2]; /* lead bytes */
-static int mmap_sync[2]; /* sync with backend stream */
-
-static const char *dir_str[] = {
- [PLAY] = "PLAY",
- [REC] = "REC",
-};
-
-static void stream_rw_callback(pa_stream *s, size_t length, void *userdata);
-
-#define __pa_err pa_strerror(pa_context_errno(context))
-#define dbg1_pa(fmt, args...) dbg1(fmt" (%s)" , ##args, __pa_err)
-#define dbg0_pa(fmt, args...) dbg0(fmt" (%s)" , ##args, __pa_err)
-#define info_pa(fmt, args...) info(fmt" (%s)" , ##args, __pa_err)
-#define warn_pa(fmt, args...) warn(fmt" (%s)" , ##args, __pa_err)
-#define err_pa(fmt, args...) err(fmt" (%s)" , ##args, __pa_err)
-
-#define round_down(v, t) ((v) / (t) * (t))
-#define round_up(v, t) (((v) + (t) - 1) / (t) * (t))
-#define is_power2(v) !((v) & ((v) - 1))
-
-static int do_mixer(int dir, int *vol);
-
-static int padsp_done(void)
-{
- fail_code = -EIO;
- mainloop_api->quit(mainloop_api, 1);
- return fail_code;
-}
-
-static int fmt_oss_to_pa(int fmt)
-{
- switch (fmt) {
- case AFMT_U8: return PA_SAMPLE_U8;
- case AFMT_A_LAW: return PA_SAMPLE_ALAW;
- case AFMT_MU_LAW: return PA_SAMPLE_ULAW;
- case AFMT_S16_LE: return PA_SAMPLE_S16LE;
- case AFMT_S16_BE: return PA_SAMPLE_S16BE;
- case AFMT_FLOAT: return PA_SAMPLE_FLOAT32NE;
- case AFMT_S32_LE: return PA_SAMPLE_S32LE;
- case AFMT_S32_BE: return PA_SAMPLE_S32BE;
- default: return PA_SAMPLE_U8;
- }
-}
-
-static int fmt_pa_to_oss(int fmt)
-{
- switch (fmt) {
- case PA_SAMPLE_U8: return AFMT_U8;
- case PA_SAMPLE_ALAW: return AFMT_A_LAW;
- case PA_SAMPLE_ULAW: return AFMT_MU_LAW;
- case PA_SAMPLE_S16LE: return AFMT_S16_LE;
- case PA_SAMPLE_S16BE: return AFMT_S16_BE;
- case PA_SAMPLE_FLOAT32NE: return AFMT_FLOAT;
- case PA_SAMPLE_S32LE: return AFMT_S32_LE;
- case PA_SAMPLE_S32BE: return AFMT_S32_BE;
- default: return AFMT_U8;
- }
-}
-
-#define EXEC_OP(op, args...) do { \
- pa_operation *_o; \
- _o = op(args); \
- if (_o) { \
- while (pa_operation_get_state(_o) != PA_OPERATION_DONE) \
- pa_threaded_mainloop_wait(mainloop); \
- pa_operation_unref(_o); \
- } } while (0)
-
-static void context_op_callback(pa_context *s, int success, void *userdata)
-{
- *(int *)userdata = success;
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-static void stream_op_callback(pa_stream *s, int success, void *userdata)
-{
- *(int *)userdata = success;
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-#define EXEC_CONTEXT_OP(op, args...) ({ \
- int _success; \
- EXEC_OP(op , ##args, context_op_callback, &_success); \
- if (!_success) \
- warn_pa("%s() failed", #op); \
- _success ? 0 : -EIO; })
-
-#define EXEC_STREAM_OP(op, args...) ({ \
- int _success; \
- EXEC_OP(op , ##args, stream_op_callback, &_success); \
- if (!_success) \
- warn_pa("%s() failed", #op); \
- _success ? 0 : -EIO; })
-
-static int mmapped(void)
-{
- return mmap_map[PLAY] || mmap_map[REC];
-}
-
-static uint64_t get_mmap_idx(int dir)
-{
- uint64_t idx;
- pa_usec_t time;
-
- if (!stream[dir])
- return mmap_idx[dir];
-
- if (pa_stream_get_time(stream[dir], &time) < 0) {
- dbg1_pa("pa_stream_get_time() failed");
- return mmap_idx[dir];
- }
-
- /* calculate the current index from time elapsed */
- idx = ((uint64_t)time * sample_bps / 1000000);
- /* round down to the nearest frame boundary */
- idx = idx / frame_size * frame_size;
-
- return idx;
-}
-
-static void flush_streams(int drain)
-{
- int i;
-
- if (!(stream[PLAY] || stream[REC]))
- return;
-
- dbg0("FLUSH drain=%d", drain);
-
- /* mmapped streams run forever, can't drain */
- if (drain && !mmapped() && stream[PLAY])
- EXEC_STREAM_OP(pa_stream_drain, stream[PLAY]);
-
- for (i = 0; i < 2; i++)
- if (stream[i])
- EXEC_STREAM_OP(pa_stream_flush, stream[i]);
-
- ring_consume(&rec_buf, ring_bytes(&rec_buf));
-}
-
-static void kill_streams(void)
-{
- int dir;
-
- if (!(stream[PLAY] || stream[REC]))
- return;
-
- flush_streams(1);
-
- dbg0("KILL");
-
- for (dir = 0; dir < 2; dir++) {
- if (!stream[dir])
- continue;
- pa_stream_disconnect(stream[dir]);
- pa_stream_unref(stream[dir]);
- stream[dir] = NULL;
- stream_ptr_timestamp[dir] = 0;
-
- ring_consume(&mmap_stg[dir], ring_bytes(&mmap_stg[dir]));
- ring_resize(&mmap_stg[dir], 0);
- }
-}
-
-static int trigger_streams(int play, int rec)
-{
- int ret = 0, dir, rc;
-
- if (play >= 0)
- stream_corked[PLAY] = !play;
- if (rec >= 0)
- stream_corked[REC] = !rec;
-
- for (dir = 0; dir < 2; dir++) {
- if (!stream[dir])
- continue;
-
- rc = EXEC_STREAM_OP(pa_stream_cork, stream[dir],
- stream_corked[dir]);
- if (!rc && dir == PLAY && !mmap_map[dir] && !stream_corked[dir])
- rc = EXEC_STREAM_OP(pa_stream_trigger, stream[dir]);
- if (!ret)
- ret = rc;
- }
-
- return ret;
-}
-
-static void stream_state_callback(pa_stream *s, void *userdata)
-{
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-static void stream_underflow_callback(pa_stream *s, void *userdata)
-{
- int dir = (s == stream[PLAY]) ? PLAY : REC;
-
- dbg0("%s stream underrun", dir_str[dir]);
-}
-
-static void stream_overflow_callback(pa_stream *s, void *userdata)
-{
- int dir = (s == stream[PLAY]) ? PLAY : REC;
-
- dbg0("%s stream overrun", dir_str[dir]);
-}
-
-static size_t duration_to_bytes(size_t dur)
-{
- return round_up(dur * sample_bps / 1000, frame_size);
-}
-
-static int prepare_streams(void)
-{
- const struct stream_params *sp;
- size_t min_frag_size, min_target_length, tmp;
- int dir, rc;
-
- /* nothing to do? */
- if ((!stream_enabled[PLAY] || stream[PLAY]) &&
- (!stream_enabled[REC] || stream[REC]))
- return 0;
-
- /* determine sample parameters */
- sample_bps = pa_bytes_per_second(&sample_spec);
- frame_size = pa_frame_size(&sample_spec);
-
- sp = &stream_params[PLAY];
- if (stream_enabled[REC])
- sp = &stream_params[REC];
-
- min_frag_size = duration_to_bytes(sp->min_process);
- min_target_length = duration_to_bytes(sp->min_latency);
-
- /* determine frag_size */
- if (user_frag_size % frame_size) {
- warn("requested frag_size (%zu) isn't multiple of frame (%zu)",
- user_frag_size, frame_size);
- user_frag_size = round_up(user_frag_size, frame_size);
- }
-
- if (user_subdivision)
- user_frag_size = round_up(sample_bps / user_subdivision,
- frame_size);
-
- if (user_frag_size) {
- frag_size = user_frag_size;
- if (frag_size < min_frag_size) {
- dbg0("requested frag_size (%zu) is smaller than "
- "minimum (%zu)", frag_size, min_frag_size);
- frag_size = min_frag_size;
- }
- } else {
- tmp = round_up(sp->dfl_process * sample_bps / 1000, frame_size);
- frag_size = tmp;
- /* if frame_size is power of two, make frag_size so too */
- if (is_power2(frame_size)) {
- frag_size = frame_size;
- while (frag_size < tmp)
- frag_size <<= 1;
- }
- user_frag_size = frag_size;
- }
-
- /* determine target and max length */
- if (user_max_frags) {
- target_length = user_max_frags * user_frag_size;
- if (target_length < min_target_length) {
- dbg0("requested target_length (%zu) is smaller than "
- "minimum (%zu)", target_length, min_target_length);
- target_length = min_target_length;
- }
- } else {
- tmp = round_up(sp->dfl_latency * sample_bps / 1000, frag_size);
- target_length = tmp;
- /* if frag_size is power of two, make target_length so
- * too and align it to page_size.
- */
- if (is_power2(frag_size)) {
- target_length = frag_size;
- while (target_length < max(tmp, page_size))
- target_length <<= 1;
- }
- user_max_frags = target_length / frag_size;
- }
-
- user_max_length = user_frag_size * user_max_frags;
- max_length = target_length + 2 * frag_size;
-
- /* If mmapped, create backend stream with fixed parameters to
- * create illusion of hardware buffer with acceptable latency.
- */
- if (mmapped()) {
- /* set parameters for backend streams */
- frag_size = duration_to_bytes(sp->mmap_process);
- target_length = duration_to_bytes(sp->mmap_latency);
- max_length = target_length + frag_size;
- prebuf_size = 0;
-
- mmap_size = round_down(mmap_raw_size, frame_size);
- if (mmap_size != mmap_raw_size)
- warn("mmap_raw_size (%zu) unaligned to frame_size "
- "(%zu), mmap_size adjusted to %zu",
- mmap_raw_size, frame_size, mmap_size);
- } else {
- prebuf_size = min(user_frag_size * 2, user_max_length / 2);
- prebuf_size = round_down(prebuf_size, frame_size);
- }
-
- for (dir = 0; dir < 2; dir++) {
- pa_buffer_attr new_ba = { };
- char buf[128];
- pa_stream *s;
- pa_stream_flags_t flags;
- int vol[2];
- size_t size;
-
- if (!stream_enabled[dir] || stream[dir])
- continue;
-
- dbg0("CREATE %s %s fsz=%zu:%zu", dir_str[dir],
- pa_sample_spec_snprint(buf, sizeof(buf), &sample_spec),
- frag_size, frag_size * 1000 / sample_bps);
- dbg0(" tlen=%zu:%zu max=%zu:%zu pre=%zu:%zu",
- target_length, target_length * 1000 / sample_bps,
- max_length, max_length * 1000 / sample_bps,
- prebuf_size, prebuf_size * 1000 / sample_bps);
- dbg0(" u_sd=%zu u_fsz=%zu:%zu u_maxf=%zu",
- user_subdivision, user_frag_size,
- user_frag_size * 1000 / sample_bps, user_max_frags);
-
- channel_map = pa_channel_map_init_auto(&channel_map_stor,
- sample_spec.channels,
- PA_CHANNEL_MAP_OSS);
-
- s = pa_stream_new(context, stream_name, &sample_spec,
- channel_map);
- if (!s) {
- err_pa("can't create streams");
- goto fail;
- }
- stream[dir] = s;
-
- pa_stream_set_state_callback(s, stream_state_callback, NULL);
- if (dir == PLAY) {
- pa_stream_set_write_callback(s,
- stream_rw_callback, NULL);
- pa_stream_set_underflow_callback(s,
- stream_underflow_callback, NULL);
- } else {
- pa_stream_set_read_callback(s,
- stream_rw_callback, NULL);
- pa_stream_set_overflow_callback(s,
- stream_overflow_callback, NULL);
- }
-
- flags = PA_STREAM_AUTO_TIMING_UPDATE |
- PA_STREAM_INTERPOLATE_TIMING;
- if (stream_corked[dir])
- flags |= PA_STREAM_START_CORKED;
-
- new_ba.maxlength = max_length;
- new_ba.tlength = target_length;
- new_ba.prebuf = prebuf_size;
- new_ba.minreq = frag_size;
- new_ba.fragsize = frag_size;
-
- if (dir == PLAY) {
- if (pa_stream_connect_playback(s, NULL, &new_ba, flags,
- NULL, NULL)) {
- err_pa("failed to connect playback stream");
- goto fail;
- }
- } else {
- if (pa_stream_connect_record(s, NULL, &new_ba, flags)) {
- err_pa("failed to connect record stream");
- goto fail;
- }
- }
-
- while (pa_stream_get_state(s) == PA_STREAM_CREATING)
- pa_threaded_mainloop_wait(mainloop);
- if (pa_stream_get_state(s) != PA_STREAM_READY) {
- err_pa("failed to connect stream (state=%d)",
- pa_stream_get_state(s));
- goto fail;
- }
-
- /* apply stored OSS volume */
- memcpy(vol, stored_oss_vol[dir], sizeof(vol));
- if (do_mixer(dir, vol))
- warn_pa("initial volume control failed");
-
- /* stream is ready setup mmap stuff */
- if (!mmap_map[dir])
- continue;
-
- /* prep mmap staging buffer */
- size = round_up(sp->mmap_staging * sample_bps / 1000,
- frag_size);
- rc = ring_resize(&mmap_stg[dir], size);
- if (rc)
- return rc;
-
- mmap_idx[dir] = mmap_last_idx[dir] = get_mmap_idx(dir);
- mmap_lead[dir] = round_up(sp->mmap_lead * sample_bps / 1000,
- frame_size);
- mmap_sync[dir] = 1;
-
- /* apply the current trigger settings */
- trigger_streams(-1, -1);
- }
-
- return 0;
- fail:
- return padsp_done();
-}
-
-struct volume_ret {
- int success;
- pa_cvolume *cv;
-};
-
-static void play_volume_callback(pa_context *c, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
- struct volume_ret *vr = userdata;
-
- if (i) {
- *vr->cv = i->volume;
- vr->success = 1;
- }
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-static void rec_volume_callback(pa_context *c, const pa_source_info *i,
- int eol, void *userdata)
-{
- struct volume_ret *vr = userdata;
-
- if (i) {
- *vr->cv = i->volume;
- vr->success = 1;
- }
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-static int get_volume(int dir, pa_cvolume *cv)
-{
- struct volume_ret vr = { .cv = cv };
- uint32_t idx;
-
- if (dir == PLAY) {
- idx = pa_stream_get_index(stream[PLAY]);
- EXEC_OP(pa_context_get_sink_input_info,
- context, idx, play_volume_callback, &vr);
- } else {
- idx = pa_stream_get_device_index(stream[REC]);
- EXEC_OP(pa_context_get_source_info_by_index,
- context, idx, rec_volume_callback, &vr);
- }
- if (!vr.success) {
- warn_pa("failed to get %s volume", dir_str[dir]);
- return -EIO;
- }
- return 0;
-}
-
-static int set_volume(int dir, pa_cvolume *cv)
-{
- uint32_t idx;
- int rc;
-
- if (dir == PLAY) {
- idx = pa_stream_get_index(stream[PLAY]);
- rc = EXEC_CONTEXT_OP(pa_context_set_sink_input_volume,
- context, idx, cv);
- } else {
- idx = pa_stream_get_device_index(stream[REC]);
- rc = EXEC_CONTEXT_OP(pa_context_set_source_volume_by_index,
- context, idx, cv);
- }
- return rc;
-}
-
-static int chan_left_right(int ch)
-{
- if (!channel_map || channel_map->channels <= ch) {
- switch (ch) {
- case 0:
- return LEFT;
- case 1:
- return RIGHT;
- default:
- return -1;
- }
- }
-
- switch (channel_map->map[ch]) {
- /*case PA_CHANNEL_POSITION_LEFT:*/ /* same as FRONT_LEFT */
- case PA_CHANNEL_POSITION_FRONT_LEFT:
- case PA_CHANNEL_POSITION_REAR_LEFT:
- case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
- case PA_CHANNEL_POSITION_SIDE_LEFT:
- case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
- case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
- return LEFT;
- /*case PA_CHANNEL_POSITION_RIGHT:*/ /* same as FRONT_RIGHT */
- case PA_CHANNEL_POSITION_FRONT_RIGHT:
- case PA_CHANNEL_POSITION_REAR_RIGHT:
- case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
- case PA_CHANNEL_POSITION_SIDE_RIGHT:
- case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
- case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
- return RIGHT;
- default:
- return -1;
- }
-}
-
-static int do_mixer(int dir, int *vol)
-{
- pa_cvolume cv;
- unsigned lv, rv;
- int i, rc;
-
- if (vol[0] >= 0) {
- int avg;
-
- stored_oss_vol[dir][LEFT] = vol[LEFT];
- stored_oss_vol[dir][RIGHT] = vol[RIGHT];
- vol[LEFT] = vol[LEFT] * PA_VOLUME_NORM / 100;
- vol[RIGHT] = vol[RIGHT] * PA_VOLUME_NORM / 100;
- avg = (vol[LEFT] + vol[RIGHT]) / 2;
-
- pa_cvolume_mute(&cv, sample_spec.channels);
-
- for (i = 0; i < cv.channels; i++)
- switch (chan_left_right(i)) {
- case LEFT: cv.values[i] = vol[LEFT]; break;
- case RIGHT: cv.values[i] = vol[RIGHT]; break;
- default: cv.values[i] = avg; break;
- }
-
- rc = set_volume(dir, &cv);
- if (rc)
- return rc;
- }
-
- rc = get_volume(dir, &cv);
- if (rc)
- return rc;
-
- if (cv.channels == 1)
- lv = rv = pa_cvolume_avg(&cv);
- else {
- unsigned lcnt = 0, rcnt = 0;
-
- for (i = 0, lv = 0, rv = 0; i < cv.channels; i++)
- switch (chan_left_right(i)) {
- case LEFT: lv += cv.values[i]; lcnt++; break;
- case RIGHT: rv += cv.values[i]; rcnt++; break;
- }
-
- if (lcnt)
- lv /= lcnt;
- if (rcnt)
- rv /= rcnt;
- }
-
- vol[LEFT] = lv * 100 / PA_VOLUME_NORM;
- vol[RIGHT] = rv * 100 / PA_VOLUME_NORM;
-
- return 0;
-}
-
-static ssize_t padsp_mixer(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- struct ossp_mixer_arg *arg = carg;
- int i, rc[2] = { };
-
- if (prepare_streams())
- return -EIO;
-
- for (i = 0; i < 2; i++)
- if (stream[i])
- rc[i] = do_mixer(i, arg->vol[i]);
- else
- memset(arg->vol[i], -1, sizeof(arg->vol[i]));
-
- *(struct ossp_mixer_arg *)rarg = *arg;
- return rc[0] ?: rc[1];
-}
-
-static void context_state_callback(pa_context *cxt, void *userdata)
-{
- pa_threaded_mainloop_signal(mainloop, 0);
-}
-
-static void context_subscribe_callback(pa_context *context,
- pa_subscription_event_type_t type,
- uint32_t idx, void *userdata)
-{
- struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC,
- .opcode = OSSP_NOTIFY_VOLCHG };
- ssize_t ret;
-
- if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) !=
- PA_SUBSCRIPTION_EVENT_CHANGE)
- return;
-
- ret = write(ossp_notify_fd, &event, sizeof(event));
- if (ret != sizeof(event) && errno != EPIPE)
- warn_e(-errno, "write to notify_fd failed");
-}
-
-static ssize_t padsp_open(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- struct ossp_dsp_open_arg *arg = carg;
- char host_name[128] = "(unknown)", opener[128] = "(unknown)";
- int state;
-
- switch (arg->flags & O_ACCMODE) {
- case O_WRONLY:
- stream_enabled[PLAY] = 1;
- break;
- case O_RDONLY:
- stream_enabled[REC] = 1;
- break;
- case O_RDWR:
- stream_enabled[PLAY] = 1;
- stream_enabled[REC] = 1;
- break;
- default:
- assert(0);
- }
-
- /* determine stream name */
- gethostname(host_name, sizeof(host_name) - 1);
- snprintf(stream_name, sizeof(stream_name), "OSS Proxy %s/%s:%ld",
- host_name, ossp_user_name, (long)arg->opener_pid);
-
- /* create and connect PA context */
- get_proc_self_info(arg->opener_pid, NULL, opener, sizeof(opener));
- context = pa_context_new(mainloop_api, opener);
- if (!context) {
- err("pa_context_new() failed");
- return -EIO;
- }
-
- pa_context_set_state_callback(context, context_state_callback, NULL);
- pa_context_set_subscribe_callback(context, context_subscribe_callback,
- NULL);
-
- pa_context_connect(context, NULL, 0, NULL);
- while (1) {
- state = pa_context_get_state(context);
- if (state != PA_CONTEXT_CONNECTING &&
- state != PA_CONTEXT_AUTHORIZING &&
- state != PA_CONTEXT_SETTING_NAME)
- break;
-
- pa_threaded_mainloop_wait(mainloop);
- }
-
- if (EXEC_CONTEXT_OP(pa_context_subscribe, context,
- PA_SUBSCRIPTION_MASK_SINK_INPUT |
- PA_SUBSCRIPTION_MASK_SOURCE))
- warn_pa("failed to subscribe to context events");
-
- if (state != PA_CONTEXT_READY) {
- err_pa("failed to connect context, state=%d", state);
- return -EIO;
- }
-
- return 0;
-}
-
-static void mmap_fill_pstg(void)
-{
- struct ring_buf *stg = &mmap_stg[PLAY];
- struct ring_buf mmap;
- uint64_t new_idx = get_mmap_idx(PLAY);
- size_t bytes, space, size;
- void *data;
-
- if (new_idx <= mmap_idx[PLAY])
- return;
-
- bytes = new_idx - mmap_idx[PLAY];
- space = ring_space(stg);
-
- if (bytes > mmap_size) {
- dbg0("mmap playback transfer chunk bigger than "
- "mmap size (bytes=%zu mmap_size=%zu)", bytes, mmap_size);
- mmap_sync[PLAY] = 1;
- bytes = mmap_size;
- }
-
- if (bytes > space) {
- dbg0("mmap playback staging buffer overflow "
- "(bytes=%zu space=%zu)", bytes, space);
- mmap_sync[PLAY] = 1;
- bytes = space;
- }
-
- ring_manual_init(&mmap, mmap_map[PLAY], mmap_size,
- new_idx % mmap_size, bytes);
-
- while ((data = ring_data(&mmap, &size))) {
- ring_fill(stg, data, size);
- ring_consume(&mmap, size);
- }
-
- mmap_idx[PLAY] = new_idx;
-}
-
-static void mmap_consume_rstg(void)
-{
- struct ring_buf *stg = &mmap_stg[REC];
- struct ring_buf mmap;
- uint64_t new_idx = get_mmap_idx(REC);
- uint64_t fill_idx = mmap_idx[REC];
- size_t bytes, space;
-
- if (new_idx <= mmap_idx[REC])
- return;
-
- space = new_idx - mmap_idx[REC]; /* mmapped space to fill in */
- bytes = ring_bytes(stg); /* recorded bytes in staging */
-
- if (space > bytes) {
- if (!mmap_sync[REC])
- dbg0("mmap recording staging buffer underflow "
- "(space=%zu bytes=%zu)", space, bytes);
- mmap_sync[REC] = 1;
- }
-
- if (space > mmap_size) {
- if (!mmap_sync[REC])
- dbg0("mmap recording transfer chunk bigger than "
- "mmap size (space=%zu mmap_size=%zu)",
- bytes, mmap_size);
- mmap_sync[REC] = 1;
- space = mmap_size;
- }
-
- /* If resync is requested, leave lead bytes in the staging
- * buffer and copy everything else such that data is filled
- * upto the new_idx. If there are more bytes in staging than
- * available space, those will be dropped.
- */
- if (mmap_sync[REC]) {
- ssize_t avail = bytes - mmap_lead[REC];
-
- /* make sure we always have lead bytes in staging */
- if (avail < 0)
- goto skip;
-
- if (avail > space) {
- dbg0("dropping %zu bytes from record staging buffer",
- avail - space);
- ring_consume(&mmap_stg[REC], avail - space);
- avail = space;
- } else {
- dbg0("skippping %zu bytes in record mmap map",
- space - avail);
- space = avail;
- }
-
- assert(new_idx >= avail);
- fill_idx = new_idx - avail;
- mmap_sync[REC] = 0;
- }
-
- ring_manual_init(&mmap, mmap_map[REC], mmap_size,
- fill_idx % mmap_size, 0);
-
- while (space) {
- void *data;
- size_t size, todo;
-
- data = ring_data(stg, &size);
- assert(data);
-
- todo = min(size, space);
- ring_fill(&mmap, data, todo);
-
- ring_consume(stg, todo);
- space -= todo;
- }
-
- skip:
- mmap_idx[REC] = new_idx;
-}
-
-static void do_mmap_write(size_t space)
-{
- struct ring_buf *stg = &mmap_stg[PLAY];
- size_t todo;
- void *data;
-
- space = round_down(space, frame_size);
- mmap_fill_pstg();
-
- while (space && (data = ring_data(stg, &todo))) {
- pa_seek_mode_t mode = PA_SEEK_RELATIVE_END;
- int64_t offset = 0;
-
- todo = min(todo, space);
-
- if (mmap_sync[PLAY]) {
- mode = PA_SEEK_RELATIVE_ON_READ;
- offset = (int64_t)mmap_lead[PLAY] - ring_bytes(stg);
- dbg0("mmap resync, offset=%ld", (long)offset);
- }
-
- if (pa_stream_write(stream[PLAY], data, todo, NULL,
- offset, mode) < 0) {
- err_pa("pa_stream_write() failed");
- padsp_done();
- return;
- }
-
- mmap_sync[PLAY] = 0;
- ring_consume(stg, todo);
- space -= todo;
- }
-}
-
-static void do_mmap_read(size_t bytes)
-{
- struct ring_buf *stg = &mmap_stg[REC];
-
- bytes = round_down(bytes, frame_size);
- mmap_consume_rstg();
-
- while (bytes) {
- const void *peek_data;
- size_t size;
-
- if (pa_stream_peek(stream[REC], &peek_data, &size)) {
- err_pa("pa_stream_peek() failed");
- padsp_done();
- return;
- }
-
- if (!peek_data)
- break;
-
- if (size <= ring_space(stg))
- ring_fill(stg, peek_data, size);
- else {
- if (!mmap_sync[REC])
- dbg0("recording staging buffer overflow, "
- "requesting resync");
- mmap_sync[REC] = 1;
- }
-
- pa_stream_drop(stream[REC]);
- bytes -= size;
- }
-}
-
-static void stream_rw_callback(pa_stream *s, size_t length, void *userdata)
-{
- int dir;
- size_t size;
-
- if (s == stream[PLAY]) {
- dir = PLAY;
- size = pa_stream_writable_size(s);
- if (mmap_map[PLAY])
- do_mmap_write(size);
- } else if (s == stream[REC]) {
- dir = REC;
- size = pa_stream_readable_size(s);
- if (mmap_map[REC])
- do_mmap_read(size);
- } else {
- dbg0("stream_rw_callback(): unknown stream %p PLAY/REC=%p/%p\n",
- s, stream[PLAY], stream[REC]);
- return;
- }
-
- if (size < user_frag_size)
- return;
- if (stream_waiting)
- pa_threaded_mainloop_signal(mainloop, 0);
- if (stream_notify) {
- struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC,
- .opcode = OSSP_NOTIFY_POLL };
- ssize_t ret;
-
- ret = write(ossp_notify_fd, &event, sizeof(event));
- if (ret != sizeof(event)) {
- if (errno != EPIPE)
- err_e(-errno, "write to notify_fd failed");
-
- /* This function is run from PA mainloop and
- * thus the following padsp_done() won't be
- * noticed before the mainthread tries to run
- * the next command. Well, that's good enough.
- */
- padsp_done();
- }
- stream_notify = 0;
- }
-}
-
-static ssize_t padsp_write(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- struct ossp_dsp_rw_arg *arg = carg;
- size_t size;
-
- if (prepare_streams() || !stream[PLAY])
- return -EIO;
-
- stream_waiting++;
- while (1) {
- size = pa_stream_writable_size(stream[PLAY]);
- if (arg->nonblock || size >= user_frag_size)
- break;
- pa_threaded_mainloop_wait(mainloop);
- }
- stream_waiting--;
-
- size = round_down(size, user_frag_size);
- if (!size)
- return -EAGAIN;
-
- size = min(size, din_sz);
-
- if (pa_stream_write(stream[PLAY], din, size, NULL,
- 0, PA_SEEK_RELATIVE) < 0) {
- err_pa("pa_stream_write() failed");
- return padsp_done();
- }
-
- return size;
-}
-
-static ssize_t padsp_read(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- struct ossp_dsp_rw_arg *arg = carg;
- size_t size;
- void *data;
-
- if (prepare_streams() || !stream[REC])
- return -EIO;
- again:
- if (!arg->nonblock) {
- stream_waiting++;
- while (1) {
- size = pa_stream_readable_size(stream[REC]);
- if (size + ring_bytes(&rec_buf) >= user_frag_size)
- break;
- pa_threaded_mainloop_wait(mainloop);
- }
- stream_waiting--;
- }
-
- while (ring_bytes(&rec_buf) < max(user_frag_size, *dout_szp)) {
- const void *peek_data;
-
- if (pa_stream_peek(stream[REC], &peek_data, &size) < 0) {
- err_pa("pa_stream_peek() failed");
- return padsp_done();
- }
-
- if (!peek_data)
- break;
-
- if (ring_space(&rec_buf) < size) {
- size_t bufsz;
-
- bufsz = ring_size(&rec_buf);
- bufsz = max(2 * bufsz, bufsz + 2 * size);
-
- if (ring_resize(&rec_buf, bufsz)) {
- err("failed to allocate recording buffer");
- return padsp_done();
- }
- }
-
- ring_fill(&rec_buf, peek_data, size);
- pa_stream_drop(stream[REC]);
- }
-
- size = round_down(ring_bytes(&rec_buf), user_frag_size);
- if (!size) {
- if (arg->nonblock)
- return -EAGAIN;
- else
- goto again;
- }
-
- *dout_szp = size = min(size, *dout_szp);
-
- while (size) {
- size_t cnt;
-
- data = ring_data(&rec_buf, &cnt);
- assert(data);
-
- cnt = min(size, cnt);
- memcpy(dout, data, cnt);
- ring_consume(&rec_buf, cnt);
- dout += cnt;
- size -= cnt;
- }
-
- return *dout_szp;
-}
-
-static ssize_t padsp_poll(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- unsigned revents = 0;
-
- if (prepare_streams() < 0)
- return -EIO;
-
- stream_notify |= *(int *)carg;
-
- if (stream[PLAY] &&
- pa_stream_writable_size(stream[PLAY]) >= user_frag_size)
- revents |= POLLOUT;
- if (stream[REC] &&
- pa_stream_readable_size(stream[REC]) >= user_frag_size)
- revents |= POLLIN;
-
- *(unsigned *)rarg = revents;
- return 0;
-}
-
-static ssize_t padsp_mmap(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- struct ossp_dsp_mmap_arg *arg = carg;
- int dir = arg->dir;
-
- assert(!mmap_map[dir]);
-
- kill_streams();
-
- /* arg->size is rounded up to the nearest page boundary.
- * There is no way to tell what the actual requested value is
- * but assume that it was the reported buffer space if it
- * falls into the same page aligned range.
- */
- mmap_raw_size = arg->size;
- if (user_max_length && user_max_length < mmap_raw_size &&
- round_up(mmap_raw_size, page_size) ==
- round_up(user_max_length, page_size)) {
- info("MMAP adjusting raw_size %zu -> %zu",
- mmap_raw_size, user_max_length);
- mmap_raw_size = user_max_length;
- }
-
- dbg0("MMAP server-addr=%p sz=%zu", ossp_mmap_addr[dir], mmap_raw_size);
-
- mmap_map[dir] = ossp_mmap_addr[dir];
-
- /* if mmapped, only mmapped streams are enabled */
- stream_enabled[PLAY] = !!mmap_map[PLAY];
- stream_enabled[REC] = !!mmap_map[REC];
-
- return 0;
-}
-
-static ssize_t padsp_munmap(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- int dir = *(int *)carg;
-
- assert(mmap_map[dir]);
- kill_streams();
- mmap_map[dir] = NULL;
- return 0;
-}
-
-static ssize_t padsp_flush(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- flush_streams(opcode == OSSP_DSP_SYNC);
- return 0;
-}
-
-static ssize_t padsp_post(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- return trigger_streams(1, -1);
-}
-
-static ssize_t padsp_get_param(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp,
- int tfd)
-{
- int v = 0;
-
- switch (opcode) {
- case OSSP_DSP_GET_RATE:
- v = sample_spec.rate;
- break;
-
- case OSSP_DSP_GET_CHANNELS:
- v = sample_spec.channels;
- break;
-
- case OSSP_DSP_GET_FORMAT:
- v = fmt_pa_to_oss(sample_spec.format);
- break;
-
- case OSSP_DSP_GET_BLKSIZE:
- if (prepare_streams() < 0)
- return -EIO;
- v = user_frag_size;
- break;
-
- case OSSP_DSP_GET_FORMATS:
- v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE |
- AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE;
- break;
-
- case OSSP_DSP_GET_TRIGGER:
- if (!stream_corked[PLAY])
- v |= PCM_ENABLE_OUTPUT;
- if (!stream_corked[REC])
- v |= PCM_ENABLE_INPUT;
- break;
-
- default:
- assert(0);
- }
-
- *(int *)rarg = v;
-
- return 0;
-}
-
-static ssize_t padsp_set_param(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp,
- int tfd)
-{
- pa_sample_spec new_spec = sample_spec;
- int v = *(int *)carg;
-
- /* kill the streams before changing parameters */
- kill_streams();
-
- switch (opcode) {
- case OSSP_DSP_SET_RATE:
- new_spec.rate = v;
- if (pa_sample_spec_valid(&new_spec))
- sample_spec = new_spec;
- v = sample_spec.rate;
- break;
-
- case OSSP_DSP_SET_CHANNELS:
- new_spec.channels = v;
- if (pa_sample_spec_valid(&new_spec))
- sample_spec = new_spec;
- v = sample_spec.channels;
- break;
-
- case OSSP_DSP_SET_FORMAT:
- new_spec.format = fmt_oss_to_pa(v);
- if (pa_sample_spec_valid(&new_spec))
- sample_spec = new_spec;
- v = fmt_pa_to_oss(sample_spec.format);
- break;
-
- case OSSP_DSP_SET_SUBDIVISION:
- if (!v) {
- v = user_subdivision ?: 1;
- break;
- }
- user_frag_size= 0;
- user_subdivision = v;
- break;
-
- case OSSP_DSP_SET_FRAGMENT:
- user_subdivision = 0;
- user_frag_size = 1 << (v & 0xffff);
- user_max_frags = (v >> 16) & 0xffff;
- if (user_frag_size < 4)
- user_frag_size = 4;
- if (user_max_frags < 2)
- user_max_frags = 2;
- break;
- default:
- assert(0);
- }
-
- if (rarg)
- *(int *)rarg = v;
- return 0;
-}
-
-static ssize_t padsp_set_trigger(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp,
- int fd)
-{
- int enable = *(int *)carg;
-
- return trigger_streams(enable & PCM_ENABLE_OUTPUT,
- enable & PCM_ENABLE_INPUT);
-}
-
-static ssize_t padsp_get_space(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC;
- struct audio_buf_info info = { };
- int rc;
-
- rc = prepare_streams();
- if (rc)
- return -EIO;
-
- if (mmapped()) {
- info.fragments = mmap_raw_size / user_frag_size;
- info.fragstotal = info.fragments;
- info.fragsize = user_frag_size;
- info.bytes = mmap_raw_size;
- } else {
- size_t space;
-
- if (dir == PLAY)
- space = pa_stream_writable_size(stream[PLAY]);
- else
- space = pa_stream_readable_size(stream[REC]);
-
- space = round_down(space, user_frag_size);
- space = min(space, user_frag_size * user_max_frags);
-
- info.fragments = space / user_frag_size;
- info.fragstotal = user_max_frags;
- info.fragsize = user_frag_size;
- info.bytes = space;
- }
-
- *(struct audio_buf_info *)rarg = info;
- return 0;
-}
-
-static ssize_t padsp_get_ptr(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp, int tfd)
-{
- int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC;
- struct count_info info = { };
-
- if (prepare_streams() < 0 || !stream[dir])
- return -EIO;
-
- if (mmap_map[dir]) {
- /* mmap operation in progress, report mmap buffer parameters */
- if (dir == PLAY)
- mmap_fill_pstg();
- else
- mmap_consume_rstg();
-
- info.bytes = mmap_idx[dir];
- info.blocks = (mmap_idx[dir] - mmap_last_idx[dir]) / frame_size;
- info.ptr = mmap_idx[dir] % mmap_size;
-
- mmap_last_idx[dir] = mmap_idx[dir];
- } else {
- /* simulate pointers using timestamps */
- double bpus = (double)sample_bps / 1000000;
- size_t bytes, delta_bytes;
- pa_usec_t usec, delta;
-
- if (pa_stream_get_time(stream[dir], &usec) < 0) {
- warn_pa("pa_stream_get_time() failed");
- return -EIO;
- }
-
- delta = usec - stream_ptr_timestamp[dir];
- stream_ptr_timestamp[dir] = usec;
- bytes = bpus * usec;
- delta_bytes = bpus * delta;
-
- info.bytes = bytes & INT_MAX;
- info.blocks = (delta_bytes + frame_size - 1) / frame_size;
- info.ptr = bytes % user_max_length;
- }
-
- *(struct count_info *)rarg = info;
- return 0;
-}
-
-static ssize_t padsp_get_odelay(enum ossp_opcode opcode,
- void *carg, void *din, size_t din_sz,
- void *rarg, void *dout, size_t *dout_szp,
- int fd)
-{
- double bpus = (double)sample_bps / 1000000;
- pa_usec_t usec;
-
- if (prepare_streams() < 0 || !stream[PLAY])
- return -EIO;
-
- if (pa_stream_get_latency(stream[PLAY], &usec, NULL) < 0) {
- warn_pa("pa_stream_get_latency() failed");
- return -EIO;
- }
-
- *(int *)rarg = bpus * usec;
- return 0;
-}
-
-static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = {
- [OSSP_MIXER] = padsp_mixer,
- [OSSP_DSP_OPEN] = padsp_open,
- [OSSP_DSP_READ] = padsp_read,
- [OSSP_DSP_WRITE] = padsp_write,
- [OSSP_DSP_POLL] = padsp_poll,
- [OSSP_DSP_MMAP] = padsp_mmap,
- [OSSP_DSP_MUNMAP] = padsp_munmap,
- [OSSP_DSP_RESET] = padsp_flush,
- [OSSP_DSP_SYNC] = padsp_flush,
- [OSSP_DSP_POST] = padsp_post,
- [OSSP_DSP_GET_RATE] = padsp_get_param,
- [OSSP_DSP_GET_CHANNELS] = padsp_get_param,
- [OSSP_DSP_GET_FORMAT] = padsp_get_param,
- [OSSP_DSP_GET_BLKSIZE] = padsp_get_param,
- [OSSP_DSP_GET_FORMATS] = padsp_get_param,
- [OSSP_DSP_SET_RATE] = padsp_set_param,
- [OSSP_DSP_SET_CHANNELS] = padsp_set_param,
- [OSSP_DSP_SET_FORMAT] = padsp_set_param,
- [OSSP_DSP_SET_SUBDIVISION] = padsp_set_param,
- [OSSP_DSP_SET_FRAGMENT] = padsp_set_param,
- [OSSP_DSP_GET_TRIGGER] = padsp_get_param,
- [OSSP_DSP_SET_TRIGGER] = padsp_set_trigger,
- [OSSP_DSP_GET_OSPACE] = padsp_get_space,
- [OSSP_DSP_GET_ISPACE] = padsp_get_space,
- [OSSP_DSP_GET_OPTR] = padsp_get_ptr,
- [OSSP_DSP_GET_IPTR] = padsp_get_ptr,
- [OSSP_DSP_GET_ODELAY] = padsp_get_odelay,
-};
-
-static int action_pre(void)
-{
- pa_threaded_mainloop_lock(mainloop);
- if (fail_code) {
- pa_threaded_mainloop_unlock(mainloop);
- return fail_code;
- }
- return 0;
-}
-
-static void action_post(void)
-{
- pa_threaded_mainloop_unlock(mainloop);
-}
-
-int main(int argc, char **argv)
-{
- int rc;
-
- ossp_slave_init(argc, argv);
-
- page_size = sysconf(_SC_PAGE_SIZE);
-
- mainloop = pa_threaded_mainloop_new();
- if (!mainloop) {
- err("failed to allocate mainloop");
- return 1;
- }
- mainloop_api = pa_threaded_mainloop_get_api(mainloop);
-
- if (pa_threaded_mainloop_start(mainloop)) {
- err("pa_mainloop_start() failed");
- return 1;
- }
-
- /* Okay, now we're open for business */
- rc = 0;
- do {
- rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl,
- action_pre, action_post);
- } while (rc > 0 && !fail_code);
- if (rc)
- fail_code = rc;
-
- pa_threaded_mainloop_lock(mainloop);
-
- kill_streams();
- if (context) {
- pa_context_disconnect(context);
- pa_context_unref(context);
- }
-
- pa_threaded_mainloop_unlock(mainloop);
-
- pa_threaded_mainloop_stop(mainloop);
- pa_threaded_mainloop_free(mainloop);
-
- return fail_code ? 1 : 0;
-}