summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Walpen <dev@submerge.ch>2021-08-17 20:47:29 +0200
committer0EVSG <dev@submerge.ch>2021-12-27 20:45:57 +0100
commit302b8e540cf0d7bf266007235cad950465788e89 (patch)
tree24069b3a9c9925fda82cff28dce21f75a8eb95ae
parent38f6b746625952726949735b08370b44f58fd662 (diff)
downloadjack2-302b8e540cf0d7bf266007235cad950465788e89.tar.gz
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.
-rw-r--r--freebsd/oss/JackOSSDriver.cpp1031
-rw-r--r--freebsd/oss/JackOSSDriver.h64
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()