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' ]