From b95f412755fdf2b892dca83f20bf8b9a9953548e Mon Sep 17 00:00:00 2001 From: Florian Walpen Date: Thu, 2 Feb 2023 18:22:11 +0100 Subject: [PATCH] FreeBSD: Rewrite the OSS driver using sosso library. Make use of the sosso library to handle the low-level operation of OSS devices on FreeBSD. Further separate this part into the JackOSSChannel class, which should make it easier to reuse the code for JackOSSAdapter (not planned yet). JackOSSChannel features an additional assist thread which helps to process the OSS device when the driver thread is busy with the JACK dependency graph. Combined with the improvements of the sosso library, these changes bring JACK into the realm of real-time audio processing on FreeBSD, for select PCI sound cards (4-6ms round trip latency). --- freebsd/oss/JackOSSChannel.cpp | 489 ++++++++++++++ freebsd/oss/JackOSSChannel.h | 132 ++++ freebsd/oss/JackOSSDriver.cpp | 1124 ++++---------------------------- freebsd/oss/JackOSSDriver.h | 52 +- wscript | 1 + 5 files changed, 766 insertions(+), 1032 deletions(-) create mode 100644 freebsd/oss/JackOSSChannel.cpp create mode 100644 freebsd/oss/JackOSSChannel.h diff --git a/freebsd/oss/JackOSSChannel.cpp b/freebsd/oss/JackOSSChannel.cpp new file mode 100644 index 000000000..d6e6ccfdf --- /dev/null +++ b/freebsd/oss/JackOSSChannel.cpp @@ -0,0 +1,489 @@ +/* +Copyright (C) 2023 Florian Walpen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "JackOSSChannel.h" +#include "JackError.h" +#include "JackThread.h" +#include "memops.h" + +#include +#include +#include +#include +#include +#include +#include + +typedef jack_default_audio_sample_t jack_sample_t; + +namespace +{ + +int SuggestSampleFormat(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; + } +} + +bool SupportedSampleFormat(int format) +{ + switch(format) { + // Only signed sample formats are supported by the conversion functions. + case AFMT_S16_NE: + case AFMT_S16_OE: + case AFMT_S24_NE: + case AFMT_S24_OE: + case AFMT_S32_NE: + case AFMT_S32_OE: + return true; + } + return false; +} + +void CopyAndConvertIn(jack_sample_t *dst, char *src, size_t nframes, int channel, int chcount, int format) +{ + switch (format) { + + case AFMT_S16_NE: + src += channel * 2; + sample_move_dS_s16(dst, src, nframes, chcount * 2); + break; + case AFMT_S16_OE: + src += channel * 2; + sample_move_dS_s16s(dst, src, nframes, chcount * 2); + break; + case AFMT_S24_NE: + src += channel * 3; + sample_move_dS_s24(dst, src, nframes, chcount * 3); + break; + case AFMT_S24_OE: + src += channel * 3; + sample_move_dS_s24s(dst, src, nframes, chcount * 3); + break; + case AFMT_S32_NE: + src += channel * 4; + sample_move_dS_s32(dst, src, nframes, chcount * 4); + break; + case AFMT_S32_OE: + src += channel * 4; + sample_move_dS_s32s(dst, src, nframes, chcount * 4); + break; + } +} + +void CopyAndConvertOut(char *dst, jack_sample_t *src, size_t nframes, int channel, int chcount, int format) +{ + switch (format) { + + case AFMT_S16_NE: + dst += channel * 2; + sample_move_d16_sS(dst, src, nframes, chcount * 2, NULL); + break; + case AFMT_S16_OE: + dst += channel * 2; + sample_move_d16_sSs(dst, src, nframes, chcount * 2, NULL); + break; + case AFMT_S24_NE: + dst += channel * 3; + sample_move_d24_sS(dst, src, nframes, chcount * 3, NULL); + break; + case AFMT_S24_OE: + dst += channel * 3; + sample_move_d24_sSs(dst, src, nframes, chcount * 3, NULL); + break; + case AFMT_S32_NE: + dst += channel * 4; + sample_move_d32_sS(dst, src, nframes, chcount * 4, NULL); + break; + case AFMT_S32_OE: + dst += channel * 4; + sample_move_d32_sSs(dst, src, nframes, chcount * 4, NULL); + break; + } +} + +} + +void sosso::Log::log(sosso::SourceLocation location, const char* message) { + jack_log(message); +} + +void sosso::Log::info(sosso::SourceLocation location, const char* message) { + jack_info(message); +} + +void sosso::Log::warn(sosso::SourceLocation location, const char* message) { + jack_error(message); +} + +namespace Jack +{ + +bool JackOSSChannel::InitialSetup(unsigned int sample_rate) +{ + fFrameStamp = 0; + fCorrection.clear(); + return fFrameClock.set_sample_rate(sample_rate); +} + +bool JackOSSChannel::OpenCapture(const char *device, bool exclusive, int bits, int &channels) +{ + if (channels == 0) channels = 2; + + int sample_format = SuggestSampleFormat(bits); + + if (!fReadChannel.set_parameters(sample_format, fFrameClock.sample_rate(), channels)) { + jack_error("JackOSSChannel::OpenCapture unsupported sample format %#x", sample_format); + return false; + } + + if (!fReadChannel.open(device, exclusive)) { + return false; + } + + if (fReadChannel.sample_rate() != fFrameClock.sample_rate()) { + jack_error("JackOSSChannel::OpenCapture driver forced sample rate %ld", fReadChannel.sample_rate()); + fReadChannel.close(); + return false; + } + + if (!SupportedSampleFormat(fReadChannel.sample_format())) { + jack_error("JackOSSChannel::OpenCapture unsupported sample format %#x", fReadChannel.sample_format()); + fReadChannel.close(); + return false; + } + + jack_log("JackOSSChannel::OpenCapture capture file descriptor = %d", fReadChannel.file_descriptor()); + + if (fReadChannel.channels() != channels) { + channels = fReadChannel.channels(); + jack_info("JackOSSChannel::OpenCapture driver forced the number of capture channels %ld", channels); + } + + fReadChannel.memory_map(); + + return true; +} + +bool JackOSSChannel::OpenPlayback(const char *device, bool exclusive, int bits, int &channels) +{ + if (channels == 0) channels = 2; + + int sample_format = SuggestSampleFormat(bits); + + if (!fWriteChannel.set_parameters(sample_format, fFrameClock.sample_rate(), channels)) { + jack_error("JackOSSChannel::OpenPlayback unsupported sample format %#x", sample_format); + return false; + } + + if (!fWriteChannel.open(device, exclusive)) { + return false; + } + + if (fWriteChannel.sample_rate() != fFrameClock.sample_rate()) { + jack_error("JackOSSChannel::OpenPlayback driver forced sample rate %ld", fWriteChannel.sample_rate()); + fWriteChannel.close(); + return false; + } + + if (!SupportedSampleFormat(fWriteChannel.sample_format())) { + jack_error("JackOSSChannel::OpenPlayback unsupported sample format %#x", fWriteChannel.sample_format()); + fWriteChannel.close(); + return false; + } + + jack_log("JackOSSChannel::OpenPlayback playback file descriptor = %d", fWriteChannel.file_descriptor()); + + if (fWriteChannel.channels() != channels) { + channels = fWriteChannel.channels(); + jack_info("JackOSSChannel::OpenPlayback driver forced the number of playback channels %ld", channels); + } + + fWriteChannel.memory_map(); + + return true; +} + +bool JackOSSChannel::Read(jack_sample_t **sample_buffers, jack_nframes_t length, std::int64_t end) +{ + if (fReadChannel.recording()) { + // Get buffer from read channel. + sosso::Buffer buffer = fReadChannel.take_buffer(); + + // Get recording audio data and then clear buffer. + for (unsigned i = 0; i < fReadChannel.channels(); i++) { + if (sample_buffers[i]) { + CopyAndConvertIn(sample_buffers[i], buffer.data(), length, i, fReadChannel.channels(), fReadChannel.sample_format()); + } + } + buffer.reset(); + + // Put buffer back to capture at requested end position. + fReadChannel.set_buffer(std::move(buffer), end); + SignalWork(); + return true; + } + return false; +} + +bool JackOSSChannel::Write(jack_sample_t **sample_buffers, jack_nframes_t length, std::int64_t end) +{ + if (fWriteChannel.playback()) { + // Get buffer from write channel. + sosso::Buffer buffer = fWriteChannel.take_buffer(); + + // Clear buffer and write new playback audio data. + memset(buffer.data(), 0, buffer.length()); + buffer.reset(); + for (unsigned i = 0; i < fWriteChannel.channels(); i++) { + if (sample_buffers[i]) { + CopyAndConvertOut(buffer.data(), sample_buffers[i], length, i, fWriteChannel.channels(), fWriteChannel.sample_format()); + } + } + + // Put buffer back to playback at requested end position. + end += PlaybackCorrection(); + fWriteChannel.set_buffer(std::move(buffer), end); + SignalWork(); + return true; + } + return false; +} + +bool JackOSSChannel::StartChannels(unsigned int buffer_frames) +{ + int group_id = 0; + + if (fReadChannel.recording()) { + // Allocate two recording buffers for double buffering. + size_t buffer_size = buffer_frames * fReadChannel.frame_size(); + sosso::Buffer buffer((char*) calloc(buffer_size, 1), buffer_size); + assert(buffer.data()); + fReadChannel.set_buffer(std::move(buffer), 0); + buffer = sosso::Buffer((char*) calloc(buffer_size, 1), buffer_size); + assert(buffer.data()); + fReadChannel.set_buffer(std::move(buffer), buffer_frames); + // Add recording channel to synced start group. + fReadChannel.add_to_sync_group(group_id); + } + + if (fWriteChannel.playback()) { + // Allocate two playback buffers for double buffering. + size_t buffer_size = buffer_frames * fWriteChannel.frame_size(); + sosso::Buffer buffer((char*) calloc(buffer_size, 1), buffer_size); + assert(buffer.data()); + fWriteChannel.set_buffer(std::move(buffer), 0); + buffer = sosso::Buffer((char*) calloc(buffer_size, 1), buffer_size); + assert(buffer.data()); + fWriteChannel.set_buffer(std::move(buffer), buffer_frames); + // Add playback channel to synced start group. + fWriteChannel.add_to_sync_group(group_id); + } + + // Start both channels in sync if supported. + if (fReadChannel.recording()) { + fReadChannel.start_sync_group(group_id); + } else { + fWriteChannel.start_sync_group(group_id); + } + + // Init frame clock here to mark start time. + if (!fFrameClock.init_clock(fFrameClock.sample_rate())) { + return false; + } + + // Small drift corrections to keep latency whithin +/- 1ms. + std::int64_t limit = fFrameClock.sample_rate() / 1000; + fCorrection.set_drift_limits(-limit, limit); + // Drastic corrections when drift exceeds half a period. + limit = std::max(limit, buffer_frames / 2); + fCorrection.set_loss_limits(-limit, limit); + + SignalWork(); + + return true; +} + +bool JackOSSChannel::StopChannels() +{ + if (fReadChannel.recording()) { + free(fReadChannel.take_buffer().data()); + free(fReadChannel.take_buffer().data()); + fReadChannel.memory_unmap(); + fReadChannel.close(); + } + + if (fWriteChannel.playback()) { + free(fWriteChannel.take_buffer().data()); + free(fWriteChannel.take_buffer().data()); + fWriteChannel.memory_unmap(); + fWriteChannel.close(); + } + + return true; +} + +bool JackOSSChannel::StartAssistThread(bool realtime, int priority) +{ + if (fAssistThread.Start() >= 0) { + if (realtime && fAssistThread.AcquireRealTime(priority) != 0) { + jack_error("JackOSSChannel::StartAssistThread realtime priority failed."); + } + return true; + } + return false; +} + +bool JackOSSChannel::StopAssistThread() +{ + if (fAssistThread.GetStatus() != JackThread::kIdle) { + fAssistThread.SetStatus(JackThread::kIdle); + SignalWork(); + fAssistThread.Kill(); + } + return true; +} + +bool JackOSSChannel::CheckTimeAndRun() +{ + // Check current frame time. + if (!fFrameClock.now(fFrameStamp)) { + jack_error("JackOSSChannel::CheckTimeAndRun(): Frame clock failed."); + return false; + } + std::int64_t now = fFrameStamp; + + // Process read channel if wakeup time passed, or OSS buffer data available. + if (fReadChannel.recording() && !fReadChannel.total_finished(now)) { + if (now >= fReadChannel.wakeup_time(now)) { + if (!fReadChannel.process(now)) { + jack_error("JackOSSChannel::CheckTimeAndRun(): Read process failed."); + return false; + } + } + } + // Process write channel if wakeup time passed, or OSS buffer space available. + if (fWriteChannel.playback() && !fWriteChannel.total_finished(now)) { + if (now >= fWriteChannel.wakeup_time(now)) { + if (!fWriteChannel.process(now)) { + jack_error("JackOSSChannel::CheckTimeAndRun(): Write process failed."); + return false; + } + } + } + + return true; +} + +bool JackOSSChannel::Sleep() const +{ + std::int64_t wakeup = NextWakeup(); + if (wakeup > fFrameStamp) { + return fFrameClock.sleep(wakeup); + } + return true; +} + +bool JackOSSChannel::CaptureFinished() const +{ + return fReadChannel.finished(fFrameStamp); +} + +bool JackOSSChannel::PlaybackFinished() const +{ + return fWriteChannel.finished(fFrameStamp); +} + +std::int64_t JackOSSChannel::PlaybackCorrection() +{ + std::int64_t correction = 0; + // If both channels are used, correct drift relative to recording balance. + if (fReadChannel.recording() && fWriteChannel.playback()) { + std::int64_t previous = fCorrection.correction(); + correction = fCorrection.correct(fWriteChannel.balance(), fReadChannel.balance()); + if (correction != previous) { + jack_info("Playback correction changed from %lld to %lld.", previous, correction); + jack_info("Read balance %lld vs write balance %lld.", fReadChannel.balance(), fWriteChannel.balance()); + } + } + return correction; +} + +bool JackOSSChannel::Init() +{ + return true; +} + +bool JackOSSChannel::Execute() +{ + if (Lock()) { + if (fAssistThread.GetStatus() != JackThread::kIdle && CheckTimeAndRun()) { + std::int64_t wakeup = NextWakeup(); + if (fReadChannel.total_finished(fFrameStamp) && fWriteChannel.total_finished(fFrameStamp)) { + // Nothing to do, wait on the mutex for work. + jack_info("JackOSSChannel::Execute waiting for work."); + fMutex.TimedWait(1000000); + jack_info("JackOSSChannel::Execute resuming work."); + } else if (fFrameStamp < wakeup) { + // Unlock mutex before going to sleep, let others process. + return Unlock() && fFrameClock.sleep(wakeup); + } + } + return Unlock(); + } + return false; +} + +std::int64_t JackOSSChannel::XRunGap() const +{ + // Compute processing gap in case we are late. + std::int64_t max_end = std::max(fReadChannel.total_end(), fWriteChannel.total_end()); + if (max_end < fFrameStamp) { + return fFrameStamp - max_end; + } + return 0; +} + +void JackOSSChannel::ResetBuffers(std::int64_t offset) +{ + // Clear buffers and offset their positions, after processing gaps. + if (fReadChannel.recording()) { + fReadChannel.reset_buffers(fReadChannel.end_frames() + offset); + } + if (fWriteChannel.playback()) { + fWriteChannel.reset_buffers(fWriteChannel.end_frames() + offset); + } +} + +std::int64_t JackOSSChannel::NextWakeup() const +{ + return std::min(fReadChannel.wakeup_time(fFrameStamp), fWriteChannel.wakeup_time(fFrameStamp)); +} + +} // end of namespace diff --git a/freebsd/oss/JackOSSChannel.h b/freebsd/oss/JackOSSChannel.h new file mode 100644 index 000000000..d1a4a969e --- /dev/null +++ b/freebsd/oss/JackOSSChannel.h @@ -0,0 +1,132 @@ +/* +Copyright (C) 2003-2007 Jussi Laako +Copyright (C) 2008 Grame & RTL 2008 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __JackOSSChannel__ +#define __JackOSSChannel__ + +#include "JackMutex.h" +#include "JackThread.h" +#include "sosso/Correction.hpp" +#include "sosso/DoubleBuffer.hpp" +#include "sosso/FrameClock.hpp" +#include "sosso/ReadChannel.hpp" +#include "sosso/WriteChannel.hpp" + +namespace Jack +{ + +typedef jack_default_audio_sample_t jack_sample_t; + +/*! +\brief The OSS driver. +*/ + +class JackOSSChannel : public JackRunnableInterface +{ + + private: + JackThread fAssistThread; + JackProcessSync fMutex; + sosso::FrameClock fFrameClock; + sosso::DoubleBuffer fReadChannel; + sosso::DoubleBuffer fWriteChannel; + sosso::Correction fCorrection; + + std::int64_t fFrameStamp = 0; + + public: + + JackOSSChannel() : fAssistThread(this) + {} + virtual ~JackOSSChannel() + {} + + sosso::DoubleBuffer &Capture() + { + return fReadChannel; + } + + sosso::DoubleBuffer &Playback() + { + return fWriteChannel; + } + + sosso::FrameClock &FrameClock() + { + return fFrameClock; + } + + bool Lock() + { + return fMutex.Lock(); + } + + bool Unlock() + { + return fMutex.Unlock(); + } + + void SignalWork() + { + fMutex.SignalAll(); + } + + bool InitialSetup(unsigned sample_rate); + + bool OpenCapture(const char* device, bool exclusive, int bits, int &channels); + bool OpenPlayback(const char* device, bool exclusive, int bits, int &channels); + + bool Read(jack_sample_t** sample_buffers, jack_nframes_t length, std::int64_t end); + bool Write(jack_sample_t** sample_buffers, jack_nframes_t length, std::int64_t end); + + bool StartChannels(unsigned buffer_frames); + bool StopChannels(); + + bool StartAssistThread(bool realtime, int priority); + bool StopAssistThread(); + + bool CheckTimeAndRun(); + + bool Sleep() const; + + bool CaptureFinished() const; + bool PlaybackFinished() const; + + std::int64_t PlaybackCorrection(); + + virtual bool Init(); + + virtual bool Execute(); + + std::int64_t XRunGap() const; + + void ResetBuffers(std::int64_t offset); + + std::int64_t FrameStamp() const + { + return fFrameStamp; + } + + std::int64_t NextWakeup() const; +}; + +} // end of namespace + +#endif diff --git a/freebsd/oss/JackOSSDriver.cpp b/freebsd/oss/JackOSSDriver.cpp index d6f3e86cf..70a9a5fff 100644 --- a/freebsd/oss/JackOSSDriver.cpp +++ b/freebsd/oss/JackOSSDriver.cpp @@ -20,103 +20,17 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "driver_interface.h" #include "JackThreadedDriver.h" -#include "JackDriverLoader.h" #include "JackOSSDriver.h" #include "JackEngineControl.h" #include "JackGraphManager.h" #include "JackError.h" #include "JackTime.h" -#include "JackShmMem.h" -#include "memops.h" - -#include -#include -#include -#include -#include + +#include #include using namespace std; -namespace -{ - -inline jack_nframes_t TimeToFrames(jack_time_t time, jack_nframes_t sample_rate) { - return ((time * sample_rate) + 500000ULL) / 1000000ULL; -} - -inline long long TimeToOffset(jack_time_t time1, jack_time_t time2, jack_nframes_t sample_rate) -{ - if (time2 > time1) { - return TimeToFrames(time2 - time1, sample_rate); - } else { - return 0LL - TimeToFrames(time1 - time2, sample_rate); - } -} - -inline jack_time_t FramesToTime(jack_nframes_t frames, jack_nframes_t sample_rate) { - return ((frames * 1000000ULL) + (sample_rate / 2ULL)) / sample_rate; -} - -inline jack_nframes_t RoundUp(jack_nframes_t frames, jack_nframes_t block) { - if (block > 0) { - frames += (block - 1); - frames -= (frames % block); - } - return frames; -} - -inline jack_time_t RoundDown(jack_time_t time, jack_time_t interval) { - if (interval > 0) { - time -= (time % interval); - } - return time; -} - -int GetSampleFormat(int bits) -{ - switch(bits) { - // Native-endian signed 32 bit samples. - case 32: - return AFMT_S32_NE; - // Native-endian signed 24 bit (packed) samples. - case 24: - return AFMT_S24_NE; - // Native-endian signed 16 bit samples, used by default. - case 16: - default: - return AFMT_S16_NE; - } -} - -unsigned int GetSampleSize(int format) -{ - switch(format) { - // Native-endian signed 32 bit samples. - case AFMT_S32_NE: - return 4; - // Native-endian signed 24 bit (packed) samples. - case AFMT_S24_NE: - return 3; - // Native-endian signed 16 bit samples. - case AFMT_S16_NE: - return 2; - // Unsupported sample format. - default: - return 0; - } -} - -inline int UpToPower2(int x) -{ - int r = 0; - while ((1 << r) < x) - r++; - return r; -} - -} - namespace Jack { @@ -144,707 +58,6 @@ int gCycleCount = 0; #endif -static inline void CopyAndConvertIn(jack_sample_t *dst, void *src, size_t nframes, int channel, int chcount, int bits) -{ - switch (bits) { - - case 16: { - signed short *s16src = (signed short*)src; - s16src += channel; - sample_move_dS_s16(dst, (char*)s16src, nframes, chcount<<1); - break; - } - case 24: { - char *s24src = (char*)src; - s24src += channel * 3; - sample_move_dS_s24(dst, s24src, nframes, chcount*3); - break; - } - case 32: { - signed int *s32src = (signed int*)src; - s32src += channel; - sample_move_dS_s32u24(dst, (char*)s32src, nframes, chcount<<2); - break; - } - } -} - -static inline void CopyAndConvertOut(void *dst, jack_sample_t *src, size_t nframes, int channel, int chcount, int bits) -{ - switch (bits) { - - case 16: { - signed short *s16dst = (signed short*)dst; - s16dst += channel; - sample_move_d16_sS((char*)s16dst, src, nframes, chcount<<1, NULL); // No dithering for now... - break; - } - case 24: { - char *s24dst = (char*)dst; - s24dst += channel * 3; - sample_move_d24_sS(s24dst, src, nframes, chcount*3, NULL); - break; - } - case 32: { - signed int *s32dst = (signed int*)dst; - s32dst += channel; - sample_move_d32u24_sS((char*)s32dst, src, nframes, chcount<<2, NULL); - break; - } - } -} - -void JackOSSDriver::DisplayDeviceInfo() -{ - audio_buf_info info; - memset(&info, 0, sizeof(audio_buf_info)); - int cap = 0; - - // Duplex cards : http://manuals.opensound.com/developer/full_duplex.html - jack_info("Audio Interface Description :"); - jack_info("Sampling Frequency : %d, Sample Size : %d", fEngineControl->fSampleRate, fInSampleSize * 8); - - if (fPlayback) { - - oss_sysinfo si; - if (ioctl(fOutFD, OSS_SYSINFO, &si) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo OSS_SYSINFO failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - jack_info("OSS product %s", si.product); - jack_info("OSS version %s", si.version); - jack_info("OSS version num %d", si.versionnum); - jack_info("OSS numaudios %d", si.numaudios); - jack_info("OSS numaudioengines %d", si.numaudioengines); - jack_info("OSS numcards %d", si.numcards); - } - - jack_info("Output capabilities - %d channels : ", fPlaybackChannels); - jack_info("Output block size = %d", fOutputBufferSize); - - if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETOSPACE failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - jack_info("output space info: fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d", - info.fragments, info.fragstotal, info.fragsize, info.bytes); - } - - if (ioctl(fOutFD, SNDCTL_DSP_GETCAPS, &cap) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETCAPS failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - if (cap & DSP_CAP_DUPLEX) jack_info(" DSP_CAP_DUPLEX"); - if (cap & DSP_CAP_REALTIME) jack_info(" DSP_CAP_REALTIME"); - if (cap & DSP_CAP_BATCH) jack_info(" DSP_CAP_BATCH"); - if (cap & DSP_CAP_COPROC) jack_info(" DSP_CAP_COPROC"); - if (cap & DSP_CAP_TRIGGER) jack_info(" DSP_CAP_TRIGGER"); - if (cap & DSP_CAP_MMAP) jack_info(" DSP_CAP_MMAP"); - if (cap & DSP_CAP_MULTI) jack_info(" DSP_CAP_MULTI"); - if (cap & DSP_CAP_BIND) jack_info(" DSP_CAP_BIND"); - } - } - - if (fCapture) { - - oss_sysinfo si; - if (ioctl(fInFD, OSS_SYSINFO, &si) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo OSS_SYSINFO failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - jack_info("OSS product %s", si.product); - jack_info("OSS version %s", si.version); - jack_info("OSS version num %d", si.versionnum); - jack_info("OSS numaudios %d", si.numaudios); - jack_info("OSS numaudioengines %d", si.numaudioengines); - jack_info("OSS numcards %d", si.numcards); - } - - jack_info("Input capabilities - %d channels : ", fCaptureChannels); - jack_info("Input block size = %d", fInputBufferSize); - - if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETOSPACE failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - jack_info("input space info: fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d", - info.fragments, info.fragstotal, info.fragsize, info.bytes); - } - - if (ioctl(fInFD, SNDCTL_DSP_GETCAPS, &cap) == -1) { - jack_error("JackOSSDriver::DisplayDeviceInfo SNDCTL_DSP_GETCAPS failed : %s@%i, errno = %d", __FILE__, __LINE__, errno); - } else { - if (cap & DSP_CAP_DUPLEX) jack_info(" DSP_CAP_DUPLEX"); - if (cap & DSP_CAP_REALTIME) jack_info(" DSP_CAP_REALTIME"); - if (cap & DSP_CAP_BATCH) jack_info(" DSP_CAP_BATCH"); - if (cap & DSP_CAP_COPROC) jack_info(" DSP_CAP_COPROC"); - if (cap & DSP_CAP_TRIGGER) jack_info(" DSP_CAP_TRIGGER"); - if (cap & DSP_CAP_MMAP) jack_info(" DSP_CAP_MMAP"); - if (cap & DSP_CAP_MULTI) jack_info(" DSP_CAP_MULTI"); - if (cap & DSP_CAP_BIND) jack_info(" DSP_CAP_BIND"); - } - } -} - -int JackOSSDriver::ProbeInBlockSize() -{ - jack_nframes_t blocks[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - int probes = 0; - int ret = 0; - // Default values in case of an error. - fInMeanStep = fEngineControl->fBufferSize; - fInBlockSize = 1; - - if (fInFD > 0) { - // Read one frame into a new hardware block so we can check its size. - // Repeat that for multiple probes, sometimes the first reads differ. - jack_nframes_t frames = 1; - for (int p = 0; p < 8 && frames > 0; ++p) { - ret = Discard(frames); - frames = 0; - if (ret == 0) { - oss_count_t ptr; - if (ioctl(fInFD, SNDCTL_DSP_CURRENT_IPTR, &ptr) == 0 && ptr.fifo_samples > 0) { - // Success, store probed hardware block size for later. - blocks[p] = 1U + ptr.fifo_samples; - ++probes; - // Proceed by reading one frame into the next hardware block. - frames = blocks[p]; - } - } else { - // Read error - abort. - jack_error("JackOSSDriver::ProbeInBlockSize read failed with %d", ret); - } - } - - // Stop recording. - ioctl(fInFD, SNDCTL_DSP_HALT_INPUT, NULL); - } - - if (probes == 8) { - // Compute mean block size of the last six probes. - jack_nframes_t sum = 0; - for (int p = 2; p < 8; ++p) { - jack_log("JackOSSDriver::ProbeInBlockSize read block of %d frames", blocks[p]); - sum += blocks[p]; - } - fInMeanStep = sum / 6; - - // Check that none of the probed block sizes deviates too much. - jack_nframes_t slack = fInMeanStep / 16; - bool strict = true; - for (int p = 2; p < 8; ++p) { - strict = strict && (blocks[p] > fInMeanStep - slack) && (blocks[p] < fInMeanStep + slack); - } - - if (strict && fInMeanStep <= fEngineControl->fBufferSize) { - // Regular hardware block size, use it for rounding. - jack_info("JackOSSDriver::ProbeInBlockSize read blocks are %d frames", fInMeanStep); - fInBlockSize = fInMeanStep; - } else { - jack_info("JackOSSDriver::ProbeInBlockSize irregular read block sizes"); - jack_info("JackOSSDriver::ProbeInBlockSize mean read block was %d frames", fInMeanStep); - } - - if (fInBlockSize > fEngineControl->fBufferSize / 2) { - jack_info("JackOSSDriver::ProbeInBlockSize less than two read blocks per cycle"); - jack_info("JackOSSDriver::ProbeInBlockSize for best results make period a multiple of %d", fInBlockSize); - } - - if (fInMeanStep > fEngineControl->fBufferSize) { - jack_error("JackOSSDriver::ProbeInBlockSize period is too small, minimum is %d frames", fInMeanStep); - return -1; - } - } - - return ret; -} - -int JackOSSDriver::ProbeOutBlockSize() -{ - jack_nframes_t blocks[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - int probes = 0; - int ret = 0; - // Default values in case of an error. - fOutMeanStep = fEngineControl->fBufferSize; - fOutBlockSize = 1; - - if (fOutFD) { - // Write one frame over the low water mark, then check the consumed block size. - // Repeat that for multiple probes, sometimes the initial ones differ. - jack_nframes_t mark = fNperiods * fEngineControl->fBufferSize; - WriteSilence(mark + 1); - for (int p = 0; p < 8 && ret >= 0; ++p) { - pollfd poll_fd; - poll_fd.fd = fOutFD; - poll_fd.events = POLLOUT; - ret = poll(&poll_fd, 1, 500); - if (ret < 0) { - jack_error("JackOSSDriver::ProbeOutBlockSize poll failed with %d", ret); - break; - } - if (poll_fd.revents & POLLOUT) { - oss_count_t ptr; - if (ioctl(fOutFD, SNDCTL_DSP_CURRENT_OPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { - // Success, store probed hardware block size for later. - blocks[p] = mark + 1 - ptr.fifo_samples; - ++probes; - // Proceed by writing one frame over the low water mark. - WriteSilence(blocks[p]); - } - poll_fd.revents = 0; - } - } - - // Stop playback. - ioctl(fOutFD, SNDCTL_DSP_HALT_INPUT, NULL); - } - - if (probes == 8) { - // Compute mean and maximum block size of the last six probes. - jack_nframes_t sum = 0; - for (int p = 2; p < 8; ++p) { - jack_log("JackOSSDriver::ProbeOutBlockSize write block of %d frames", blocks[p]); - sum += blocks[p]; - } - fOutMeanStep = sum / 6; - - // Check that none of the probed block sizes deviates too much. - jack_nframes_t slack = fOutMeanStep / 16; - bool strict = true; - for (int p = 2; p < 8; ++p) { - strict = strict && (blocks[p] > fOutMeanStep - slack) && (blocks[p] < fOutMeanStep + slack); - } - - if (strict && fOutMeanStep <= fEngineControl->fBufferSize) { - // Regular hardware block size, use it for rounding. - jack_info("JackOSSDriver::ProbeOutBlockSize write blocks are %d frames", fOutMeanStep); - fOutBlockSize = fOutMeanStep; - } else { - jack_info("JackOSSDriver::ProbeOutBlockSize irregular write block sizes"); - jack_info("JackOSSDriver::ProbeOutBlockSize mean write block was %d frames", fOutMeanStep); - } - - if (fOutBlockSize > fEngineControl->fBufferSize / 2) { - jack_info("JackOSSDriver::ProbeOutBlockSize less than two write blocks per cycle"); - jack_info("JackOSSDriver::ProbeOutBlockSize for best results make period a multiple of %d", fOutBlockSize); - } - - if (fOutMeanStep > fEngineControl->fBufferSize) { - jack_error("JackOSSDriver::ProbeOutBlockSize period is too small, minimum is %d frames", fOutMeanStep); - return -1; - } - } - - return ret; -} - -int JackOSSDriver::Discard(jack_nframes_t frames) -{ - if (fInFD < 0) { - return -1; - } - - // Read frames from OSS capture buffer to be discarded. - ssize_t size = frames * fInSampleSize * fCaptureChannels; - while (size > 0) { - ssize_t chunk = (size > fInputBufferSize) ? fInputBufferSize : size; - ssize_t count = ::read(fInFD, fInputBuffer, chunk); - if (count <= 0) { - jack_error("JackOSSDriver::Discard error bytes read = %ld", count); - return -1; - } - fOSSReadOffset += count / (fInSampleSize * fCaptureChannels); - size -= count; - } - return 0; -} - -int JackOSSDriver::WriteSilence(jack_nframes_t frames) -{ - if (fOutFD < 0) { - return -1; - } - - // Fill OSS playback buffer, write some periods of silence. - memset(fOutputBuffer, 0, fOutputBufferSize); - ssize_t size = frames * fOutSampleSize * fPlaybackChannels; - while (size > 0) { - ssize_t chunk = (size > fOutputBufferSize) ? fOutputBufferSize : size; - ssize_t count = ::write(fOutFD, fOutputBuffer, chunk); - if (count <= 0) { - jack_error("JackOSSDriver::WriteSilence error bytes written = %ld", count); - return -1; - } - fOSSWriteOffset += (count / (fOutSampleSize * fPlaybackChannels)); - size -= count; - } - return 0; -} - -int JackOSSDriver::WaitAndSync() -{ - oss_count_t ptr = {0, 0, {0}}; - if (fInFD > 0 && fOSSReadSync != 0) { - // Predict time of next capture sync (poll() return). - if (fOSSReadOffset + fEngineControl->fBufferSize > 0) { - jack_nframes_t frames = fOSSReadOffset + fEngineControl->fBufferSize; - jack_nframes_t rounded = RoundUp(frames, fInBlockSize); - fOSSReadSync += FramesToTime(rounded, fEngineControl->fSampleRate); - fOSSReadOffset -= rounded; - } - } - if (fOutFD > 0 && fOSSWriteSync != 0) { - // Predict time of next playback sync (poll() return). - if (fOSSWriteOffset > fNperiods * fEngineControl->fBufferSize) { - jack_nframes_t frames = fOSSWriteOffset - fNperiods * fEngineControl->fBufferSize; - jack_nframes_t rounded = RoundUp(frames, fOutBlockSize); - fOSSWriteSync += FramesToTime(rounded, fEngineControl->fSampleRate); - fOSSWriteOffset -= rounded; - } - } - jack_time_t poll_start = GetMicroSeconds(); - // Poll until recording and playback buffer are ready for this cycle. - pollfd poll_fd[2]; - poll_fd[0].fd = fInFD; - if (fInFD > 0 && (fForceSync || poll_start < fOSSReadSync)) { - poll_fd[0].events = POLLIN; - } else { - poll_fd[0].events = 0; - } - poll_fd[1].fd = fOutFD; - if (fOutFD > 0 && (fForceSync || poll_start < fOSSWriteSync)) { - poll_fd[1].events = POLLOUT; - } else { - poll_fd[1].events = 0; - } - while (poll_fd[0].events != 0 || poll_fd[1].events != 0) { - poll_fd[0].revents = 0; - poll_fd[1].revents = 0; - int ret = poll(poll_fd, 2, 500); - jack_time_t now = GetMicroSeconds(); - if (ret <= 0) { - jack_error("JackOSSDriver::WaitAndSync poll failed with %d after %ld us", ret, now - poll_start); - return ret; - } - if (poll_fd[0].revents & POLLIN) { - // Check the excess recording frames. - if (ioctl(fInFD, SNDCTL_DSP_CURRENT_IPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { - if (fInBlockSize <= 1) { - // Irregular block size, let sync time converge slowly when late. - fOSSReadSync = min(fOSSReadSync, now) / 2 + now / 2; - fOSSReadOffset = -ptr.fifo_samples; - } else if (ptr.fifo_samples - fEngineControl->fBufferSize >= fInBlockSize) { - // Too late for a reliable sync, make sure sync time is not in the future. - if (now < fOSSReadSync) { - fOSSReadOffset = -ptr.fifo_samples; - jack_info("JackOSSDriver::WaitAndSync capture sync %ld us early, %ld frames", fOSSReadSync - now, fOSSReadOffset); - fOSSReadSync = now; - } - } else if (fForceSync) { - // Uncertain previous sync, just use sync time directly. - fOSSReadSync = now; - fOSSReadOffset = -ptr.fifo_samples; - } else { - // Adapt expected sync time when early or late - in whole block intervals. - // Account for some speed drift, but otherwise round down to earlier interval. - jack_time_t interval = FramesToTime(fInBlockSize, fEngineControl->fSampleRate); - jack_time_t remainder = fOSSReadSync % interval; - jack_time_t max_drift = interval / 4; - jack_time_t rounded = RoundDown((now - remainder) + max_drift, interval) + remainder; - // Let sync time converge slowly when late, prefer earlier sync times. - fOSSReadSync = min(rounded, now) / 2 + now / 2; - fOSSReadOffset = -ptr.fifo_samples; - } - } - poll_fd[0].events = 0; - } - if (poll_fd[1].revents & POLLOUT) { - // Check the remaining playback frames. - if (ioctl(fOutFD, SNDCTL_DSP_CURRENT_OPTR, &ptr) != -1 && ptr.fifo_samples >= 0) { - if (fOutBlockSize <= 1) { - // Irregular block size, let sync time converge slowly when late. - fOSSWriteSync = min(fOSSWriteSync, now) / 2 + now / 2; - fOSSWriteOffset = ptr.fifo_samples; - } else if (ptr.fifo_samples + fOutBlockSize <= fNperiods * fEngineControl->fBufferSize) { - // Too late for a reliable sync, make sure sync time is not in the future. - if (now < fOSSWriteSync) { - fOSSWriteOffset = ptr.fifo_samples; - jack_info("JackOSSDriver::WaitAndSync playback sync %ld us early, %ld frames", fOSSWriteSync - now, fOSSWriteOffset); - fOSSWriteSync = now; - } - } else if (fForceSync) { - // Uncertain previous sync, just use sync time directly. - fOSSWriteSync = now; - fOSSWriteOffset = ptr.fifo_samples; - } else { - // Adapt expected sync time when early or late - in whole block intervals. - // Account for some speed drift, but otherwise round down to earlier interval. - jack_time_t interval = FramesToTime(fOutBlockSize, fEngineControl->fSampleRate); - jack_time_t remainder = fOSSWriteSync % interval; - jack_time_t max_drift = interval / 4; - jack_time_t rounded = RoundDown((now - remainder) + max_drift, interval) + remainder; - // Let sync time converge slowly when late, prefer earlier sync times. - fOSSWriteSync = min(rounded, now) / 2 + now / 2; - fOSSWriteOffset = ptr.fifo_samples; - } - } - poll_fd[1].events = 0; - } - } - - fForceSync = false; - - // Compute balance of read and write buffers combined. - fBufferBalance = 0; - if (fInFD > 0 && fOutFD > 0) { - // Compare actual buffer content with target of (1 + n) * period. - fBufferBalance += ((1 + fNperiods) * fEngineControl->fBufferSize); - fBufferBalance -= (fOSSWriteOffset - fOSSReadOffset); - fBufferBalance += TimeToOffset(fOSSWriteSync, fOSSReadSync, fEngineControl->fSampleRate); - - // Force balancing if sync times deviate too much. - jack_time_t slack = FramesToTime((fEngineControl->fBufferSize * 2) / 3, fEngineControl->fSampleRate); - fForceBalancing = fForceBalancing || (fOSSReadSync > fOSSWriteSync + slack); - fForceBalancing = fForceBalancing || (fOSSWriteSync > fOSSReadSync + slack); - // Force balancing if buffer is badly balanced. - fForceBalancing = fForceBalancing || (abs(fBufferBalance) > max(fInMeanStep, fOutMeanStep)); - } - - // Print debug info every 10 seconds. - if (ptr.samples > 0 && (ptr.samples % (10 * fEngineControl->fSampleRate)) < fEngineControl->fBufferSize) { - jack_log("JackOSSDriver::Read buffer balance is %ld frames", fBufferBalance); - jack_time_t now = GetMicroSeconds(); - jack_log("JackOSSDriver::Read recording sync %ld frames %ld us ago", fOSSReadOffset, now - fOSSReadSync); - jack_log("JackOSSDriver::Read playback sync %ld frames %ld us ago", fOSSWriteOffset, now - fOSSWriteSync); - } - - return 0; -} - -int JackOSSDriver::OpenInput() -{ - int flags = 0; - int gFragFormat; - int cur_capture_channels; - int cur_sample_format; - jack_nframes_t cur_sample_rate; - audio_buf_info info; - - if (fCaptureChannels == 0) fCaptureChannels = 2; - - if ((fInFD = open(fCaptureDriverName, O_RDONLY | ((fExcl) ? O_EXCL : 0))) < 0) { - jack_error("JackOSSDriver::OpenInput failed to open device : %s@%i, errno = %d", __FILE__, __LINE__, errno); - return -1; - } - - jack_log("JackOSSDriver::OpenInput input fInFD = %d", fInFD); - - if (fExcl) { - if (ioctl(fInFD, SNDCTL_DSP_COOKEDMODE, &flags) == -1) { - jack_error("JackOSSDriver::OpenInput failed to set cooked mode : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - } - - cur_sample_format = GetSampleFormat(fBits); - if (ioctl(fInFD, SNDCTL_DSP_SETFMT, &cur_sample_format) == -1) { - jack_error("JackOSSDriver::OpenInput failed to set format : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fInSampleSize = GetSampleSize(cur_sample_format); - if (cur_sample_format != GetSampleFormat(fBits)) { - if (fInSampleSize > 0) { - jack_info("JackOSSDriver::OpenInput driver forced %d bit sample format", fInSampleSize * 8); - } else { - jack_error("JackOSSDriver::OpenInput unsupported sample format %#x", cur_sample_format); - goto error; - } - } - - cur_capture_channels = fCaptureChannels; - if (ioctl(fInFD, SNDCTL_DSP_CHANNELS, &fCaptureChannels) == -1) { - jack_error("JackOSSDriver::OpenInput failed to set channels : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - if (cur_capture_channels != fCaptureChannels) { - jack_info("JackOSSDriver::OpenInput driver forced the number of capture channels %ld", fCaptureChannels); - } - - cur_sample_rate = fEngineControl->fSampleRate; - if (ioctl(fInFD, SNDCTL_DSP_SPEED, &fEngineControl->fSampleRate) == -1) { - jack_error("JackOSSDriver::OpenInput failed to set sample rate : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - if (cur_sample_rate != fEngineControl->fSampleRate) { - jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); - } - - // Internal buffer size required for one period. - fInputBufferSize = fEngineControl->fBufferSize * fInSampleSize * fCaptureChannels; - - // Get the total size of the OSS recording buffer, in sample frames. - info = {0, 0, 0, 0}; - if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { - jack_error("JackOSSDriver::OpenInput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fOSSInBuffer = info.fragstotal * info.fragsize / (fInSampleSize * fCaptureChannels); - - if (fOSSInBuffer < fEngineControl->fBufferSize * (1 + fNperiods)) { - // Total size of the OSS recording buffer is too small, resize it. - unsigned int buf_size = fInputBufferSize * (1 + fNperiods); - // Keep current fragment size if possible - respect OSS latency settings. - gFragFormat = UpToPower2(info.fragsize); - unsigned int frag_size = 1U << gFragFormat; - gFragFormat |= ((buf_size + frag_size - 1) / frag_size) << 16; - jack_info("JackOSSDriver::OpenInput request %d fragments of %d", (gFragFormat >> 16), frag_size); - if (ioctl(fInFD, SNDCTL_DSP_SETFRAGMENT, &gFragFormat) == -1) { - jack_error("JackOSSDriver::OpenInput failed to set fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - // Check the new OSS recording buffer size. - info = {0, 0, 0, 0}; - if (ioctl(fInFD, SNDCTL_DSP_GETISPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { - jack_error("JackOSSDriver::OpenInput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fOSSInBuffer = info.fragstotal * info.fragsize / (fInSampleSize * fCaptureChannels); - } - - if (fOSSInBuffer > fEngineControl->fBufferSize) { - int mark = fInputBufferSize; - if (ioctl(fInFD, SNDCTL_DSP_LOW_WATER, &mark) != 0) { - jack_error("JackOSSDriver::OpenInput failed to set low water mark : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - jack_info("JackOSSDriver::OpenInput set low water mark to %d", mark); - } - - fInputBuffer = (void*)calloc(fInputBufferSize, 1); - assert(fInputBuffer); - - if (ProbeInBlockSize() < 0) { - goto error; - } - - return 0; - -error: - ::close(fInFD); - return -1; -} - -int JackOSSDriver::OpenOutput() -{ - int flags = 0; - int gFragFormat; - int cur_sample_format; - int cur_playback_channels; - jack_nframes_t cur_sample_rate; - audio_buf_info info; - - if (fPlaybackChannels == 0) fPlaybackChannels = 2; - - if ((fOutFD = open(fPlaybackDriverName, O_WRONLY | ((fExcl) ? O_EXCL : 0))) < 0) { - jack_error("JackOSSDriver::OpenOutput failed to open device : %s@%i, errno = %d", __FILE__, __LINE__, errno); - return -1; - } - - jack_log("JackOSSDriver::OpenOutput output fOutFD = %d", fOutFD); - - if (fExcl) { - if (ioctl(fOutFD, SNDCTL_DSP_COOKEDMODE, &flags) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to set cooked mode : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - } - - cur_sample_format = GetSampleFormat(fBits); - if (ioctl(fOutFD, SNDCTL_DSP_SETFMT, &cur_sample_format) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to set format : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fOutSampleSize = GetSampleSize(cur_sample_format); - if (cur_sample_format != GetSampleFormat(fBits)) { - if (fOutSampleSize > 0) { - jack_info("JackOSSDriver::OpenOutput driver forced %d bit sample format", fOutSampleSize * 8); - } else { - jack_error("JackOSSDriver::OpenOutput unsupported sample format %#x", cur_sample_format); - goto error; - } - } - - cur_playback_channels = fPlaybackChannels; - if (ioctl(fOutFD, SNDCTL_DSP_CHANNELS, &fPlaybackChannels) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to set channels : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - if (cur_playback_channels != fPlaybackChannels) { - jack_info("JackOSSDriver::OpenOutput driver forced the number of playback channels %ld", fPlaybackChannels); - } - - cur_sample_rate = fEngineControl->fSampleRate; - if (ioctl(fOutFD, SNDCTL_DSP_SPEED, &fEngineControl->fSampleRate) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to set sample rate : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - if (cur_sample_rate != fEngineControl->fSampleRate) { - jack_info("JackOSSDriver::OpenInput driver forced the sample rate %ld", fEngineControl->fSampleRate); - } - - // Internal buffer size required for one period. - fOutputBufferSize = fEngineControl->fBufferSize * fOutSampleSize * fPlaybackChannels; - - // Get the total size of the OSS playback buffer, in sample frames. - info = {0, 0, 0, 0}; - if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { - jack_error("JackOSSDriver::OpenOutput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fOSSOutBuffer = info.fragstotal * info.fragsize / (fOutSampleSize * fPlaybackChannels); - - if (fOSSOutBuffer < fEngineControl->fBufferSize * (1 + fNperiods)) { - // Total size of the OSS playback buffer is too small, resize it. - unsigned int buf_size = fOutputBufferSize * (1 + fNperiods); - // Keep current fragment size if possible - respect OSS latency settings. - // Some sound cards like Intel HDA may stutter when changing the fragment size. - gFragFormat = UpToPower2(info.fragsize); - unsigned int frag_size = 1U << gFragFormat; - gFragFormat |= ((buf_size + frag_size - 1) / frag_size) << 16; - jack_info("JackOSSDriver::OpenOutput request %d fragments of %d", (gFragFormat >> 16), frag_size); - if (ioctl(fOutFD, SNDCTL_DSP_SETFRAGMENT, &gFragFormat) == -1) { - jack_error("JackOSSDriver::OpenOutput failed to set fragments : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - // Check the new OSS playback buffer size. - info = {0, 0, 0, 0}; - if (ioctl(fOutFD, SNDCTL_DSP_GETOSPACE, &info) == -1 || info.fragsize <= 0 || info.fragstotal <= 0) { - jack_error("JackOSSDriver::OpenOutput failed to get buffer info : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - fOSSOutBuffer = info.fragstotal * info.fragsize / (fOutSampleSize * fPlaybackChannels); - } - - if (fOSSOutBuffer > fEngineControl->fBufferSize * fNperiods) { - jack_nframes_t low = fOSSOutBuffer - (fNperiods * fEngineControl->fBufferSize); - int mark = low * fOutSampleSize * fPlaybackChannels; - if (ioctl(fOutFD, SNDCTL_DSP_LOW_WATER, &mark) != 0) { - jack_error("JackOSSDriver::OpenOutput failed to set low water mark : %s@%i, errno = %d", __FILE__, __LINE__, errno); - goto error; - } - jack_info("JackOSSDriver::OpenOutput set low water mark to %d", mark); - } - - fOutputBuffer = (void*)calloc(fOutputBufferSize, 1); - assert(fOutputBuffer); - - if (ProbeOutBlockSize() < 0) { - goto error; - } - - return 0; - -error: - ::close(fOutFD); - return -1; -} - int JackOSSDriver::Open(jack_nframes_t nframes, int user_nperiods, jack_nframes_t samplerate, @@ -884,6 +97,7 @@ int JackOSSDriver::Open(jack_nframes_t nframes, Close(); return -1; } else { + fChannel.StartAssistThread(fEngineControl->fRealTime, fEngineControl->fServerPriority); return 0; } } @@ -938,6 +152,11 @@ int JackOSSDriver::Close() fclose(file); } #endif + + fChannel.Lock(); + fChannel.StopAssistThread(); + fChannel.Unlock(); + int res = JackAudioDriver::Close(); CloseAux(); return res; @@ -946,286 +165,214 @@ int JackOSSDriver::Close() int JackOSSDriver::OpenAux() { - // (Re-)Initialize runtime variables. - fInSampleSize = fOutSampleSize = 0; - fInputBufferSize = fOutputBufferSize = 0; - fInBlockSize = fOutBlockSize = 1; - fInMeanStep = fOutMeanStep = 0; - fOSSInBuffer = fOSSOutBuffer = 0; - fOSSReadSync = fOSSWriteSync = 0; - fOSSReadOffset = fOSSWriteOffset = 0; - fBufferBalance = 0; - fForceBalancing = false; - fForceSync = false; - - if (fCapture && (OpenInput() < 0)) { + if (!fChannel.Lock()) { return -1; } - if (fPlayback && (OpenOutput() < 0)) { + // (Re-)Initialize runtime variables. + fCycleEnd = 0; + fLastRun = 0; + fMaxRunGap = 0; + + if (!fChannel.InitialSetup(fEngineControl->fSampleRate)) { return -1; } - DisplayDeviceInfo(); - return 0; -} + if (fCapture) { + if (!fChannel.OpenCapture(fCaptureDriverName, fExcl, fBits, fCaptureChannels)) { + return -1; + } + } -void JackOSSDriver::CloseAux() -{ - if (fCapture && fInFD > 0) { - close(fInFD); - fInFD = -1; + if (fPlayback) { + if (!fChannel.OpenPlayback(fPlaybackDriverName, fExcl, fBits, fPlaybackChannels)) { + return -1; + } } - if (fPlayback && fOutFD > 0) { - close(fOutFD); - fOutFD = -1; + if (!fChannel.StartChannels(fEngineControl->fBufferSize)) { + return -1; } - if (fInputBuffer) - free(fInputBuffer); - fInputBuffer = NULL; + if (fCapture) { + fChannel.Capture().log_device_info(); + } + if (fPlayback) { + fChannel.Playback().log_device_info(); + } + + if (size_t max_channels = std::max(fCaptureChannels, fPlaybackChannels)) { + fSampleBuffers = new jack_sample_t * [max_channels]; + } + + if (!fChannel.Unlock()) { + return -1; + } - if (fOutputBuffer) - free(fOutputBuffer); - fOutputBuffer = NULL; + return 0; } -int JackOSSDriver::Read() +void JackOSSDriver::CloseAux() { - 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; - } + fChannel.Lock(); - // 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); + fChannel.StopChannels(); - fForceSync = true; - fForceBalancing = true; + if (fSampleBuffers) { + delete[] fSampleBuffers; + fSampleBuffers = nullptr; } - 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; - } + fChannel.Unlock(); +} +int JackOSSDriver::Read() +{ #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeRead = GetMicroSeconds(); #endif - if (WaitAndSync() < 0) { + if (!fChannel.Lock()) { return -1; } - // Keep begin cycle time - JackDriver::CycleTakeBeginTime(); + // Mark the end time of this cycle, in frames. + fCycleEnd += fEngineControl->fBufferSize; - if (fInFD < 0) { - return 0; + // Process read and write channels at least once. + std::int64_t channel_stamp = fChannel.FrameStamp(); + if (!fChannel.CheckTimeAndRun()) { + return -1; + } + if (fChannel.FrameStamp() - fLastRun > fMaxRunGap) { + fMaxRunGap = fChannel.FrameStamp() - fLastRun; + std::int64_t channel_gap = fChannel.FrameStamp() - channel_stamp; + jack_log("JackOSSDriver::Read max run gap %lld frames vs channel %lld.", fMaxRunGap, channel_gap); } - // 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; + // Check for over- and underruns. + if (fChannel.XRunGap() > 0) { + std::int64_t skip = fChannel.XRunGap() + fEngineControl->fBufferSize; + NotifyXRun(GetMicroSeconds(), (float) (fChannel.FrameClock().frames_to_time(skip) / 1000)); + fCycleEnd += skip; + jack_error("JackOSSDriver::Read(): XRun, late by %lld frames.", skip); + fChannel.ResetBuffers(skip); } - // 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))); + // Wait and process channels until read, or else write, buffer is finished. + while ((fCapture && !fChannel.CaptureFinished()) || + (!fCapture && !fChannel.PlaybackFinished())) { + if (!(fChannel.Sleep() && fChannel.CheckTimeAndRun())) { + return -1; } - fOSSReadOffset += count / (fInSampleSize * fCaptureChannels); } -#ifdef JACK_MONITOR - if (count > 0 && count != (int)fInputBufferSize) - jack_log("JackOSSDriver::Read count = %ld", count / (fInSampleSize * fCaptureChannels)); - gCycleTable.fTable[gCycleCount].fAfterRead = GetMicroSeconds(); -#endif - - // Check and clear OSS errors. - audio_errinfo ei_in; - if (ioctl(fInFD, SNDCTL_DSP_GETERROR, &ei_in) == 0) { - - // Not reliable for overrun detection, virtual_oss doesn't implement it. - if (ei_in.rec_overruns > 0 ) { - jack_error("JackOSSDriver::Read %d overrun events", ei_in.rec_overruns); - } + // Keep begin cycle time + JackDriver::CycleTakeBeginTime(); - if (ei_in.rec_errorcount > 0 && ei_in.rec_lasterror != 0) { - jack_error("%d OSS rec event(s), last=%05d:%d", ei_in.rec_errorcount, ei_in.rec_lasterror, ei_in.rec_errorparm); + if (!fCapture) { + if (!fChannel.Unlock()) { + return -1; } + return 0; } - if (count < fInputBufferSize) { - jack_error("JackOSSDriver::Read incomplete read of %ld bytes", count); - return -1; + if ((fChannel.FrameStamp() / fEngineControl->fBufferSize) % ((5 * fEngineControl->fSampleRate) / fEngineControl->fBufferSize) == 0) { + fChannel.Capture().log_state(fChannel.FrameStamp()); + fMaxRunGap = 0; } +#ifdef JACK_MONITOR + gCycleTable.fTable[gCycleCount].fAfterRead = GetMicroSeconds(); +#endif + for (int i = 0; i < fCaptureChannels; i++) { + fSampleBuffers[i] = nullptr; if (fGraphManager->GetConnectionsNum(fCapturePortList[i]) > 0) { - CopyAndConvertIn(GetInputBuffer(i), fInputBuffer, fEngineControl->fBufferSize, i, fCaptureChannels, fInSampleSize * 8); + fSampleBuffers[i] = GetInputBuffer(i); } } + std::int64_t buffer_end = fCycleEnd + fEngineControl->fBufferSize; + if (!fChannel.Read(fSampleBuffers, fEngineControl->fBufferSize, buffer_end)) { + return -1; + } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fAfterReadConvert = GetMicroSeconds(); #endif + if (!fChannel.CheckTimeAndRun() || !fChannel.Unlock()) { + return -1; + } + fLastRun = fChannel.FrameStamp(); + return 0; } int JackOSSDriver::Write() { - if (fOutFD < 0) { + if (!fPlayback) { return 0; } - unsigned int skip = 0; - jack_time_t start = GetMicroSeconds(); - - if (fOSSWriteSync > 0) { - // Check for underruns, rounded to hardware block size if available. - long long passed = TimeToFrames(start - fOSSWriteSync, fEngineControl->fSampleRate); - long long consumed = passed - (passed % fOutBlockSize); - long long tolerance = (fOutBlockSize > 1) ? 0 : fOutMeanStep; - long long overdue = 0; - if (consumed > fOSSWriteOffset + tolerance) { - // Skip playback data that already passed. - overdue = consumed - fOSSWriteOffset - tolerance; - jack_error("JackOSSDriver::Write underrun, late by %ld, skip %ld frames", passed - fOSSWriteOffset, overdue); - jack_log("JackOSSDriver::Write playback offset %ld frames synced %ld us ago", fOSSWriteOffset, start - fOSSWriteSync); - // Also consider buffer balance, there was a gap in playback anyway. - fForceBalancing = true; - } - // Account for buffer balance if needed. - long long progress = fEngineControl->fBufferSize; - if (fForceBalancing) { - fForceBalancing = false; - progress = max(progress + fBufferBalance, 0LL); - jack_info("JackOSSDriver::Write buffer balancing %ld frames", fBufferBalance); - jack_log("JackOSSDriver::Write recording sync %ld frames %ld us ago", fOSSReadOffset, start - fOSSReadSync); - jack_log("JackOSSDriver::Write playback sync %ld frames %ld us ago", fOSSWriteOffset, start - fOSSWriteSync); - } - // How many samples to skip or prepend due to underrun and balancing. - long long write_length = progress - overdue; - if (write_length <= 0) { - skip += fOutputBufferSize; - fOSSWriteOffset += progress; - } else if (write_length < fEngineControl->fBufferSize) { - skip += (fEngineControl->fBufferSize - write_length) * fOutSampleSize * fPlaybackChannels; - fOSSWriteOffset += overdue; - } else if (write_length > fEngineControl->fBufferSize) { - jack_nframes_t fill = write_length - fEngineControl->fBufferSize; - WriteSilence(fill); + if (!fChannel.Lock()) { + return -1; + } + + // Process read and write channels at least once. + std::int64_t channel_stamp = fChannel.FrameStamp(); + if (!fChannel.CheckTimeAndRun()) { + return -1; + } + if (fChannel.FrameStamp() - fLastRun > fMaxRunGap) { + fMaxRunGap = fChannel.FrameStamp() - fLastRun; + std::int64_t channel_gap = fChannel.FrameStamp() - channel_stamp; + jack_log("JackOSSDriver::Write max run gap %lld frames vs channel %lld.", fMaxRunGap, channel_gap); + } + + // Wait and process channels until write buffer is finished. + while (!fChannel.PlaybackFinished()) { + if (!(fChannel.Sleep() && fChannel.CheckTimeAndRun())) { + return -1; } } + if ((fChannel.FrameStamp() / fEngineControl->fBufferSize) % ((5 * fEngineControl->fSampleRate) / fEngineControl->fBufferSize) == 0) { + fChannel.Playback().log_state(fChannel.FrameStamp()); + fMaxRunGap = 0; + } + #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeWriteConvert = GetMicroSeconds(); #endif - memset(fOutputBuffer, 0, fOutputBufferSize); for (int i = 0; i < fPlaybackChannels; i++) { + fSampleBuffers[i] = nullptr; if (fGraphManager->GetConnectionsNum(fPlaybackPortList[i]) > 0) { - CopyAndConvertOut(fOutputBuffer, GetOutputBuffer(i), fEngineControl->fBufferSize, i, fPlaybackChannels, fOutSampleSize * 8); + fSampleBuffers[i] = GetOutputBuffer(i); } } + std::int64_t buffer_end = fCycleEnd + fEngineControl->fBufferSize; + if (!fChannel.Write(fSampleBuffers, fEngineControl->fBufferSize, buffer_end)) { + return -1; + } #ifdef JACK_MONITOR gCycleTable.fTable[gCycleCount].fBeforeWrite = GetMicroSeconds(); #endif - // Try multiple times in case of short writes. - ssize_t count = skip; - for (int i = 0; i < 3 && count < fOutputBufferSize; ++i) { - ssize_t ret = ::write(fOutFD, ((char*)fOutputBuffer) + count, fOutputBufferSize - count); - if (ret < 0) { - jack_error("JackOSSDriver::Write error = %s", strerror(errno)); - return -1; - } - count += ret; - } - - fOSSWriteOffset += ((count - skip) / (fOutSampleSize * fPlaybackChannels)); - - jack_time_t duration = GetMicroSeconds() - start; - if (duration > 1000) { - // Blocking write() may indicate sample loss in OSS - force resync. - jack_log("JackOSSDriver::Write long write duration of %ld us", duration); - fForceSync = true; + // Do a processing step here. + if (!fChannel.CheckTimeAndRun()) { + return -1; } + fLastRun = fChannel.FrameStamp(); #ifdef JACK_MONITOR - if (count > 0 && count != (int)fOutputBufferSize) - jack_log("JackOSSDriver::Write count = %ld", (count - skip) / (fOutSampleSize * fPlaybackChannels)); gCycleTable.fTable[gCycleCount].fAfterWrite = GetMicroSeconds(); gCycleCount = (gCycleCount == CYCLE_POINTS - 1) ? gCycleCount: gCycleCount + 1; #endif - // Check and clear OSS errors. - audio_errinfo ei_out; - if (ioctl(fOutFD, SNDCTL_DSP_GETERROR, &ei_out) == 0) { - - // Not reliable for underrun detection, virtual_oss does not implement it. - if (ei_out.play_underruns > 0) { - jack_error("JackOSSDriver::Write %d underrun events", ei_out.play_underruns); - } - - if (ei_out.play_errorcount > 0 && ei_out.play_lasterror != 0) { - jack_error("%d OSS play event(s), last=%05d:%d",ei_out.play_errorcount, ei_out.play_lasterror, ei_out.play_errorparm); - } - } - - if (count < (int)fOutputBufferSize) { - jack_error("JackOSSDriver::Write incomplete write of %ld bytes", count - skip); + if (!fChannel.Unlock()) { return -1; } @@ -1245,6 +392,7 @@ void JackOSSDriver::UpdateLatencies() } for (int i = 0; i < fPlaybackChannels; i++) { + // TODO: Move this half period to capture latency. output_range.max = (fEngineControl->fBufferSize / 2) + fPlaybackLatency; // Additional latency introduced by the OSS buffer. output_range.max += fNperiods * fEngineControl->fBufferSize; diff --git a/freebsd/oss/JackOSSDriver.h b/freebsd/oss/JackOSSDriver.h index c5449b376..4cdb160fe 100644 --- a/freebsd/oss/JackOSSDriver.h +++ b/freebsd/oss/JackOSSDriver.h @@ -22,6 +22,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #define __JackOSSDriver__ #include "JackAudioDriver.h" +#include "JackOSSChannel.h" namespace Jack { @@ -44,9 +45,6 @@ class JackOSSDriver : public JackAudioDriver { private: - int fInFD; - int fOutFD; - int fBits; int fNperiods; bool fCapture; @@ -54,42 +52,15 @@ class JackOSSDriver : public JackAudioDriver bool fExcl; bool fIgnoreHW; - unsigned int fInSampleSize; - unsigned int fOutSampleSize; - - unsigned int fInputBufferSize; - unsigned int fOutputBufferSize; - - void* fInputBuffer; - void* fOutputBuffer; - - 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; + std::int64_t fCycleEnd; + std::int64_t fLastRun; + std::int64_t fMaxRunGap; + jack_sample_t** fSampleBuffers; - // Buffer balance and sync correction - long long fBufferBalance; - bool fForceBalancing; - bool fForceSync; + JackOSSChannel fChannel; - int OpenInput(); - int OpenOutput(); int OpenAux(); void CloseAux(); - void DisplayDeviceInfo(); - int ProbeInBlockSize(); - int ProbeOutBlockSize(); - int Discard(jack_nframes_t frames); - int WriteSilence(jack_nframes_t frames); - int WaitAndSync(); protected: virtual void UpdateLatencies(); @@ -98,16 +69,9 @@ class JackOSSDriver : public JackAudioDriver JackOSSDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table) : JackAudioDriver(name, alias, engine, table), - fInFD(-1), fOutFD(-1), fBits(0), + fBits(0), fNperiods(0), fCapture(false), fPlayback(false), fExcl(false), fIgnoreHW(true), - fInSampleSize(0), fOutSampleSize(0), - fInputBufferSize(0), fOutputBufferSize(0), - 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) + fCycleEnd(0), fLastRun(0), fMaxRunGap(0), fSampleBuffers(nullptr) {} virtual ~JackOSSDriver() diff --git a/wscript b/wscript index 86eb39544..7cd03b77e 100644 --- a/wscript +++ b/wscript @@ -687,6 +687,7 @@ def build_drivers(bld): freebsd_oss_src = [ 'common/memops.c', + 'freebsd/oss/JackOSSChannel.cpp', 'freebsd/oss/JackOSSDriver.cpp' ]