/* Sun Audio API driver for Jack Copyright (C) 2008 Jacob Meuser Based heavily on oss_driver.c which came with the following copyright notice. Copyright (C) 2003-2007 Jussi Laako This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #ifndef _REENTRANT #define _REENTRANT #endif #ifndef _THREAD_SAFE #define _THREAD_SAFE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "engine.h" #include #include "sun_driver.h" #define SUN_DRIVER_N_PARAMS 11 const static jack_driver_param_desc_t sun_params[SUN_DRIVER_N_PARAMS] = { { "rate", 'r', JackDriverParamUInt, { .ui = SUN_DRIVER_DEF_FS }, "sample rate", "sample rate" }, { "period", 'p', JackDriverParamUInt, { .ui = SUN_DRIVER_DEF_BLKSIZE }, "period size", "period size" }, { "nperiods", 'n', JackDriverParamUInt, { .ui = SUN_DRIVER_DEF_NPERIODS }, "number of periods in buffer", "number of periods in buffer" }, { "wordlength", 'w', JackDriverParamInt, { .i = SUN_DRIVER_DEF_BITS }, "word length", "word length" }, { "inchannels", 'i', JackDriverParamUInt, { .ui = SUN_DRIVER_DEF_INS }, "capture channels", "capture channels" }, { "outchannels", 'o', JackDriverParamUInt, { .ui = SUN_DRIVER_DEF_OUTS }, "playback channels", "playback channels" }, { "capture", 'C', JackDriverParamString, { .str = SUN_DRIVER_DEF_DEV }, "input device", "input device" }, { "playback", 'P', JackDriverParamString, { .str = SUN_DRIVER_DEF_DEV }, "output device", "output device" }, { "ignorehwbuf", 'b', JackDriverParamBool, { }, "ignore hardware period size", "ignore hardware period size" }, { "input latency", 'I', JackDriverParamUInt, { .ui = 0 }, "system input latency", "system input latency" }, { "output latency", 'O', JackDriverParamUInt, { .ui = 0 }, "system output latency", "system output latency" } }; /* internal functions */ static void set_period_size (sun_driver_t *driver, jack_nframes_t new_period_size) { driver->period_size = new_period_size; driver->period_usecs = ((double)driver->period_size / (double)driver->sample_rate) * 1e6; driver->last_wait_ust = 0; driver->iodelay = 0.0F; driver->poll_timeout = (int)(driver->period_usecs / 666); } static void sun_driver_write_silence (sun_driver_t *driver, jack_nframes_t nframes) { size_t localsize; ssize_t io_res; void *localbuf; localsize = nframes * driver->sample_bytes * driver->playback_channels; localbuf = malloc (localsize); if (localbuf == NULL) { jack_error ("sun_driver: malloc() failed: %s@%i", __FILE__, __LINE__); return; } bzero (localbuf, localsize); io_res = write (driver->outfd, localbuf, localsize); if (io_res < (ssize_t)localsize) { jack_error ("sun_driver: write() failed: %s: " "count=%d/%d: %s@%i", strerror (errno), io_res, localsize, __FILE__, __LINE__); } free (localbuf); } static void sun_driver_read_silence (sun_driver_t *driver, jack_nframes_t nframes) { size_t localsize; ssize_t io_res; void *localbuf; localsize = nframes * driver->sample_bytes * driver->capture_channels; localbuf = malloc (localsize); if (localbuf == NULL) { jack_error ("sun_driver: malloc() failed: %s@%i", __FILE__, __LINE__); return; } io_res = read (driver->infd, localbuf, localsize); if (io_res < (ssize_t)localsize) { jack_error ("sun_driver: read() failed: %s: " "count=%d/%d: %s@%i", strerror (errno), io_res, localsize, __FILE__, __LINE__); } free (localbuf); } static jack_nframes_t sun_driver_wait (sun_driver_t *driver, int *status, float *iodelay) { audio_info_t auinfo; struct pollfd pfd[2]; nfds_t nfds; float delay; jack_time_t poll_enter; jack_time_t poll_ret; int need_capture = 0; int need_playback = 0; int capture_errors = 0; int playback_errors = 0; int capture_seek; int playback_seek; *status = -1; *iodelay = 0; pfd[0].fd = driver->infd; pfd[0].events = POLLIN; if (driver->infd >= 0) { need_capture = 1; } pfd[1].fd = driver->outfd; pfd[1].events = POLLOUT; if (driver->outfd >= 0) { need_playback = 1; } poll_enter = driver->engine->get_microseconds (); if (poll_enter > driver->poll_next) { /* late. don't count as wakeup delay. */ driver->poll_next = 0; } while (need_capture || need_playback) { nfds = poll (pfd, 2, driver->poll_timeout); if ( nfds == -1 || ((pfd[0].revents | pfd[1].revents) & (POLLERR | POLLHUP | POLLNVAL)) ) { jack_error ("sun_driver: poll() error: %s: %s@%i", strerror (errno), __FILE__, __LINE__); *status = -3; return 0; } if (nfds == 0) { jack_error ("sun_driver: poll() timeout: %s@%i", __FILE__, __LINE__); *status = -5; return 0; } if (need_capture && (pfd[0].revents & POLLIN)) { need_capture = 0; pfd[0].fd = -1; } if (need_playback && (pfd[1].revents & POLLOUT)) { need_playback = 0; pfd[1].fd = -1; } } poll_ret = driver->engine->get_microseconds (); if (driver->poll_next && poll_ret > driver->poll_next) { *iodelay = poll_ret - driver->poll_next; } driver->poll_last = poll_ret; driver->poll_next = poll_ret + driver->period_usecs; driver->engine->transport_cycle_start (driver->engine, poll_ret); #if defined(AUDIO_RERROR) && defined(AUDIO_PERROR) /* low level error reporting and recovery. recovery is necessary * when doing both playback and capture and using AUMODE_PLAY, * because we process one period of both playback and capture data * in each cycle, and we wait in each cycle for that to be possible. * for example, playback will continuously underrun if it underruns * and we have to wait for capture data to become available * before we can write enough playback data to catch up. */ if (driver->infd >= 0) { if (ioctl (driver->infd, AUDIO_RERROR, &capture_errors) < 0) { jack_error ("sun_driver: AUDIO_RERROR failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); return 0; } capture_errors -= driver->capture_drops; driver->capture_drops += capture_errors; } if (capture_errors > 0) { delay = (capture_errors * 1000.0) / driver->sample_rate; printf ("sun_driver: capture xrun of %d frames (%f msec)\n", capture_errors, delay); } if (driver->outfd >= 0) { if (ioctl (driver->outfd, AUDIO_PERROR, &playback_errors) < 0) { jack_error ("sun_driver: AUDIO_PERROR failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); return 0; } playback_errors -= driver->playback_drops; driver->playback_drops += playback_errors; } if (playback_errors > 0) { delay = (playback_errors * 1000.0) / driver->sample_rate; printf ("sun_driver: playback xrun of %d frames (%f msec)\n", playback_errors, delay); } if ((driver->outfd >= 0 && driver->infd >= 0) && (capture_errors || playback_errors)) { if (ioctl (driver->infd, AUDIO_GETINFO, &auinfo) < 0) { jack_error ("sun_driver: AUDIO_GETINFO failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return 0; } capture_seek = auinfo.record.seek; if (driver->infd == driver->outfd) { playback_seek = auinfo.play.seek; } else { if (ioctl (driver->outfd, AUDIO_GETINFO, &auinfo) < 0) { jack_error ("sun_driver: AUDIO_GETINFO failed: " "%s: %s@%i", strerror (errno), __FILE__, __LINE__); return 0; } playback_seek = auinfo.play.seek; } capture_seek /= driver->capture_channels * driver->sample_bytes; playback_seek /= driver->playback_channels * driver->sample_bytes; if (playback_seek == driver->period_size && capture_seek == driver->period_size && playback_errors) { /* normally, 1 period in each buffer is exactly * what we want, but if there was an error then * we effectively have 0 periods in the playback * buffer, because the period in the buffer will * be used to catch up to realtime. */ printf ("sun_driver: writing %d frames of silence " "to correct I/O sync\n", driver->period_size); sun_driver_write_silence (driver, driver->period_size); } else if (capture_errors && playback_errors) { /* serious delay. we've lost the ability to * write capture_errors frames to catch up on * playback. */ printf ("sun_driver: writing %d frames of silence " "to correct I/O sync\n", capture_errors); sun_driver_write_silence (driver, capture_errors); } } #endif // AUDIO_RERROR && AUDIO_PERROR driver->last_wait_ust = poll_ret; *status = 0; return driver->period_size; } static inline int sun_driver_run_cycle (sun_driver_t *driver) { jack_nframes_t nframes; jack_time_t now; int wait_status; float iodelay; nframes = sun_driver_wait (driver, &wait_status, &iodelay); if (wait_status < 0) { switch (wait_status) { case -3: /* poll() error */ return -1; case -5: /* poll() timeout */ now = driver->engine->get_microseconds (); if (now > driver->poll_next) { iodelay = now - driver->poll_next; driver->poll_next = now + driver->period_usecs; driver->engine->delay (driver->engine, iodelay); printf ("sun_driver: iodelay = %f\n", iodelay); } break; default: /* any other fatal error */ return -1; } } return driver->engine->run_cycle (driver->engine, nframes, iodelay); } static void copy_and_convert_in (jack_sample_t *dst, void *src, size_t nframes, int channel, int chcount, int bits) { int srcidx; int dstidx; signed short *s16src = (signed short*)src; signed int *s32src = (signed int*)src; double *f64src = (double*)src; jack_sample_t scale; srcidx = channel; switch (bits) { case 16: scale = 1.0f / 0x7fff; for (dstidx = 0; dstidx < nframes; dstidx++) { dst[dstidx] = (jack_sample_t) s16src[srcidx] * scale; srcidx += chcount; } break; case 24: scale = 1.0f / 0x7fffff; for (dstidx = 0; dstidx < nframes; dstidx++) { dst[dstidx] = (jack_sample_t) s32src[srcidx] * scale; srcidx += chcount; } break; case 32: scale = 1.0f / 0x7fffffff; for (dstidx = 0; dstidx < nframes; dstidx++) { dst[dstidx] = (jack_sample_t) s32src[srcidx] * scale; srcidx += chcount; } break; case 64: for (dstidx = 0; dstidx < nframes; dstidx++) { dst[dstidx] = (jack_sample_t)f64src[srcidx]; srcidx += chcount; } break; } } static void copy_and_convert_out (void *dst, jack_sample_t *src, size_t nframes, int channel, int chcount, int bits) { int srcidx; int dstidx; signed short *s16dst = (signed short*)dst; signed int *s32dst = (signed int*)dst; double *f64dst = (double*)dst; jack_sample_t scale; dstidx = channel; switch (bits) { case 16: scale = 0x7fff; for (srcidx = 0; srcidx < nframes; srcidx++) { s16dst[dstidx] = (signed short) (src[srcidx] >= 0.0f) ? (src[srcidx] * scale + 0.5f) : (src[srcidx] * scale - 0.5f); dstidx += chcount; } break; case 24: scale = 0x7fffff; for (srcidx = 0; srcidx < nframes; srcidx++) { s32dst[dstidx] = (signed int) (src[srcidx] >= 0.0f) ? (src[srcidx] * scale + 0.5f) : (src[srcidx] * scale - 0.5f); dstidx += chcount; } break; case 32: scale = 0x7fffffff; for (srcidx = 0; srcidx < nframes; srcidx++) { s32dst[dstidx] = (signed int) (src[srcidx] >= 0.0f) ? (src[srcidx] * scale + 0.5f) : (src[srcidx] * scale - 0.5f); dstidx += chcount; } break; case 64: for (srcidx = 0; srcidx < nframes; srcidx++) { f64dst[dstidx] = (double)src[srcidx]; dstidx += chcount; } break; } } /* jack driver interface */ static int sun_driver_attach (sun_driver_t *driver) { int port_flags; int channel; char channel_name[64]; jack_port_t *port; jack_latency_range_t range; if (driver->engine->set_buffer_size (driver->engine, driver->period_size)) { jack_error ("sun_driver: cannot set engine buffer size to %d (check MIDI)", driver->period_size); return -1; } driver->engine->set_sample_rate (driver->engine, driver->sample_rate); port_flags = JackPortIsOutput | JackPortIsPhysical | JackPortIsTerminal; for (channel = 0; channel < driver->capture_channels; channel++) { snprintf (channel_name, sizeof(channel_name), "capture_%u", channel + 1); port = jack_port_register (driver->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); if (port == NULL) { jack_error ("sun_driver: cannot register port for %s: " "%s@%i", channel_name, __FILE__, __LINE__); break; } range.min = range.max = driver->period_size + driver->sys_in_latency; jack_port_set_latency_range (port, JackCaptureLatency, &range); driver->capture_ports = jack_slist_append (driver->capture_ports, port); } port_flags = JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal; for (channel = 0; channel < driver->playback_channels; channel++) { snprintf (channel_name, sizeof(channel_name), "playback_%u", channel + 1); port = jack_port_register (driver->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); if (port == NULL) { jack_error ("sun_driver: cannot register port for " "%s: %s@%i", channel_name, __FILE__, __LINE__); break; } range.min = range.max = driver->period_size + driver->sys_out_latency; jack_port_set_latency_range (port, JackPlaybackLatency, &range); driver->playback_ports = jack_slist_append (driver->playback_ports, port); } return jack_activate (driver->client); } static int sun_driver_detach (sun_driver_t *driver) { JSList *node; if (driver->engine == NULL) { return 0; } node = driver->capture_ports; while (node != NULL) { jack_port_unregister (driver->client, ((jack_port_t*)node->data)); node = jack_slist_next (node); } jack_slist_free (driver->capture_ports); driver->capture_ports = NULL; node = driver->playback_ports; while (node != NULL) { jack_port_unregister (driver->client, ((jack_port_t*)node->data)); node = jack_slist_next (node); } jack_slist_free (driver->playback_ports); driver->playback_ports = NULL; return 0; } static int sun_driver_start (sun_driver_t *driver) { audio_info_t audio_if; if (driver->infd >= 0) { #if defined(AUDIO_FLUSH) if (ioctl (driver->infd, AUDIO_FLUSH, NULL) < 0) { jack_error ("sun_driver: capture flush failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } #endif AUDIO_INITINFO (&audio_if); audio_if.record.pause = 1; if (driver->outfd == driver->infd) { audio_if.play.pause = 1; } if (ioctl (driver->infd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: pause capture failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } if ((driver->outfd >= 0) && (driver->outfd != driver->infd)) { #if defined(AUDIO_FLUSH) if (ioctl (driver->outfd, AUDIO_FLUSH, NULL) < 0) { jack_error ("sun_driver: playback flush failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } #endif AUDIO_INITINFO (&audio_if); audio_if.play.pause = 1; if (ioctl (driver->outfd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: pause playback failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } /* AUDIO_FLUSH resets the counters these work with */ driver->playback_drops = driver->capture_drops = 0; if (driver->outfd >= 0) { /* "prime" the playback buffer. if we don't do this, we'll * end up underrunning. it would get really ugly in duplex * mode, for example, where we have to wait for a period to * be available to read before we can write. also helps to * keep constant latency from the beginning. */ sun_driver_write_silence (driver, driver->nperiods * driver->period_size); } if (driver->infd >= 0) { AUDIO_INITINFO (&audio_if); audio_if.record.pause = 0; if (driver->outfd == driver->infd) { audio_if.play.pause = 0; } if (ioctl (driver->infd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: start capture failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } if ((driver->outfd >= 0) && (driver->outfd != driver->infd)) { AUDIO_INITINFO (&audio_if); audio_if.play.pause = 0; if (ioctl (driver->outfd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: trigger playback failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } return 0; } static int enc_equal (int a, int b) { if (a == b) { return 1; } #if defined(AUDIO_ENCODING_SLINEAR) #if BYTE_ORDER == LITTLE_ENDIAN if ((a == AUDIO_ENCODING_SLINEAR && b == AUDIO_ENCODING_SLINEAR_LE) || (a == AUDIO_ENCODING_SLINEAR_LE && b == AUDIO_ENCODING_SLINEAR) || (a == AUDIO_ENCODING_ULINEAR && b == AUDIO_ENCODING_ULINEAR_LE) || (a == AUDIO_ENCODING_ULINEAR_LE && b == AUDIO_ENCODING_ULINEAR)) { return 1; } #elif BYTE_ORDER == BIG_ENDIAN if ((a == AUDIO_ENCODING_SLINEAR && b == AUDIO_ENCODING_SLINEAR_BE) || (a == AUDIO_ENCODING_SLINEAR_BE && b == AUDIO_ENCODING_SLINEAR) || (a == AUDIO_ENCODING_ULINEAR && b == AUDIO_ENCODING_ULINEAR_BE) || (a == AUDIO_ENCODING_ULINEAR_BE && b == AUDIO_ENCODING_ULINEAR)) { return 1; } #endif #endif // AUDIO_ENCODING_SLINEAR return 0; } static int sun_driver_set_parameters (sun_driver_t *driver) { audio_info_t audio_if_in, audio_if_out; int infd = -1; int outfd = -1; int s = 1; unsigned int cap_period = 0, play_period = 0, period_size = 0; const char *indev = driver->indev; const char *outdev = driver->outdev; driver->indevbuf = NULL; driver->outdevbuf = NULL; driver->sample_bytes = driver->bits / 8; if ((strcmp (indev, outdev) == 0) && ((driver->capture_channels > 0) && (driver->playback_channels > 0))) { infd = outfd = open (indev, O_RDWR); if (infd < 0) { jack_error ("sun_driver: failed to open duplex device " "%s: %s: %s@%i", indev, strerror (errno), __FILE__, __LINE__); return -1; } #if defined(AUDIO_SETFD) if (ioctl (infd, AUDIO_SETFD, &s) < 0) { jack_error ("sun_driver: failed to enable full duplex: " "%s: %s@%i", strerror (errno), __FILE__, __LINE__); return -1; } #endif } else { if (driver->capture_channels > 0) { infd = open (indev, O_RDONLY); if (infd < 0) { jack_error ("sun_driver: failed to open input " "device %s: %s: %s@%i", indev, strerror (errno), __FILE__, __LINE__); return -1; } } if (driver->playback_channels > 0) { outfd = open (outdev, O_WRONLY); if (outfd < 0) { jack_error ("sun_driver: failed to open output " "device %s: %s: %s@%i", outdev, strerror (errno), __FILE__, __LINE__); return -1; } } } if (infd == -1 && outfd == -1) { jack_error ("sun_driver: no device was opened: %s@%i", __FILE__, __LINE__); return -1; } driver->infd = infd; driver->outfd = outfd; AUDIO_INITINFO (&audio_if_in); AUDIO_INITINFO (&audio_if_out); if (infd >= 0) { audio_if_in.record.encoding = driver->format; audio_if_in.record.precision = driver->bits; audio_if_in.record.channels = driver->capture_channels; audio_if_in.record.sample_rate = driver->sample_rate; audio_if_in.record.pause = 1; } if (outfd >= 0) { audio_if_out.play.encoding = driver->format; audio_if_out.play.precision = driver->bits; audio_if_out.play.channels = driver->playback_channels; audio_if_out.play.sample_rate = driver->sample_rate; audio_if_out.play.pause = 1; } #if defined(__OpenBSD__) || defined(__NetBSD__) #if defined(__OpenBSD__) if (driver->infd >= 0) { audio_if_in.record.block_size = driver->capture_channels * driver->period_size * driver->sample_bytes; } if (driver->outfd >= 0) { audio_if_out.play.block_size = driver->playback_channels * driver->period_size * driver->sample_bytes; } #else if (driver->infd >= 0) { audio_if_in.blocksize = driver->capture_channels * driver->period_size * driver->sample_bytes; } if (driver->outfd >= 0) { audio_if_out.blocksize = driver->playback_channels * driver->period_size * driver->sample_bytes; } #endif if (infd == outfd) { audio_if_in.play = audio_if_out.play; } /* this only affects playback. the capture buffer is * always the max (64k on OpenBSD). */ audio_if_in.hiwat = audio_if_out.hiwat = driver->nperiods; /* AUMODE_PLAY makes us "catch up to realtime" if we underrun * playback. that means, if we are N frames late, the next * N frames written will be discarded. this keeps playback * time from expanding with each underrun. */ if (infd == outfd) { audio_if_in.mode = AUMODE_PLAY | AUMODE_RECORD; } else { if (infd >= 0) { audio_if_in.mode = AUMODE_RECORD; } if (outfd >= 0) { audio_if_out.mode = AUMODE_PLAY; } } #endif // OpenBSD || NetBSD if (infd >= 0) { if (ioctl (infd, AUDIO_SETINFO, &audio_if_in) < 0) { jack_error ("sun_driver: failed to set parameters for " "%s: %s: %s@%i", indev, strerror (errno), __FILE__, __LINE__); return -1; } } if (outfd >= 0 && outfd != infd) { if (ioctl (outfd, AUDIO_SETINFO, &audio_if_out) < 0) { jack_error ("sun_driver: failed to set parameters for " "%s: %s: %s@%i", outdev, strerror (errno), __FILE__, __LINE__); return -1; } } if (infd >= 0) { if (ioctl (infd, AUDIO_GETINFO, &audio_if_in) < 0) { jack_error ("sun_driver: AUDIO_GETINFO failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } if (!enc_equal (audio_if_in.record.encoding, driver->format) || audio_if_in.record.precision != driver->bits || audio_if_in.record.channels != driver->capture_channels || audio_if_in.record.sample_rate != driver->sample_rate) { jack_error ("sun_driver: setting capture parameters " "failed: %s@%i", __FILE__, __LINE__); return -1; } #if defined(__OpenBSD__) cap_period = audio_if_in.record.block_size / driver->capture_channels / driver->sample_bytes; #elif defined(__NetBSD__) cap_period = audio_if_in.blocksize / driver->capture_channels / driver->sample_bytes; #else /* how is this done on Solaris? */ cap_period = driver->period_size; #endif } if (outfd >= 0) { if (outfd == infd) { audio_if_out.play = audio_if_in.play; } else { if (ioctl (outfd, AUDIO_GETINFO, &audio_if_out) < 0) { jack_error ("sun_driver: AUDIO_GETINFO failed: " "%s: %s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } if (!enc_equal (audio_if_out.play.encoding, driver->format) || audio_if_out.play.precision != driver->bits || audio_if_out.play.channels != driver->playback_channels || audio_if_out.play.sample_rate != driver->sample_rate) { jack_error ("sun_driver: playback settings failed: " "%s@%i", __FILE__, __LINE__); return -1; } #if defined(__OpenBSD__) play_period = audio_if_out.play.block_size / driver->playback_channels / driver->sample_bytes; #elif defined(__NetBSD__) play_period = audio_if_out.blocksize / driver->playback_channels / driver->sample_bytes; #else /* how is this done on Solaris? */ play_period = driver->period_size; #endif } if (infd >= 0 && outfd >= 0 && play_period != cap_period) { jack_error ("sun_driver: play and capture periods differ: " "%s@%i", __FILE__, __LINE__); return -1; } if (infd >= 0) { period_size = cap_period; } else if (outfd >= 0) { period_size = play_period; } if (period_size != 0 && period_size != driver->period_size && !driver->ignorehwbuf) { printf ("sun_driver: period size update: %u\n", period_size); set_period_size (driver, period_size); if (driver->engine) { if (driver->engine->set_buffer_size (driver->engine, driver->period_size)) { jack_error ("sun_driver: cannot set engine buffer size to %d (check MIDI)", driver->period_size); return -1; } } } if (driver->infd >= 0 && driver->capture_channels > 0) { driver->indevbufsize = driver->period_size * driver->capture_channels * driver->sample_bytes; driver->indevbuf = malloc (driver->indevbufsize); if (driver->indevbuf == NULL) { jack_error ( "sun_driver: malloc() failed: %s@%i", __FILE__, __LINE__); return -1; } bzero (driver->indevbuf, driver->indevbufsize); } else { driver->indevbufsize = 0; driver->indevbuf = NULL; } if (driver->outfd >= 0 && driver->playback_channels > 0) { driver->outdevbufsize = driver->period_size * driver->playback_channels * driver->sample_bytes; driver->outdevbuf = malloc (driver->outdevbufsize); if (driver->outdevbuf == NULL) { jack_error ("sun_driver: malloc() failed: %s@%i", __FILE__, __LINE__); return -1; } bzero (driver->outdevbuf, driver->outdevbufsize); } else { driver->outdevbufsize = 0; driver->outdevbuf = NULL; } printf ("sun_driver: indevbuf %zd B, outdevbuf %zd B\n", driver->indevbufsize, driver->outdevbufsize); return 0; } static int sun_driver_stop (sun_driver_t *driver) { audio_info_t audio_if; if (driver->infd >= 0) { AUDIO_INITINFO (&audio_if); audio_if.record.pause = 1; if (driver->outfd == driver->infd) { audio_if.play.pause = 1; } if (ioctl (driver->infd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: capture pause failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } if ((driver->outfd >= 0) && (driver->outfd != driver->infd)) { AUDIO_INITINFO (&audio_if); audio_if.play.pause = 1; if (ioctl (driver->outfd, AUDIO_SETINFO, &audio_if) < 0) { jack_error ("sun_driver: playback pause failed: %s: " "%s@%i", strerror (errno), __FILE__, __LINE__); return -1; } } return 0; } static int sun_driver_read (sun_driver_t *driver, jack_nframes_t nframes) { jack_nframes_t nbytes; int channel; ssize_t io_res; jack_sample_t *portbuf; JSList *node; jack_port_t *port; if (driver->engine->freewheeling || driver->infd < 0) { return 0; } if (nframes > driver->period_size) { jack_error ("sun_driver: read failed: nframes > period_size: " "(%u/%u): %s@%i", nframes, driver->period_size, __FILE__, __LINE__); return -1; } node = driver->capture_ports; channel = 0; while (node != NULL) { port = (jack_port_t*)node->data; if (jack_port_connected (port)) { portbuf = jack_port_get_buffer (port, nframes); copy_and_convert_in (portbuf, driver->indevbuf, nframes, channel, driver->capture_channels, driver->bits); } node = jack_slist_next (node); channel++; } nbytes = nframes * driver->capture_channels * driver->sample_bytes; io_res = 0; while (nbytes) { io_res = read (driver->infd, driver->indevbuf, nbytes); if (io_res < 0) { jack_error ("sun_driver: read() failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); break; } else { nbytes -= io_res; } } return 0; } static int sun_driver_write (sun_driver_t *driver, jack_nframes_t nframes) { jack_nframes_t nbytes; int channel; ssize_t io_res; jack_sample_t *portbuf; JSList *node; jack_port_t *port; if (driver->engine->freewheeling || driver->outfd < 0) { return 0; } if (nframes > driver->period_size) { jack_error ("sun_driver: write failed: nframes > period_size " "(%u/%u): %s@%i", nframes, driver->period_size, __FILE__, __LINE__); return -1; } bzero (driver->outdevbuf, driver->outdevbufsize); node = driver->playback_ports; channel = 0; while (node != NULL) { port = (jack_port_t*)node->data; if (jack_port_connected (port)) { portbuf = jack_port_get_buffer (port, nframes); copy_and_convert_out (driver->outdevbuf, portbuf, nframes, channel, driver->playback_channels, driver->bits); } node = jack_slist_next (node); channel++; } nbytes = nframes * driver->playback_channels * driver->sample_bytes; io_res = 0; while (nbytes) { io_res = write (driver->outfd, driver->outdevbuf, nbytes); if (io_res < 0) { jack_error ("sun_driver: write() failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); break; } else { nbytes -= io_res; } } return 0; } static int sun_driver_null_cycle (sun_driver_t *driver, jack_nframes_t nframes) { if (nframes > driver->period_size) { jack_error ("sun_driver: null cycle failed: " "nframes > period_size (%u/%u): %s@%i", nframes, driver->period_size, __FILE__, __LINE__); return -1; } printf ("sun_driver: running null cycle\n"); if (driver->outfd >= 0) { sun_driver_write_silence (driver, nframes); } if (driver->infd >= 0) { sun_driver_read_silence (driver, nframes); } return 0; } static int sun_driver_bufsize (sun_driver_t *driver, jack_nframes_t nframes) { return sun_driver_set_parameters (driver); } static void sun_driver_delete (sun_driver_t *driver) { if (driver->outfd >= 0 && driver->outfd != driver->infd) { close (driver->outfd); driver->outfd = -1; } if (driver->infd >= 0) { close (driver->infd); driver->infd = -1; } if (driver->indevbuf != NULL) { free (driver->indevbuf); driver->indevbuf = NULL; } if (driver->outdevbuf != NULL) { free (driver->outdevbuf); driver->outdevbuf = NULL; } if (driver->indev != NULL) { free (driver->indev); } if (driver->outdev != NULL) { free (driver->outdev); } jack_driver_nt_finish ((jack_driver_nt_t*)driver); free (driver); } void driver_finish (jack_driver_t *driver) { sun_driver_delete ((sun_driver_t*)driver); } static jack_driver_t * sun_driver_new (char *indev, char *outdev, jack_client_t *client, jack_nframes_t sample_rate, jack_nframes_t period_size, jack_nframes_t nperiods, int bits, int capture_channels, int playback_channels, jack_nframes_t in_latency, jack_nframes_t out_latency, int ignorehwbuf) { sun_driver_t *driver; driver = (sun_driver_t*)malloc (sizeof(sun_driver_t)); if (driver == NULL) { jack_error ("sun_driver: malloc() failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); return NULL; } driver->engine = NULL; jack_driver_nt_init ((jack_driver_nt_t*)driver); driver->nt_attach = (JackDriverNTAttachFunction)sun_driver_attach; driver->nt_detach = (JackDriverNTDetachFunction)sun_driver_detach; driver->read = (JackDriverReadFunction)sun_driver_read; driver->write = (JackDriverWriteFunction)sun_driver_write; driver->null_cycle = (JackDriverNullCycleFunction) sun_driver_null_cycle; driver->nt_bufsize = (JackDriverNTBufSizeFunction)sun_driver_bufsize; driver->nt_start = (JackDriverNTStartFunction)sun_driver_start; driver->nt_stop = (JackDriverNTStopFunction)sun_driver_stop; driver->nt_run_cycle = (JackDriverNTRunCycleFunction)sun_driver_run_cycle; if (indev != NULL) { driver->indev = strdup (indev); } if (outdev != NULL) { driver->outdev = strdup (outdev); } driver->ignorehwbuf = ignorehwbuf; driver->sample_rate = sample_rate; driver->period_size = period_size; driver->nperiods = nperiods; driver->bits = bits; driver->capture_channels = capture_channels; driver->playback_channels = playback_channels; driver->sys_in_latency = in_latency; driver->sys_out_latency = out_latency; set_period_size (driver, period_size); if (driver->indev == NULL) { driver->indev = strdup (SUN_DRIVER_DEF_DEV); } if (driver->outdev == NULL) { driver->outdev = strdup (SUN_DRIVER_DEF_DEV); } driver->infd = -1; driver->outfd = -1; #if defined(AUDIO_ENCODING_SLINEAR) driver->format = AUDIO_ENCODING_SLINEAR; #else driver->format = AUDIO_ENCODING_LINEAR; #endif driver->indevbuf = driver->outdevbuf = NULL; driver->capture_ports = NULL; driver->playback_ports = NULL; driver->iodelay = 0.0F; driver->poll_last = driver->poll_next = 0; if (sun_driver_set_parameters (driver) < 0) { free (driver); return NULL; } driver->client = client; return (jack_driver_t*)driver; } /* jack driver published interface */ const char driver_client_name[] = "sun"; jack_driver_desc_t * driver_get_descriptor () { jack_driver_desc_t *desc; jack_driver_param_desc_t *params; desc = (jack_driver_desc_t*)calloc (1, sizeof(jack_driver_desc_t)); if (desc == NULL) { jack_error ("sun_driver: calloc() failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); return NULL; } strcpy (desc->name, driver_client_name); desc->nparams = SUN_DRIVER_N_PARAMS; params = calloc (desc->nparams, sizeof(jack_driver_param_desc_t)); if (params == NULL) { jack_error ("sun_driver: calloc() failed: %s: %s@%i", strerror (errno), __FILE__, __LINE__); return NULL; } memcpy (params, sun_params, desc->nparams * sizeof(jack_driver_param_desc_t)); desc->params = params; return desc; } jack_driver_t * driver_initialize (jack_client_t *client, JSList * params) { int bits = SUN_DRIVER_DEF_BITS; jack_nframes_t sample_rate = SUN_DRIVER_DEF_FS; jack_nframes_t period_size = SUN_DRIVER_DEF_BLKSIZE; jack_nframes_t in_latency = 0; jack_nframes_t out_latency = 0; unsigned int nperiods = SUN_DRIVER_DEF_NPERIODS; unsigned int capture_channels = SUN_DRIVER_DEF_INS; unsigned int playback_channels = SUN_DRIVER_DEF_OUTS; const JSList *pnode; const jack_driver_param_t *param; char *indev; char *outdev; int ignorehwbuf = 0; indev = strdup (SUN_DRIVER_DEF_DEV); outdev = strdup (SUN_DRIVER_DEF_DEV); pnode = params; while (pnode != NULL) { param = (const jack_driver_param_t*)pnode->data; switch (param->character) { case 'r': sample_rate = param->value.ui; break; case 'p': period_size = param->value.ui; break; case 'n': nperiods = param->value.ui; break; case 'w': bits = param->value.i; break; case 'i': capture_channels = param->value.ui; break; case 'o': playback_channels = param->value.ui; break; case 'C': indev = strdup (param->value.str); break; case 'P': outdev = strdup (param->value.str); break; case 'b': ignorehwbuf = 1; break; case 'I': in_latency = param->value.ui; break; case 'O': out_latency = param->value.ui; break; } pnode = jack_slist_next (pnode); } return sun_driver_new (indev, outdev, client, sample_rate, period_size, nperiods, bits, capture_channels, playback_channels, in_latency, out_latency, ignorehwbuf); }