#include <sys/queue.h>
#include <sys/types.h>
#include <sys/soundcard.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <time.h>
#include <assert.h>
#include "backend.h"
#include "int.h"
uint64_t
virtual_oss_timestamp(void)
{
struct timespec ts;
uint64_t nsec;
clock_gettime(CLOCK_MONOTONIC, &ts);
nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
return (nsec);
}
uint64_t
virtual_oss_delay_ns(void)
{
uint64_t delay;
delay = voss_dsp_samples;
delay *= 1000000000ULL;
delay /= voss_dsp_sample_rate;
return (delay);
}
void
virtual_oss_wait(void)
{
uint64_t delay;
uint64_t nsec;
nsec = virtual_oss_timestamp();
delay = virtual_oss_delay_ns();
usleep((delay - (nsec % delay)) / 1000);
}
static size_t
vclient_read_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
int64_t *dst, size_t total) __requires_exclusive(atomic_mtx)
{
size_t total_read = 0;
pvc->sync_busy = 1;
while (1) {
size_t read = vring_read_linear(pvr, (uint8_t *)dst, 8 * total) / 8;
total_read += read;
dst += read;
total -= read;
if (!pvc->profile->synchronized || pvc->sync_wakeup ||
total == 0) {
if (total_read != 0 && total != 0)
memset(dst, 0, 8 * total);
break;
}
atomic_wait();
}
pvc->sync_busy = 0;
if (pvc->sync_wakeup)
atomic_wakeup();
vclient_tx_equalizer(pvc, dst - total_read, total_read);
return (total_read);
}
static size_t
vclient_write_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
int64_t *src, size_t total) __requires_exclusive(atomic_mtx)
{
size_t total_written = 0;
vclient_rx_equalizer(pvc, src, total);
pvc->sync_busy = 1;
while (1) {
size_t written = vring_write_linear(pvr, (uint8_t *)src, total * 8) / 8;
total_written += written;
src += written;
total -= written;
if (!pvc->profile->synchronized || pvc->sync_wakeup ||
total == 0)
break;
atomic_wait();
}
pvc->sync_busy = 0;
if (pvc->sync_wakeup)
atomic_wakeup();
return (total_written);
}
static inline void
virtual_oss_mixer_core_sub(const int64_t *src, int64_t *dst,
uint32_t *pnoise, int src_chan, int dst_chan, int num,
int64_t volume, int shift, int shift_orig, bool pol,
bool assign)
{
if (pol)
volume = -volume;
if (shift < 0) {
shift = -shift;
while (num--) {
if (assign)
*dst = (*src * volume) >> shift;
else
*dst += (*src * volume) >> shift;
if (__predict_true(pnoise != NULL))
*dst += vclient_noise(pnoise, volume, shift_orig);
src += src_chan;
dst += dst_chan;
}
} else {
while (num--) {
if (assign)
*dst = (*src * volume) << shift;
else
*dst += (*src * volume) << shift;
if (__predict_true(pnoise != NULL))
*dst += vclient_noise(pnoise, volume, shift_orig);
src += src_chan;
dst += dst_chan;
}
}
}
static inline void
virtual_oss_mixer_core(const int64_t *src, int64_t *dst,
uint32_t *pnoise, int src_chan, int dst_chan, int num,
int64_t volume, int shift, int shift_orig, bool pol,
bool assign)
{
const uint8_t selector = (shift_orig > 0) + assign * 2;
switch (selector) {
case 0:
virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, false);
break;
case 1:
virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, false);
break;
case 2:
virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, true);
break;
case 3:
virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, true);
break;
}
}
void *
virtual_oss_process(void *arg __unused)
{
vprofile_t *pvp;
vclient_t *pvc;
vmonitor_t *pvm;
struct voss_backend *rx_be = voss_rx_backend;
struct voss_backend *tx_be = voss_tx_backend;
int rx_fmt;
int tx_fmt;
int rx_chn;
int tx_chn;
int off;
int src_chans;
int dst_chans;
int src;
int len;
int samples;
int shift;
int shift_orig;
int shift_fmt;
int buffer_dsp_max_size;
int buffer_dsp_half_size;
int buffer_dsp_rx_sample_size;
int buffer_dsp_rx_size;
int buffer_dsp_tx_size_ref;
int buffer_dsp_tx_size;
uint64_t nice_timeout = 0;
uint64_t last_timestamp;
int blocks;
int volume;
int x_off;
int x;
int y;
uint8_t *buffer_dsp;
int64_t *buffer_monitor;
int64_t *buffer_temp;
int64_t *buffer_data;
int64_t *buffer_local;
int64_t *buffer_orig;
bool need_delay = false;
buffer_dsp_max_size = voss_dsp_samples *
voss_dsp_max_channels * (voss_dsp_bits / 8);
buffer_dsp_half_size = (voss_dsp_samples / 2) *
voss_dsp_max_channels * (voss_dsp_bits / 8);
buffer_dsp = malloc(buffer_dsp_max_size);
buffer_temp = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_monitor = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_local = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_data = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_orig = malloc(voss_dsp_samples * voss_max_channels * 8);
if (buffer_dsp == NULL || buffer_temp == NULL ||
buffer_monitor == NULL || buffer_local == NULL ||
buffer_data == NULL || buffer_orig == NULL)
errx(1, "Cannot allocate buffer memory");
while (1) {
rx_be->close(rx_be);
tx_be->close(tx_be);
if (voss_exit)
break;
if (need_delay)
sleep(2);
voss_dsp_rx_refresh = 0;
voss_dsp_tx_refresh = 0;
rx_be = voss_rx_backend;
tx_be = voss_tx_backend;
switch (voss_dsp_bits) {
case 8:
rx_fmt = tx_fmt =
AFMT_S8 | AFMT_U8;
break;
case 16:
rx_fmt = tx_fmt =
AFMT_S16_BE | AFMT_S16_LE |
AFMT_U16_BE | AFMT_U16_LE;
break;
case 24:
rx_fmt = tx_fmt =
AFMT_S24_BE | AFMT_S24_LE |
AFMT_U24_BE | AFMT_U24_LE;
break;
case 32:
rx_fmt = tx_fmt =
AFMT_S32_BE | AFMT_S32_LE |
AFMT_U32_BE | AFMT_U32_LE |
AFMT_F32_BE | AFMT_F32_LE;
break;
default:
rx_fmt = tx_fmt = 0;
break;
}
rx_chn = voss_dsp_max_channels;
if (rx_be->open(rx_be, voss_dsp_rx_device, voss_dsp_sample_rate,
buffer_dsp_half_size, &rx_chn, &rx_fmt) < 0) {
need_delay = true;
continue;
}
buffer_dsp_rx_sample_size = rx_chn * (voss_dsp_bits / 8);
buffer_dsp_rx_size = voss_dsp_samples * buffer_dsp_rx_sample_size;
tx_chn = voss_dsp_max_channels;
if (tx_be->open(tx_be, voss_dsp_tx_device, voss_dsp_sample_rate,
buffer_dsp_max_size, &tx_chn, &tx_fmt) < 0) {
need_delay = true;
continue;
}
buffer_dsp_tx_size_ref = voss_dsp_samples *
tx_chn * (voss_dsp_bits / 8);
for (x = 0; x != VMAX_CHAN; x++)
voss_output_compressor_gain[x] = 1.0;
memset(buffer_local, 0, 8 * voss_dsp_samples * voss_max_channels);
while (1) {
uint64_t delta_time;
if (voss_exit)
break;
if (voss_dsp_rx_refresh || voss_dsp_tx_refresh) {
need_delay = false;
break;
}
delta_time = nice_timeout - virtual_oss_timestamp();
nice_timeout = virtual_oss_delay_ns() / 2;
if (delta_time >= 1000 && delta_time <= nice_timeout) {
usleep(delta_time / 1000);
}
nice_timeout += virtual_oss_timestamp();
len = rx_be->transfer(rx_be, buffer_dsp, buffer_dsp_rx_size);
if (len < 0 || (len % buffer_dsp_rx_sample_size) != 0) {
need_delay = true;
break;
}
if (len == 0)
continue;
format_import(rx_fmt, buffer_dsp, len, buffer_data);
samples = len / buffer_dsp_rx_sample_size;
src_chans = voss_mix_channels;
format_maximum(buffer_data, voss_input_peak, rx_chn, samples, 0);
format_remix(buffer_data, rx_chn, src_chans, samples);
last_timestamp = virtual_oss_timestamp();
atomic_lock();
if (TAILQ_FIRST(&virtual_monitor_input) != NULL) {
memcpy(buffer_monitor, buffer_data, 8 * samples * src_chans);
}
TAILQ_FOREACH(pvm, &virtual_monitor_local, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_local[(y * src_chans) + src] >> shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_local[(y * src_chans) + src] << shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_local[(y * src_chans) + src] >> shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_local[(y * src_chans) + src] << shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
memcpy(buffer_orig, buffer_data, 8 * samples * src_chans);
TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
if (TAILQ_FIRST(&pvp->head) == NULL)
continue;
voss_compressor(buffer_data, pvp->rx_compressor_gain,
&pvp->rx_compressor_param, samples * src_chans,
src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
TAILQ_FOREACH(pvc, &pvp->head, entry) {
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
for (x = 0; x != dst_chans; x++) {
src = pvp->rx_src[x];
shift_orig = pvp->rx_shift[x] - shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->rx_volume;
if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
for (y = 0; y != (samples * dst_chans); y += dst_chans)
buffer_temp[y + x] = 0;
continue;
}
virtual_oss_mixer_core(buffer_data + src, buffer_temp + x,
&pvc->rx_noise_rem, src_chans, dst_chans, samples,
volume, shift, shift_orig, pvp->rx_pol[x], true);
}
format_maximum(buffer_temp, pvp->rx_peak_value,
dst_chans, samples, shift_fmt);
if (pvc->rx_enabled == 0 ||
(voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
continue;
pvc->rx_timestamp = last_timestamp;
pvc->rx_samples += samples * dst_chans;
vclient_write_linear(pvc, &pvc->rx_ring[0],
buffer_temp, samples * dst_chans);
}
if (pvp->rx_compressor_param.enabled)
memcpy(buffer_data, buffer_orig, 8 * samples * src_chans);
}
memset(buffer_temp, 0, sizeof(buffer_temp[0]) *
samples * src_chans);
if (voss_ad_enabled != 0) {
y = (samples * voss_mix_channels);
for (x = 0; x != y; x += voss_mix_channels) {
buffer_temp[x + voss_ad_output_channel] +=
voss_ad_getput_sample(buffer_data
[x + voss_ad_input_channel]);
}
}
TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
TAILQ_FOREACH(pvc, &pvp->head, entry) {
if (pvc->tx_enabled == 0)
continue;
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
pvc->tx_timestamp = last_timestamp;
pvc->tx_samples += samples * dst_chans;
if (vclient_read_linear(pvc, &pvc->tx_ring[0],
buffer_data, samples * dst_chans) == 0)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
format_maximum(buffer_data, pvp->tx_peak_value,
dst_chans, samples, shift_fmt);
for (x = 0; x != pvp->channels; x++) {
src = pvp->tx_dst[x];
shift_orig = pvp->tx_shift[x] + shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->tx_volume;
if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
continue;
if (__predict_false(x >= dst_chans))
x_off = x % dst_chans;
else
x_off = x;
virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
&pvc->tx_noise_rem, dst_chans, src_chans, samples,
volume, shift, shift_orig, pvp->tx_pol[x], false);
}
}
}
TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
TAILQ_FOREACH(pvc, &pvp->head, entry) {
if (pvc->tx_enabled == 0)
continue;
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
if (vclient_read_linear(pvc, &pvc->tx_ring[0],
buffer_data, samples * dst_chans) == 0)
continue;
pvc->tx_timestamp = last_timestamp;
pvc->tx_samples += samples * dst_chans;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
format_maximum(buffer_data, pvp->tx_peak_value,
dst_chans, samples, shift_fmt);
for (x = 0; x != pvp->channels; x++) {
src = pvp->tx_dst[x];
shift_orig = pvp->tx_shift[x] + shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->tx_volume;
if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
continue;
if (__predict_false(x >= dst_chans))
x_off = x % dst_chans;
else
x_off = x;
virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
&pvc->tx_noise_rem, dst_chans, src_chans, samples,
volume, shift, shift_orig, pvp->tx_pol[x], false);
}
}
}
TAILQ_FOREACH(pvm, &virtual_monitor_input, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
if (TAILQ_FIRST(&virtual_monitor_output) != NULL) {
memcpy(buffer_monitor, buffer_temp,
8 * samples * src_chans);
}
TAILQ_FOREACH(pvm, &virtual_monitor_output, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
memcpy(buffer_data, buffer_temp, 8 * samples * src_chans);
if (TAILQ_FIRST(&virtual_monitor_local) != NULL) {
const int end = src_chans * (voss_dsp_samples - samples);
const int offs = src_chans * samples;
assert(end >= 0);
for (int xx = 0; xx != end; xx++)
buffer_local[xx] = buffer_local[xx + offs];
memcpy(buffer_local + end, buffer_temp, 8 * samples * src_chans);
}
TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
if (TAILQ_FIRST(&pvp->head) == NULL)
continue;
voss_compressor(buffer_temp, pvp->rx_compressor_gain,
&pvp->rx_compressor_param, samples,
samples * src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
TAILQ_FOREACH(pvc, &pvp->head, entry) {
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
for (x = 0; x != dst_chans; x++) {
src = pvp->rx_src[x];
shift_orig = pvp->rx_shift[x] - shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->rx_volume;
if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
for (y = 0; y != (samples * dst_chans); y += dst_chans)
buffer_monitor[y + x] = 0;
continue;
}
virtual_oss_mixer_core(buffer_temp + src, buffer_monitor + x,
&pvc->rx_noise_rem, src_chans, dst_chans, samples,
volume, shift, shift_orig, pvp->rx_pol[x], true);
}
format_maximum(buffer_monitor, pvp->rx_peak_value,
dst_chans, samples, shift_fmt);
if (pvc->rx_enabled == 0 ||
(voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
continue;
pvc->rx_timestamp = last_timestamp;
pvc->rx_samples += samples * dst_chans;
vclient_write_linear(pvc, &pvc->rx_ring[0],
buffer_monitor, samples * dst_chans);
}
if (pvp->rx_compressor_param.enabled)
memcpy(buffer_temp, buffer_data, 8 * samples * src_chans);
}
atomic_wakeup();
format_remix(buffer_temp, voss_mix_channels, tx_chn, samples);
format_maximum(buffer_temp, voss_output_peak,
tx_chn, samples, 0);
voss_compressor(buffer_temp, voss_output_compressor_gain,
&voss_output_compressor_param, samples * tx_chn,
tx_chn, format_max(tx_fmt));
buffer_dsp_tx_size = samples * tx_chn * (voss_dsp_bits / 8);
format_export(tx_fmt, buffer_temp, buffer_dsp,
buffer_dsp_tx_size);
atomic_unlock();
tx_be->delay(tx_be, &blocks);
if (blocks == 0) {
blocks = 2;
voss_jitter_up++;
} else if (blocks >= (3 * buffer_dsp_tx_size_ref)) {
blocks = 0;
voss_jitter_down++;
} else {
blocks = 1;
}
len = 0;
while (blocks--) {
off = 0;
while (off < (int)buffer_dsp_tx_size) {
len = tx_be->transfer(tx_be, buffer_dsp + off,
buffer_dsp_tx_size - off);
if (len <= 0)
break;
off += len;
}
if (len <= 0)
break;
}
if (len < 0) {
need_delay = true;
break;
}
}
}
free(buffer_dsp);
free(buffer_temp);
free(buffer_monitor);
free(buffer_local);
free(buffer_data);
free(buffer_orig);
return (NULL);
}