summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_audioloop
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_audioloop')
-rw-r--r--kernel/drv/oss_audioloop/.config2
-rw-r--r--kernel/drv/oss_audioloop/.devices1
-rw-r--r--kernel/drv/oss_audioloop/.name1
-rw-r--r--kernel/drv/oss_audioloop/.params4
-rw-r--r--kernel/drv/oss_audioloop/oss_audioloop.c919
-rw-r--r--kernel/drv/oss_audioloop/oss_audioloop.man80
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
+