diff options
Diffstat (limited to 'kernel/drv/oss_audioloop')
-rw-r--r-- | kernel/drv/oss_audioloop/.config | 2 | ||||
-rw-r--r-- | kernel/drv/oss_audioloop/.devices | 1 | ||||
-rw-r--r-- | kernel/drv/oss_audioloop/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_audioloop/.params | 4 | ||||
-rw-r--r-- | kernel/drv/oss_audioloop/oss_audioloop.c | 919 | ||||
-rw-r--r-- | kernel/drv/oss_audioloop/oss_audioloop.man | 80 |
6 files changed, 1007 insertions, 0 deletions
diff --git a/kernel/drv/oss_audioloop/.config b/kernel/drv/oss_audioloop/.config new file mode 100644 index 0000000..828bd4f --- /dev/null +++ b/kernel/drv/oss_audioloop/.config @@ -0,0 +1,2 @@ +bus=VIRTUAL +targetcpu=any diff --git a/kernel/drv/oss_audioloop/.devices b/kernel/drv/oss_audioloop/.devices new file mode 100644 index 0000000..fd543fb --- /dev/null +++ b/kernel/drv/oss_audioloop/.devices @@ -0,0 +1 @@ +oss_audioloop AUDIOLOOP OSS loopback audio driver diff --git a/kernel/drv/oss_audioloop/.name b/kernel/drv/oss_audioloop/.name new file mode 100644 index 0000000..4d501dc --- /dev/null +++ b/kernel/drv/oss_audioloop/.name @@ -0,0 +1 @@ +OSS loopback audio driver diff --git a/kernel/drv/oss_audioloop/.params b/kernel/drv/oss_audioloop/.params new file mode 100644 index 0000000..b8c8f7b --- /dev/null +++ b/kernel/drv/oss_audioloop/.params @@ -0,0 +1,4 @@ +int audioloop_instances=1; +/* + * audioloop_instances: Number of instances (client/server pairs) to create. + */ diff --git a/kernel/drv/oss_audioloop/oss_audioloop.c b/kernel/drv/oss_audioloop/oss_audioloop.c new file mode 100644 index 0000000..c51cc48 --- /dev/null +++ b/kernel/drv/oss_audioloop/oss_audioloop.c @@ -0,0 +1,919 @@ +/* + * Purpose: OSS audio loopback (virtual) driver + * + * Description: + * OSS audio loopback driver is a virtual/pseudo driver that can be used + * for example to user land based virtual audio devices. + * + * Each audioloop instance has two sides or endpoints. The server side is + * typically used by the application that implements the virtual audio device. + * Client side in turn is used by any audio application that wants to record or + * play audio. Server side device must be open before the client side can be + * opened. + * + * + * + * CAUTION! Certain portc fields (mutex, rate/format) are only available + * on the server side portc structure. Care must be taken. + * + * + * + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "oss_audioloop_cfg.h" + +#define MAX_RATE 192000 +#define MAX_CHANNELS 64 +#define SUPPORTED_FORMATS (AFMT_S16_NE|AFMT_S32_NE) + +extern int audioloop_instances; /* Config option */ + +#define MAX_INSTANCES 16 + +typedef struct _audioloop_devc_t audioloop_devc_t; +typedef struct _audioloop_portc_t audioloop_portc_t; + +struct _audioloop_portc_t +{ + audioloop_devc_t *devc; + audioloop_portc_t *peer; + int audio_dev; + int open_mode; + int port_type; +#define PT_CLIENT 1 +#define PT_SERVER 2 + int instance; + + /* State variables */ + int input_triggered, output_triggered; + oss_wait_queue_t *wq; + + /* Server side (only) fields */ + int rate; + int channels; + unsigned int fmt, fmt_bytes; + oss_mutex_t mutex; + timeout_id_t timeout_id; +}; + +struct _audioloop_devc_t +{ + oss_device_t *osdev; + oss_mutex_t mutex; + + int num_instances; + + audioloop_portc_t *client_portc[MAX_INSTANCES]; + audioloop_portc_t *server_portc[MAX_INSTANCES]; +}; + +#define MAX_ATTACH_COUNT 1 +static audioloop_devc_t audioloop_devices[MAX_ATTACH_COUNT]; +static int attach_count; + +static void +transfer_audio (audioloop_portc_t * server_portc, dmap_t * dmap_from, + dmap_t * dmap_to) +{ + int l = dmap_from->fragment_size; + unsigned char *fromp, *top; + + if (dmap_to->fragment_size != l) + { + cmn_err (CE_WARN, "Fragment size mismatch (%d != %d)\n", + dmap_to->fragment_size, l); + + /* Perform emergency stop */ + server_portc->input_triggered = 0; + server_portc->output_triggered = 0; + server_portc->peer->input_triggered = 0; + server_portc->peer->output_triggered = 0; + return; + } + + fromp = + dmap_from->dmabuf + (dmap_from->byte_counter % dmap_from->bytes_in_use); + top = dmap_to->dmabuf + (dmap_to->byte_counter % dmap_to->bytes_in_use); + + memcpy (top, fromp, l); + +} + +static void +handle_input (audioloop_portc_t * server_portc) +{ + audioloop_portc_t *client_portc = server_portc->peer; + + if (client_portc->output_triggered) + { + transfer_audio (server_portc, + audio_engines[client_portc->audio_dev]->dmap_out, + audio_engines[server_portc->audio_dev]->dmap_in); + oss_audio_outputintr (client_portc->audio_dev, 0); + } + + oss_audio_inputintr (server_portc->audio_dev, 0); +} + +static void +handle_output (audioloop_portc_t * server_portc) +{ + audioloop_portc_t *client_portc = server_portc->peer; + + if (client_portc->input_triggered) + { + transfer_audio (server_portc, + audio_engines[server_portc->audio_dev]->dmap_out, + audio_engines[client_portc->audio_dev]->dmap_in); + oss_audio_inputintr (client_portc->audio_dev, 0); + } + + oss_audio_outputintr (server_portc->audio_dev, 0); +} + +static void +audioloop_cb (void *pc) +{ +/* + * This timer callback routine will get called 100 times/second. It handles + * movement of audio data between the client and server sides. + */ + audioloop_portc_t *server_portc = pc; + int tmout = OSS_HZ / 100; + + if (tmout < 1) + tmout = 1; + + server_portc->timeout_id = 0; /* No longer valid */ + + if (server_portc->input_triggered) + handle_input (server_portc); + + if (server_portc->output_triggered) + handle_output (server_portc); + + /* Retrigger timer callback */ + if (server_portc->input_triggered || server_portc->output_triggered) + server_portc->timeout_id = timeout (audioloop_cb, server_portc, tmout); +} + +static int +audioloop_check_input (int dev) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + if (!portc->peer->output_triggered) + { + return OSS_ECONNRESET; + } + return 0; +} + +static int +audioloop_check_output (int dev) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + if (!portc->peer->input_triggered) + { + return OSS_ECONNRESET; + } + + if (portc->peer->open_mode == 0) + return OSS_EIO; + return 0; +} + +static void +setup_sample_format (audioloop_portc_t * portc) +{ + adev_t *adev; + int fragsize, frame_size; + + frame_size = portc->channels * portc->fmt_bytes; + if (frame_size == 0) + frame_size = 4; + + fragsize = (portc->rate * frame_size) / 100; /* Number of bytes/fragment (100Hz) */ + portc->rate = fragsize * 100 / frame_size; + +/* Setup the server side */ + adev = audio_engines[portc->audio_dev]; + adev->min_block = adev->max_block = fragsize; + +/* Setup the client side */ + adev = audio_engines[portc->peer->audio_dev]; + adev->min_block = adev->max_block = fragsize; + + adev->max_rate = adev->min_rate = portc->rate; + adev->iformat_mask = portc->fmt; + adev->oformat_mask = portc->fmt; + adev->xformat_mask = portc->fmt; + adev->min_channels = adev->max_channels = portc->channels; +} + +static int +audioloop_server_set_rate (int dev, int arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->rate; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return portc->rate; + + if (arg < 5000) + arg = 5000; + if (arg > MAX_RATE) + arg = MAX_RATE; + + /* Force the sample rate to be multiple of 100 */ + arg = (arg / 100) * 100; + + portc->rate = arg; + + setup_sample_format (portc); + + return portc->rate = arg; +} + +/*ARGSUSED*/ +static int +audioloop_client_set_rate (int dev, int arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + return portc->peer->rate; +} + +static short +audioloop_server_set_channels (int dev, short arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->channels; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return portc->channels; + + if (arg < 1) + arg = 1; + if (arg > MAX_CHANNELS) + arg = MAX_CHANNELS; + + portc->channels = arg; + + setup_sample_format (portc); + + return portc->channels; +} + +/*ARGSUSED*/ +static short +audioloop_client_set_channels (int dev, short arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + return portc->peer->channels; /* Server side channels */ +} + +static unsigned int +audioloop_server_set_format (int dev, unsigned int arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->fmt; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return portc->fmt; + + switch (arg) + { + case AFMT_S16_NE: + portc->fmt_bytes = 2; + break; + + case AFMT_S32_NE: + portc->fmt_bytes = 4; + break; + + default: /* Unsupported format */ + arg = AFMT_S16_NE; + portc->fmt_bytes = 2; + + } + + portc->fmt = arg; + + setup_sample_format (portc); + + return portc->fmt; +} + +/*ARGSUSED*/ +static unsigned int +audioloop_client_set_format (int dev, unsigned int arg) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + return portc->peer->fmt; /* Server side sample format */ +} + +static void audioloop_trigger (int dev, int state); + +static void +audioloop_reset (int dev) +{ + audioloop_trigger (dev, 0); +} + +/*ARGSUSED*/ +static int +audioloop_server_open (int dev, int mode, int open_flags) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + audioloop_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + adev_t *adev; + + if ((mode & OPEN_READ) && (mode & OPEN_WRITE)) + return OSS_EACCES; + + if (portc == NULL || portc->peer == NULL) + return OSS_ENXIO; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + +/* + * Update client device flags + */ + adev = audio_engines[portc->peer->audio_dev]; + adev->flags &= ~(ADEV_NOINPUT | ADEV_NOOUTPUT); + if (!(mode & OPEN_READ)) + adev->flags |= ADEV_NOOUTPUT; + if (!(mode & OPEN_WRITE)) + adev->flags |= ADEV_NOINPUT; + adev->enabled = 1; /* Enable client side */ + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_client_open (int dev, int mode, int open_flags) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + audioloop_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + + if (portc == NULL || portc->peer == NULL) + return OSS_ENXIO; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static void +audioloop_server_close (int dev, int mode) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + audioloop_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + audio_engines[portc->peer->audio_dev]->enabled = 0; /* Disable client side */ + portc->open_mode = 0; + + /* Stop the client side */ + portc->peer->input_triggered = 0; + portc->peer->output_triggered = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static void +audioloop_client_close (int dev, int mode) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + audioloop_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->open_mode = 0; + + /* Stop the server side */ + portc->peer->input_triggered = 0; + portc->peer->output_triggered = 0; + + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +audioloop_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + switch (cmd) + { + case SNDCTL_GETLABEL: + { + /* + * Return an empty string so that this feature can be tested. + * Complete functionality is to be implemented later. + */ + oss_label_t *s = (oss_label_t *) arg; + memset (s, 0, sizeof (oss_label_t)); + return 0; + } + break; + + case SNDCTL_GETSONG: + { + /* + * Return an empty string so that this feature can be tested. + * Complete functionality is to be implemented later. + */ + oss_longname_t *s = (oss_longname_t *) arg; + memset (s, 0, sizeof (oss_longname_t)); + return 0; + } + break; + } + + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static void +audioloop_output_block (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +/*ARGSUSED*/ +static void +audioloop_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +static void +audioloop_trigger (int dev, int state) +{ + audioloop_portc_t *portc = audio_engines[dev]->portc; + + if (portc->open_mode & OPEN_READ) /* Handle input */ + { + portc->input_triggered = !!(state & OPEN_READ); + if (!portc->input_triggered) + portc->peer->output_triggered = 0; + } + + if (portc->open_mode & OPEN_WRITE) /* Handle output */ + { + portc->output_triggered = !!(state & OPEN_WRITE); + if (!portc->output_triggered) + portc->peer->input_triggered = 0; + } + + if (portc->output_triggered || portc->input_triggered) /* Something is going on */ + { + int tmout = OSS_HZ / 100; + + if (tmout < 1) + tmout = 1; + + if (portc->port_type != PT_SERVER) + portc = portc->peer; /* Switch to the server side */ + + if (portc->output_triggered || portc->input_triggered) /* Something is going on */ + if (portc->timeout_id == 0) + portc->timeout_id = timeout (audioloop_cb, portc, tmout); + } + else + { + if (portc->port_type == PT_SERVER) + if (portc->timeout_id != 0) + { + untimeout (portc->timeout_id); + portc->timeout_id = 0; + } + } +} + +/*ARGSUSED*/ +static int +audioloop_server_prepare_for_input (int dev, int bsize, int bcount) +{ + oss_native_word flags; + unsigned int status; + + audioloop_portc_t *portc = audio_engines[dev]->portc; + + MUTEX_ENTER_IRQDISABLE (portc->mutex, flags); + portc->input_triggered = 0; + + /* + * Wake the client which may be in waiting in close() + */ + oss_wakeup (portc->peer->wq, &portc->mutex, &flags, POLLOUT | POLLWRNORM); + + if (!(portc->peer->open_mode & OPEN_WRITE)) + { + /* Sleep until the client side becomes ready */ + oss_sleep (portc->wq, &portc->mutex, 0, &flags, &status); + if (status & WK_SIGNAL) + { + MUTEX_EXIT_IRQRESTORE (portc->mutex, flags); + return OSS_EINTR; + } + } + MUTEX_EXIT_IRQRESTORE (portc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_server_prepare_for_output (int dev, int bsize, int bcount) +{ + oss_native_word flags; + unsigned int status; + + audioloop_portc_t *portc = audio_engines[dev]->portc; + + MUTEX_ENTER_IRQDISABLE (portc->mutex, flags); + portc->output_triggered = 0; + + /* + * Wake the client which may be in waiting in close() + */ + oss_wakeup (portc->peer->wq, &portc->mutex, &flags, POLLIN | POLLRDNORM); + + if (!(portc->peer->open_mode & OPEN_READ)) + { + /* Sleep until the client side becomes ready */ + oss_sleep (portc->wq, &portc->mutex, 0, &flags, &status); + if (status & WK_SIGNAL) + { + MUTEX_EXIT_IRQRESTORE (portc->mutex, flags); + return OSS_EINTR; + } + } + MUTEX_EXIT_IRQRESTORE (portc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_client_prepare_for_input (int dev, int bsize, int bcount) +{ + oss_native_word flags; + audioloop_portc_t *portc = audio_engines[dev]->portc; + unsigned int status; + + MUTEX_ENTER_IRQDISABLE (portc->peer->mutex, flags); + portc->input_triggered = 0; + /* Wake the server side */ + oss_wakeup (portc->peer->wq, &portc->peer->mutex, &flags, + POLLIN | POLLRDNORM); + + /* + * Delay a moment so that the server side gets chance to reinit itself + * for next file/stream. + */ + oss_sleep (portc->wq, &portc->peer->mutex, OSS_HZ, &flags, &status); + MUTEX_EXIT_IRQRESTORE (portc->peer->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_client_prepare_for_output (int dev, int bsize, int bcount) +{ + oss_native_word flags; + audioloop_portc_t *portc = audio_engines[dev]->portc; + unsigned int status; + + MUTEX_ENTER_IRQDISABLE (portc->peer->mutex, flags); + portc->output_triggered = 0; + /* Wake the server side */ + oss_wakeup (portc->peer->wq, &portc->peer->mutex, &flags, + POLLIN | POLLRDNORM); + + /* + * Delay a moment so that the server side gets chance to reinit itself + * for next file/stream. + */ + oss_sleep (portc->wq, &portc->peer->mutex, OSS_HZ, &flags, &status); + MUTEX_EXIT_IRQRESTORE (portc->peer->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ +#define MY_BUFFSIZE (64*1024) + if (dmap->dmabuf != NULL) + return 0; + dmap->dmabuf_phys = 0; /* Not mmap() capable */ + dmap->dmabuf = KERNEL_MALLOC (MY_BUFFSIZE); + if (dmap->dmabuf == NULL) + return OSS_ENOSPC; + dmap->buffsize = MY_BUFFSIZE; + + return 0; +} + +/*ARGSUSED*/ +static int +audioloop_free_buffer (int dev, dmap_t * dmap, int direction) +{ + if (dmap->dmabuf == NULL) + return 0; + KERNEL_FREE (dmap->dmabuf); + + dmap->dmabuf = NULL; + return 0; +} + +#if 0 +static int +audioloop_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ +} +#endif + +static audiodrv_t audioloop_server_driver = { + audioloop_server_open, + audioloop_server_close, + audioloop_output_block, + audioloop_start_input, + audioloop_ioctl, + audioloop_server_prepare_for_input, + audioloop_server_prepare_for_output, + audioloop_reset, + NULL, + NULL, + NULL, + NULL, + audioloop_trigger, + audioloop_server_set_rate, + audioloop_server_set_format, + audioloop_server_set_channels, + NULL, + NULL, + audioloop_check_input, + audioloop_check_output, + audioloop_alloc_buffer, + audioloop_free_buffer, + NULL, + NULL, + NULL /* audioloop_get_buffer_pointer */ +}; + +static audiodrv_t audioloop_client_driver = { + audioloop_client_open, + audioloop_client_close, + audioloop_output_block, + audioloop_start_input, + audioloop_ioctl, + audioloop_client_prepare_for_input, + audioloop_client_prepare_for_output, + audioloop_reset, + NULL, + NULL, + NULL, + NULL, + audioloop_trigger, + audioloop_client_set_rate, + audioloop_client_set_format, + audioloop_client_set_channels, + NULL, + NULL, + audioloop_check_input, + audioloop_check_output, + audioloop_alloc_buffer, + audioloop_free_buffer, + NULL, + NULL, + NULL /* audioloop_get_buffer_pointer */ +}; + + +static int +install_server (audioloop_devc_t * devc, int num) +{ + audioloop_portc_t *portc; + char tmp[64], devname[16]; + int adev; + + int opts = + ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL | + ADEV_FIXEDRATE | ADEV_SPECIAL; + + if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL) + return OSS_ENOMEM; + memset (portc, 0, sizeof (*portc)); + + portc->devc = devc; + MUTEX_INIT (devc->osdev, portc->mutex, MH_DRV + 1); + if ((portc->wq = oss_create_wait_queue (devc->osdev, "audioloop")) == NULL) + { + cmn_err (CE_WARN, "Cannot create audioloop wait queue\n"); + return OSS_EIO; + } + + portc->instance = num; + portc->port_type = PT_SERVER; + + devc->server_portc[num] = portc; + + sprintf (devname, "server%d", num); + + sprintf (tmp, "Audio loopback %d server side", num); + + if ((adev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp, + &audioloop_server_driver, + sizeof (audiodrv_t), + opts, SUPPORTED_FORMATS, devc, -1, + devname)) < 0) + { + return adev; + } + + audio_engines[adev]->portc = portc; + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = MAX_RATE; + audio_engines[adev]->min_channels = 1; + audio_engines[adev]->caps |= PCM_CAP_HIDDEN; + audio_engines[adev]->max_channels = MAX_CHANNELS; + + portc->audio_dev = adev; + portc->rate = 48000; + portc->fmt = AFMT_S16_NE; + portc->fmt_bytes = 2; + portc->channels = 2; + + return 0; +} + + +static int +install_client (audioloop_devc_t * devc, int num) +{ + audioloop_portc_t *portc; + char tmp[64]; + int adev; + + int opts = + ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL | + ADEV_FIXEDRATE | ADEV_SPECIAL | ADEV_LOOP; + + if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL) + return OSS_ENOMEM; + memset (portc, 0, sizeof (*portc)); + + portc->devc = devc; + if ((portc->wq = oss_create_wait_queue (devc->osdev, "audioloop")) == NULL) + { + cmn_err (CE_WARN, "Cannot create audioloop wait queue\n"); + return OSS_EIO; + } + + portc->instance = num; + portc->port_type = PT_CLIENT; + + devc->client_portc[num] = portc; + + sprintf (tmp, "Audio loopback %d", num); + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp, + &audioloop_client_driver, + sizeof (audiodrv_t), + opts, SUPPORTED_FORMATS, devc, -1)) < 0) + { + return adev; + } + + audio_engines[adev]->portc = portc; + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = MAX_RATE; + audio_engines[adev]->min_channels = 1; + audio_engines[adev]->max_channels = MAX_CHANNELS;; + audio_engines[adev]->enabled = 0; /* Not enabled until server side is opened */ + + portc->audio_dev = adev; + + + return 0; +} + +int +oss_audioloop_attach (oss_device_t * osdev) +{ + audioloop_devc_t *devc; + int i; + + if (attach_count >= MAX_ATTACH_COUNT) + { + cmn_err (CE_WARN, "Attach limit reached (%d)\n", MAX_ATTACH_COUNT); + return 0; + } + + if (audioloop_instances < 1) + audioloop_instances = 1; + if (audioloop_instances > MAX_INSTANCES) + audioloop_instances = MAX_INSTANCES; + + DDB (cmn_err + (CE_CONT, "Initailzing audioloop %d instances\n", + audioloop_instances)); + devc = &audioloop_devices[0]; + + osdev->devc = devc; + devc->osdev = osdev; + MUTEX_INIT (osdev, devc->mutex, MH_DRV); + + oss_register_device (osdev, "audioloop"); + + for (i = 0; i < audioloop_instances; i++) + { + if (install_server (devc, i) < 0) + break; + + if (install_client (devc, i) < 0) + break; + + devc->client_portc[i]->peer = devc->server_portc[i]; + devc->server_portc[i]->peer = devc->client_portc[i]; + devc->num_instances = i + 1; + } + + return 1; +} + +int +oss_audioloop_detach (oss_device_t * osdev) +{ + audioloop_devc_t *devc = osdev->devc; + int i; + + if (oss_disable_device (osdev) < 0) + return 0; + + MUTEX_CLEANUP (devc->mutex); + + for (i = 0; i < devc->num_instances; i++) + { + MUTEX_CLEANUP (devc->server_portc[i]->mutex); + } + + oss_unregister_device (osdev); + + return 1; +} diff --git a/kernel/drv/oss_audioloop/oss_audioloop.man b/kernel/drv/oss_audioloop/oss_audioloop.man new file mode 100644 index 0000000..46932cb --- /dev/null +++ b/kernel/drv/oss_audioloop/oss_audioloop.man @@ -0,0 +1,80 @@ +NAME +oss_audioloop - Loopback audio driver. + +DESCRIPTION +The loopback audio driver makes it possible to create special purpose +virtual audio devices based on user land server processes. + +Loopback devices are driven by a timer interrupt and no real audio +hardware is required. + + INTRODUCTION +Audio loopback devices are like named pipes or pseudo terminals. They are +grouped in client and server device pairs. The server side device must be open +before the client side device can be opened. + +Loopback devices are typically used to implement server based special purpose +audio devices. This kind of server can for example transfer the audio data +played by the client application to some remote system using some VoIP +protocol. However the server application doesn't need to be any dedicated +server. Practically any audio application can be used as the server. + + SERVER SIDE DEVICE +The server side applications sets up the native sampling rate and sample format +(number of bits and channels). The server side device can be opened for input +(O_RDONLY) pr output (O_WRONLY). Opening for simultaneous input and output +(O_RDWR) is not permitted. + +The server application will automatically be paused at the moment it tries to +read or write audio data for the first time. It will be kept in sleep until the +client side application starts writing or reading data. This sleep period may +last forever and in some cases the first write/read call never returns. For +this reason it's not recommended to use GUI based audio applications as the +server. Note that this wait will occur even in the non-blocking +(O_NONBLOCK) mode (this is intentional feature and not a bug). + + CLIENT SIDE DEVICE +The client side device is typically used by any ordinary audio application. +There is nothing special in loopback devices. + +Since the loop is unidirectional the client side will be forced to be write +only if the server side device is open for recording and vice versa. + +The loop will use the sample rate and sample format (number of bits and +channels) set by the server side application. If the client uses different +settings then OSS will perform the required sampling rate and format conversions +automatically. + +COMPATIBILITY ISSUES +Audio loopback devices differ from "normal" audio devices because an +application is needed at the both ends of the loop. The loop device +will return a "Connection reset by peer" error (ECONNRESET) error. Applications +designed to be used as loopback based server applications can/should use this +error (returned by read or write) as an end-of-stream indication. + +OPTIONS + +o audioloop_instances: Specifies how many loopback client/server audio device + pairs to be created. + Values: 1-16 Default: 1 + +KNOWN PROBLEMS +o There is no mixer (volume control) related with loopback audio devices. This +may prevent poorly designed audio applications (that expect/require a mixer) +from working. There is no workaround available. + +o The server side application will wait until the client side application +starts using it. This wait may last forever which in turn may cause +unrecoverable (network) problems with some applications. + +o Loopback devices may return "Connection reset by peer" error when the +reote side of the loop disconnects the device. Some recording applications +may fail to save the recorded data properly because of this. Use some +other application (such as ossrecord) if this happens. + +FILES +CONFIGFILEPATH/oss_audioloop.conf Device configuration file + +AUTHOR +4Front Technologies + |