/* Copyright (C) 2003-2007 Jussi Laako Copyright (C) 2008 Grame & RTL 2008 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "driver_interface.h" #include "JackThreadedDriver.h" #include "JackDriverLoader.h" #include "JackOSSDriver.h" #include "JackEngineControl.h" #include "JackGraphManager.h" #include "JackError.h" #include "JackTime.h" #include "JackShmMem.h" #include "memops.h" #include #include #include #include #include #include using namespace std; namespace { inline jack_nframes_t TimeToFrames(jack_time_t time, jack_nframes_t sample_rate) { return ((time * sample_rate) + 500000ULL) / 1000000ULL; } inline long long TimeToOffset(jack_time_t time1, jack_time_t time2, jack_nframes_t sample_rate) { if (time2 > time1) { return TimeToFrames(time2 - time1, sample_rate); } else { return 0LL - TimeToFrames(time1 - time2, sample_rate); } } inline jack_time_t FramesToTime(jack_nframes_t frames, jack_nframes_t sample_rate) { return ((frames * 1000000ULL) + (sample_rate / 2ULL)) / sample_rate; } inline jack_nframes_t RoundUp(jack_nframes_t frames, jack_nframes_t block) { if (block > 0) { frames += (block - 1); frames -= (frames % block); } return frames; } inline jack_time_t RoundDown(jack_time_t time, jack_time_t interval) { if (interval > 0) { time -= (time % interval); } return time; } int GetSampleFormat(int bits) { switch(bits) { // Native-endian signed 32 bit samples. case 32: return AFMT_S32_NE; // Native-endian signed 24 bit (packed) samples. case 24: return AFMT_S24_NE; // Native-endian signed 16 bit samples, used by default. case 16: default: return AFMT_S16_NE; } } unsigned int GetSampleSize(int format) { switch(format) { // Native-endian signed 32 bit samples. case AFMT_S32_NE: return 4; // Native-endian signed 24 bit (packed) samples. case AFMT_S24_NE: return 3; // Native-endian signed 16 bit samples. case AFMT_S16_NE: return 2; // Unsupported sample format. default: return 0; } } inline int UpToPower2(int x) { int r = 0; while ((1 << r) < x) r++; return r; } } namespace Jack { #ifdef JACK_MONITOR #define CYCLE_POINTS 500000 struct OSSCycle { jack_time_t fBeforeRead; jack_time_t fAfterRead; jack_time_t fAfterReadConvert; jack_time_t fBeforeWrite; jack_time_t fAfterWrite; jack_time_t fBeforeWriteConvert; }; struct OSSCycleTable { jack_time_t fBeforeFirstWrite; jack_time_t fAfterFirstWrite; OSSCycle fTable[CYCLE_POINTS]; }; OSSCycleTable gCycleTable; int gCycleCount = 0; #endif static inline void CopyAndConvertIn(jack_sample_t *dst, void *src, size_t nframes, int channel, int chcount, int bits) { switch (bits) { case 16: { signed short *s16src = (signed short*)src; s16src += channel; sample_move_dS_s16(dst, (char*)s16src, nframes, chcount<<1); break; } case 24: { char *s24src = (char*)src; s24src += channel * 3; sample_move_dS_s24(dst, s24src, nframes, chcount*3); break; } case 32: { signed int *s32src = (signed int*)src; s32src += channel; sample_move_dS_s32u24(dst, (char*)s32src, nframes, chcount<<2); break; } } } static inline void CopyAndConvertOut(void *dst, jack_sample_t *src, size_t nframes, int channel, int chcount, int bits) { switch (bits) { case 16: { signed short *s16dst = (signed short*)dst; s16dst += channel; sample_move_d16_sS((char*)s16dst, src, nframes, chcount<<1, NULL); // No dithering for now... break; } case 24: { char *s24dst = (char*)dst; s24dst += channel * 3; sample_move_d24_sS(s24dst, src, nframes, chcount*3, NULL); break; } case 32: { signed int *s32dst = (signed int*)dst; s32dst += channel; sample_move_d32u24_sS((char*)s32dst, src, nframes, chcount<<2, NULL); break; } } } void JackOSSDriver::DisplayDeviceInfo() { audio_buf_info info; memset(&info, 0, sizeof(audio_buf_info)); int cap = 0; // Duplex cards : http://manuals.opensound.com/developer/full_duplex.html jack_info("Audio Interface Description :"); jack_info("Sampling Frequency : %d, Sample Size : %d", fEngineControl->fSampleRate, fInSampleSize * 8); if (fPlayback) { oss_sysinfo si; if (ioctl(fOutFD, OSS_SYSINFO, &si) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo OSS_SYSINFO failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { jack_info("OSS product %s", si.product); jack_info("OSS version %s", si.version); jack_info("OSS version num %d", si.versionnum); jack_info("OSS numaudios %d", si.numaudios); jack_info("OSS numaudioengines %d", si.numaudioengines); jack_info("OSS numcards %d", si.numcards); } jack_info("Output capabilities - %d channels : ", fPlaybackChannels); jack_info("Output block size = %d", fOutputBufferSize); if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETOSPACE failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { jack_info("output space info: fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d", info.fragments, info.fragstotal, info.fragsize, info.bytes); } if (ioctl(fOutFD, SNDCTL_DSP_GETCAPS, &cap) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETCAPS failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { if (cap & DSP_CAP_DUPLEX) jack_info(" DSP_CAP_DUPLEX"); if (cap & DSP_CAP_REALTIME) jack_info(" DSP_CAP_REALTIME"); if (cap & DSP_CAP_BATCH) jack_info(" DSP_CAP_BATCH"); if (cap & DSP_CAP_COPROC) jack_info(" DSP_CAP_COPROC"); if (cap & DSP_CAP_TRIGGER) jack_info(" DSP_CAP_TRIGGER"); if (cap & DSP_CAP_MMAP) jack_info(" DSP_CAP_MMAP"); if (cap & DSP_CAP_MULTI) jack_info(" DSP_CAP_MULTI"); if (cap & DSP_CAP_BIND) jack_info(" DSP_CAP_BIND"); } } if (fCapture) { oss_sysinfo si; if (ioctl(fInFD, OSS_SYSINFO, &si) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo OSS_SYSINFO failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { jack_info("OSS product %s", si.product); jack_info("OSS version %s", si.version); jack_info("OSS version num %d", si.versionnum); jack_info("OSS numaudios %d", si.numaudios); jack_info("OSS numaudioengines %d", si.numaudioengines); jack_info("OSS numcards %d", si.numcards); } jack_info("Input capabilities - %d channels : ", fCaptureChannels); jack_info("Input block size = %d", fInputBufferSize); if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETOSPACE failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { jack_info("input space info: fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d", info.fragments, info.fragstotal, info.fragsize, info.bytes); } if (ioctl(fInFD, SNDCTL_DSP_GETCAPS, &cap) == -1) { jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETCAPS failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); } else { if (cap & DSP_CAP_DUPLEX) jack_info(" DSP_CAP_DUPLEX"); if (cap & DSP_CAP_REALTIME) jack_info(" DSP_CAP_REALTIME"); if (cap & DSP_CAP_BATCH) jack_info(" DSP_CAP_BATCH"); if (cap & DSP_CAP_COPROC) jack_info(" DSP_CAP_COPROC"); if (cap & DSP_CAP_TRIGGER) jack_info(" DSP_CAP_TRIGGER"); if (cap & DSP_CAP_MMAP) jack_info(" DSP_CAP_MMAP"); if (cap & DSP_CAP_MULTI) jack_info(" DSP_CAP_MULTI"); if (cap & DSP_CAP_BIND) jack_info(" DSP_CAP_BIND"); } } } int JackOSSDriver::ProbeInBlockSize() { jack_nframes_t blocks[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int probes = 0; int ret = 0; // Default values in case of an error. fInMeanStep = fEngineControl->fBufferSize; fInBlockSize = 1; if (fInFD > 0) { // Read one frame into a new hardware block so we can check its size. // Repeat that for multiple probes, sometimes the first reads differ. jack_nframes_t frames = 1; for (int p = 0; p < 8 && frames > 0; ++p) { ret = Discard(frames); frames = 0; if (ret == 0) { oss_count_t ptr; if (ioctl(fInFD, SNDCTL_DSP_CURRENT_IPTR, &ptr) == 0 && ptr.fifo_samples > 0) { // Success, store probed hardware block size for later. blocks[p] = 1U + ptr.fifo_samples; ++probes; // Proceed by reading one frame into the next hardware block. frames = blocks[p]; } } else { // Read error - abort. jack_error("JackOSSDriver::ProbeInBlockSize read failed with %d", ret); } } // Stop recording. ioctl(fInFD, SNDCTL_DSP_HALT_INPUT, NULL); } if (probes == 8) { // Compute mean block size of the last six probes. jack_nframes_t sum = 0; for (int p = 2; p < 8; ++p) { jack_log("JackOSSDriver::ProbeInBlockSize read block of %d frames", blocks[p]); sum += blocks[p]; } fInMeanStep = sum / 6; // Check that none of the probed block sizes deviates too much. jack_nframes_t slack = fInMeanStep / 16; bool strict = true; for (int p = 2; p < 8; ++p) { strict = strict && (blocks[p] > fInMeanStep - slack) && (blocks[p] < fInMeanStep + slack); } if (strict && fInMeanStep <= fEngineControl->fBufferSize) { // Regular hardware block size, use it for rounding. jack_info("JackOSSDriver::ProbeInBlockSize read blocks are %d frames", fInMeanStep); fInBlockSize = fInMeanStep; } else { jack_info("JackOSSDriver::ProbeInBlockSize irregular read block sizes"); jack_info("JackOSSDriver::ProbeInBlockSize mean read block was %d frames", fInMeanStep); } if (fInBlockSize > fEngineControl->fBufferSize / 2) { jack_info("JackOSSDriver::ProbeInBlockSize less than two read blocks per cycle"); jack_info("JackOSSDriver::ProbeInBlockSize for best results make period a multiple of %d", fInBlockSize); } if (fInMeanStep > fEngineControl->fBufferSize) { jack_error("JackOSSDriver::ProbeInBlockSize period is too small, minimum is %d frames", fInMeanStep); return -1; } } return ret; } int JackOSSDriver::ProbeOutBlockSize() { jack_nframes_t blocks[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int probes = 0; int ret = 0; // Default values in case of an error. fOutMeanStep = fEngineControl->fBufferSize; fOutBlockSize = 1; if (fOutFD) { // Write one frame over the low water mark, then check the consumed block size. // Repeat that for multiple probes, sometimes the initial ones differ. jack_nframes_t mark = fNperiods * fEngineControl->fBufferSize; WriteSilence(mark + 1); for (int p = 0; p < 8 && ret >= 0; ++p) { pollfd poll_fd; poll_fd.fd = fOutFD; poll_fd.events = POLLOUT; ret = poll(&poll_fd, 1, 500); if (ret < 0) { jack_error("JackOSSDriver::ProbeOutBlockSize poll failed with %d", ret); break; } if (poll_fd.revents & POLLOUT) { oss_count_t ptr; if (ioctl(fOutFD, SNDCTL_DSP_CURRENT_OPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { // Success, store probed hardware block size for later. blocks[p] = mark + 1 - ptr.fifo_samples; ++probes; // Proceed by writing one frame over the low water mark. WriteSilence(blocks[p]); } poll_fd.revents = 0; } } // Stop playback. ioctl(fOutFD, SNDCTL_DSP_HALT_INPUT, NULL); } if (probes == 8) { // Compute mean and maximum block size of the last six probes. jack_nframes_t sum = 0; for (int p = 2; p < 8; ++p) { jack_log("JackOSSDriver::ProbeOutBlockSize write block of %d frames", blocks[p]); sum += blocks[p]; } fOutMeanStep = sum / 6; // Check that none of the probed block sizes deviates too much. jack_nframes_t slack = fOutMeanStep / 16; bool strict = true; for (int p = 2; p < 8; ++p) { strict = strict && (blocks[p] > fOutMeanStep - slack) && (blocks[p] < fOutMeanStep + slack); } if (strict && fOutMeanStep <= fEngineControl->fBufferSize) { // Regular hardware block size, use it for rounding. jack_info("JackOSSDriver::ProbeOutBlockSize write blocks are %d frames", fOutMeanStep); fOutBlockSize = fOutMeanStep; } else { jack_info("JackOSSDriver::ProbeOutBlockSize irregular write block sizes"); jack_info("JackOSSDriver::ProbeOutBlockSize mean write block was %d frames", fOutMeanStep); } if (fOutBlockSize > fEngineControl->fBufferSize / 2) { jack_info("JackOSSDriver::ProbeOutBlockSize less than two write blocks per cycle"); jack_info("JackOSSDriver::ProbeOutBlockSize for best results make period a multiple of %d", fOutBlockSize); } if (fOutMeanStep > fEngineControl->fBufferSize) { jack_error("JackOSSDriver::ProbeOutBlockSize period is too small, minimum is %d frames", fOutMeanStep); return -1; } } return ret; } int JackOSSDriver::Discard(jack_nframes_t frames) { if (fInFD < 0) { return -1; } // Read frames from OSS capture buffer to be discarded. ssize_t size = frames * fInSampleSize * fCaptureChannels; while (size > 0) { ssize_t chunk = (size > fInputBufferSize) ? fInputBufferSize : size; ssize_t count = ::read(fInFD, fInputBuffer, chunk); if (count <= 0) { jack_error("JackOSSDriver::Discard error bytes read = %ld", count); return -1; } fOSSReadOffset += count / (fInSampleSize * fCaptureChannels); size -= count; } return 0; } int JackOSSDriver::WriteSilence(jack_nframes_t frames) { if (fOutFD < 0) { return -1; } // Fill OSS playback buffer, write some periods of silence. memset(fOutputBuffer, 0, fOutputBufferSize); ssize_t size = frames * fOutSampleSize * fPlaybackChannels; while (size > 0) { ssize_t chunk = (size > fOutputBufferSize) ? fOutputBufferSize : size; ssize_t count = ::write(fOutFD, fOutputBuffer, chunk); if (count <= 0) { jack_error("JackOSSDriver::WriteSilence error bytes written = %ld", count); return -1; } fOSSWriteOffset += (count / (fOutSampleSize * fPlaybackChannels)); size -= count; } return 0; } int JackOSSDriver::WaitAndSync() { oss_count_t ptr = {0, 0, {0}}; if (fInFD > 0 && fOSSReadSync != 0) { // Predict time of next capture sync (poll() return). if (fOSSReadOffset + fEngineControl->fBufferSize > 0) { jack_nframes_t frames = fOSSReadOffset + fEngineControl->fBufferSize; jack_nframes_t rounded = RoundUp(frames, fInBlockSize); fOSSReadSync += FramesToTime(rounded, fEngineControl->fSampleRate); fOSSReadOffset -= rounded; } } if (fOutFD > 0 && fOSSWriteSync != 0) { // Predict time of next playback sync (poll() return). if (fOSSWriteOffset > fNperiods * fEngineControl->fBufferSize) { jack_nframes_t frames = fOSSWriteOffset - fNperiods * fEngineControl->fBufferSize; jack_nframes_t rounded = RoundUp(frames, fOutBlockSize); fOSSWriteSync += FramesToTime(rounded, fEngineControl->fSampleRate); fOSSWriteOffset -= rounded; } } jack_time_t poll_start = GetMicroSeconds(); // Poll until recording and playback buffer are ready for this cycle. pollfd poll_fd[2]; poll_fd[0].fd = fInFD; if (fInFD > 0 && (fForceSync || poll_start < fOSSReadSync)) { poll_fd[0].events = POLLIN; } else { poll_fd[0].events = 0; } poll_fd[1].fd = fOutFD; if (fOutFD > 0 && (fForceSync || poll_start < fOSSWriteSync)) { poll_fd[1].events = POLLOUT; } else { poll_fd[1].events = 0; } while (poll_fd[0].events != 0 || poll_fd[1].events != 0) { poll_fd[0].revents = 0; poll_fd[1].revents = 0; int ret = poll(poll_fd, 2, 500); jack_time_t now = GetMicroSeconds(); if (ret <= 0) { jack_error("JackOSSDriver::WaitAndSync poll failed with %d after %ld us", ret, now - poll_start); return ret; } if (poll_fd[0].revents & POLLIN) { // Check the excess recording frames. if (ioctl(fInFD, SNDCTL_DSP_CURRENT_IPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { if (fInBlockSize <= 1) { // Irregular block size, let sync time converge slowly when late. fOSSReadSync = min(fOSSReadSync, now) / 2 + now / 2; fOSSReadOffset = -ptr.fifo_samples; } else if (ptr.fifo_samples - fEngineControl->fBufferSize >= fInBlockSize) { // Too late for a reliable sync, make sure sync time is not in the future. if (now < fOSSReadSync) { fOSSReadOffset = -ptr.fifo_samples; jack_info("JackOSSDriver::WaitAndSync capture sync %ld us early, %ld frames", fOSSReadSync - now, fOSSReadOffset); fOSSReadSync = now; } } else if (fForceSync) { // Uncertain previous sync, just use sync time directly. fOSSReadSync = now; fOSSReadOffset = -ptr.fifo_samples; } else { // Adapt expected sync time when early or late - in whole block intervals. // Account for some speed drift, but otherwise round down to earlier interval. jack_time_t interval = FramesToTime(fInBlockSize, fEngineControl->fSampleRate); jack_time_t remainder = fOSSReadSync % interval; jack_time_t max_drift = interval / 4; jack_time_t rounded = RoundDown((now - remainder) + max_drift, interval) + remainder; // Let sync time converge slowly when late, prefer earlier sync times. fOSSReadSync = min(rounded, now) / 2 + now / 2; fOSSReadOffset = -ptr.fifo_samples; } } poll_fd[0].events = 0; } if (poll_fd[1].revents & POLLOUT) { // Check the remaining playback frames. if (ioctl(fOutFD, SNDCTL_DSP_CURRENT_OPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { if (fOutBlockSize <= 1) { // Irregular block size, let sync time converge slowly when late. fOSSWriteSync = min(fOSSWriteSync, now) / 2 + now / 2; fOSSWriteOffset = ptr.fifo_samples; } else if (ptr.fifo_samples + fOutBlockSize <= fNperiods * fEngineControl->fBufferSize) { // Too late for a reliable sync, make sure sync time is not in the future. if (now < fOSSWriteSync) { fOSSWriteOffset = ptr.fifo_samples; jack_info("JackOSSDriver::WaitAndSync playback sync %ld us early, %ld frames", fOSSWriteSync - now, fOSSWriteOffset); fOSSWriteSync = now; } } else if (fForceSync) { // Uncertain previous sync, just use sync time directly. fOSSWriteSync = now; fOSSWriteOffset = ptr.fifo_samples; } else { // Adapt expected sync time when early or late - in whole block intervals. // Account for some speed drift, but otherwise round down to earlier interval. jack_time_t interval = FramesToTime(fOutBlockSize, fEngineControl->fSampleRate); jack_time_t remainder = fOSSWriteSync % interval; jack_time_t max_drift = interval / 4; jack_time_t rounded = RoundDown((now - remainder) + max_drift, interval) + remainder; // Let sync time converge slowly when late, prefer earlier sync times. fOSSWriteSync = min(rounded, now) / 2 + now / 2; fOSSWriteOffset = ptr.fifo_samples; } } poll_fd[1].events = 0; } } fForceSync = false; // Compute balance of read and write buffers combined. fBufferBalance = 0; if (fInFD > 0 && fOutFD > 0) { // Compare actual buffer content with target of (1 + n) * period. fBufferBalance += ((1 + fNperiods) * fEngineControl->fBufferSize); fBufferBalance -= (fOSSWriteOffset - fOSSReadOffset); fBufferBalance += TimeToOffset(fOSSWriteSync, fOSSReadSync, fEngineControl->fSampleRate); // Force balancing if sync times deviate too much. jack_time_t slack = FramesToTime((fEngineControl->fBufferSize * 2) / 3, fEngineControl->fSampleRate); fForceBalancing = fForceBalancing || (fOSSReadSync > fOSSWriteSync + slack); fForceBalancing = fForceBalancing || (fOSSWriteSync > fOSSReadSync + slack); // Force balancing if buffer is badly balanced. fForceBalancing = fForceBalancing || (abs(fBufferBalance) > max(fInMeanStep, fOutMeanStep)); } // Print debug info every 10 seconds. if (ptr.samples > 0 && (ptr.samples % (10 * fEngineControl->fSampleRate)) < fEngineControl->fBufferSize) { jack_log("JackOSSDriver::Read buffer balance is %ld frames", fBufferBalance); jack_time_t now = GetMicroSeconds(); jack_log("JackOSSDriver::Read recording sync %ld frames %ld us ago", fOSSReadOffset, now - fOSSReadSync); jack_log("JackOSSDriver::Read playback sync %ld frames %ld us ago", fOSSWriteOffset, now - fOSSWriteSync); } return 0; } int JackOSSDriver::OpenInput() { int flags = 0; int gFragFormat; int cur_capture_channels; int cur_sample_format; jack_nframes_t cur_sample_rate; audio_buf_info info; if (fCaptureChannels == 0) fCaptureChannels = 2; if ((fInFD = open(fCaptureDriverName, O_RDONLY | ((fExcl) ? O_EXCL : 0))) < 0) { jack_error("JackOSSDriver::OpenInput failed to open device : %s@%i, errno = %d", __FILE__, __LINE__, errno); return -1; } jack_log("JackOSSDriver::OpenInput input fInFD = %d", fInFD); if (fExcl) { if (ioctl(fInFD, SNDCTL_DSP_COOKEDMODE, &flags) == -1) { jack_error("JackOSSDriver::OpenInput failed to set cooked mode : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } } cur_sample_format = GetSampleFormat(fBits); if (ioctl(fInFD, SNDCTL_DSP_SETFMT, &cur_sample_format) == -1) { jack_error("JackOSSDriver::OpenInput failed to set format : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fInSampleSize = GetSampleSize(cur_sample_format); if (cur_sample_format != GetSampleFormat(fBits)) { if (fInSampleSize > 0) { jack_info("JackOSSDriver::OpenInput driver forced %d bit sample format", fInSampleSize * 8); } else { jack_error("JackOSSDriver::OpenInput unsupported sample format %#x", cur_sample_format); goto error; } } cur_capture_channels = fCaptureChannels; if (ioctl(fInFD, SNDCTL_DSP_CHANNELS, &fCaptureChannels) == -1) { jack_error("JackOSSDriver::OpenInput failed to set channels : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } if (cur_capture_channels != fCaptureChannels) { jack_info("JackOSSDriver::OpenInput driver forced the number of capture channels %ld", fCaptureChannels); } cur_sample_rate = fEngineControl->fSampleRate; if (ioctl(fInFD, SNDCTL_DSP_SPEED, &fEngineControl->fSampleRate) == -1) { jack_error("JackOSSDriver::OpenInput failed to set sample rate : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } if (cur_sample_rate != fEngineControl->fSampleRate) { jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); } // Internal buffer size required for one period. fInputBufferSize = fEngineControl->fBufferSize * fInSampleSize * fCaptureChannels; // Get the total size of the OSS recording buffer, in sample frames. info = {0, 0, 0, 0}; if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { jack_error("JackOSSDriver::OpenInput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fOSSInBuffer = info.fragstotal * info.fragsize / (fInSampleSize * fCaptureChannels); if (fOSSInBuffer < fEngineControl->fBufferSize * (1 + fNperiods)) { // Total size of the OSS recording buffer is too small, resize it. unsigned int buf_size = fInputBufferSize * (1 + fNperiods); // Keep current fragment size if possible - respect OSS latency settings. gFragFormat = UpToPower2(info.fragsize); unsigned int frag_size = 1U << gFragFormat; gFragFormat |= ((buf_size + frag_size - 1) / frag_size) << 16; jack_info("JackOSSDriver::OpenInput request %d fragments of %d", (gFragFormat >> 16), frag_size); if (ioctl(fInFD, SNDCTL_DSP_SETFRAGMENT, &gFragFormat) == -1) { jack_error("JackOSSDriver::OpenInput failed to set fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } // Check the new OSS recording buffer size. info = {0, 0, 0, 0}; if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { jack_error("JackOSSDriver::OpenInput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fOSSInBuffer = info.fragstotal * info.fragsize / (fInSampleSize * fCaptureChannels); } if (fOSSInBuffer > fEngineControl->fBufferSize) { int mark = fInputBufferSize; if (ioctl(fInFD, SNDCTL_DSP_LOW_WATER, &mark) != 0) { jack_error("JackOSSDriver::OpenInput failed to set low water mark : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } jack_info("JackOSSDriver::OpenInput set low water mark to %d", mark); } fInputBuffer = (void*)calloc(fInputBufferSize, 1); assert(fInputBuffer); if (ProbeInBlockSize() < 0) { goto error; } return 0; error: ::close(fInFD); return -1; } int JackOSSDriver::OpenOutput() { int flags = 0; int gFragFormat; int cur_sample_format; int cur_playback_channels; jack_nframes_t cur_sample_rate; audio_buf_info info; if (fPlaybackChannels == 0) fPlaybackChannels = 2; if ((fOutFD = open(fPlaybackDriverName, O_WRONLY | ((fExcl) ? O_EXCL : 0))) < 0) { jack_error("JackOSSDriver::OpenOutput failed to open device : %s@%i, errno = %d", __FILE__, __LINE__, errno); return -1; } jack_log("JackOSSDriver::OpenOutput output fOutFD = %d", fOutFD); if (fExcl) { if (ioctl(fOutFD, SNDCTL_DSP_COOKEDMODE, &flags) == -1) { jack_error("JackOSSDriver::OpenOutput failed to set cooked mode : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } } cur_sample_format = GetSampleFormat(fBits); if (ioctl(fOutFD, SNDCTL_DSP_SETFMT, &cur_sample_format) == -1) { jack_error("JackOSSDriver::OpenOutput failed to set format : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fOutSampleSize = GetSampleSize(cur_sample_format); if (cur_sample_format != GetSampleFormat(fBits)) { if (fOutSampleSize > 0) { jack_info("JackOSSDriver::OpenOutput driver forced %d bit sample format", fOutSampleSize * 8); } else { jack_error("JackOSSDriver::OpenOutput unsupported sample format %#x", cur_sample_format); goto error; } } cur_playback_channels = fPlaybackChannels; if (ioctl(fOutFD, SNDCTL_DSP_CHANNELS, &fPlaybackChannels) == -1) { jack_error("JackOSSDriver::OpenOutput failed to set channels : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } if (cur_playback_channels != fPlaybackChannels) { jack_info("JackOSSDriver::OpenOutput driver forced the number of playback channels %ld", fPlaybackChannels); } cur_sample_rate = fEngineControl->fSampleRate; if (ioctl(fOutFD, SNDCTL_DSP_SPEED, &fEngineControl->fSampleRate) == -1) { jack_error("JackOSSDriver::OpenOutput failed to set sample rate : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } if (cur_sample_rate != fEngineControl->fSampleRate) { jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); } // Internal buffer size required for one period. fOutputBufferSize = fEngineControl->fBufferSize * fOutSampleSize * fPlaybackChannels; // Get the total size of the OSS playback buffer, in sample frames. info = {0, 0, 0, 0}; if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { jack_error("JackOSSDriver::OpenOutput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fOSSOutBuffer = info.fragstotal * info.fragsize / (fOutSampleSize * fPlaybackChannels); if (fOSSOutBuffer < fEngineControl->fBufferSize * (1 + fNperiods)) { // Total size of the OSS playback buffer is too small, resize it. unsigned int buf_size = fOutputBufferSize * (1 + fNperiods); // Keep current fragment size if possible - respect OSS latency settings. // Some sound cards like Intel HDA may stutter when changing the fragment size. gFragFormat = UpToPower2(info.fragsize); unsigned int frag_size = 1U << gFragFormat; gFragFormat |= ((buf_size + frag_size - 1) / frag_size) << 16; jack_info("JackOSSDriver::OpenOutput request %d fragments of %d", (gFragFormat >> 16), frag_size); if (ioctl(fOutFD, SNDCTL_DSP_SETFRAGMENT, &gFragFormat) == -1) { jack_error("JackOSSDriver::OpenOutput failed to set fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } // Check the new OSS playback buffer size. info = {0, 0, 0, 0}; if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { jack_error("JackOSSDriver::OpenOutput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } fOSSOutBuffer = info.fragstotal * info.fragsize / (fOutSampleSize * fPlaybackChannels); } if (fOSSOutBuffer > fEngineControl->fBufferSize * fNperiods) { jack_nframes_t low = fOSSOutBuffer - (fNperiods * fEngineControl->fBufferSize); int mark = low * fOutSampleSize * fPlaybackChannels; if (ioctl(fOutFD, SNDCTL_DSP_LOW_WATER, &mark) != 0) { jack_error("JackOSSDriver::OpenOutput failed to set low water mark : %s@%i, errno = %d", __FILE__, __LINE__, errno); goto error; } jack_info("JackOSSDriver::OpenOutput set low water mark to %d", mark); } fOutputBuffer = (void*)calloc(fOutputBufferSize, 1); assert(fOutputBuffer); if (ProbeOutBlockSize() < 0) { goto error; } return 0; error: ::close(fOutFD); return -1; } int JackOSSDriver::Open(jack_nframes_t nframes, int user_nperiods, jack_nframes_t samplerate, bool capturing, bool playing, int inchannels, int outchannels, bool excl, bool monitor, const char* capture_driver_uid, const char* playback_driver_uid, jack_nframes_t capture_latency, jack_nframes_t playback_latency, int bits, bool ignorehwbuf) { // Store local settings first. fCapture = capturing; fPlayback = playing; fBits = bits; fIgnoreHW = ignorehwbuf; fNperiods = user_nperiods; fExcl = excl; // Generic JackAudioDriver Open if (JackAudioDriver::Open(nframes, samplerate, capturing, playing, inchannels, outchannels, monitor, capture_driver_uid, playback_driver_uid, capture_latency, playback_latency) != 0) { return -1; } else { #ifdef JACK_MONITOR // Force memory page in memset(&gCycleTable, 0, sizeof(gCycleTable)); #endif if (OpenAux() < 0) { Close(); return -1; } else { return 0; } } } int JackOSSDriver::Close() { #ifdef JACK_MONITOR FILE* file = fopen("OSSProfiling.log", "w"); if (file) { jack_info("Writing OSS driver timing data...."); for (int i = 1; i < gCycleCount; i++) { int d1 = gCycleTable.fTable[i].fAfterRead - gCycleTable.fTable[i].fBeforeRead; int d2 = gCycleTable.fTable[i].fAfterReadConvert - gCycleTable.fTable[i].fAfterRead; int d3 = gCycleTable.fTable[i].fAfterWrite - gCycleTable.fTable[i].fBeforeWrite; int d4 = gCycleTable.fTable[i].fBeforeWrite - gCycleTable.fTable[i].fBeforeWriteConvert; fprintf(file, "%d \t %d \t %d \t %d \t \n", d1, d2, d3, d4); } fclose(file); } else { jack_error("JackOSSDriver::Close : cannot open OSSProfiling.log file"); } file = fopen("TimingOSS.plot", "w"); if (file == NULL) { jack_error("JackOSSDriver::Close cannot open TimingOSS.plot file"); } else { fprintf(file, "set grid\n"); fprintf(file, "set title \"OSS audio driver timing\"\n"); fprintf(file, "set xlabel \"audio cycles\"\n"); fprintf(file, "set ylabel \"usec\"\n"); fprintf(file, "plot \"OSSProfiling.log\" using 1 title \"Driver read wait\" with lines, \ \"OSSProfiling.log\" using 2 title \"Driver read convert duration\" with lines, \ \"OSSProfiling.log\" using 3 title \"Driver write wait\" with lines, \ \"OSSProfiling.log\" using 4 title \"Driver write convert duration\" with lines\n"); fprintf(file, "set output 'TimingOSS.pdf\n"); fprintf(file, "set terminal pdf\n"); fprintf(file, "set grid\n"); fprintf(file, "set title \"OSS audio driver timing\"\n"); fprintf(file, "set xlabel \"audio cycles\"\n"); fprintf(file, "set ylabel \"usec\"\n"); fprintf(file, "plot \"OSSProfiling.log\" using 1 title \"Driver read wait\" with lines, \ \"OSSProfiling.log\" using 2 title \"Driver read convert duration\" with lines, \ \"OSSProfiling.log\" using 3 title \"Driver write wait\" with lines, \ \"OSSProfiling.log\" using 4 title \"Driver write convert duration\" with lines\n"); fclose(file); } #endif int res = JackAudioDriver::Close(); CloseAux(); return res; } int JackOSSDriver::OpenAux() { // (Re-)Initialize runtime variables. fInSampleSize = fOutSampleSize = 0; fInputBufferSize = fOutputBufferSize = 0; fInBlockSize = fOutBlockSize = 1; fInMeanStep = fOutMeanStep = 0; fOSSInBuffer = fOSSOutBuffer = 0; fOSSReadSync = fOSSWriteSync = 0; fOSSReadOffset = fOSSWriteOffset = 0; fBufferBalance = 0; fForceBalancing = false; fForceSync = false; if (fCapture && (OpenInput() < 0)) { return -1; } if (fPlayback && (OpenOutput() < 0)) { return -1; } DisplayDeviceInfo(); return 0; } void JackOSSDriver::CloseAux() { if (fCapture && fInFD > 0) { close(fInFD); fInFD = -1; } if (fPlayback && fOutFD > 0) { close(fOutFD); fOutFD = -1; } if (fInputBuffer) free(fInputBuffer); fInputBuffer = NULL; if (fOutputBuffer) free(fOutputBuffer); fOutputBuffer = NULL; } int JackOSSDriver::Read() { if (fInFD > 0 && fOSSReadSync == 0) { // First cycle, account for leftover samples from previous reads. fOSSReadOffset = 0; oss_count_t ptr; if (ioctl(fInFD, SNDCTL_DSP_CURRENT_IPTR, &ptr) == 0 && ptr.fifo_samples > 0) { jack_log("JackOSSDriver::Read pre recording samples = %ld, fifo_samples = %d", ptr.samples, ptr.fifo_samples); fOSSReadOffset = -ptr.fifo_samples; } // Start capture by reading a new hardware block., jack_nframes_t discard = fInMeanStep - fOSSReadOffset; // Let half a block or at most 1ms remain in buffer, avoid drift issues at start. discard -= min(TimeToFrames(1000, fEngineControl->fSampleRate), (fInMeanStep / 2)); jack_log("JackOSSDriver::Read start recording discard %ld frames", discard); fOSSReadSync = GetMicroSeconds(); Discard(discard); fForceSync = true; fForceBalancing = true; } if (fOutFD > 0 && fOSSWriteSync == 0) { // First cycle, account for leftover samples from previous writes. fOSSWriteOffset = 0; oss_count_t ptr; if (ioctl(fOutFD, SNDCTL_DSP_CURRENT_OPTR, &ptr) == 0 && ptr.fifo_samples > 0) { jack_log("JackOSSDriver::Read pre playback samples = %ld, fifo_samples = %d", ptr.samples, ptr.fifo_samples); fOSSWriteOffset = ptr.fifo_samples; } // Start playback with silence, target latency as given by the user. jack_nframes_t silence = (fNperiods + 1) * fEngineControl->fBufferSize; // Minus half a block or at most 1ms of frames, avoid drift issues at start. silence -= min(TimeToFrames(1000, fEngineControl->fSampleRate), (fOutMeanStep / 2)); silence = max(silence - fOSSWriteOffset, 1LL); jack_log("JackOSSDriver::Read start playback with %ld frames of silence", silence); fOSSWriteSync = GetMicroSeconds(); WriteSilence(silence); fForceSync = true; fForceBalancing = true; } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeRead = GetMicroSeconds(); #endif if (WaitAndSync() < 0) { return -1; } // Keep begin cycle time JackDriver::CycleTakeBeginTime(); if (fInFD < 0) { return 0; } // Try to read multiple times in case of short reads. size_t count = 0; for (int i = 0; i < 3 && count < fInputBufferSize; ++i) { ssize_t ret = ::read(fInFD, ((char*)fInputBuffer) + count, fInputBufferSize - count); if (ret < 0) { jack_error("JackOSSDriver::Read error = %s", strerror(errno)); return -1; } count += ret; } // Read offset accounting and overrun detection. if (count > 0) { jack_time_t now = GetMicroSeconds(); jack_time_t sync = max(fOSSReadSync, fOSSWriteSync); if (now - sync > 1000) { // Blocking read() may indicate sample loss in OSS - force resync. jack_log("JackOSSDriver::Read long read duration of %ld us", now - sync); fForceSync = true; } long long passed = TimeToFrames(now - fOSSReadSync, fEngineControl->fSampleRate); passed -= (passed % fInBlockSize); if (passed > fOSSReadOffset + fOSSInBuffer) { // Overrun, adjust read and write position. long long missed = passed - (fOSSReadOffset + fOSSInBuffer); jack_error("JackOSSDriver::Read missed %ld frames by overrun, passed=%ld, sync=%ld, now=%ld", missed, passed, fOSSReadSync, now); fOSSReadOffset += missed; fOSSWriteOffset += missed; NotifyXRun(now, float(FramesToTime(missed, fEngineControl->fSampleRate))); } fOSSReadOffset += count / (fInSampleSize * fCaptureChannels); } #ifdef JACK_MONITOR if (count > 0 && count != (int)fInputBufferSize) jack_log("JackOSSDriver::Read count = %ld", count / (fInSampleSize * fCaptureChannels)); gCycleTable.fTable[gCycleCount].fAfterRead = GetMicroSeconds(); #endif // Check and clear OSS errors. audio_errinfo ei_in; if (ioctl(fInFD, SNDCTL_DSP_GETERROR, &ei_in) == 0) { // Not reliable for overrun detection, virtual_oss doesn't implement it. if (ei_in.rec_overruns > 0 ) { jack_error("JackOSSDriver::Read %d overrun events", ei_in.rec_overruns); } if (ei_in.rec_errorcount > 0 && ei_in.rec_lasterror != 0) { jack_error("%d OSS rec event(s), last=%05d:%d", ei_in.rec_errorcount, ei_in.rec_lasterror, ei_in.rec_errorparm); } } if (count < fInputBufferSize) { jack_error("JackOSSDriver::Read incomplete read of %ld bytes", count); return -1; } for (int i = 0; i < fCaptureChannels; i++) { if (fGraphManager->GetConnectionsNum(fCapturePortList[i]) > 0) { CopyAndConvertIn(GetInputBuffer(i), fInputBuffer, fEngineControl->fBufferSize, i, fCaptureChannels, fInSampleSize * 8); } } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fAfterReadConvert = GetMicroSeconds(); #endif return 0; } int JackOSSDriver::Write() { if (fOutFD < 0) { return 0; } unsigned int skip = 0; jack_time_t start = GetMicroSeconds(); if (fOSSWriteSync > 0) { // Check for underruns, rounded to hardware block size if available. long long passed = TimeToFrames(start - fOSSWriteSync, fEngineControl->fSampleRate); long long consumed = passed - (passed % fOutBlockSize); long long tolerance = (fOutBlockSize > 1) ? 0 : fOutMeanStep; long long overdue = 0; if (consumed > fOSSWriteOffset + tolerance) { // Skip playback data that already passed. overdue = consumed - fOSSWriteOffset - tolerance; jack_error("JackOSSDriver::Write underrun, late by %ld, skip %ld frames", passed - fOSSWriteOffset, overdue); jack_log("JackOSSDriver::Write playback offset %ld frames synced %ld us ago", fOSSWriteOffset, start - fOSSWriteSync); // Also consider buffer balance, there was a gap in playback anyway. fForceBalancing = true; } // Account for buffer balance if needed. long long progress = fEngineControl->fBufferSize; if (fForceBalancing) { fForceBalancing = false; progress = max(progress + fBufferBalance, 0LL); jack_info("JackOSSDriver::Write buffer balancing %ld frames", fBufferBalance); jack_log("JackOSSDriver::Write recording sync %ld frames %ld us ago", fOSSReadOffset, start - fOSSReadSync); jack_log("JackOSSDriver::Write playback sync %ld frames %ld us ago", fOSSWriteOffset, start - fOSSWriteSync); } // How many samples to skip or prepend due to underrun and balancing. long long write_length = progress - overdue; if (write_length <= 0) { skip += fOutputBufferSize; fOSSWriteOffset += progress; } else if (write_length < fEngineControl->fBufferSize) { skip += (fEngineControl->fBufferSize - write_length) * fOutSampleSize * fPlaybackChannels; fOSSWriteOffset += overdue; } else if (write_length > fEngineControl->fBufferSize) { jack_nframes_t fill = write_length - fEngineControl->fBufferSize; WriteSilence(fill); } } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeWriteConvert = GetMicroSeconds(); #endif memset(fOutputBuffer, 0, fOutputBufferSize); for (int i = 0; i < fPlaybackChannels; i++) { if (fGraphManager->GetConnectionsNum(fPlaybackPortList[i]) > 0) { CopyAndConvertOut(fOutputBuffer, GetOutputBuffer(i), fEngineControl->fBufferSize, i, fPlaybackChannels, fOutSampleSize * 8); } } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeWrite = GetMicroSeconds(); #endif // Try multiple times in case of short writes. ssize_t count = skip; for (int i = 0; i < 3 && count < fOutputBufferSize; ++i) { ssize_t ret = ::write(fOutFD, ((char*)fOutputBuffer) + count, fOutputBufferSize - count); if (ret < 0) { jack_error("JackOSSDriver::Write error = %s", strerror(errno)); return -1; } count += ret; } fOSSWriteOffset += ((count - skip) / (fOutSampleSize * fPlaybackChannels)); jack_time_t duration = GetMicroSeconds() - start; if (duration > 1000) { // Blocking write() may indicate sample loss in OSS - force resync. jack_log("JackOSSDriver::Write long write duration of %ld us", duration); fForceSync = true; } #ifdef JACK_MONITOR if (count > 0 && count != (int)fOutputBufferSize) jack_log("JackOSSDriver::Write count = %ld", (count - skip) / (fOutSampleSize * fPlaybackChannels)); gCycleTable.fTable[gCycleCount].fAfterWrite = GetMicroSeconds(); gCycleCount = (gCycleCount == CYCLE_POINTS - 1) ? gCycleCount: gCycleCount + 1; #endif // Check and clear OSS errors. audio_errinfo ei_out; if (ioctl(fOutFD, SNDCTL_DSP_GETERROR, &ei_out) == 0) { // Not reliable for underrun detection, virtual_oss does not implement it. if (ei_out.play_underruns > 0) { jack_error("JackOSSDriver::Write %d underrun events", ei_out.play_underruns); } if (ei_out.play_errorcount > 0 && ei_out.play_lasterror != 0) { jack_error("%d OSS play event(s), last=%05d:%d",ei_out.play_errorcount, ei_out.play_lasterror, ei_out.play_errorparm); } } if (count < (int)fOutputBufferSize) { jack_error("JackOSSDriver::Write incomplete write of %ld bytes", count - skip); return -1; } return 0; } void JackOSSDriver::UpdateLatencies() { // Reimplement from JackAudioDriver. Base latency is smaller, and there's // additional latency due to OSS playback buffer management. jack_latency_range_t input_range; jack_latency_range_t output_range; for (int i = 0; i < fCaptureChannels; i++) { input_range.max = input_range.min = (fEngineControl->fBufferSize / 2) + fCaptureLatency; fGraphManager->GetPort(fCapturePortList[i])->SetLatencyRange(JackCaptureLatency, &input_range); } for (int i = 0; i < fPlaybackChannels; i++) { output_range.max = (fEngineControl->fBufferSize / 2) + fPlaybackLatency; // Additional latency introduced by the OSS buffer. output_range.max += fNperiods * fEngineControl->fBufferSize; // Plus one period if in async mode. if (!fEngineControl->fSyncMode) { output_range.max += fEngineControl->fBufferSize; } output_range.min = output_range.max; fGraphManager->GetPort(fPlaybackPortList[i])->SetLatencyRange(JackPlaybackLatency, &output_range); } } int JackOSSDriver::SetBufferSize(jack_nframes_t buffer_size) { // Close and reopen device, we have to adjust the OSS buffer management. CloseAux(); JackAudioDriver::SetBufferSize(buffer_size); // Generic change, never fails return OpenAux(); } } // end of namespace #ifdef __cplusplus extern "C" { #endif SERVER_EXPORT jack_driver_desc_t* driver_get_descriptor() { jack_driver_desc_t * desc; jack_driver_desc_filler_t filler; jack_driver_param_value_t value; desc = jack_driver_descriptor_construct("oss", JackDriverMaster, "OSS API based audio backend", &filler); value.ui = OSS_DRIVER_DEF_FS; jack_driver_descriptor_add_parameter(desc, &filler, "rate", 'r', JackDriverParamUInt, &value, NULL, "Sample rate", NULL); value.ui = OSS_DRIVER_DEF_BLKSIZE; jack_driver_descriptor_add_parameter(desc, &filler, "period", 'p', JackDriverParamUInt, &value, NULL, "Frames per period", NULL); value.ui = OSS_DRIVER_DEF_NPERIODS; jack_driver_descriptor_add_parameter(desc, &filler, "nperiods", 'n', JackDriverParamUInt, &value, NULL, "Number of periods to prefill output buffer", NULL); value.i = OSS_DRIVER_DEF_BITS; jack_driver_descriptor_add_parameter(desc, &filler, "wordlength", 'w', JackDriverParamInt, &value, NULL, "Word length", NULL); value.ui = OSS_DRIVER_DEF_INS; jack_driver_descriptor_add_parameter(desc, &filler, "inchannels", 'i', JackDriverParamUInt, &value, NULL, "Capture channels", NULL); value.ui = OSS_DRIVER_DEF_OUTS; jack_driver_descriptor_add_parameter(desc, &filler, "outchannels", 'o', JackDriverParamUInt, &value, NULL, "Playback channels", NULL); value.i = false; jack_driver_descriptor_add_parameter(desc, &filler, "excl", 'e', JackDriverParamBool, &value, NULL, "Exclusive and direct device access", NULL); strcpy(value.str, OSS_DRIVER_DEF_DEV); jack_driver_descriptor_add_parameter(desc, &filler, "capture", 'C', JackDriverParamString, &value, NULL, "Input device", NULL); jack_driver_descriptor_add_parameter(desc, &filler, "playback", 'P', JackDriverParamString, &value, NULL, "Output device", NULL); jack_driver_descriptor_add_parameter(desc, &filler, "device", 'd', JackDriverParamString, &value, NULL, "OSS device name", NULL); value.i = false; jack_driver_descriptor_add_parameter(desc, &filler, "ignorehwbuf", 'b', JackDriverParamBool, &value, NULL, "Ignore hardware period size", NULL); value.ui = 0; jack_driver_descriptor_add_parameter(desc, &filler, "input-latency", 'I', JackDriverParamUInt, &value, NULL, "Extra input latency", NULL); jack_driver_descriptor_add_parameter(desc, &filler, "output-latency", 'O', JackDriverParamUInt, &value, NULL, "Extra output latency", NULL); return desc; } SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) { int bits = OSS_DRIVER_DEF_BITS; jack_nframes_t srate = OSS_DRIVER_DEF_FS; jack_nframes_t frames_per_interrupt = OSS_DRIVER_DEF_BLKSIZE; const char* capture_pcm_name = OSS_DRIVER_DEF_DEV; const char* playback_pcm_name = OSS_DRIVER_DEF_DEV; bool capture = false; bool playback = false; int chan_in = 0; int chan_out = 0; bool monitor = false; bool excl = false; unsigned int nperiods = OSS_DRIVER_DEF_NPERIODS; const JSList *node; const jack_driver_param_t *param; bool ignorehwbuf = false; jack_nframes_t systemic_input_latency = 0; jack_nframes_t systemic_output_latency = 0; for (node = params; node; node = jack_slist_next(node)) { param = (const jack_driver_param_t *)node->data; switch (param->character) { case 'r': srate = param->value.ui; break; case 'p': frames_per_interrupt = (unsigned int)param->value.ui; break; case 'n': nperiods = (unsigned int)param->value.ui; break; case 'w': bits = param->value.i; break; case 'i': chan_in = (int)param->value.ui; break; case 'o': chan_out = (int)param->value.ui; break; case 'C': capture = true; if (strcmp(param->value.str, "none") != 0) { capture_pcm_name = param->value.str; } break; case 'P': playback = true; if (strcmp(param->value.str, "none") != 0) { playback_pcm_name = param->value.str; } break; case 'd': playback_pcm_name = param->value.str; capture_pcm_name = param->value.str; break; case 'b': ignorehwbuf = true; break; case 'e': excl = true; break; case 'I': systemic_input_latency = param->value.ui; break; case 'O': systemic_output_latency = param->value.ui; break; } } // duplex is the default if (!capture && !playback) { capture = true; playback = true; } Jack::JackOSSDriver* oss_driver = new Jack::JackOSSDriver("system", "oss", engine, table); Jack::JackDriverClientInterface* threaded_driver = new Jack::JackThreadedDriver(oss_driver); // Special open for OSS driver... if (oss_driver->Open(frames_per_interrupt, nperiods, srate, capture, playback, chan_in, chan_out, excl, monitor, capture_pcm_name, playback_pcm_name, systemic_input_latency, systemic_output_latency, bits, ignorehwbuf) == 0) { return threaded_driver; } else { delete threaded_driver; // Delete the decorated driver return NULL; } } #ifdef __cplusplus } #endif