Browse Source

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).
pull/943/head
Florian Walpen 2 years ago
parent
commit
0762eac06a
No known key found for this signature in database GPG Key ID: E11C40B3BC8863BC
5 changed files with 780 additions and 1032 deletions
  1. +492
    -0
      freebsd/oss/JackOSSChannel.cpp
  2. +132
    -0
      freebsd/oss/JackOSSChannel.h
  3. +147
    -988
      freebsd/oss/JackOSSDriver.cpp
  4. +8
    -44
      freebsd/oss/JackOSSDriver.h
  5. +1
    -0
      wscript

+ 492
- 0
freebsd/oss/JackOSSChannel.cpp View File

@@ -0,0 +1,492 @@
/*
Copyright (C) 2023 Florian Walpen <dev@submerge.ch>

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 <cstdint>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <fcntl.h>
#include <iostream>
#include <assert.h>
#include <stdio.h>

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<std::int64_t>(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

+ 132
- 0
freebsd/oss/JackOSSChannel.h View File

@@ -0,0 +1,132 @@
/*
Copyright (C) 2003-2007 Jussi Laako <jussi@sonarnerd.net>
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<sosso::ReadChannel> fReadChannel;
sosso::DoubleBuffer<sosso::WriteChannel> fWriteChannel;
sosso::Correction fCorrection;

std::int64_t fFrameStamp = 0;

public:

JackOSSChannel() : fAssistThread(this)
{}
virtual ~JackOSSChannel()
{}

sosso::DoubleBuffer<sosso::ReadChannel> &Capture()
{
return fReadChannel;
}

sosso::DoubleBuffer<sosso::WriteChannel> &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

+ 147
- 988
freebsd/oss/JackOSSDriver.cpp
File diff suppressed because it is too large
View File


+ 8
- 44
freebsd/oss/JackOSSDriver.h View File

@@ -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()


+ 1
- 0
wscript View File

@@ -695,6 +695,7 @@ def build_drivers(bld):

freebsd_oss_src = [
'common/memops.c',
'freebsd/oss/JackOSSChannel.cpp',
'freebsd/oss/JackOSSDriver.cpp'
]



Loading…
Cancel
Save