/* 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) { if (!CheckTimeAndRun()) { return Unlock() && false; } 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