diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c index c61eaf1c338d..0e406cccca21 100644 --- a/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c +++ b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c @@ -1,542 +1,541 @@ /*- * Copyright (c) 2019 Google LLC, written by Richard Kralovic * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define L2CAP_SOCKET_CHECKED #include #include #include "avdtp_signal.h" #include "bt.h" -#include "utils.h" static int (*bt_receive_f)(struct bt_config *, void *, int, int); static int (*avdtpACPHandlePacket_f)(struct bt_config *cfg); static void (*avdtpACPFree_f)(struct bt_config *); static int bt_in_background; static void message(const char *fmt,...) { va_list list; if (bt_in_background) return; va_start(list, fmt); vfprintf(stderr, fmt, list); va_end(list); } struct bt_audio_receiver { const char *devname; const char *sdp_socket_path; uint16_t l2cap_psm; int fd_listen; void *sdp_session; uint32_t sdp_handle; }; static int register_sdp(struct bt_audio_receiver *r) { struct sdp_audio_sink_profile record = {}; r->sdp_session = sdp_open_local(r->sdp_socket_path); if (r->sdp_session == NULL || sdp_error(r->sdp_session)) { sdp_close(r->sdp_session); r->sdp_session = NULL; return (0); } record.psm = r->l2cap_psm; record.protover = 0x100; record.features = 0x01; /* player only */ if (sdp_register_service(r->sdp_session, SDP_SERVICE_CLASS_AUDIO_SINK, NG_HCI_BDADDR_ANY, (const uint8_t *)&record, sizeof(record), &r->sdp_handle)) { message("SDP failed to register: %s\n", strerror(sdp_error(r->sdp_session))); sdp_close(r->sdp_session); r->sdp_session = NULL; return (0); } return (1); } static void unregister_sdp(struct bt_audio_receiver *r) { sdp_unregister_service(r->sdp_session, r->sdp_handle); sdp_close(r->sdp_session); r->sdp_session = NULL; } static int start_listen(struct bt_audio_receiver *r) { struct sockaddr_l2cap addr = {}; r->fd_listen = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); if (r->fd_listen < 0) return (0); addr.l2cap_len = sizeof(addr); addr.l2cap_family = AF_BLUETOOTH; addr.l2cap_psm = r->l2cap_psm; if (bind(r->fd_listen, (struct sockaddr *)&addr, sizeof(addr)) < 0 || listen(r->fd_listen, 4) < 0) { close(r->fd_listen); return (0); } return (1); } static void stop_listen(struct bt_audio_receiver *r) { close(r->fd_listen); } struct bt_audio_connection { struct bt_audio_receiver *r; struct sockaddr_l2cap peer_addr; struct bt_config cfg; int oss_fd; }; static void close_connection(struct bt_audio_connection *c) { avdtpACPFree_f(&c->cfg); if (c->cfg.fd != -1) close(c->cfg.fd); if (c->cfg.hc != -1) close(c->cfg.hc); if (c->oss_fd != -1) close(c->oss_fd); free(c); } static struct bt_audio_connection * wait_for_connection(struct bt_audio_receiver *r) { struct bt_audio_connection *c = malloc(sizeof(struct bt_audio_connection)); socklen_t addrlen; memset(c, 0, sizeof(*c)); c->r = r; c->cfg.fd = -1; c->oss_fd = -1; addrlen = sizeof(c->peer_addr); c->cfg.hc = accept(r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen); message("Accepted control connection, %d\n", c->cfg.hc); if (c->cfg.hc < 0) { close_connection(c); return NULL; } c->cfg.sep = 0; /* to be set later */ c->cfg.media_Type = mediaTypeAudio; c->cfg.chmode = MODE_DUAL; c->cfg.aacMode1 = 0; /* TODO: support AAC */ c->cfg.aacMode2 = 0; c->cfg.acceptor_state = acpInitial; return (c); } static void setup_oss(struct bt_audio_connection *c) { c->oss_fd = open(c->r->devname, O_WRONLY); if (c->oss_fd < 0) goto err; int v; switch (c->cfg.chmode) { case MODE_STEREO: case MODE_JOINT: case MODE_DUAL: v = 2; break; case MODE_MONO: v = 1; break; default: message("Wrong chmode\n"); goto err; } if (ioctl(c->oss_fd, SNDCTL_DSP_CHANNELS, &v) < 0) { message("SNDCTL_DSP_CHANNELS failed\n"); goto err; } v = AFMT_S16_NE; if (ioctl(c->oss_fd, SNDCTL_DSP_SETFMT, &v) < 0) { message("SNDCTL_DSP_SETFMT failed\n"); goto err; } switch (c->cfg.freq) { case FREQ_16K: v = 16000; break; case FREQ_32K: v = 32000; break; case FREQ_44_1K: v = 44100; break; case FREQ_48K: v = 48000; break; default: message("Wrong freq\n"); goto err; } if (ioctl(c->oss_fd, SNDCTL_DSP_SPEED, &v) < 0) { message("SNDCTL_DSP_SETFMT failed\n"); goto err; } v = (2 << 16) | 15; /* 2 fragments of 32k each */ if (ioctl(c->oss_fd, SNDCTL_DSP_SETFRAGMENT, &v) < 0) { message("SNDCTL_DSP_SETFRAGMENT failed\n"); goto err; } return; err: c->oss_fd = -1; message("Cannot open oss device %s\n", c->r->devname); } static void process_connection(struct bt_audio_connection *c) { struct pollfd pfd[3] = {}; time_t oss_attempt = 0; while (c->cfg.acceptor_state != acpStreamClosed) { int np; pfd[0].fd = c->r->fd_listen; pfd[0].events = POLLIN | POLLRDNORM; pfd[0].revents = 0; pfd[1].fd = c->cfg.hc; pfd[1].events = POLLIN | POLLRDNORM; pfd[1].revents = 0; pfd[2].fd = c->cfg.fd; pfd[2].events = POLLIN | POLLRDNORM; pfd[2].revents = 0; if (c->cfg.fd != -1) np = 3; else np = 2; if (poll(pfd, np, INFTIM) < 0) return; if (pfd[1].revents != 0) { int retval; message("Handling packet: state = %d, ", c->cfg.acceptor_state); retval = avdtpACPHandlePacket_f(&c->cfg); message("retval = %d\n", retval); if (retval < 0) return; } if (pfd[0].revents != 0) { socklen_t addrlen = sizeof(c->peer_addr); int fd = accept4(c->r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen, SOCK_NONBLOCK); if (fd < 0) return; if (c->cfg.fd < 0) { if (c->cfg.acceptor_state == acpStreamOpened) { socklen_t mtusize = sizeof(uint16_t); c->cfg.fd = fd; if (getsockopt(c->cfg.fd, SOL_L2CAP, SO_L2CAP_IMTU, &c->cfg.mtu, &mtusize) == -1) { message("Could not get MTU size\n"); return; } int temp = c->cfg.mtu * 32; if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) { message("Could not set send buffer size\n"); return; } temp = 1; if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) { message("Could not set low water mark\n"); return; } message("Accepted data connection, %d\n", c->cfg.fd); } } else { close(fd); } } if (pfd[2].revents != 0) { uint8_t data[65536]; int len; if ((len = bt_receive_f(&c->cfg, data, sizeof(data), 0)) < 0) { return; } if (c->cfg.acceptor_state != acpStreamSuspended && c->oss_fd < 0 && time(NULL) != oss_attempt) { message("Trying to open dsp\n"); setup_oss(c); oss_attempt = time(NULL); } if (c->oss_fd > -1) { uint8_t *end = data + len; uint8_t *ptr = data; unsigned delay; unsigned jitter_limit; switch (c->cfg.freq) { case FREQ_16K: jitter_limit = (16000 / 20); break; case FREQ_32K: jitter_limit = (32000 / 20); break; case FREQ_44_1K: jitter_limit = (44100 / 20); break; default: jitter_limit = (48000 / 20); break; } if (c->cfg.chmode == MODE_MONO) { if (len >= 2 && ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 && delay < (jitter_limit * 2)) { uint8_t jitter[jitter_limit * 4] __aligned(4); size_t x; /* repeat last sample */ for (x = 0; x != sizeof(jitter); x++) jitter[x] = ptr[x % 2]; write(c->oss_fd, jitter, sizeof(jitter)); } } else { if (len >= 4 && ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 && delay < (jitter_limit * 4)) { uint8_t jitter[jitter_limit * 8] __aligned(4); size_t x; /* repeat last sample */ for (x = 0; x != sizeof(jitter); x++) jitter[x] = ptr[x % 4]; write(c->oss_fd, jitter, sizeof(jitter)); } } while (ptr != end) { int written = write(c->oss_fd, ptr, end - ptr); if (written < 0) { if (errno != EINTR && errno != EAGAIN) break; written = 0; } ptr += written; } if (ptr != end) { message("Not all written, closing dsp\n"); close(c->oss_fd); c->oss_fd = -1; oss_attempt = time(NULL); } } } if (c->cfg.acceptor_state == acpStreamSuspended && c->oss_fd > -1) { close(c->oss_fd); c->oss_fd = -1; } } } static struct option bt_speaker_opts[] = { {"device", required_argument, NULL, 'd'}, {"sdp_socket_path", required_argument, NULL, 'p'}, {"rtprio", required_argument, NULL, 'i'}, {"background", no_argument, NULL, 'B'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; static void usage(void) { fprintf(stderr, "Usage: virtual_bt_speaker -d /dev/dsp\n" "\t" "-d, --device [device]\n" "\t" "-p, --sdp_socket_path [path]\n" "\t" "-i, --rtprio [priority]\n" "\t" "-B, --background\n" ); exit(EX_USAGE); } int main(int argc, char **argv) { struct bt_audio_receiver r = {}; struct rtprio rtp = {}; void *hdl; int ch; r.devname = NULL; r.sdp_socket_path = NULL; r.l2cap_psm = SDP_UUID_PROTOCOL_AVDTP; while ((ch = getopt_long(argc, argv, "p:i:d:Bh", bt_speaker_opts, NULL)) != -1) { switch (ch) { case 'd': r.devname = optarg; break; case 'p': r.sdp_socket_path = optarg; break; case 'B': bt_in_background = 1; break; case 'i': rtp.type = RTP_PRIO_REALTIME; rtp.prio = atoi(optarg); if (rtprio(RTP_SET, getpid(), &rtp) != 0) { message("Cannot set realtime priority\n"); } break; default: usage(); break; } } if (r.devname == NULL) errx(EX_USAGE, "No devicename specified"); if (bt_in_background) { if (daemon(0, 0) != 0) errx(EX_SOFTWARE, "Cannot become daemon"); } if ((hdl = dlopen("/usr/lib/virtual_oss/voss_bt.so", RTLD_NOW)) == NULL) errx(1, "%s", dlerror()); if ((bt_receive_f = dlsym(hdl, "bt_receive")) == NULL) goto err_dlsym; if ((avdtpACPHandlePacket_f = dlsym(hdl, "avdtpACPHandlePacket")) == NULL) goto err_dlsym; if ((avdtpACPFree_f = dlsym(hdl, "avdtpACPFree")) == NULL) goto err_dlsym; while (1) { message("Starting to listen\n"); if (!start_listen(&r)) { message("Failed to initialize server socket\n"); goto err_listen; } message("Registering service via SDP\n"); if (!register_sdp(&r)) { message("Failed to register in SDP\n"); goto err_sdp; } while (1) { message("Waiting for connection...\n"); struct bt_audio_connection *c = wait_for_connection(&r); if (c == NULL) { message("Failed to get connection\n"); goto err_conn; } message("Got connection...\n"); process_connection(c); message("Connection finished...\n"); close_connection(c); } err_conn: message("Unregistering service\n"); unregister_sdp(&r); err_sdp: stop_listen(&r); err_listen: sleep(5); } return (0); err_dlsym: warnx("%s", dlerror()); dlclose(hdl); exit(EXIT_FAILURE); } diff --git a/usr.sbin/virtual_oss/virtual_oss/int.h b/usr.sbin/virtual_oss/virtual_oss/int.h index b3cc573ba8a9..69a943832074 100644 --- a/usr.sbin/virtual_oss/virtual_oss/int.h +++ b/usr.sbin/virtual_oss/virtual_oss/int.h @@ -1,331 +1,328 @@ /*- * Copyright (c) 2012-2022 Hans Petter Selasky * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _VIRTUAL_INT_H_ #define _VIRTUAL_INT_H_ #include #include #include #include extern pthread_mutex_t atomic_mtx; extern pthread_cond_t atomic_cv; #define atomic_lock() pthread_mutex_lock(&atomic_mtx) #define atomic_unlock() pthread_mutex_unlock(&atomic_mtx) #define atomic_wait() pthread_cond_wait(&atomic_cv, &atomic_mtx) #define atomic_wakeup() do { \ pthread_cond_broadcast(&atomic_cv); \ cuse_poll_wakeup(); \ } while (0) #define AFMT_32BIT \ (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \ AFMT_F32_LE | AFMT_F32_BE) #define AFMT_24BIT \ (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT \ (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_8BIT \ (AFMT_U8 | AFMT_S8) #define VMAX_CHAN 64 /* * XXX 32 - strlen("/dev") to not exceed OSS_DEVNODE_SIZE in soundcard.h. Also * silences GCC warnings. */ #define VMAX_STRING 27 #define VTYPE_OSS_DAT 0 #define VTYPE_WAV_HDR 1 #define VTYPE_WAV_DAT 2 #define VPREFERRED_SNE_AFMT \ (AFMT_S8 | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define VPREFERRED_UNE_AFMT \ (AFMT_U8 | AFMT_U16_NE | AFMT_U24_NE | AFMT_U32_NE) #define VPREFERRED_SLE_AFMT \ (AFMT_S8 | AFMT_S16_LE | AFMT_S24_LE | AFMT_S32_LE) #define VPREFERRED_SBE_AFMT \ (AFMT_S8 | AFMT_S16_BE | AFMT_S24_BE | AFMT_S32_BE) #define VPREFERRED_ULE_AFMT \ (AFMT_U8 | AFMT_U16_LE | AFMT_U24_LE | AFMT_U32_LE) #define VPREFERRED_UBE_AFMT \ (AFMT_U8 | AFMT_U16_BE | AFMT_U24_BE | AFMT_U32_BE) #define VSUPPORTED_AFMT \ (AFMT_S16_BE | AFMT_S16_LE | AFMT_U16_BE | AFMT_U16_LE | \ AFMT_S24_BE | AFMT_S24_LE | AFMT_U24_BE | AFMT_U24_LE | \ AFMT_S32_BE | AFMT_S32_LE | AFMT_U32_BE | AFMT_U32_LE | \ AFMT_F32_BE | AFMT_F32_LE | \ AFMT_U8 | AFMT_S8) #define VVOLUME_UNIT_SHIFT 7 struct virtual_profile; typedef TAILQ_ENTRY(virtual_profile) vprofile_entry_t; typedef TAILQ_HEAD(, virtual_profile) vprofile_head_t; typedef struct virtual_profile vprofile_t; struct virtual_client; typedef TAILQ_ENTRY(virtual_client) vclient_entry_t; typedef TAILQ_HEAD(, virtual_client) vclient_head_t; typedef struct virtual_client vclient_t; struct virtual_monitor; typedef TAILQ_ENTRY(virtual_monitor) vmonitor_entry_t; typedef TAILQ_HEAD(, virtual_monitor) vmonitor_head_t; typedef struct virtual_monitor vmonitor_t; struct virtual_resample; typedef struct virtual_resample vresample_t; struct cuse_methods; struct virtual_compressor { uint8_t enabled; /* 0..1 */ uint8_t knee; /* 0..255 */ uint8_t attack; /* 0..62 */ uint8_t decay; /* 0..62 */ }; struct virtual_profile { vprofile_entry_t entry; vclient_head_t head; char oss_name[VMAX_STRING]; char wav_name[VMAX_STRING]; uint32_t rx_filter_size; uint32_t tx_filter_size; double *rx_filter_data[VMAX_CHAN]; double *tx_filter_data[VMAX_CHAN]; int64_t rx_peak_value[VMAX_CHAN]; int64_t tx_peak_value[VMAX_CHAN]; int8_t rx_shift[VMAX_CHAN]; int8_t tx_shift[VMAX_CHAN]; uint8_t rx_src[VMAX_CHAN]; uint8_t tx_dst[VMAX_CHAN]; uint8_t rx_mute[VMAX_CHAN]; uint8_t tx_mute[VMAX_CHAN]; uint8_t rx_pol[VMAX_CHAN]; uint8_t tx_pol[VMAX_CHAN]; uint8_t bits; uint8_t channels; struct virtual_compressor rx_compressor_param; double rx_compressor_gain[VMAX_CHAN]; uint8_t synchronized; uint32_t rec_delay; int fd_sta; struct { const char * host; const char * port; const char * rtp_ifname; const char * rtp_port; volatile struct http_state * state; size_t nstate; int rtp_fd; int rtp_vlanid; uint32_t rtp_ts; uint16_t rtp_seqnum; } http; }; struct virtual_ring { uint8_t *buf_start; uint32_t pos_read; uint32_t total_size; uint32_t len_write; }; struct virtual_resample { SRC_DATA data; SRC_STATE *state; float *data_in; float *data_out; }; struct virtual_client { vclient_entry_t entry; uint32_t tx_filter_offset; uint32_t rx_filter_offset; int64_t *tx_filter_in[VMAX_CHAN]; int64_t *rx_filter_in[VMAX_CHAN]; double *tx_filter_out[VMAX_CHAN]; double *rx_filter_out[VMAX_CHAN]; struct virtual_ring rx_ring[2]; struct virtual_ring tx_ring[2]; vresample_t rx_resample; vresample_t tx_resample; struct virtual_profile *profile; uint64_t rx_samples; uint64_t rx_timestamp; uint64_t tx_samples; uint64_t tx_timestamp; uint32_t buffer_frags; uint32_t buffer_size; uint32_t low_water; uint32_t rec_delay; uint32_t rx_noise_rem; uint32_t tx_noise_rem; int rx_busy; int tx_busy; int channels; int format; int rx_enabled; int tx_enabled; int rx_volume; int tx_volume; int type; /* VTYPE_XXX */ int sample_rate; uint32_t buffer_size_set:1; uint32_t buffer_frags_set:1; uint32_t sync_busy:1; uint32_t sync_wakeup:1; int padding:28; }; struct virtual_monitor { vmonitor_entry_t entry; int64_t peak_value; uint8_t src_chan; uint8_t dst_chan; uint8_t pol; uint8_t mute; int8_t shift; }; extern vprofile_head_t virtual_profile_client_head; extern vprofile_head_t virtual_profile_loopback_head; extern vmonitor_head_t virtual_monitor_input; extern vmonitor_head_t virtual_monitor_local; extern vmonitor_head_t virtual_monitor_output; extern const struct cuse_methods vctl_methods; extern struct virtual_compressor voss_output_compressor_param; extern double voss_output_compressor_gain[VMAX_CHAN]; extern int64_t voss_output_peak[VMAX_CHAN]; extern int64_t voss_input_peak[VMAX_CHAN]; extern uint32_t voss_jitter_up; extern uint32_t voss_jitter_down; extern uint32_t voss_max_channels; extern uint32_t voss_mix_channels; extern uint32_t voss_dsp_samples; extern uint32_t voss_dsp_max_channels; extern uint32_t voss_dsp_sample_rate; extern uint32_t voss_dsp_bits; extern uint8_t voss_libsamplerate_enable; extern uint8_t voss_libsamplerate_quality; extern int voss_is_recording; extern int voss_has_synchronization; extern char voss_dsp_rx_device[VMAX_STRING]; extern char voss_dsp_tx_device[VMAX_STRING]; extern char voss_ctl_device[VMAX_STRING]; extern volatile sig_atomic_t voss_exit; extern int vring_alloc(struct virtual_ring *, size_t); extern void vring_free(struct virtual_ring *); extern void vring_reset(struct virtual_ring *); extern void vring_get_read(struct virtual_ring *, uint8_t **, size_t *); extern void vring_get_write(struct virtual_ring *, uint8_t **, size_t *); extern void vring_inc_read(struct virtual_ring *, size_t); extern void vring_inc_write(struct virtual_ring *, size_t); extern size_t vring_total_read_len(struct virtual_ring *); extern size_t vring_total_write_len(struct virtual_ring *); extern size_t vring_write_linear(struct virtual_ring *, const uint8_t *, size_t); extern size_t vring_read_linear(struct virtual_ring *, uint8_t *, size_t); extern size_t vring_write_zero(struct virtual_ring *, size_t); extern vclient_t *vclient_alloc(void); extern void vclient_free(vclient_t *); extern int vclient_get_default_fmt(vprofile_t *, int type); extern int vclient_setup_buffers(vclient_t *, int size, int frags, int channels, int format, int sample_rate); extern int vclient_export_read_locked(vclient_t *); extern void vclient_import_write_locked(vclient_t *); extern uint32_t vclient_sample_bytes(vclient_t *); extern uint32_t vclient_bufsize_internal(vclient_t *); extern uint32_t vclient_bufsize_scaled(vclient_t *); extern int64_t vclient_noise(uint32_t *, int64_t, int8_t); extern vmonitor_t *vmonitor_alloc(int *, vmonitor_head_t *); extern uint32_t format_best(uint32_t); extern void format_import(uint32_t, const uint8_t *, uint32_t, int64_t *); extern void format_export(uint32_t, const int64_t *, uint8_t *, uint32_t); extern int64_t format_max(uint32_t); extern void format_maximum(const int64_t *, int64_t *, uint32_t, uint32_t, int8_t); extern void format_remix(int64_t *, uint32_t, uint32_t, uint32_t); extern void format_silence(uint32_t, uint8_t *, uint32_t); extern void *virtual_oss_process(void *); /* Audio Delay prototypes */ extern uint32_t voss_ad_last_delay; extern uint32_t voss_dsp_rx_refresh; extern uint32_t voss_dsp_tx_refresh; extern uint8_t voss_ad_enabled; extern uint8_t voss_ad_output_signal; extern uint8_t voss_ad_input_channel; extern uint8_t voss_ad_output_channel; extern void voss_ad_reset(void); extern void voss_ad_init(uint32_t); extern double voss_ad_getput_sample(double); /* Add audio options prototype */ extern void voss_add_options(char *); /* Get current timestamp */ extern uint64_t virtual_oss_delay_ns(void); extern void virtual_oss_wait(void); extern uint64_t virtual_oss_timestamp(void); /* Fast array multiplication */ extern void voss_x3_multiply_double(const int64_t *, const double *, double *, const size_t); /* Equalizer support */ extern void vclient_tx_equalizer(struct virtual_client *, int64_t *, size_t); extern void vclient_rx_equalizer(struct virtual_client *, int64_t *, size_t); extern int vclient_eq_alloc(struct virtual_client *); extern void vclient_eq_free(struct virtual_client *); -/* Internal utilities */ -extern int bt_speaker_main(int argc, char **argv); - /* Internal compressor */ extern void voss_compressor(int64_t *, double *, const struct virtual_compressor *, const unsigned, const unsigned, const int64_t); /* HTTP daemon support */ extern const char *voss_httpd_start(vprofile_t *); #endif /* _VIRTUAL_INT_H_ */ diff --git a/usr.sbin/virtual_oss/virtual_oss/main.c b/usr.sbin/virtual_oss/virtual_oss/main.c index 3f7fb84ce4c6..afa4ad0727ca 100644 --- a/usr.sbin/virtual_oss/virtual_oss/main.c +++ b/usr.sbin/virtual_oss/virtual_oss/main.c @@ -1,2625 +1,2624 @@ /*- * Copyright (c) 2012-2022 Hans Petter Selasky * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "int.h" -#include "utils.h" #include "virtual_oss.h" pthread_mutex_t atomic_mtx; pthread_cond_t atomic_cv; static void atomic_init(void) { if (pthread_mutex_init(&atomic_mtx, NULL) != 0) err(1, "pthread_mutex_init"); if (pthread_cond_init(&atomic_cv, NULL) != 0) err(1, "pthread_cond_init"); } uint32_t vclient_sample_bytes(vclient_t *pvc) { uint32_t fmt = pvc->format; if (fmt & AFMT_16BIT) return (2); else if (fmt & AFMT_24BIT) return (3); else if (fmt & AFMT_32BIT) return (4); else if (fmt & AFMT_8BIT) return (1); else return (0); /* TODO AFMT_BPS */ } static uint32_t vclient_output_delay(vclient_t *pvc) { uint64_t size; uint64_t mod; if (pvc->tx_busy == 0) vclient_import_write_locked(pvc); mod = pvc->channels * vclient_sample_bytes(pvc); size = vring_total_read_len(&pvc->tx_ring[0]); size = (size / 8) * vclient_sample_bytes(pvc); size = (size * (uint64_t)pvc->sample_rate) / (uint64_t)voss_dsp_sample_rate; size += vring_total_read_len(&pvc->tx_ring[1]); size -= size % mod; return (size); } static uint32_t vclient_input_delay(vclient_t *pvc) { if (pvc->rx_busy == 0) vclient_export_read_locked(pvc); return (vring_total_read_len(&pvc->rx_ring[1])); } uint32_t vclient_bufsize_scaled(vclient_t *pvc) { uint32_t samples_scaled = ((uint64_t)voss_dsp_samples * (uint64_t)pvc->sample_rate) / (uint64_t)voss_dsp_sample_rate; if (samples_scaled == 0) samples_scaled = 1; return (pvc->channels * samples_scaled * vclient_sample_bytes(pvc)); } static uint64_t vclient_bufsize_consumed(vclient_t *pvc, uint64_t ts) { int64_t delta; int64_t samples_scaled; int64_t retval; delta = virtual_oss_timestamp() - ts; if (delta < 0) delta = 0; samples_scaled = (delta * (uint64_t)pvc->sample_rate) / 1000000000ULL; if (samples_scaled < 0) samples_scaled = 0; retval = pvc->channels * samples_scaled * vclient_sample_bytes(pvc); if (retval < 0) retval = 0; return (retval); } /* * VLC and some other audio player use this value for jitter * computations and expect it to be very accurate. VirtualOSS is block * based and does not have sample accuracy. Use the system clock to * update this value as we go along instead: */ static uint32_t vclient_output_delay_adjusted(vclient_t *pvc) { int64_t retval = vclient_output_delay(pvc) - vclient_bufsize_consumed(pvc, pvc->tx_timestamp); if (retval < 0) retval = 0; return (retval); } vmonitor_t * vmonitor_alloc(int *pid, vmonitor_head_t *phead) { int id = 0; vmonitor_t *pvm; TAILQ_FOREACH(pvm, phead, entry) id++; if (id >= 64) { *pid = 0; return (NULL); } pvm = malloc(sizeof(*pvm)); if (pvm == NULL) { *pid = 0; return (NULL); } memset(pvm, 0, sizeof(*pvm)); pvm->mute = 1; TAILQ_INSERT_TAIL(phead, pvm, entry); *pid = id; return (pvm); } int64_t vclient_noise(uint32_t *pnoise, int64_t volume, int8_t shift) { const uint32_t prime = 0xFFFF1DU; int64_t temp; /* compute next noise sample */ temp = *pnoise; if (temp & 1) temp += prime; temp /= 2; *pnoise = temp; /* unsigned to signed conversion */ temp ^= 0x800000ULL; if (temp & 0x800000U) temp |= -0x800000ULL; /* properly amplify */ temp *= volume; /* bias shift */ shift -= 23 + VVOLUME_UNIT_SHIFT; /* range check and shift noise */ if (__predict_false(shift < -63 || shift > 63)) temp = 0; else if (shift < 0) temp >>= -shift; else temp <<= shift; return (temp); } static void vresample_free(vresample_t *pvr) { if (pvr->state != NULL) src_delete(pvr->state); free(pvr->data_in); free(pvr->data_out); memset(pvr, 0, sizeof(*pvr)); } static int vresample_setup(vclient_t *pvc, vresample_t *pvr, int samples) { int code = 0; if (pvr->state != NULL) return (0); pvr->state = src_new(voss_libsamplerate_quality, pvc->channels, &code); if (pvr->state == NULL) goto error; pvr->data_in = malloc(sizeof(float) * samples); if (pvr->data_in == NULL) goto error; pvr->data_out = malloc(sizeof(float) * samples); if (pvr->data_out == NULL) goto error; pvr->data.data_in = pvr->data_in; pvr->data.data_out = pvr->data_out; return (0); error: vresample_free(pvr); return (CUSE_ERR_NO_MEMORY); } void vclient_free(vclient_t *pvc) { vresample_free(&pvc->rx_resample); vresample_free(&pvc->tx_resample); /* free equalizer */ vclient_eq_free(pvc); /* free ring buffers */ vring_free(&pvc->rx_ring[0]); vring_free(&pvc->rx_ring[1]); vring_free(&pvc->tx_ring[0]); vring_free(&pvc->tx_ring[1]); free(pvc); } vclient_t * vclient_alloc(void) { vclient_t *pvc; pvc = malloc(sizeof(*pvc)); if (pvc == NULL) return (NULL); memset(pvc, 0, sizeof(*pvc)); pvc->rx_noise_rem = 1; pvc->tx_noise_rem = 1; pvc->rx_volume = 1 << VVOLUME_UNIT_SHIFT; pvc->tx_volume = 1 << VVOLUME_UNIT_SHIFT; return (pvc); } int vclient_get_default_fmt(vprofile_t *pvp, int type) { int retval; if (type == VTYPE_WAV_HDR) { switch (pvp->bits) { case 16: retval = AFMT_S16_LE; break; case 24: retval = AFMT_S24_LE; break; case 32: retval = AFMT_S32_LE; break; default: retval = AFMT_S8; break; } } else { switch (pvp->bits) { case 16: retval = AFMT_S16_NE; break; case 24: retval = AFMT_S24_NE; break; case 32: retval = AFMT_S32_NE; break; default: retval = AFMT_S8; break; } } return (retval); } int vclient_setup_buffers(vclient_t *pvc, int size, int frags, int channels, int format, int sample_rate) { size_t bufsize_internal; size_t bufsize_min; size_t mod_internal; size_t mod; uint64_t ts; int bufsize; /* check we are not busy */ if (pvc->rx_busy || pvc->tx_busy) return (CUSE_ERR_BUSY); /* free equalizer */ vclient_eq_free(pvc); /* free existing ring buffers */ vring_free(&pvc->rx_ring[0]); vring_free(&pvc->rx_ring[1]); vring_free(&pvc->tx_ring[0]); vring_free(&pvc->tx_ring[1]); /* reset resampler */ vresample_free(&pvc->rx_resample); vresample_free(&pvc->tx_resample); if (sample_rate > 0) pvc->sample_rate = sample_rate; if (format != 0) pvc->format = format; if (channels > 0) pvc->channels = channels; mod = pvc->channels * vclient_sample_bytes(pvc); mod_internal = pvc->channels * 8; if (size > 0) { size += mod - 1; size -= size % mod; pvc->buffer_size = size; pvc->buffer_size_set = 1; } else if (pvc->buffer_size_set == 0) pvc->buffer_size = vclient_bufsize_scaled(pvc); pvc->low_water = pvc->buffer_size; if (frags > 0) { pvc->buffer_frags = frags; pvc->buffer_frags_set = 1; } else if (pvc->buffer_frags_set == 0) pvc->buffer_frags = 2; /* sanity checks */ if (frags < 0 || size < 0) return (CUSE_ERR_INVALID); if (pvc->format == 0) return (CUSE_ERR_INVALID); if (pvc->buffer_frags <= 0 || pvc->buffer_frags >= 1024) return (CUSE_ERR_INVALID); if (pvc->buffer_size <= 0 || pvc->buffer_size >= (1024 * 1024)) return (CUSE_ERR_INVALID); if ((pvc->buffer_size * pvc->buffer_frags) >= (128 * 1024 * 1024)) return (CUSE_ERR_INVALID); if (pvc->channels <= 0 || pvc->channels > pvc->profile->channels) return (CUSE_ERR_INVALID); /* get buffer sizes */ bufsize = pvc->buffer_frags * pvc->buffer_size; bufsize_internal = ((uint64_t)bufsize * (uint64_t)voss_dsp_sample_rate * 8ULL) / ((uint64_t)pvc->sample_rate * (uint64_t)vclient_sample_bytes(pvc)); bufsize_min = voss_dsp_samples * pvc->channels * 8; /* check for too small buffer size */ if (bufsize_internal < bufsize_min) return (CUSE_ERR_INVALID); /* allow for jitter */ bufsize_internal *= 2ULL; /* align buffer size */ bufsize_internal += (mod_internal - 1); bufsize_internal -= (bufsize_internal % mod_internal); /* allocate new buffers */ if (vring_alloc(&pvc->rx_ring[0], bufsize_internal)) goto err_0; if (vring_alloc(&pvc->rx_ring[1], bufsize)) goto err_1; if (vring_alloc(&pvc->tx_ring[0], bufsize_internal)) goto err_2; if (vring_alloc(&pvc->tx_ring[1], bufsize)) goto err_3; if (vclient_eq_alloc(pvc)) goto err_4; ts = virtual_oss_timestamp(); pvc->rx_samples = 0; pvc->tx_samples = 0; pvc->tx_timestamp = ts; pvc->rx_timestamp = ts; return (0); err_4: vring_free(&pvc->tx_ring[1]); err_3: vring_free(&pvc->tx_ring[0]); err_2: vring_free(&pvc->rx_ring[1]); err_1: vring_free(&pvc->rx_ring[0]); err_0: return (CUSE_ERR_NO_MEMORY); } static int vclient_open_sub(struct cuse_dev *pdev, int fflags __unused, int type) { vclient_t *pvc; vprofile_t *pvp; int error; pvp = cuse_dev_get_priv0(pdev); pvc = vclient_alloc(); if (pvc == NULL) return (CUSE_ERR_NO_MEMORY); pvc->profile = pvp; /* setup buffers */ error = vclient_setup_buffers(pvc, 0, 0, pvp->channels, vclient_get_default_fmt(pvp, type), voss_dsp_sample_rate); if (error != 0) { vclient_free(pvc); return (error); } pvc->type = type; cuse_dev_set_per_file_handle(pdev, pvc); atomic_lock(); /* only allow one synchronization source at a time */ if (pvc->profile->synchronized) { if (voss_has_synchronization != 0) error = CUSE_ERR_BUSY; else voss_has_synchronization++; } if (error == 0) TAILQ_INSERT_TAIL(&pvc->profile->head, pvc, entry); atomic_unlock(); return (error); } static int vclient_open_wav(struct cuse_dev *pdev, int fflags) { return (vclient_open_sub(pdev, fflags, VTYPE_WAV_HDR)); } static int vclient_open_oss(struct cuse_dev *pdev, int fflags) { return (vclient_open_sub(pdev, fflags, VTYPE_OSS_DAT)); } static int vclient_close(struct cuse_dev *pdev, int fflags __unused) { vclient_t *pvc; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (CUSE_ERR_INVALID); atomic_lock(); if (pvc->profile->synchronized) { voss_has_synchronization--; /* wait for virtual_oss_process(), if any */ while (pvc->sync_busy) { pvc->sync_wakeup = 1; atomic_wakeup(); atomic_wait(); } } TAILQ_REMOVE(&pvc->profile->head, pvc, entry); atomic_unlock(); vclient_free(pvc); return (0); } static int vclient_read_silence_locked(vclient_t *pvc) { size_t size; int delta_in; delta_in = pvc->profile->rec_delay - pvc->rec_delay; if (delta_in < 1) return (0); size = delta_in * pvc->channels * 8; size = vring_write_zero(&pvc->rx_ring[0], size); pvc->rec_delay += size / (pvc->channels * 8); delta_in = pvc->profile->rec_delay - pvc->rec_delay; if (delta_in < 1) return (0); return (1); } static int vclient_generate_wav_header_locked(vclient_t *pvc) { uint8_t *ptr; size_t mod; size_t len; vring_get_write(&pvc->rx_ring[1], &ptr, &len); mod = pvc->channels * vclient_sample_bytes(pvc); if (mod == 0 || len < (44 + mod - 1)) return (CUSE_ERR_INVALID); /* align to next sample */ len = 44 + mod - 1; len -= len % mod; /* pre-advance write pointer */ vring_inc_write(&pvc->rx_ring[1], len); /* clear block */ memset(ptr, 0, len); /* fill out data header */ ptr[len - 8] = 'd'; ptr[len - 7] = 'a'; ptr[len - 6] = 't'; ptr[len - 5] = 'a'; /* magic for unspecified length */ ptr[len - 4] = 0x00; ptr[len - 3] = 0xF0; ptr[len - 2] = 0xFF; ptr[len - 1] = 0x7F; /* fill out header */ *ptr++ = 'R'; *ptr++ = 'I'; *ptr++ = 'F'; *ptr++ = 'F'; /* total chunk size - unknown */ *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 'W'; *ptr++ = 'A'; *ptr++ = 'V'; *ptr++ = 'E'; *ptr++ = 'f'; *ptr++ = 'm'; *ptr++ = 't'; *ptr++ = ' '; /* make sure header fits in PCM block */ len -= 28; *ptr++ = len; *ptr++ = len >> 8; *ptr++ = len >> 16; *ptr++ = len >> 24; /* audioformat = PCM */ *ptr++ = 0x01; *ptr++ = 0x00; /* number of channels */ len = pvc->channels; *ptr++ = len; *ptr++ = len >> 8; /* sample rate */ len = pvc->sample_rate; *ptr++ = len; *ptr++ = len >> 8; *ptr++ = len >> 16; *ptr++ = len >> 24; /* byte rate */ len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); *ptr++ = len; *ptr++ = len >> 8; *ptr++ = len >> 16; *ptr++ = len >> 24; /* block align */ len = pvc->channels * vclient_sample_bytes(pvc); *ptr++ = len; *ptr++ = len >> 8; /* bits per sample */ len = vclient_sample_bytes(pvc) * 8; *ptr++ = len; *ptr++ = len >> 8; return (0); } int vclient_export_read_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx) { enum { MAX_FRAME = 1024 }; size_t dst_mod; size_t src_mod; int error; if (pvc->type == VTYPE_WAV_HDR) { error = vclient_generate_wav_header_locked(pvc); if (error != 0) return (error); /* only write header once */ pvc->type = VTYPE_WAV_DAT; } error = vclient_read_silence_locked(pvc); if (error != 0) return (0); dst_mod = pvc->channels * vclient_sample_bytes(pvc); src_mod = pvc->channels * 8; if (pvc->sample_rate == (int)voss_dsp_sample_rate) { while (1) { uint8_t *src_ptr; size_t src_len; uint8_t *dst_ptr; size_t dst_len; vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len); vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len); src_len /= src_mod; dst_len /= dst_mod; /* compare number of samples */ if (dst_len > src_len) dst_len = src_len; else src_len = dst_len; if (dst_len == 0) break; src_len *= src_mod; dst_len *= dst_mod; format_export(pvc->format, (const int64_t *)(uintptr_t)src_ptr, dst_ptr, dst_len); vring_inc_read(&pvc->rx_ring[0], src_len); vring_inc_write(&pvc->rx_ring[1], dst_len); } } else { vresample_t *pvr = &pvc->rx_resample; if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0) return (CUSE_ERR_NO_MEMORY); while (1) { uint8_t *src_ptr; size_t src_len; uint8_t *dst_ptr; size_t dst_len; int64_t temp[MAX_FRAME * pvc->channels]; size_t samples; size_t y; vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len); vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len); src_len /= src_mod; dst_len /= dst_mod; /* compare number of samples */ if (dst_len > src_len) dst_len = src_len; else src_len = dst_len; if (dst_len > MAX_FRAME) dst_len = src_len = MAX_FRAME; if (dst_len == 0) break; src_len *= src_mod; dst_len *= dst_mod; for (y = 0; y != src_len; y += 8) { pvr->data_in[y / 8] = *(int64_t *)(uintptr_t)(src_ptr + y); } /* setup parameters for transform */ pvr->data.input_frames = src_len / src_mod; pvr->data.output_frames = dst_len / dst_mod; pvr->data.src_ratio = (float)pvc->sample_rate / (float)voss_dsp_sample_rate; pvc->rx_busy = 1; atomic_unlock(); error = src_process(pvr->state, &pvr->data); atomic_lock(); pvc->rx_busy = 0; if (error != 0) break; src_len = pvr->data.input_frames_used * src_mod; dst_len = pvr->data.output_frames_gen * dst_mod; samples = pvr->data.output_frames_gen * pvc->channels; for (y = 0; y != samples; y++) temp[y] = pvr->data_out[y]; format_export(pvc->format, temp, dst_ptr, dst_len); vring_inc_read(&pvc->rx_ring[0], src_len); vring_inc_write(&pvc->rx_ring[1], dst_len); /* check if no data was moved */ if (src_len == 0 && dst_len == 0) break; } } if (pvc->sync_busy) atomic_wakeup(); return (0); } static int vclient_read(struct cuse_dev *pdev, int fflags, void *peer_ptr, int len) { vclient_t *pvc; int error; int retval; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (CUSE_ERR_INVALID); atomic_lock(); if (pvc->rx_busy) { atomic_unlock(); return (CUSE_ERR_BUSY); } pvc->rx_enabled = 1; retval = 0; while (len > 0) { uint8_t *buf_ptr; size_t buf_len; error = vclient_export_read_locked(pvc); if (error != 0) { retval = error; break; } vring_get_read(&pvc->rx_ring[1], &buf_ptr, &buf_len); if (buf_len == 0) { /* out of data */ if (fflags & CUSE_FFLAG_NONBLOCK) { if (retval == 0) retval = CUSE_ERR_WOULDBLOCK; break; } pvc->rx_busy = 1; atomic_wait(); pvc->rx_busy = 0; if (cuse_got_peer_signal() == 0) { if (retval == 0) retval = CUSE_ERR_SIGNAL; break; } continue; } if ((int)buf_len > len) buf_len = len; pvc->rx_busy = 1; atomic_unlock(); error = cuse_copy_out(buf_ptr, peer_ptr, buf_len); atomic_lock(); pvc->rx_busy = 0; if (error != 0) { retval = error; break; } peer_ptr = ((uint8_t *)peer_ptr) + buf_len; retval += buf_len; len -= buf_len; vring_inc_read(&pvc->rx_ring[1], buf_len); } atomic_unlock(); return (retval); } void vclient_import_write_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx) { enum { MAX_FRAME = 1024 }; size_t dst_mod; size_t src_mod; dst_mod = pvc->channels * 8; src_mod = pvc->channels * vclient_sample_bytes(pvc); if (pvc->sample_rate == (int)voss_dsp_sample_rate) { while (1) { uint8_t *src_ptr; size_t src_len; uint8_t *dst_ptr; size_t dst_len; vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len); vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len); src_len /= src_mod; dst_len /= dst_mod; /* compare number of samples */ if (dst_len > src_len) dst_len = src_len; else src_len = dst_len; if (dst_len == 0) break; src_len *= src_mod; dst_len *= dst_mod; format_import(pvc->format, src_ptr, src_len, (int64_t *)(uintptr_t)dst_ptr); vring_inc_read(&pvc->tx_ring[1], src_len); vring_inc_write(&pvc->tx_ring[0], dst_len); } } else { vresample_t *pvr = &pvc->tx_resample; if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0) return; while (1) { uint8_t *src_ptr; size_t src_len; uint8_t *dst_ptr; size_t dst_len; int64_t temp[MAX_FRAME * pvc->channels]; size_t samples; size_t y; int error; vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len); vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len); src_len /= src_mod; dst_len /= dst_mod; /* compare number of samples */ if (dst_len > src_len) dst_len = src_len; else src_len = dst_len; if (dst_len > MAX_FRAME) dst_len = src_len = MAX_FRAME; if (dst_len == 0) break; src_len *= src_mod; dst_len *= dst_mod; format_import(pvc->format, src_ptr, src_len, temp); src_len /= vclient_sample_bytes(pvc); for (y = 0; y != src_len; y++) pvr->data_in[y] = temp[y]; src_len *= vclient_sample_bytes(pvc); /* setup parameters for transform */ pvr->data.input_frames = src_len / src_mod; pvr->data.output_frames = dst_len / dst_mod; pvr->data.src_ratio = (float)voss_dsp_sample_rate / (float)pvc->sample_rate; pvc->tx_busy = 1; atomic_unlock(); error = src_process(pvr->state, &pvr->data); atomic_lock(); pvc->tx_busy = 0; if (error != 0) break; src_len = pvr->data.input_frames_used * src_mod; dst_len = pvr->data.output_frames_gen * dst_mod; samples = pvr->data.output_frames_gen * pvc->channels; for (y = 0; y != samples; y++) { ((int64_t *)(uintptr_t)dst_ptr)[y] = pvr->data_out[y]; } vring_inc_read(&pvc->tx_ring[1], src_len); vring_inc_write(&pvc->tx_ring[0], dst_len); /* check if no data was moved */ if (src_len == 0 && dst_len == 0) break; } } if (pvc->sync_busy) atomic_wakeup(); } static int vclient_write_oss(struct cuse_dev *pdev, int fflags, const void *peer_ptr, int len) { vclient_t *pvc; int error; int retval; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (CUSE_ERR_INVALID); retval = 0; atomic_lock(); if (pvc->tx_busy) { atomic_unlock(); return (CUSE_ERR_BUSY); } pvc->tx_enabled = 1; while (1) { uint8_t *buf_ptr; size_t buf_len; vclient_import_write_locked(pvc); if (len < 1) break; vring_get_write(&pvc->tx_ring[1], &buf_ptr, &buf_len); if (buf_len == 0) { /* out of data */ if (fflags & CUSE_FFLAG_NONBLOCK) { if (retval == 0) retval = CUSE_ERR_WOULDBLOCK; break; } pvc->tx_busy = 1; atomic_wait(); pvc->tx_busy = 0; if (cuse_got_peer_signal() == 0) { if (retval == 0) retval = CUSE_ERR_SIGNAL; break; } continue; } if ((int)buf_len > len) buf_len = len; pvc->tx_busy = 1; atomic_unlock(); error = cuse_copy_in(peer_ptr, buf_ptr, buf_len); atomic_lock(); pvc->tx_busy = 0; if (error != 0) { retval = error; break; } peer_ptr = ((const uint8_t *)peer_ptr) + buf_len; retval += buf_len; len -= buf_len; vring_inc_write(&pvc->tx_ring[1], buf_len); } atomic_unlock(); return (retval); } static int vclient_write_wav(struct cuse_dev *pdev __unused, int fflags __unused, const void *peer_ptr __unused, int len __unused) { return (CUSE_ERR_INVALID); } static int vclient_set_channels(vclient_t *pvc, int channels) { if (pvc->channels == channels) return (0); return (vclient_setup_buffers(pvc, 0, 0, channels, 0, 0)); } /* greatest common divisor, Euclid equation */ static uint64_t vclient_gcd_64(uint64_t a, uint64_t b) { uint64_t an; uint64_t bn; while (b != 0) { an = b; bn = a % b; a = an; b = bn; } return (a); } static uint64_t vclient_scale(uint64_t value, uint64_t mul, uint64_t div) { uint64_t gcd = vclient_gcd_64(mul, div); mul /= gcd; div /= gcd; return ((value * mul) / div); } static int vclient_ioctl_oss(struct cuse_dev *pdev, int fflags __unused, unsigned long cmd, void *peer_data) { union { int val; unsigned long long lval; oss_sysinfo sysinfo; oss_card_info card_info; oss_audioinfo audioinfo; audio_buf_info buf_info; oss_count_t oss_count; count_info oss_count_info; audio_errinfo errinfo; oss_label_t label; oss_longname_t longname; } data; vclient_t *pvc; uint64_t bytes; int len; int error; int temp; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (CUSE_ERR_INVALID); len = IOCPARM_LEN(cmd); if (len < 0 || len > (int)sizeof(data)) return (CUSE_ERR_INVALID); if (cmd & IOC_IN) { error = cuse_copy_in(peer_data, &data, len); if (error) return (error); } else { error = 0; } atomic_lock(); switch (cmd) { case OSS_GETVERSION: data.val = SOUND_VERSION; break; case SNDCTL_SYSINFO: memset(&data.sysinfo, 0, sizeof(data.sysinfo)); strcpy(data.sysinfo.product, "VOSS"); strcpy(data.sysinfo.version, "1.0"); data.sysinfo.versionnum = SOUND_VERSION; data.sysinfo.numaudios = 1; data.sysinfo.numcards = 1; data.sysinfo.numaudioengines = 1; strcpy(data.sysinfo.license, "BSD"); memset(data.sysinfo.filler, -1, sizeof(data.sysinfo.filler)); break; case SNDCTL_CARDINFO: memset(&data.card_info, 0, sizeof(data.card_info)); strlcpy(data.card_info.shortname, pvc->profile->oss_name, sizeof(data.card_info.shortname)); break; case SNDCTL_AUDIOINFO: case SNDCTL_AUDIOINFO_EX: case SNDCTL_ENGINEINFO: memset(&data.audioinfo, 0, sizeof(data.audioinfo)); strlcpy(data.audioinfo.name, pvc->profile->oss_name, sizeof(data.audioinfo.name)); snprintf(data.audioinfo.devnode, sizeof(data.audioinfo.devnode), "/dev/%s", pvc->profile->oss_name); data.audioinfo.caps = DSP_CAP_INPUT | DSP_CAP_OUTPUT; data.audioinfo.iformats = VSUPPORTED_AFMT; data.audioinfo.oformats = VSUPPORTED_AFMT; data.audioinfo.enabled = 1; data.audioinfo.min_rate = (int)8000; data.audioinfo.max_rate = (int)voss_dsp_sample_rate; data.audioinfo.max_channels = pvc->profile->channels; /* range check */ if (voss_libsamplerate_enable == 0 || data.audioinfo.min_rate > data.audioinfo.max_rate) data.audioinfo.min_rate = data.audioinfo.max_rate; data.audioinfo.nrates = 1; data.audioinfo.rates[0] = (int)voss_dsp_sample_rate; if (voss_libsamplerate_enable != 0 && 96000 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 96000; if (voss_libsamplerate_enable != 0 && 48000 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 48000; if (voss_libsamplerate_enable != 0 && 44100 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 44100; if (voss_libsamplerate_enable != 0 && 24000 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 24000; if (voss_libsamplerate_enable != 0 && 16000 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 16000; if (voss_libsamplerate_enable != 0 && 8000 != voss_dsp_sample_rate) data.audioinfo.rates[data.audioinfo.nrates++] = 8000; data.audioinfo.latency = -1; break; case FIONREAD: data.val = vclient_input_delay(pvc); break; case FIONWRITE: data.val = vring_total_read_len(&pvc->tx_ring[1]); break; case FIOASYNC: case SNDCTL_DSP_NONBLOCK: case FIONBIO: break; case SNDCTL_DSP_SETBLKSIZE: case _IOWR('P', 4, int): error = vclient_setup_buffers(pvc, data.val, 0, 0, 0, 0); /* FALLTHROUGH */ case SNDCTL_DSP_GETBLKSIZE: data.val = pvc->buffer_size; break; case SNDCTL_DSP_SETFRAGMENT: if ((data.val & 0xFFFF) < 4) { /* need at least 16 bytes of buffer */ data.val &= ~0xFFFF; data.val |= 4; } else if ((data.val & 0xFFFF) > 24) { /* no more than 16MBytes of buffer */ data.val &= ~0xFFFF; data.val |= 24; } error = vclient_setup_buffers(pvc, (1 << (data.val & 0xFFFF)), (data.val >> 16), 0, 0, 0); if (error) { /* fallback to defaults */ pvc->buffer_size_set = 0; pvc->buffer_frags_set = 0; error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0); if (error) break; /* figure out log2() of actual buffer size */ for (data.val = 0; data.val < 24 && (1U << data.val) < pvc->buffer_size; data.val++) ; /* or in the actual number of fragments */ data.val |= (pvc->buffer_frags << 16); } break; case SNDCTL_DSP_RESET: error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0); break; case SNDCTL_DSP_SYNC: break; case SNDCTL_DSP_SPEED: if (data.val >= 8000 && data.val <= 96000 && voss_libsamplerate_enable != 0) { error = vclient_setup_buffers(pvc, 0, 0, 0, 0, data.val); } /* return current speed */ data.val = (int)pvc->sample_rate; break; case SOUND_PCM_READ_RATE: data.val = (int)pvc->sample_rate; break; case SNDCTL_DSP_STEREO: if (data.val != 0) { error = vclient_set_channels(pvc, 2); } else { error = vclient_set_channels(pvc, 1); } data.val = (pvc->channels == 2); break; case SOUND_PCM_WRITE_CHANNELS: if (data.val < 0) { data.val = 0; error = CUSE_ERR_INVALID; break; } if (data.val == 0) { data.val = pvc->channels; } else { error = vclient_set_channels(pvc, data.val); } break; case SOUND_PCM_READ_CHANNELS: data.val = pvc->channels; break; case AIOGFMT: case SNDCTL_DSP_GETFMTS: data.val = VSUPPORTED_AFMT | AFMT_FULLDUPLEX | (pvc->profile->channels > 1 ? AFMT_STEREO : 0); break; case AIOSFMT: case SNDCTL_DSP_SETFMT: if (data.val != AFMT_QUERY) { temp = data.val & VSUPPORTED_AFMT; if (temp == 0 || (temp & (temp - 1)) != 0) { error = CUSE_ERR_INVALID; } else { error = vclient_setup_buffers(pvc, 0, 0, 0, temp, 0); } } else { data.val = pvc->format; } break; case SNDCTL_DSP_GETISPACE: memset(&data.buf_info, 0, sizeof(data.buf_info)); data.buf_info.fragsize = pvc->buffer_size; data.buf_info.fragstotal = pvc->buffer_frags; bytes = (pvc->buffer_size * pvc->buffer_frags); temp = vclient_input_delay(pvc); if (temp < 0 || (uint64_t)temp > bytes) temp = bytes; data.buf_info.fragments = temp / pvc->buffer_size; data.buf_info.bytes = temp; break; case SNDCTL_DSP_GETOSPACE: memset(&data.buf_info, 0, sizeof(data.buf_info)); data.buf_info.fragsize = pvc->buffer_size; data.buf_info.fragstotal = pvc->buffer_frags; bytes = (pvc->buffer_size * pvc->buffer_frags); temp = vclient_output_delay(pvc); if (temp < 0 || (uint64_t)temp >= bytes) { /* buffer is full */ data.buf_info.fragments = 0; data.buf_info.bytes = 0; } else { /* buffer is not full */ bytes -= temp; data.buf_info.fragments = bytes / pvc->buffer_size; data.buf_info.bytes = bytes; } break; case SNDCTL_DSP_GETCAPS: data.val = PCM_CAP_REALTIME | PCM_CAP_DUPLEX | PCM_CAP_INPUT | PCM_CAP_OUTPUT | PCM_CAP_TRIGGER | PCM_CAP_VIRTUAL; break; case SOUND_PCM_READ_BITS: data.val = vclient_sample_bytes(pvc) * 8; break; case SNDCTL_DSP_SETTRIGGER: if (data.val & PCM_ENABLE_INPUT) { pvc->rx_enabled = 1; } else { pvc->rx_enabled = 0; vring_reset(&pvc->rx_ring[1]); } if (data.val & PCM_ENABLE_OUTPUT) { pvc->tx_enabled = 1; } else { pvc->tx_enabled = 0; vring_reset(&pvc->tx_ring[1]); } break; case SNDCTL_DSP_GETTRIGGER: data.val = 0; if (pvc->rx_enabled) data.val |= PCM_ENABLE_INPUT; if (pvc->tx_enabled) data.val |= PCM_ENABLE_OUTPUT; break; case SNDCTL_DSP_GETODELAY: data.val = vclient_output_delay_adjusted(pvc); break; case SNDCTL_DSP_POST: break; case SNDCTL_DSP_SETDUPLEX: break; case SNDCTL_DSP_GETRECVOL: temp = (pvc->rx_volume * 100) >> VVOLUME_UNIT_SHIFT; data.val = (temp & 0x00FF) | ((temp << 8) & 0xFF00); break; case SNDCTL_DSP_SETRECVOL: pvc->rx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100; break; case SNDCTL_DSP_GETPLAYVOL: temp = (pvc->tx_volume * 100) >> VVOLUME_UNIT_SHIFT; data.val = (temp & 0x00FF) | ((temp << 8) & 0xFF00); break; case SNDCTL_DSP_SETPLAYVOL: pvc->tx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100; break; case SNDCTL_DSP_CURRENT_IPTR: memset(&data.oss_count, 0, sizeof(data.oss_count)); /* compute input samples per channel */ data.oss_count.samples = vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate); data.oss_count.samples /= pvc->channels; data.oss_count.fifo_samples = vclient_input_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc)); break; case SNDCTL_DSP_CURRENT_OPTR: memset(&data.oss_count, 0, sizeof(data.oss_count)); /* compute output samples per channel */ data.oss_count.samples = vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate); data.oss_count.samples /= pvc->channels; data.oss_count.fifo_samples = vclient_output_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc)); break; case SNDCTL_DSP_GETIPTR: memset(&data.oss_count_info, 0, sizeof(data.oss_count_info)); /* compute input bytes */ bytes = vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate) * vclient_sample_bytes(pvc); data.oss_count_info.bytes = bytes; data.oss_count_info.blocks = bytes / pvc->buffer_size; data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags); break; case SNDCTL_DSP_GETOPTR: memset(&data.oss_count_info, 0, sizeof(data.oss_count_info)); /* compute output bytes */ bytes = vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate) * vclient_sample_bytes(pvc); data.oss_count_info.bytes = bytes; data.oss_count_info.blocks = bytes / pvc->buffer_size; data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags); break; case SNDCTL_DSP_HALT_OUTPUT: pvc->tx_enabled = 0; break; case SNDCTL_DSP_HALT_INPUT: pvc->rx_enabled = 0; break; case SNDCTL_DSP_LOW_WATER: if (data.val > 0 && data.val < (int)(pvc->buffer_frags * pvc->buffer_size)) { pvc->low_water = data.val; } else { error = CUSE_ERR_INVALID; } break; case SNDCTL_DSP_GETERROR: memset(&data.errinfo, 0, sizeof(data.errinfo)); break; case SNDCTL_DSP_SYNCGROUP: case SNDCTL_DSP_SYNCSTART: break; case SNDCTL_DSP_POLICY: break; case SNDCTL_DSP_COOKEDMODE: break; case SNDCTL_DSP_GET_CHNORDER: data.lval = CHNORDER_NORMAL; break; case SNDCTL_DSP_GETCHANNELMASK: data.val = DSP_BIND_FRONT; break; case SNDCTL_DSP_BIND_CHANNEL: break; case SNDCTL_GETLABEL: memset(&data.label, 0, sizeof(data.label)); break; case SNDCTL_SETLABEL: break; case SNDCTL_GETSONG: memset(&data.longname, 0, sizeof(data.longname)); break; case SNDCTL_SETSONG: break; case SNDCTL_SETNAME: break; default: error = CUSE_ERR_INVALID; break; } atomic_unlock(); if (error == 0) { if (cmd & IOC_OUT) error = cuse_copy_out(&data, peer_data, len); } return (error); } static int vclient_ioctl_wav(struct cuse_dev *pdev, int fflags __unused, unsigned long cmd, void *peer_data) { union { int val; } data; vclient_t *pvc; int len; int error; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (CUSE_ERR_INVALID); len = IOCPARM_LEN(cmd); if (len < 0 || len > (int)sizeof(data)) return (CUSE_ERR_INVALID); if (cmd & IOC_IN) { error = cuse_copy_in(peer_data, &data, len); if (error) return (error); } else { error = 0; } atomic_lock(); switch (cmd) { case FIONREAD: data.val = vclient_input_delay(pvc); break; case FIOASYNC: case SNDCTL_DSP_NONBLOCK: case FIONBIO: break; default: error = CUSE_ERR_INVALID; break; } atomic_unlock(); if (error == 0) { if (cmd & IOC_OUT) error = cuse_copy_out(&data, peer_data, len); } return (error); } static int vclient_poll(struct cuse_dev *pdev, int fflags, int events) { vclient_t *pvc; int retval = CUSE_POLL_NONE; pvc = cuse_dev_get_per_file_handle(pdev); if (pvc == NULL) return (retval); atomic_lock(); if ((events & CUSE_POLL_READ) && (fflags & CUSE_FFLAG_READ)) { pvc->rx_enabled = 1; if (vclient_input_delay(pvc) >= pvc->low_water) retval |= CUSE_POLL_READ; } if ((events & CUSE_POLL_WRITE) && (fflags & CUSE_FFLAG_WRITE)) { const uint32_t out_dly = vclient_output_delay(pvc); const uint32_t out_buf = (pvc->buffer_frags * pvc->buffer_size); if (out_dly < out_buf && (out_buf - out_dly) >= pvc->low_water) retval |= CUSE_POLL_WRITE; } atomic_unlock(); return (retval); } static const struct cuse_methods vclient_oss_methods = { .cm_open = vclient_open_oss, .cm_close = vclient_close, .cm_read = vclient_read, .cm_write = vclient_write_oss, .cm_ioctl = vclient_ioctl_oss, .cm_poll = vclient_poll, }; static const struct cuse_methods vclient_wav_methods = { .cm_open = vclient_open_wav, .cm_close = vclient_close, .cm_read = vclient_read, .cm_write = vclient_write_wav, .cm_ioctl = vclient_ioctl_wav, .cm_poll = vclient_poll, }; vprofile_head_t virtual_profile_client_head; vprofile_head_t virtual_profile_loopback_head; vmonitor_head_t virtual_monitor_input; vmonitor_head_t virtual_monitor_output; vmonitor_head_t virtual_monitor_local; uint32_t voss_max_channels; uint32_t voss_mix_channels; uint32_t voss_dsp_samples; uint32_t voss_dsp_max_channels; uint32_t voss_dsp_sample_rate; uint32_t voss_dsp_bits; uint8_t voss_libsamplerate_enable; uint8_t voss_libsamplerate_quality = SRC_SINC_FASTEST; int voss_is_recording = 1; int voss_has_synchronization; volatile sig_atomic_t voss_exit = 0; static int voss_dsp_perm = 0666; static int voss_do_background; static const char *voss_pid_path; uint32_t voss_dsp_rx_refresh; uint32_t voss_dsp_tx_refresh; char voss_dsp_rx_device[VMAX_STRING]; char voss_dsp_tx_device[VMAX_STRING]; char voss_ctl_device[VMAX_STRING]; uint32_t voss_jitter_up; uint32_t voss_jitter_down; struct voss_backend *voss_rx_backend; struct voss_backend *voss_tx_backend; static int voss_dups; static int voss_ntds; static pthread_t *voss_tds; /* XXX I do not like the prefix argument... */ static struct voss_backend * voss_load_backend(const char *prefix, const char *name, const char *dir) { struct voss_backend *backend; void *hdl; char lpath[64], bsym[64]; snprintf(lpath, sizeof(lpath), "%s/lib/virtual_oss/voss_%s.so", prefix, name); snprintf(bsym, sizeof(bsym), "voss_backend_%s_%s", name, dir); if ((hdl = dlopen(lpath, RTLD_NOW)) == NULL) errx(1, "%s", dlerror()); if ((backend = dlsym(hdl, bsym)) == NULL) { warnx("%s", dlerror()); dlclose(hdl); exit(EXIT_FAILURE); } return (backend); } static void voss_rx_backend_refresh(void) { /* setup RX backend */ if (strcmp(voss_dsp_rx_device, "/dev/null") == 0) { voss_rx_backend = voss_load_backend("/usr", "null", "rec"); } else if (strstr(voss_dsp_rx_device, "/dev/bluetooth/") == voss_dsp_rx_device) { voss_rx_backend = voss_load_backend("/usr/local", "bt", "rec"); } else if (strstr(voss_dsp_rx_device, "/dev/sndio/") == voss_dsp_rx_device) { voss_rx_backend = voss_load_backend("/usr/local", "sndio", "rec"); } else { voss_rx_backend = voss_load_backend("/usr", "oss", "rec"); } } static void voss_tx_backend_refresh(void) { /* setup TX backend */ if (strcmp(voss_dsp_tx_device, "/dev/null") == 0) { voss_tx_backend = voss_load_backend("/usr", "null", "play"); } else if (strstr(voss_dsp_tx_device, "/dev/bluetooth/") == voss_dsp_tx_device) { voss_tx_backend = voss_load_backend("/usr/local", "bt", "play"); } else if (strstr(voss_dsp_tx_device, "/dev/sndio/") == voss_dsp_tx_device) { voss_tx_backend = voss_load_backend("/usr/local", "sndio", "play"); } else { voss_tx_backend = voss_load_backend("/usr", "oss", "play"); } } static void usage(void) { fprintf(stderr, "Usage: virtual_oss [options...] [device] \\\n" "\t" "-C 2 -c 2 -r 48000 -b 16 -s 100.0ms -f /dev/dsp3 \\\n" "\t" "-P /dev/dsp3 -R /dev/dsp1 \\\n" "\t" "-O /dev/dsp3 -R /dev/null \\\n" "\t" "-c 1 -m 0,0 [-w wav.0] -d dsp100.0 \\\n" "\t" "-c 1 -m 0,0 [-w wav.0] -d vdsp.0 \\\n" "\t" "-c 2 -m 0,0,1,1 [-w wav.1] -d vdsp.1 \\\n" "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -l vdsp.loopback \\\n" "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -L vdsp.loopback \\\n" "\t" "-B # run in background \\\n" "\t" "-s or ms \\\n" "\t" "-S # enable automatic resampling using libsamplerate \\\n" "\t" "-Q <0,1,2> # quality of resampling 0=best,1=medium,2=fastest (default) \\\n" "\t" "-b \\\n" "\t" "-r \\\n" "\t" "-i \\\n" "\t" "-a \\\n" "\t" "-a i, \\\n" "\t" "-a o, \\\n" "\t" "-g # enable device RX compressor\\\n" "\t" "-x # enable output compressor\\\n" "\t" "-p \\\n" "\t" "-e \\\n" "\t" "-e , \\\n" "\t" "-m \\\n" "\t" "-m \\\n" "\t" "-C \\\n" "\t" "-c \\\n" "\t" "-M \\\n" "\t" "-M i,,,,, \\\n" "\t" "-M o,,,,, \\\n" "\t" "-M x,,,,, \\\n" "\t" "-F or ms \\\n" "\t" "-G or ms \\\n" "\t" "-E \\\n" "\t" "-N \\\n" "\t" "-H \\\n" "\t" "-o \\\n" "\t" "-J \\\n" "\t" "-k \\\n" "\t" "-t vdsp.ctl \n" "\t" "Left channel = 0\n" "\t" "Right channel = 1\n" "\t" "Max channels = %d\n", VMAX_CHAN); exit(EX_USAGE); } static void init_compressor(struct virtual_profile *pvp) { int x; memset(&pvp->rx_compressor_param, 0, sizeof(pvp->rx_compressor_param)); pvp->rx_compressor_param.knee = 85; pvp->rx_compressor_param.attack = 3; pvp->rx_compressor_param.decay = 20; for (x = 0; x != VMAX_CHAN; x++) pvp->rx_compressor_gain[x] = 1.0; } static void init_mapping(struct virtual_profile *pvp) { int x; for (x = 0; x != VMAX_CHAN; x++) { pvp->rx_src[x] = x; pvp->tx_dst[x] = x; } } static void init_sndstat(vprofile_t *ptr) { int err; nvlist_t *nvl; nvlist_t *di = NULL, *dichild = NULL; struct sndstioc_nv_arg arg; unsigned int min_rate, max_rate; nvl = nvlist_create(0); if (nvl == NULL) { warn("Failed to create nvlist"); goto done; } di = nvlist_create(0); if (di == NULL) { warn("Failed to create nvlist"); goto done; } dichild = nvlist_create(0); if (dichild == NULL) { warn("Failed to create nvlist"); goto done; } nvlist_add_string(di, SNDST_DSPS_PROVIDER, "virtual_oss"); nvlist_add_string(di, SNDST_DSPS_DESC, "virtual_oss device"); nvlist_add_number(di, SNDST_DSPS_PCHAN, 1); nvlist_add_number(di, SNDST_DSPS_RCHAN, 1); min_rate = 8000; max_rate = voss_dsp_sample_rate; if (voss_libsamplerate_enable == 0 || min_rate > max_rate) min_rate = max_rate; if (voss_libsamplerate_enable != 0 && max_rate < 96000) max_rate = 96000; nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_RATE, min_rate); nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_RATE, max_rate); nvlist_add_number(dichild, SNDST_DSPS_INFO_FORMATS, VSUPPORTED_AFMT); nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_CHN, ptr->channels); nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_CHN, ptr->channels); nvlist_add_nvlist(di, SNDST_DSPS_INFO_PLAY, dichild); nvlist_add_nvlist(di, SNDST_DSPS_INFO_REC, dichild); nvlist_add_string(di, SNDST_DSPS_DEVNODE, ptr->oss_name); nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); if (nvlist_error(nvl)) { warn("Failed building nvlist"); goto done; } arg.buf = nvlist_pack(nvl, &arg.nbytes); if (arg.buf == NULL) { warn("Failed to pack nvlist"); goto done; } err = ioctl(ptr->fd_sta, SNDSTIOC_ADD_USER_DEVS, &arg); free(arg.buf); if (err != 0) { warn("Failed to issue ioctl(SNDSTIOC_ADD_USER_DEVS)"); goto done; } done: nvlist_destroy(di); nvlist_destroy(dichild); nvlist_destroy(nvl); } static const char * dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute, int tx_mute, int synchronized, int is_client) { vprofile_t *ptr; struct cuse_dev *pdev; int x; rx_mute = rx_mute ? 1 : 0; tx_mute = tx_mute ? 1 : 0; pol = pol ? 1 : 0; /* Range check amplitude argument. */ for (x = 0; x != 2; x++) { if (pamp[x] < -63) pamp[x] = -63; else if (pamp[x] > 63) pamp[x] = 63; } ptr = malloc(sizeof(*ptr)); if (ptr == NULL) return ("Out of memory"); memcpy(ptr, pvp, sizeof(*ptr)); ptr->synchronized = synchronized; ptr->fd_sta = -1; TAILQ_INIT(&ptr->head); for (x = 0; x != ptr->channels; x++) { ptr->tx_mute[x] = tx_mute; ptr->rx_mute[x] = rx_mute; ptr->tx_shift[x] = pamp[1]; ptr->rx_shift[x] = pamp[0]; ptr->tx_pol[x] = pol; ptr->rx_pol[x] = pol; } /* create DSP device */ if (ptr->oss_name[0] != 0) { /* * Detect /dev/dsp creation and try to disable system * basename cloning automatically: */ if (strcmp(ptr->oss_name, "dsp") == 0) system("sysctl hw.snd.basename_clone=0"); /* create DSP character device */ pdev = cuse_dev_create(&vclient_oss_methods, ptr, NULL, 0, 0, voss_dsp_perm, ptr->oss_name); if (pdev == NULL) { free(ptr); return ("Could not create CUSE DSP device"); } /* register to sndstat */ ptr->fd_sta = open("/dev/sndstat", O_WRONLY); if (ptr->fd_sta < 0) { warn("Could not open /dev/sndstat"); } else { init_sndstat(ptr); } } /* create WAV device */ if (ptr->wav_name[0] != 0) { pdev = cuse_dev_create(&vclient_wav_methods, ptr, NULL, 0, 0, voss_dsp_perm, ptr->wav_name); if (pdev == NULL) { free(ptr); return ("Could not create CUSE WAV device"); } } atomic_lock(); if (is_client) TAILQ_INSERT_TAIL(&virtual_profile_client_head, ptr, entry); else TAILQ_INSERT_TAIL(&virtual_profile_loopback_head, ptr, entry); atomic_unlock(); voss_dups++; /* need new names next time */ memset(pvp->oss_name, 0, sizeof(pvp->oss_name)); memset(pvp->wav_name, 0, sizeof(pvp->wav_name)); /* need to set new filter sizes */ pvp->rx_filter_size = 0; pvp->tx_filter_size = 0; /* need to specify new HTTP parameters next time */ pvp->http.host = NULL; pvp->http.port = NULL; pvp->http.nstate = 0; pvp->http.rtp_ifname = NULL; pvp->http.rtp_port = NULL; /* need to specify new amplification next time */ pamp[0] = 0; pamp[1] = 0; /* need to set new compressor parameters next time */ init_compressor(pvp); return (voss_httpd_start(ptr)); } static void virtual_pipe(int sig __unused) { voss_dsp_tx_refresh = 1; voss_dsp_rx_refresh = 1; } static void virtual_cuse_hup(int sig __unused) { atomic_wakeup(); } static void * virtual_cuse_process(void *arg __unused) { signal(SIGHUP, &virtual_cuse_hup); while (1) { if (cuse_wait_and_process() != 0) break; } return (NULL); } static void virtual_cuse_init_profile(struct virtual_profile *pvp) { memset(pvp, 0, sizeof(*pvp)); init_compressor(pvp); init_mapping(pvp); } static void virtual_sig_exit(int sig __unused) { voss_exit = 1; } static const char * parse_options(int narg, char **pparg, int is_main) { const char *ptr; int a, b, c; int val; int idx; int type; int opt_mute[2] = {0, 0}; int opt_amp[2] = {0, 0}; int opt_pol = 0; const char *optstr; struct virtual_profile profile; struct rtprio rtp; float samples_ms; if (is_main) optstr = "N:J:k:H:o:F:G:w:e:p:a:C:c:r:b:f:g:x:i:m:M:d:l:L:s:t:h?O:P:Q:R:SBD:E:"; else optstr = "F:G:w:e:p:a:c:b:f:m:M:d:l:L:s:O:P:R:E:"; virtual_cuse_init_profile(&profile); /* reset getopt parsing */ optreset = 1; optind = 1; while ((c = getopt(narg, pparg, optstr)) != -1) { switch (c) { case 'B': voss_do_background = 1; break; case 'D': voss_pid_path = optarg; break; case 'C': if (voss_mix_channels != 0) { return ("The -C argument may only be used once"); } voss_mix_channels = atoi(optarg); if (voss_mix_channels >= VMAX_CHAN) { return ("Number of mixing channels is too high"); } break; case 'a': switch (optarg[0]) { case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': opt_amp[0] = -(opt_amp[1] = atoi(optarg)); break; case 'i': if (optarg[1] != ',') return ("Expected comma after 'i'"); opt_amp[0] = atoi(optarg + 2); break; case 'o': if (optarg[1] != ',') return ("Expected comma after 'o'"); opt_amp[1] = atoi(optarg + 2); break; default: return ("Invalid syntax for amplitude argument"); } break; case 'E': voss_is_recording = (atoi(optarg) != 0); break; case 'e': idx = 0; ptr = optarg; memset(opt_mute, 0, sizeof(opt_mute)); while (1) { c = *ptr++; if (c == ',' || c == 0) { idx++; if (c == 0) break; continue; } if (idx < 2 && c >= '0' && c <= '1') { opt_mute[idx] = c - '0'; } else { return ("Invalid -e parameter"); } } switch (idx) { case 1: opt_mute[1] = opt_mute[0]; break; case 2: break; default: return ("Invalid -e parameter"); } break; case 'p': opt_pol = atoi(optarg); break; case 'c': profile.channels = atoi(optarg); if (profile.channels == 0) return ("Number of channels is zero"); if (profile.channels > VMAX_CHAN) return ("Number of channels is too high"); break; case 'r': voss_dsp_sample_rate = atoi(optarg); if (voss_dsp_sample_rate < 8000) return ("Sample rate is too low, 8000 Hz"); if (voss_dsp_sample_rate > 0xFFFFFF) return ("Sample rate is too high"); break; case 'i': memset(&rtp, 0, sizeof(rtp)); rtp.type = RTP_PRIO_REALTIME; rtp.prio = atoi(optarg); if (rtprio(RTP_SET, getpid(), &rtp) != 0) printf("Cannot set realtime priority\n"); break; case 'b': profile.bits = atoi(optarg); switch (profile.bits) { case 8: case 16: case 24: case 32: break; default: return ("Invalid number of sample bits"); } break; case 'g': if (profile.rx_compressor_param.enabled) return ("Compressor already enabled for this device"); if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 || a < VIRTUAL_OSS_KNEE_MIN || a > VIRTUAL_OSS_KNEE_MAX || b < VIRTUAL_OSS_ATTACK_MIN || b > VIRTUAL_OSS_ATTACK_MAX || c < VIRTUAL_OSS_DECAY_MIN || c > VIRTUAL_OSS_DECAY_MAX) return ("Invalid device compressor argument(s)"); profile.rx_compressor_param.enabled = 1; profile.rx_compressor_param.knee = a; profile.rx_compressor_param.attack = b; profile.rx_compressor_param.decay = c; break; case 'x': if (voss_output_compressor_param.enabled) return ("Compressor already enabled for output"); if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 || a < VIRTUAL_OSS_KNEE_MIN || a > VIRTUAL_OSS_KNEE_MAX || b < VIRTUAL_OSS_ATTACK_MIN || b > VIRTUAL_OSS_ATTACK_MAX || c < VIRTUAL_OSS_DECAY_MIN || c > VIRTUAL_OSS_DECAY_MAX) return ("Invalid output compressor argument(s)"); voss_output_compressor_param.enabled = 1; voss_output_compressor_param.knee = a; voss_output_compressor_param.attack = b; voss_output_compressor_param.decay = c; break; case 'f': case 'O': case 'P': case 'R': if (voss_dsp_sample_rate == 0 || voss_dsp_samples == 0) return ("Missing -r or -s parameters"); if (voss_dsp_bits == 0) { if (profile.bits == 0) return ("Missing -b parameter"); voss_dsp_bits = profile.bits; } if (voss_dsp_max_channels == 0) { if (profile.channels == 0) return ("Missing -c parameter"); voss_dsp_max_channels = profile.channels; } if (c == 'f' || c == 'R') { if (strlen(optarg) > VMAX_STRING - 1) return ("Device name too long"); strncpy(voss_dsp_rx_device, optarg, sizeof(voss_dsp_rx_device)); voss_rx_backend_refresh(); voss_dsp_rx_refresh = 1; } if (c == 'f' || c == 'P' || c == 'O') { if (strlen(optarg) > VMAX_STRING - 1) return ("Device name too long"); strncpy(voss_dsp_tx_device, optarg, sizeof(voss_dsp_tx_device)); voss_tx_backend_refresh(); voss_dsp_tx_refresh = 1; if (c == 'O' && voss_has_synchronization == 0) voss_has_synchronization++; } break; case 'w': if (strlen(optarg) > VMAX_STRING - 1) return ("Device name too long"); strncpy(profile.wav_name, optarg, sizeof(profile.wav_name)); break; case 'd': if (strlen(optarg) > VMAX_STRING - 1) return ("Device name too long"); strncpy(profile.oss_name, optarg, sizeof(profile.oss_name)); if (profile.bits == 0 || voss_dsp_sample_rate == 0 || profile.channels == 0 || voss_dsp_samples == 0) return ("Missing -b, -r, -c or -s parameters"); val = (voss_dsp_samples * profile.bits * profile.channels) / 8; if (val <= 0 || val >= (1024 * 1024)) return ("-s option value is too big"); ptr = dup_profile(&profile, opt_amp, opt_pol, opt_mute[0], opt_mute[1], 0, 1); if (ptr != NULL) return (ptr); break; case 'L': case 'l': if (strlen(optarg) > VMAX_STRING - 1) return ("Device name too long"); strncpy(profile.oss_name, optarg, sizeof(profile.oss_name)); if (profile.bits == 0 || voss_dsp_sample_rate == 0 || profile.channels == 0 || voss_dsp_samples == 0) return ("Missing -b, -r, -r or -s parameters"); val = (voss_dsp_samples * profile.bits * profile.channels) / 8; if (val <= 0 || val >= (1024 * 1024)) return ("-s option value is too big"); ptr = dup_profile(&profile, opt_amp, opt_pol, opt_mute[0], opt_mute[1], c == 'L', 0); if (ptr != NULL) return (ptr); break; case 'S': voss_libsamplerate_enable = 1; break; case 'Q': c = atoi(optarg); switch (c) { case 0: voss_libsamplerate_quality = SRC_SINC_BEST_QUALITY; break; case 1: voss_libsamplerate_quality = SRC_SINC_MEDIUM_QUALITY; break; default: voss_libsamplerate_quality = SRC_SINC_FASTEST; break; } break; case 's': if (voss_dsp_samples != 0) return ("-s option may only be used once"); if (profile.bits == 0 || profile.channels == 0) return ("-s option requires -b and -c options"); if (strlen(optarg) > 2 && sscanf(optarg, "%f", &samples_ms) == 1 && strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { if (voss_dsp_sample_rate == 0) return ("-s ms option requires -r option"); if (samples_ms < 0.125 || samples_ms >= 1000.0) return ("-s ms option has invalid value"); voss_dsp_samples = voss_dsp_sample_rate * samples_ms / 1000.0; } else { voss_dsp_samples = atoi(optarg); } if (voss_dsp_samples >= (1U << 24)) return ("-s option requires a non-zero positive value"); break; case 't': if (voss_ctl_device[0]) return ("-t parameter may only be used once"); strlcpy(voss_ctl_device, optarg, sizeof(voss_ctl_device)); break; case 'm': ptr = optarg; val = 0; idx = 0; init_mapping(&profile); while (1) { c = *ptr++; if (c == ',' || c == 0) { if (idx >= (2 * VMAX_CHAN)) return ("Too many channels in mask"); if (idx & 1) profile.tx_dst[idx / 2] = val; else profile.rx_src[idx / 2] = val; if (c == 0) break; val = 0; idx++; continue; } if (c >= '0' && c <= '9') { val *= 10; val += c - '0'; } } break; case 'M': ptr = optarg; type = *ptr; if (type == 'i' || type == 'o' || type == 'x') { vmonitor_t *pvm; int src = 0; int dst = 0; int pol = 0; int mute = 0; int amp = 0; int neg; ptr++; if (*ptr == ',') ptr++; else if (type == 'i') return ("Expected comma after 'i'"); else if (type == 'o') return ("Expected comma after 'o'"); else return ("Expected comma after 'x'"); val = 0; neg = 0; idx = 0; while (1) { c = *ptr++; if (c == '-') { neg = 1; continue; } if (c == ',' || c == 0) { switch (idx) { case 0: src = val; break; case 1: dst = val; break; case 2: pol = val ? 1 : 0; break; case 3: mute = val ? 1 : 0; break; case 4: if (val > 31) { return ("Absolute amplitude " "for -M parameter " "cannot exceed 31"); } amp = neg ? -val : val; break; default: break; } if (c == 0) break; val = 0; neg = 0; idx++; continue; } if (c >= '0' && c <= '9') { val *= 10; val += c - '0'; } } if (idx < 4) return ("Too few parameters for -M"); pvm = vmonitor_alloc(&idx, (type == 'i') ? &virtual_monitor_input : (type == 'x') ? &virtual_monitor_local : &virtual_monitor_output); if (pvm == NULL) return ("Out of memory"); pvm->src_chan = src; pvm->dst_chan = dst; pvm->pol = pol; pvm->mute = mute; pvm->shift = amp; } else { return ("Invalid -M parameter"); } break; case 'F': if (strlen(optarg) > 2 && sscanf(optarg, "%f", &samples_ms) == 1 && strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { if (voss_dsp_sample_rate == 0) return ("-F ms option requires -r option"); if (samples_ms < 0.125 || samples_ms >= 1000.0) return ("-F ms option has invalid value"); profile.rx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0; } else { profile.rx_filter_size = atoi(optarg); } /* make value power of two */ while ((profile.rx_filter_size - 1) & profile.rx_filter_size) profile.rx_filter_size += ~(profile.rx_filter_size - 1) & profile.rx_filter_size; /* range check */ if (profile.rx_filter_size > VIRTUAL_OSS_FILTER_MAX) return ("Invalid -F parameter is out of range"); break; case 'G': if (strlen(optarg) > 2 && sscanf(optarg, "%f", &samples_ms) == 1 && strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { if (voss_dsp_sample_rate == 0) return ("-G ms option requires -r option"); if (samples_ms < 0.125 || samples_ms >= 1000.0) return ("-G ms option has invalid value"); profile.tx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0; } else { profile.tx_filter_size = atoi(optarg); } /* make value power of two */ while ((profile.tx_filter_size - 1) & profile.tx_filter_size) profile.tx_filter_size += ~(profile.tx_filter_size - 1) & profile.tx_filter_size; /* range check */ if (profile.tx_filter_size > VIRTUAL_OSS_FILTER_MAX) return ("Invalid -F parameter is out of range"); break; case 'N': profile.http.nstate = atoi(optarg); break; case 'H': profile.http.host = optarg; if (profile.http.port == NULL) profile.http.port = "80"; if (profile.http.nstate == 0) profile.http.nstate = 1; break; case 'o': profile.http.port = optarg; break; case 'J': profile.http.rtp_ifname = optarg; if (profile.http.rtp_port == NULL) profile.http.rtp_port = "8080"; break; case 'k': profile.http.rtp_port = optarg; break; default: if (is_main) usage(); else return ("Invalid option detected"); break; } } return (NULL); } static void create_threads(void) { int idx; /* Give each DSP device 4 threads */ voss_ntds = voss_dups * 4; voss_tds = malloc(voss_ntds * sizeof(pthread_t)); if (voss_tds == NULL) err(1, "malloc"); for (idx = 0; idx < voss_ntds; idx++) { if (pthread_create(&voss_tds[idx], NULL, &virtual_cuse_process, NULL) != 0) err(1, "pthread_create"); } /* Reset until next time called */ voss_dups = 0; } static void destroy_threads(void) { int idx; for (idx = 0; idx < voss_ntds; idx++) pthread_cancel(voss_tds[idx]); free(voss_tds); } void voss_add_options(char *str) { static char name[] = { "virtual_oss" }; const char sep[] = "\t "; const char *ptrerr; char *parg[64]; char *word; char *brkt; int narg = 0; parg[narg++] = name; for (word = strtok_r(str, sep, &brkt); word != NULL; word = strtok_r(NULL, sep, &brkt)) { if (narg >= 64) { ptrerr = "Too many arguments"; goto done; } parg[narg++] = word; } ptrerr = parse_options(narg, parg, 0); done: if (ptrerr != NULL) { strlcpy(str, ptrerr, VIRTUAL_OSS_OPTIONS_MAX); } else { str[0] = 0; create_threads(); } } int main(int argc, char **argv) { const char *ptrerr; struct sigaction sa; struct cuse_dev *pdev = NULL; TAILQ_INIT(&virtual_profile_client_head); TAILQ_INIT(&virtual_profile_loopback_head); TAILQ_INIT(&virtual_monitor_input); TAILQ_INIT(&virtual_monitor_output); TAILQ_INIT(&virtual_monitor_local); atomic_init(); /* automagically load the cuse.ko module, if any */ if (feature_present("cuse") == 0) { if (system("kldload cuse") == -1) warn("Failed to kldload cuse"); } if (cuse_init() != 0) errx(EX_USAGE, "Could not connect to cuse module"); signal(SIGPIPE, &virtual_pipe); memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sa.sa_handler = virtual_sig_exit; if (sigaction(SIGINT, &sa, NULL) < 0) err(1, "sigaction(SIGINT)"); if (sigaction(SIGTERM, &sa, NULL) < 0) err(1, "sigaction(SIGTERM)"); ptrerr = parse_options(argc, argv, 1); if (ptrerr != NULL) errx(EX_USAGE, "%s", ptrerr); if (voss_dsp_rx_device[0] == 0 || voss_dsp_tx_device[0] == 0) errx(EX_USAGE, "Missing -f argument"); /* use DSP channels as default */ if (voss_mix_channels == 0) voss_mix_channels = voss_dsp_max_channels; if (voss_mix_channels > voss_dsp_max_channels) voss_max_channels = voss_mix_channels; else voss_max_channels = voss_dsp_max_channels; if (voss_dsp_samples > (voss_dsp_sample_rate / 4)) errx(EX_USAGE, "Too many buffer samples given by -s argument"); /* check if daemon mode is requested */ if (voss_do_background != 0 && daemon(0, 0) != 0) errx(EX_SOFTWARE, "Cannot become daemon"); if (voss_pid_path != NULL) { int pidfile = open(voss_pid_path, O_RDWR | O_CREAT | O_TRUNC, 0600); pid_t mypid = getpid(); char mypidstr[8]; snprintf(mypidstr, sizeof(mypidstr), "%d\n", mypid); if (pidfile < 0) errx(EX_SOFTWARE, "Cannot create PID file '%s'", voss_pid_path); if (write(pidfile, mypidstr, strlen(mypidstr)) != (ssize_t)strlen(mypidstr)) errx(EX_SOFTWARE, "Cannot write PID file"); close(pidfile); } /* setup audio delay unit */ voss_ad_init(voss_dsp_sample_rate); /* Create CTL device */ if (voss_ctl_device[0] != 0) { pdev = cuse_dev_create(&vctl_methods, NULL, NULL, 0, 0, voss_dsp_perm, voss_ctl_device); if (pdev == NULL) errx(EX_USAGE, "Could not create '/dev/%s'", voss_ctl_device); voss_dups++; } /* Create worker threads */ create_threads(); /* Run DSP threads */ virtual_oss_process(NULL); destroy_threads(); if (voss_ctl_device[0] != 0) cuse_dev_destroy(pdev); return (0); } diff --git a/usr.sbin/virtual_oss/virtual_oss/utils.h b/usr.sbin/virtual_oss/virtual_oss/utils.h deleted file mode 100644 index f0998dc75dae..000000000000 --- a/usr.sbin/virtual_oss/virtual_oss/utils.h +++ /dev/null @@ -1,31 +0,0 @@ -/*- - * Copyright (c) 2019 Hans Petter Selasky - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef _VIRTUAL_UTILS_H_ -#define _VIRTUAL_UTILS_H_ - -int bt_speaker_main(int argc, char **argv); - -#endif /* _VIRTUAL_UTILS_H_ */