From b5597128c25aae3423683c101e72f0ff8107ec3d Mon Sep 17 00:00:00 2001 From: Florian Walpen Date: Tue, 17 Aug 2021 20:47:29 +0200 Subject: [PATCH] FreeBSD: Big driver overhaul for FreeBSD OSS. Features: * Internal workings more in line with other Jack drivers. * Use poll() for wait and sync instead of blocking I/O. * Allows to use Jack in "async" mode. * Calculate DSP usage correctly. * OSS buffer management to achieve stable latencies. * Latency correction for asymmetric OSS buffer use. * More robust handling of over- and underruns. * Handle format changes forced by the OSS interface. * FreeBSD 24bit samples are always packed. No changes to the driver parameters or the user interface. --- freebsd/oss/JackOSSDriver.cpp | 1031 +++++++++++++++++++++++++++++++---------- freebsd/oss/JackOSSDriver.h | 64 ++- 2 files changed, 824 insertions(+), 271 deletions(-) diff --git a/freebsd/oss/JackOSSDriver.cpp b/freebsd/oss/JackOSSDriver.cpp index 36bc5855..7cd44156 100644 --- a/freebsd/oss/JackOSSDriver.cpp +++ b/freebsd/oss/JackOSSDriver.cpp @@ -38,6 +38,85 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 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 { @@ -65,75 +144,53 @@ int gCycleCount = 0; #endif -inline int int2pow2(int x) { int r = 0; while ((1 << r) < x) r++; return r; } - 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; + case 16: { + signed short *s16src = (signed short*)src; s16src += channel; sample_move_dS_s16(dst, (char*)s16src, nframes, chcount<<1); - break; + break; } - case 24: { - signed int *s32src = (signed int*)src; - s32src += channel; - sample_move_dS_s24(dst, (char*)s32src, nframes, chcount<<2); - 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; + case 32: { + signed int *s32src = (signed int*)src; s32src += channel; sample_move_dS_s32u24(dst, (char*)s32src, nframes, chcount<<2); - break; + break; } - } + } } static inline void CopyAndConvertOut(void *dst, jack_sample_t *src, size_t nframes, int channel, int chcount, int bits) { - switch (bits) { + switch (bits) { - case 16: { - signed short *s16dst = (signed short*)dst; + 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; + break; } - case 24: { - signed int *s32dst = (signed int*)dst; - s32dst += channel; - sample_move_d24_sS((char*)s32dst, src, nframes, chcount<<2, 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: { + case 32: { signed int *s32dst = (signed int*)dst; s32dst += channel; sample_move_d32u24_sS((char*)s32dst, src, nframes, chcount<<2, NULL); - break; + break; } - } -} - -void JackOSSDriver::SetSampleFormat() -{ - switch (fBits) { - - case 24: /* native-endian LSB aligned 24-bits in 32-bits integer */ - fSampleFormat = AFMT_S24_NE; - fSampleSize = sizeof(int); - break; - case 32: /* native-endian 32-bit integer */ - fSampleFormat = AFMT_S32_NE; - fSampleSize = sizeof(int); - break; - case 16: /* native-endian 16-bit integer */ - default: - fSampleFormat = AFMT_S16_NE; - fSampleSize = sizeof(short); - break; } } @@ -146,9 +203,9 @@ void JackOSSDriver::DisplayDeviceInfo() // Duplex cards : http://manuals.opensound.com/developer/full_duplex.html jack_info("Audio Interface Description :"); - jack_info("Sampling Frequency : %d, Sample Format : %d, Mode : %d", fEngineControl->fSampleRate, fSampleFormat, fRWMode); + jack_info("Sampling Frequency : %d, Sample Size : %d", fEngineControl->fSampleRate, fInSampleSize * 8); - if (fRWMode & kWrite) { + if (fPlayback) { oss_sysinfo si; if (ioctl(fOutFD, OSS_SYSINFO, &si) == -1) { @@ -175,20 +232,20 @@ void JackOSSDriver::DisplayDeviceInfo() 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_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_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 (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 (fRWMode & kRead) { + if (fCapture) { - oss_sysinfo si; + 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 { @@ -213,14 +270,14 @@ void JackOSSDriver::DisplayDeviceInfo() 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_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_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 (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"); } } @@ -229,6 +286,342 @@ void JackOSSDriver::DisplayDeviceInfo() } } +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; @@ -236,6 +629,7 @@ int JackOSSDriver::OpenInput() int cur_capture_channels; int cur_sample_format; jack_nframes_t cur_sample_rate; + audio_buf_info info; if (fCaptureChannels == 0) fCaptureChannels = 2; @@ -253,19 +647,19 @@ int JackOSSDriver::OpenInput() } } - gFragFormat = (2 << 16) + int2pow2(fEngineControl->fBufferSize * fSampleSize * fCaptureChannels); - 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; - } - - cur_sample_format = fSampleFormat; - if (ioctl(fInFD, SNDCTL_DSP_SETFMT, &fSampleFormat) == -1) { + 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; } - if (cur_sample_format != fSampleFormat) { - jack_info("JackOSSDriver::OpenInput driver forced the sample format %ld", fSampleFormat); + 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; @@ -286,25 +680,54 @@ int JackOSSDriver::OpenInput() jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); } - fInputBufferSize = 0; - if (ioctl(fInFD, SNDCTL_DSP_GETBLKSIZE, &fInputBufferSize) == -1) { - jack_error("JackOSSDriver::OpenInput failed to get fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); + // 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 (fInputBufferSize != fEngineControl->fBufferSize * fSampleSize * fCaptureChannels) { - if (fIgnoreHW) { - int new_buffer_size = fInputBufferSize / (fSampleSize * fCaptureChannels); - jack_info("JackOSSDriver::OpenInput driver forced buffer size %ld", new_buffer_size); - JackAudioDriver::SetBufferSize(new_buffer_size); // never fails - } else { - jack_error("JackOSSDriver::OpenInput wanted buffer size cannot be obtained"); - goto error; - } + 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: @@ -319,6 +742,7 @@ int JackOSSDriver::OpenOutput() int cur_sample_format; int cur_playback_channels; jack_nframes_t cur_sample_rate; + audio_buf_info info; if (fPlaybackChannels == 0) fPlaybackChannels = 2; @@ -336,19 +760,19 @@ int JackOSSDriver::OpenOutput() } } - gFragFormat = (2 << 16) + int2pow2(fEngineControl->fBufferSize * fSampleSize * fPlaybackChannels); - 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; - } - - cur_sample_format = fSampleFormat; - if (ioctl(fOutFD, SNDCTL_DSP_SETFMT, &fSampleFormat) == -1) { + 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; } - if (cur_sample_format != fSampleFormat) { - jack_info("JackOSSDriver::OpenOutput driver forced the sample format %ld", fSampleFormat); + 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; @@ -369,26 +793,56 @@ int JackOSSDriver::OpenOutput() jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); } - fOutputBufferSize = 0; - if (ioctl(fOutFD, SNDCTL_DSP_GETBLKSIZE, &fOutputBufferSize) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to get fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); + // 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 (fOutputBufferSize != fEngineControl->fBufferSize * fSampleSize * fPlaybackChannels) { - if (fIgnoreHW) { - int new_buffer_size = fOutputBufferSize / (fSampleSize * fPlaybackChannels); - jack_info("JackOSSDriver::OpenOutput driver forced buffer size %ld", new_buffer_size); - JackAudioDriver::SetBufferSize(new_buffer_size); // never fails - } else { - jack_error("JackOSSDriver::OpenInput wanted buffer size cannot be obtained"); - goto error; - } + 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); - fFirstCycle = true; assert(fOutputBuffer); + + if (ProbeOutBlockSize() < 0) { + goto error; + } + return 0; error: @@ -397,43 +851,44 @@ error: } 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) + 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; + fExtraCaptureLatency = capture_latency; + fExtraPlaybackLatency = playback_latency; + + // Additional playback latency introduced by the OSS buffer. The extra hardware + // latency given by the user should then be symmetric as reported by jack_iodelay. + playback_latency += user_nperiods * nframes; // 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 { - if (!fEngineControl->fSyncMode) { - jack_error("Cannot run in asynchronous mode, use the -S parameter for jackd"); - return -1; - } - - fRWMode |= ((capturing) ? kRead : 0); - fRWMode |= ((playing) ? kWrite : 0); - fBits = bits; - fIgnoreHW = ignorehwbuf; - fNperiods = user_nperiods; - fExcl = excl; - - #ifdef JACK_MONITOR +#ifdef JACK_MONITOR // Force memory page in memset(&gCycleTable, 0, sizeof(gCycleTable)); - #endif +#endif if (OpenAux() < 0) { Close(); @@ -446,7 +901,7 @@ int JackOSSDriver::Open(jack_nframes_t nframes, int JackOSSDriver::Close() { - #ifdef JACK_MONITOR +#ifdef JACK_MONITOR FILE* file = fopen("OSSProfiling.log", "w"); if (file) { @@ -492,7 +947,7 @@ int JackOSSDriver::Close() fclose(file); } - #endif +#endif int res = JackAudioDriver::Close(); CloseAux(); return res; @@ -501,39 +956,38 @@ int JackOSSDriver::Close() int JackOSSDriver::OpenAux() { - SetSampleFormat(); - - if ((fRWMode & kRead) && (OpenInput() < 0)) { + // (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 ((fRWMode & kWrite) && (OpenOutput() < 0)) { + if (fPlayback && (OpenOutput() < 0)) { return -1; } - // In duplex mode, check that input and output use the same buffer size - /* - - 10/02/09 : deactivated for now, needs more check (only needed when *same* device is used for input and output ??) - - if ((fRWMode & kRead) && (fRWMode & kWrite) && (fInputBufferSize != fOutputBufferSize)) { - jack_error("JackOSSDriver::OpenAux input and output buffer size are not the same!!"); - return -1; - } - */ - DisplayDeviceInfo(); return 0; } void JackOSSDriver::CloseAux() { - if (fRWMode & kRead && fInFD > 0) { + if (fCapture && fInFD > 0) { close(fInFD); fInFD = -1; } - if (fRWMode & kWrite && fOutFD > 0) { + if (fPlayback && fOutFD > 0) { close(fOutFD); fOutFD = -1; } @@ -549,34 +1003,110 @@ void JackOSSDriver::CloseAux() int JackOSSDriver::Read() { - if (fInFD < 0) { - // Keep begin cycle time - JackDriver::CycleTakeBeginTime(); - return 0; + 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; } - ssize_t count; + 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 - audio_errinfo ei_in; - count = ::read(fInFD, fInputBuffer, fInputBufferSize); + 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 / (fSampleSize * fCaptureChannels)); + jack_log("JackOSSDriver::Read count = %ld", count / (fInSampleSize * fCaptureChannels)); gCycleTable.fTable[gCycleCount].fAfterRead = GetMicroSeconds(); #endif - // XRun detection + // 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 overruns"); - jack_time_t cur_time = GetMicroSeconds(); - NotifyXRun(cur_time, float(cur_time - fBeginDateUst)); // Better this value than nothing... + jack_error("JackOSSDriver::Read %d overrun events", ei_in.rec_overruns); } if (ei_in.rec_errorcount > 0 && ei_in.rec_lasterror != 0) { @@ -584,64 +1114,68 @@ int JackOSSDriver::Read() } } - if (count < 0) { - jack_log("JackOSSDriver::Read error = %s", strerror(errno)); + if (count < fInputBufferSize) { + jack_error("JackOSSDriver::Read incomplete read of %ld bytes", count); return -1; - } else if (count < (int)fInputBufferSize) { - jack_error("JackOSSDriver::Read error bytes read = %ld", count); - return -1; - } else { + } - // Keep begin cycle time - JackDriver::CycleTakeBeginTime(); - for (int i = 0; i < fCaptureChannels; i++) { - if (fGraphManager->GetConnectionsNum(fCapturePortList[i]) > 0) { - CopyAndConvertIn(GetInputBuffer(i), fInputBuffer, fEngineControl->fBufferSize, i, fCaptureChannels, fBits); - } + 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 +#ifdef JACK_MONITOR + gCycleTable.fTable[gCycleCount].fAfterReadConvert = GetMicroSeconds(); +#endif - return 0; - } + return 0; } int JackOSSDriver::Write() { if (fOutFD < 0) { - // Keep end cycle time - JackDriver::CycleTakeEndTime(); return 0; } - ssize_t count; - audio_errinfo ei_out; - - // Maybe necessary to write an empty output buffer first time : see http://manuals.opensound.com/developer/fulldup.c.html - if (fFirstCycle) { - - fFirstCycle = false; - memset(fOutputBuffer, 0, fOutputBufferSize); - - // Prefill output buffer - for (int i = 0; i < fNperiods; i++) { - count = ::write(fOutFD, fOutputBuffer, fOutputBufferSize); - if (count < (int)fOutputBufferSize) { - jack_error("JackOSSDriver::Write error bytes written = %ld", count); - return -1; - } + 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; } - - int delay; - if (ioctl(fOutFD, SNDCTL_DSP_GETODELAY, &delay) == -1) { - jack_error("JackOSSDriver::Write error get out delay : %s@%i, errno = %d", __FILE__, __LINE__, errno); - return -1; + // 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); } - - delay /= fSampleSize * fPlaybackChannels; - jack_info("JackOSSDriver::Write output latency frames = %ld", delay); } #ifdef JACK_MONITOR @@ -651,32 +1185,48 @@ int JackOSSDriver::Write() 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, fBits); + CopyAndConvertOut(fOutputBuffer, GetOutputBuffer(i), fEngineControl->fBufferSize, i, fPlaybackChannels, fOutSampleSize * 8); } } - #ifdef JACK_MONITOR +#ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeWrite = GetMicroSeconds(); - #endif +#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)); - // Keep end cycle time - JackDriver::CycleTakeEndTime(); - count = ::write(fOutFD, fOutputBuffer, fOutputBufferSize); + 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 +#ifdef JACK_MONITOR if (count > 0 && count != (int)fOutputBufferSize) - jack_log("JackOSSDriver::Write count = %ld", count / (fSampleSize * fPlaybackChannels)); + jack_log("JackOSSDriver::Write count = %ld", (count - skip) / (fOutSampleSize * fPlaybackChannels)); gCycleTable.fTable[gCycleCount].fAfterWrite = GetMicroSeconds(); gCycleCount = (gCycleCount == CYCLE_POINTS - 1) ? gCycleCount: gCycleCount + 1; - #endif +#endif - // XRun detection + // 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 underruns"); - jack_time_t cur_time = GetMicroSeconds(); - NotifyXRun(cur_time, float(cur_time - fBeginDateUst)); // Better this value than nothing... + jack_error("JackOSSDriver::Write %d underrun events", ei_out.play_underruns); } if (ei_out.play_errorcount > 0 && ei_out.play_lasterror != 0) { @@ -684,45 +1234,24 @@ int JackOSSDriver::Write() } } - if (count < 0) { - jack_log("JackOSSDriver::Write error = %s", strerror(errno)); + if (count < (int)fOutputBufferSize) { + jack_error("JackOSSDriver::Write incomplete write of %ld bytes", count - skip); return -1; - } else if (count < (int)fOutputBufferSize) { - jack_error("JackOSSDriver::Write error bytes written = %ld", count); - return -1; - } else { - return 0; } + + return 0; } int JackOSSDriver::SetBufferSize(jack_nframes_t buffer_size) { CloseAux(); - JackAudioDriver::SetBufferSize(buffer_size); // Generic change, never fails - return OpenAux(); -} - -int JackOSSDriver::ProcessSync() -{ - // Read input buffers for the current cycle - if (Read() < 0) { - jack_error("ProcessSync: read error, skip cycle"); - return 0; // Non fatal error here, skip cycle, but continue processing... - } - - if (fIsMaster) { - ProcessGraphSync(); - } else { - ResumeRefNum(); - } - // Write output buffers for the current cycle - if (Write() < 0) { - jack_error("JackAudioDriver::ProcessSync: write error, skip cycle"); - return 0; // Non fatal error here, skip cycle, but continue processing... - } + // Additional latency introduced by the OSS buffer, depends on buffer size. + fCaptureLatency = fExtraCaptureLatency; + fPlaybackLatency = fExtraPlaybackLatency + fNperiods * buffer_size; - return 0; + JackAudioDriver::SetBufferSize(buffer_size); // Generic change, never fails + return OpenAux(); } } // end of namespace diff --git a/freebsd/oss/JackOSSDriver.h b/freebsd/oss/JackOSSDriver.h index 32758d40..6175fe06 100644 --- a/freebsd/oss/JackOSSDriver.h +++ b/freebsd/oss/JackOSSDriver.h @@ -28,13 +28,13 @@ namespace Jack typedef jack_default_audio_sample_t jack_sample_t; -#define OSS_DRIVER_DEF_DEV "/dev/dsp" -#define OSS_DRIVER_DEF_FS 48000 -#define OSS_DRIVER_DEF_BLKSIZE 1024 -#define OSS_DRIVER_DEF_NPERIODS 1 -#define OSS_DRIVER_DEF_BITS 16 -#define OSS_DRIVER_DEF_INS 2 -#define OSS_DRIVER_DEF_OUTS 2 +#define OSS_DRIVER_DEF_DEV "/dev/dsp" +#define OSS_DRIVER_DEF_FS 48000 +#define OSS_DRIVER_DEF_BLKSIZE 1024 +#define OSS_DRIVER_DEF_NPERIODS 1 +#define OSS_DRIVER_DEF_BITS 16 +#define OSS_DRIVER_DEF_INS 2 +#define OSS_DRIVER_DEF_OUTS 2 /*! \brief The OSS driver. @@ -42,21 +42,22 @@ typedef jack_default_audio_sample_t jack_sample_t; class JackOSSDriver : public JackAudioDriver { - - enum { kRead = 1, kWrite = 2, kReadWrite = 3 }; - private: int fInFD; int fOutFD; int fBits; - int fSampleFormat; int fNperiods; - unsigned int fSampleSize; - int fRWMode; + bool fCapture; + bool fPlayback; bool fExcl; bool fIgnoreHW; + jack_nframes_t fExtraCaptureLatency; + jack_nframes_t fExtraPlaybackLatency; + + unsigned int fInSampleSize; + unsigned int fOutSampleSize; unsigned int fInputBufferSize; unsigned int fOutputBufferSize; @@ -64,26 +65,49 @@ class JackOSSDriver : public JackAudioDriver void* fInputBuffer; void* fOutputBuffer; - bool fFirstCycle; + jack_nframes_t fInBlockSize; + jack_nframes_t fOutBlockSize; + jack_nframes_t fInMeanStep; + jack_nframes_t fOutMeanStep; + jack_nframes_t fOSSInBuffer; + jack_nframes_t fOSSOutBuffer; + + jack_time_t fOSSReadSync; + long long fOSSReadOffset; + jack_time_t fOSSWriteSync; + long long fOSSWriteOffset; + + // Buffer balance and sync correction + long long fBufferBalance; + bool fForceBalancing; + bool fForceSync; int OpenInput(); int OpenOutput(); int OpenAux(); void CloseAux(); - void SetSampleFormat(); void DisplayDeviceInfo(); - - // Redefining since timing for CPU load is specific - int ProcessSync(); + int ProbeInBlockSize(); + int ProbeOutBlockSize(); + int Discard(jack_nframes_t frames); + int WriteSilence(jack_nframes_t frames); + int WaitAndSync(); public: JackOSSDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table) : JackAudioDriver(name, alias, engine, table), fInFD(-1), fOutFD(-1), fBits(0), - fSampleFormat(0), fNperiods(0), fRWMode(0), fExcl(false), fIgnoreHW(true), + fNperiods(0), fCapture(false), fPlayback(false), fExcl(false), fIgnoreHW(true), + fExtraCaptureLatency(0), fExtraPlaybackLatency(0), + fInSampleSize(0), fOutSampleSize(0), fInputBufferSize(0), fOutputBufferSize(0), - fInputBuffer(NULL), fOutputBuffer(NULL), fFirstCycle(true) + fInputBuffer(NULL), fOutputBuffer(NULL), + fInBlockSize(1), fOutBlockSize(1), + fInMeanStep(0), fOutMeanStep(0), + fOSSInBuffer(0), fOSSOutBuffer(0), + fOSSReadSync(0), fOSSReadOffset(0), fOSSWriteSync(0), fOSSWriteOffset(0), + fBufferBalance(0), fForceBalancing(false), fForceSync(false) {} virtual ~JackOSSDriver() -- 2.11.4.GIT