Browse Source

Merge branch 'develop'

tags/v1.9.18^0
falkTX 4 years ago
parent
commit
5041ab0fe7
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
19 changed files with 2136 additions and 13 deletions
  1. +8
    -0
      ChangeLog.rst
  2. +2
    -1
      common/JackClient.cpp
  3. +1
    -1
      common/JackConstants.h
  4. +4
    -2
      common/JackMetadata.cpp
  5. +13
    -4
      linux/JackLinuxFutex.cpp
  6. +5
    -0
      tools/midi_dump.c
  7. +24
    -4
      tools/wscript
  8. +218
    -0
      tools/zalsa/alsathread.cc
  9. +68
    -0
      tools/zalsa/alsathread.h
  10. +549
    -0
      tools/zalsa/jackclient.cc
  11. +120
    -0
      tools/zalsa/jackclient.h
  12. +89
    -0
      tools/zalsa/lfqueue.cc
  13. +182
    -0
      tools/zalsa/lfqueue.h
  14. +78
    -0
      tools/zalsa/pxthread.cc
  15. +52
    -0
      tools/zalsa/pxthread.h
  16. +53
    -0
      tools/zalsa/timers.h
  17. +333
    -0
      tools/zalsa/zita-a2j.cc
  18. +331
    -0
      tools/zalsa/zita-j2a.cc
  19. +6
    -1
      wscript

+ 8
- 0
ChangeLog.rst View File

@@ -1,6 +1,14 @@
ChangeLog ChangeLog
######### #########


* 1.9.18 (2021-04-15)

* Add zalsa_in/out as internal client (based on zita-a2j/j2a and jack1 code)
* Fix jack_midi_dump deadlock on close after the jack server is restarted
* Fix interrupt signal for linux futex waits
* Fix usage of meta-data in official macOS builds (private DB errors)
* Log error message when cleaning previous DB (macOS and Windows)

* 1.9.17 (2021-01-15) * 1.9.17 (2021-01-15)


* Fix jack_control stopping after first command iteration * Fix jack_control stopping after first command iteration


+ 2
- 1
common/JackClient.cpp View File

@@ -31,6 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <math.h> #include <math.h>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
#include <climits>


using namespace std; using namespace std;


@@ -636,7 +637,7 @@ inline int JackClient::CallProcessCallback()
inline bool JackClient::WaitSync() inline bool JackClient::WaitSync()
{ {
// Suspend itself: wait on the input synchro // Suspend itself: wait on the input synchro
if (GetGraphManager()->SuspendRefNum(GetClientControl(), fSynchroTable, 0x7FFFFFFF) < 0) {
if (GetGraphManager()->SuspendRefNum(GetClientControl(), fSynchroTable, LONG_MAX) < 0) {
jack_error("SuspendRefNum error"); jack_error("SuspendRefNum error");
return false; return false;
} else { } else {


+ 1
- 1
common/JackConstants.h View File

@@ -24,7 +24,7 @@
#include "config.h" #include "config.h"
#endif #endif


#define VERSION "1.9.17"
#define VERSION "1.9.18"


#define BUFFER_SIZE_MAX 8192 #define BUFFER_SIZE_MAX 8192




+ 4
- 2
common/JackMetadata.cpp View File

@@ -123,9 +123,11 @@ int JackMetadata::PropertyInit()
#endif #endif


if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) { if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) {
#ifdef WIN32
#if defined(WIN32) || defined(__APPLE__)
// new versions of jack2 are built with HAVE_MIXED_SIZE_ADDRESSING, which induces this error, this is expected // new versions of jack2 are built with HAVE_MIXED_SIZE_ADDRESSING, which induces this error, this is expected
if (ret == DB_VERSION_MISMATCH) { if (ret == DB_VERSION_MISMATCH) {
jack_error ("Failed to open previous DB environment, trying again clean...");

// cleanup old stuff // cleanup old stuff
snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir); snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir);
remove (dbpath); remove (dbpath);
@@ -145,7 +147,7 @@ int JackMetadata::PropertyInit()
if (ret != 0) if (ret != 0)
#endif #endif
{ {
jack_error ("cannot open DB environment: %s", db_strerror (ret));
jack_error ("Cannot open DB environment: %s", db_strerror (ret));
fDBenv = NULL; fDBenv = NULL;
return -1; return -1;
} }


+ 13
- 4
linux/JackLinuxFutex.cpp View File

@@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "JackConstants.h" #include "JackConstants.h"
#include "JackError.h" #include "JackError.h"
#include "promiscuous.h" #include "promiscuous.h"
#include <climits>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <sys/mman.h> #include <sys/mman.h>
@@ -93,18 +94,24 @@ bool JackLinuxFutex::Wait()
fFutex->internal = !fFutex->internal; fFutex->internal = !fFutex->internal;
} }


const int wait_mode = fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT;

for (;;) for (;;)
{ {
if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0))
return true; return true;


if (::syscall(SYS_futex, fFutex, fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, 0, NULL, NULL, 0) != 0 && errno != EWOULDBLOCK)
return false;
if (::syscall(SYS_futex, fFutex, wait_mode, 0, NULL, NULL, 0) != 0)
if (errno != EAGAIN && errno != EINTR)
return false;
} }
} }


bool JackLinuxFutex::TimedWait(long usec) bool JackLinuxFutex::TimedWait(long usec)
{ {
if (usec == LONG_MAX)
return Wait();

if (!fFutex) { if (!fFutex) {
jack_error("JackLinuxFutex::TimedWait name = %s already deallocated!!", fName); jack_error("JackLinuxFutex::TimedWait name = %s already deallocated!!", fName);
return false; return false;
@@ -120,14 +127,16 @@ bool JackLinuxFutex::TimedWait(long usec)
const int nsecs = (usec % 1000000) * 1000; const int nsecs = (usec % 1000000) * 1000;


const timespec timeout = { static_cast<time_t>(secs), nsecs }; const timespec timeout = { static_cast<time_t>(secs), nsecs };
const int wait_mode = fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT;


for (;;) for (;;)
{ {
if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0))
return true; return true;


if (::syscall(SYS_futex, fFutex, fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, 0, &timeout, NULL, 0) != 0 && errno != EWOULDBLOCK)
return false;
if (::syscall(SYS_futex, fFutex, wait_mode, 0, &timeout, NULL, 0) != 0)
if (errno != EAGAIN && errno != EINTR)
return false;
} }
} }




+ 5
- 0
tools/midi_dump.c View File

@@ -112,6 +112,11 @@ process (jack_nframes_t frames, void* arg)
static void wearedone(int sig) { static void wearedone(int sig) {
fprintf(stderr, "Shutting down\n"); fprintf(stderr, "Shutting down\n");
keeprunning = 0; keeprunning = 0;
/* main loop might be blocked by data_ready when jack server dies. */
if (pthread_mutex_trylock (&msg_thread_lock) == 0) {
pthread_cond_signal (&data_ready);
pthread_mutex_unlock (&msg_thread_lock);
}
} }


static void usage (int status) { static void usage (int status) {


+ 24
- 4
tools/wscript View File

@@ -19,8 +19,9 @@ example_tools = {
} }


def configure(conf): def configure(conf):
conf.env['BUILD_EXAMPLE_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA']
conf.env['BUILD_EXAMPLE_CLIENT_TRANSPORT'] = conf.env['READLINE']
conf.env['BUILD_TOOL_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA']
conf.env['BUILD_TOOL_CLIENT_TRANSPORT'] = conf.env['READLINE']
conf.env['BUILD_TOOL_ZALSA'] = conf.env['ZALSA']


def build(bld): def build(bld):
if bld.env['IS_LINUX']: if bld.env['IS_LINUX']:
@@ -50,7 +51,7 @@ def build(bld):


prog.target = example_tool prog.target = example_tool


if bld.env['BUILD_EXAMPLE_CLIENT_TRANSPORT']:
if bld.env['BUILD_TOOL_CLIENT_TRANSPORT']:
prog = bld(features = 'c cprogram') prog = bld(features = 'c cprogram')
prog.includes = os_incdir + ['../common/jack', '../common'] prog.includes = os_incdir + ['../common/jack', '../common']
prog.source = 'transport.c' prog.source = 'transport.c'
@@ -74,7 +75,7 @@ def build(bld):
prog.target = 'jack_netsource' prog.target = 'jack_netsource'
prog.defines = ['HAVE_CONFIG_H'] prog.defines = ['HAVE_CONFIG_H']


if bld.env['IS_LINUX'] and bld.env['BUILD_EXAMPLE_ALSA_IO']:
if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ALSA_IO']:
prog = bld(features = 'c cprogram') prog = bld(features = 'c cprogram')
prog.includes = os_incdir + ['../common/jack', '../common'] prog.includes = os_incdir + ['../common/jack', '../common']
prog.source = ['alsa_in.c', '../common/memops.c'] prog.source = ['alsa_in.c', '../common/memops.c']
@@ -89,6 +90,25 @@ def build(bld):
prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M'] prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M']
prog.target = 'alsa_out' prog.target = 'alsa_out'


if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ZALSA']:
prog = bld(features = ['cxx', 'cxxshlib'])
prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_in"','VERSION="0.4.0"']
prog.install_path = '${ADDON_DIR}/'
prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa']
prog.source = ['zalsa/zita-a2j.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc']
prog.target = 'zalsa_in'
prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib']
prog.env['cxxshlib_PATTERN'] = '%s.so'

prog = bld(features = ['cxx', 'cxxshlib'])
prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_out"','VERSION="0.4.0"']
prog.install_path = '${ADDON_DIR}/'
prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa']
prog.source = ['zalsa/zita-j2a.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc']
prog.target = 'zalsa_out'
prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib']
prog.env['cxxshlib_PATTERN'] = '%s.so'

if not bld.env['IS_WINDOWS']: if not bld.env['IS_WINDOWS']:
bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect') bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect')
bld.install_files('${PREFIX}/bin', 'jack_control', chmod=0o755) bld.install_files('${PREFIX}/bin', 'jack_control', chmod=0o755)

+ 218
- 0
tools/zalsa/alsathread.cc View File

@@ -0,0 +1,218 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include "alsathread.h"
#include "timers.h"


Alsathread::Alsathread (Alsa_pcmi *alsadev, int mode) :
_alsadev (alsadev ),
_mode (mode),
_state (INIT),
_fsize (alsadev->fsize ()),
_audioq (0),
_commq (0),
_alsaq (0)
{
// Compute DLL filter coefficients.
_dt = (double) _fsize / _alsadev->fsamp ();
_w1 = 2 * M_PI * 0.1 * _dt;
_w2 = _w1 * _w1;
_w1 *= 1.6;
}


Alsathread::~Alsathread (void)
{
_alsadev->pcm_stop ();
}


int Alsathread::start (Lfq_audio *audioq, Lfq_int32 *commq, Lfq_adata *alsaq, int rtprio)
{
// Start the ALSA thread.
_audioq = audioq;
_commq = commq;
_alsaq = alsaq;
_state = WAIT;
if (thr_start (SCHED_FIFO, rtprio, 0x10000)) return 1;
return 0;
}


void Alsathread::send (int k, double t)
{
Adata *D;

// Send (state, frame count, timestamp) to Jack thread.
if (_alsaq->wr_avail ())
{
D = _alsaq->wr_datap ();
D->_state = _state;
D->_nsamp = k;
D->_timer = t;
_alsaq->wr_commit ();
}
}


// The following two functions transfer data between the audio queue
// and the ALSA device. Note that we do *not* check the queue's fill
// state, and it may overrun or underrun. It actually will in the first
// few iterations and in error conditions. This is entirely intentional.
// The queue keeps correct read and write counters even in that case,
// and the main control loop and error recovery depend on it working
// and being used in this way.

int Alsathread::capture (void)
{
int c, n, k;
float *p;

// Start reading from ALSA device.
_alsadev->capt_init (_fsize);
if (_state == PROC)
{
// Input frames from the ALSA device to the audio queue.
// The outer loop takes care of wraparound.
for (n = _fsize; n; n -= k)
{
p = _audioq->wr_datap (); // Audio queue write pointer.
k = _audioq->wr_linav (); // Number of frames that can be
if (k > n) k = n; // written without wraparound.
for (c = 0; c < _audioq->nchan (); c++)
{
// Copy and interleave one channel.
_alsadev->capt_chan (c, p + c, k, _audioq->nchan ());
}
_audioq->wr_commit (k); // Update audio queue state.
}
}
// Finish reading from ALSA device.
_alsadev->capt_done (_fsize);
return _fsize;
}


int Alsathread::playback (void)
{
int c, n, k;
float *p;

// Start writing to ALSA device.
_alsadev->play_init (_fsize);
c = 0;
if (_state == PROC)
{
// Output frames from the audio queue to the ALSA device.
// The outer loop takes care of wraparound.
for (n = _fsize; n; n -= k)
{
p = _audioq->rd_datap (); // Audio queue read pointer.
k = _audioq->rd_linav (); // Number of frames that can
if (k > n) k = n; // be read without wraparound.
for (c = 0; c < _audioq->nchan (); c++)
{
// De-interleave and copy one channel.
_alsadev->play_chan (c, p + c, k, _audioq->nchan ());
}
_audioq->rd_commit (k); // Update audio queue state.
}
}
// Clear all or remaining channels.
while (c < _alsadev->nplay ()) _alsadev->clear_chan (c++, _fsize);
// Finish writing to ALSA device.
_alsadev->play_done (_fsize);
return _fsize;
}


void Alsathread::thr_main (void)
{
int na, nu;
double tw, er;

_alsadev->pcm_start ();
while (_state != TERM)
{
// Wait for next cycle, then take timestamp.
na = _alsadev->pcm_wait ();

tw = tjack (jack_get_time ());
// Check for errors - requires restart.
if (_alsadev->state () && (na == 0))
{
_state = WAIT;
send (0, 0);
usleep (10000);
continue;
}
// Check for commands from the Jack thread.
if (_commq->rd_avail ())
{
_state = _commq->rd_int32 ();
if (_state == PROC) _first = true;
if (_state == TERM) send (0, 0);
}

// We could have more than one period.
nu = 0;
while (na >= _fsize)
{
// Transfer frames.
if (_mode == PLAY) nu += playback ();
else nu += capture ();
// Update loop condition.
na -= _fsize;
// Run the DLL if in PROC state.
if (_state == PROC)
{
if (_first)
{
// Init DLL in first iteration.
_first = false;
_dt = (double) _fsize / _alsadev->fsamp ();
_t0 = tw;
_t1 = tw + _dt;
}
else
{
// Update the DLL.
// If we have more than one period, use
// the time error only for the last one.
if (na >= _fsize) er = 0;
else er = tjack_diff (tw, _t1);
_t0 = _t1;
_t1 = tjack_diff (_t1 + _dt + _w1 * er, 0.0);
_dt += _w2 * er;
}
}
}

// Send number of frames used and timestamp to Jack thread.
if (_state == PROC) send (nu, _t1);
}
_alsadev->pcm_stop ();
}

+ 68
- 0
tools/zalsa/alsathread.h View File

@@ -0,0 +1,68 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#ifndef __ALSATHREAD_H
#define __ALSATHREAD_H


#include <zita-alsa-pcmi.h>
#include "jack/jack.h"
#include "pxthread.h"
#include "lfqueue.h"


class Alsathread : public Pxthread
{
public:

enum { INIT, WAIT, PROC, TERM };
enum { PLAY, CAPT };

Alsathread (Alsa_pcmi *alsadev, int mode);
virtual ~Alsathread (void);
virtual void thr_main (void);

int start (Lfq_audio *audioq, Lfq_int32 *commq, Lfq_adata *alsaq, int rtprio);

private:

void send (int k, double t);
int capture (void);
int playback (void);
Alsa_pcmi *_alsadev;
int _mode;
int _state;
int _nfail;
int _fsize;
Lfq_audio *_audioq;
Lfq_int32 *_commq;
Lfq_adata *_alsaq;
bool _first;
// double _jtmod;
double _t0;
double _t1;
double _dt;
double _w1;
double _w2;
};


#endif

+ 549
- 0
tools/zalsa/jackclient.cc View File

@@ -0,0 +1,549 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <stdio.h>
#include <math.h>
#include "jackclient.h"
#include "alsathread.h"
#include "timers.h"


Jackclient::Jackclient (jack_client_t* cl, const char *jserv, int mode, int nchan, bool sync, void *arg) :
_client (cl),
_arg (arg),
_mode (mode),
_nchan (nchan),
_state (INIT),
_freew (false),
_resamp (0)
{
init (jserv);
if (!sync) _resamp = new VResampler ();
}


Jackclient::~Jackclient (void)
{
fini ();
}


bool Jackclient::init (const char *jserv)
{
int i, spol, flags;
char s [64];
struct sched_param spar;

if (_client == 0)
{
fprintf (stderr, "Can't connect to Jack, is the server running ?\n");
return false;
}
jack_set_process_callback (_client, jack_static_process, (void *) this);
jack_set_latency_callback (_client, jack_static_latency, (void *) this);
jack_set_freewheel_callback (_client, jack_static_freewheel, (void *) this);
jack_set_buffer_size_callback (_client, jack_static_buffsize, (void *) this);
jack_on_shutdown (_client, jack_static_shutdown, (void *) this);

_bsize = 0;
_fsamp = 0;
if (jack_activate (_client))
{
fprintf(stderr, "Can't activate Jack");
return false;
}
_jname = jack_get_client_name (_client);
_bsize = jack_get_buffer_size (_client);
_fsamp = jack_get_sample_rate (_client);

flags = JackPortIsTerminal | JackPortIsPhysical;
for (i = 0; i < _nchan; i++)
{
if (_mode == PLAY)
{
sprintf (s, "playback_%d", i + 1);
_ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE,
flags | JackPortIsInput, 0);
}
else
{
sprintf (s, "capture_%d", i + 1);
_ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE,
flags | JackPortIsOutput, 0);
}
}
pthread_getschedparam (jack_client_thread_id (_client), &spol, &spar);
_rprio = spar.sched_priority - sched_get_priority_max (spol);
_buff = new float [_bsize * _nchan];
return true;
}


void Jackclient::fini (void)
{
delete[] _buff;
delete _resamp;
}


void Jackclient::jack_static_shutdown (void *arg)
{
((Jackclient *) arg)->sendinfo (TERM, 0, 0);
}


int Jackclient::jack_static_buffsize (jack_nframes_t nframes, void *arg)
{
Jackclient *J = (Jackclient *) arg;

if (J->_bsize == 0) J->_bsize = nframes;
else if (J->_bsize != (int) nframes) J->_state = Jackclient::TERM;
return 0;
}


void Jackclient::jack_static_freewheel (int state, void *arg)
{
((Jackclient *) arg)->jack_freewheel (state);
}


void Jackclient::jack_static_latency (jack_latency_callback_mode_t jlcm, void *arg)
{
((Jackclient *) arg)->jack_latency (jlcm);
}


int Jackclient::jack_static_process (jack_nframes_t nframes, void *arg)
{
return ((Jackclient *) arg)->jack_process (nframes);
}


void Jackclient::start (Lfq_audio *audioq,
Lfq_int32 *commq,
Lfq_adata *alsaq,
Lfq_jdata *infoq,
double ratio,
int delay,
int ltcor,
int rqual)
{
double d;

_audioq = audioq;
_commq = commq;
_alsaq = alsaq;
_infoq = infoq;
_ratio = ratio;
_delay = delay;
_rcorr = 1.0;
if (_resamp)
{
_resamp->setup (_ratio, _nchan, rqual);
_resamp->set_rrfilt (100);
d = _resamp->inpsize () / 2.0;
if (_mode == PLAY) d *= _ratio;
_delay += d;
}
_ltcor = ltcor;
_ppsec = (_fsamp + _bsize / 2) / _bsize;
initwait (_ppsec / 2);
jack_recompute_total_latencies (_client);
}


void Jackclient::initwait (int nwait)
{
_count = -nwait;
_commq->wr_int32 (Alsathread::WAIT);
_state = WAIT;
if (nwait > _ppsec) sendinfo (WAIT, 0, 0);
}


void Jackclient::initsync (void)
{
// Reset all lock-free queues.
_commq->reset ();
_alsaq->reset ();
_audioq->reset ();
if (_resamp)
{
// Reset and prefill the resampler.
_resamp->reset ();
_resamp->inp_count = _resamp->inpsize () / 2 - 1;
_resamp->out_count = 99999;
_resamp->process ();
}
// Initiliase state variables.
_t_a0 = _t_a1 = 0;
_k_a0 = _k_a1 = 0;
// Initialise loop filter state.
_z1 = _z2 = _z3 = 0;
// Activate the ALSA thread,
_commq->wr_int32 (Alsathread::PROC);
_state = SYNC0;
sendinfo (SYNC0, 0, 0);
}


void Jackclient::setloop (double bw)
{
double w;

// Set the loop bandwidth to bw Hz.
w = 6.28 * bw * _bsize / _fsamp;
_w0 = 1.0 - exp (-20.0 * w);
_w1 = w * 2 / _bsize;
_w2 = w / 2;
if (_mode == PLAY) _w1 /= _ratio;
else _w1 *= _ratio;
}


void Jackclient::playback (int nframes)
{
int i, j, n;
float *p, *q;
float *inp [MAXCHAN];

_bstat = _audioq->rd_avail ();
for (i = 0; i < _nchan; i++)
{
inp [i] = (float *)(jack_port_get_buffer (_ports [i], nframes));
}
if (_resamp)
{
// Interleave inputs into _buff.
for (i = 0; i < _nchan; i++)
{
p = inp [i];
q = _buff + i;
for (j = 0; j < _bsize; j++) q [j * _nchan] = p [j];
}
// Resample _buff and write to audio queue.
// The while loop takes care of wraparound.
_resamp->inp_count = _bsize;
_resamp->inp_data = _buff;
while (_resamp->inp_count)
{
_resamp->out_count = _audioq->wr_linav ();
_resamp->out_data = _audioq->wr_datap ();
n = _resamp->out_count;
_resamp->process ();
n -= _resamp->out_count;
_audioq->wr_commit (n);
}
}
else
{
// Interleave inputs into audio queue.
// The while loop takes care of wraparound.
while (nframes)
{
q = _audioq->wr_datap ();
n = _audioq->wr_linav ();
if (n > nframes) n = nframes;
for (i = 0; i < _nchan; i++)
{
p = inp [i];
for (j = 0; j < n; j++) q [j * _nchan] = p [j];
inp [i] += n;
q += 1;
}
_audioq->wr_commit (n);
nframes -= n;
}
}
}


void Jackclient::capture (int nframes)
{
int i, j, n;
float *p, *q;
float *out [MAXCHAN];

for (i = 0; i < _nchan; i++)
{
out [i] = (float *)(jack_port_get_buffer (_ports [i], nframes));
}
if (_resamp)
{
// Read from audio queue and resample.
// The while loop takes care of wraparound.
_resamp->out_count = _bsize;
_resamp->out_data = _buff;
while (_resamp->out_count)
{
_resamp->inp_count = _audioq->rd_linav ();
_resamp->inp_data = _audioq->rd_datap ();
n = _resamp->inp_count;
_resamp->process ();
n -= _resamp->inp_count;
_audioq->rd_commit (n);
}
// Deinterleave _buff to outputs.
for (i = 0; i < _nchan; i++)
{
p = _buff + i;
q = out [i];
for (j = 0; j < _bsize; j++) q [j] = p [j * _nchan];
}
}
else
{
// Deinterleave audio queue to outputs.
// The while loop takes care of wraparound.
while (nframes)
{
p = _audioq->rd_datap ();
n = _audioq->rd_linav ();
if (n > nframes) n = nframes;
for (i = 0; i < _nchan; i++)
{
q = out [i];
for (j = 0; j < n; j++) q [j] = p [j * _nchan];
out [i] += n;
p += 1;
}
_audioq->rd_commit (n);
nframes -= n;
}
}
_bstat = _audioq->rd_avail ();
}


void Jackclient::silence (int nframes)
{
int i;
float *q;

// Write silence to all jack ports.
for (i = 0; i < _nchan; i++)
{
q = (float *)(jack_port_get_buffer (_ports [i], nframes));
memset (q, 0, nframes * sizeof (float));
}
}


void Jackclient::sendinfo (int state, double error, double ratio)
{
Jdata *J;

if (_infoq->wr_avail ())
{
J = _infoq->wr_datap ();
J->_state = state;
J->_error = error;
J->_ratio = ratio;
J->_bstat = _bstat;
_infoq->wr_commit ();
}
}


void Jackclient::jack_freewheel (int state)
{
_freew = state ? true : false;
if (_freew) initwait (_ppsec / 4);
}


void Jackclient::jack_latency (jack_latency_callback_mode_t jlcm)
{
jack_latency_range_t R;
int i;

if (_state < WAIT) return;
if (_mode == PLAY)
{
if (jlcm != JackPlaybackLatency) return;
R.min = R.max = (int)(_delay / _ratio) + _ltcor;
}
else
{
if (jlcm != JackCaptureLatency) return;
R.min = R.max = (int)(_delay * _ratio) + _ltcor;
}
for (i = 0; i < _nchan; i++)
{
jack_port_set_latency_range (_ports [i], jlcm, &R);
}
}


int Jackclient::jack_process (int nframes)
{
int dk, n;
Adata *D;
jack_time_t t0, t1;
jack_nframes_t ft;
float us;
double tj, err, d1, d2, rd;

// Buffer size change or other evil.
if (_state == TERM)
{
sendinfo (TERM, 0, 0);
return 0;
}
// Skip cylce if ports may not yet exist.
if (_state < WAIT) return 0;

// Start synchronisation 1/2 second after entering
// the WAIT state. This delay allows the ALSA thread
// to restart cleanly if necessary. Disabled while
// freewheeling.
if (_state == WAIT)
{
if (_freew) return 0;
if (_mode == CAPT) silence (nframes);
if (++_count == 0) initsync ();
else return 0;
}

// Get the start time of the current cycle.
jack_get_cycle_times (_client, &ft, &t0, &t1, &us);
tj = tjack (t0);

// Check for any skipped cycles.
if (_state >= SYNC1)
{
dk = ft - _ft - _bsize;
if (_mode == PLAY)
{
dk = (int)(dk * _ratio + 0.5);
_audioq->wr_commit (dk);
}
else
{
dk = (int)(dk / _ratio + 0.5);
_audioq->rd_commit (dk);
}
}
_ft = ft;
// Check if we have timing data from the ALSA thread.
n = _alsaq->rd_avail ();
// If the data queue is full restart synchronisation.
// This can happen e.g. on a jack engine timeout, or
// when too many cycles have been skipped.
if (n == _alsaq->size ())
{
initwait (_ppsec / 2);
return 0;
}
if (n)
{
// Else move interval end to start, and update the
// interval end keeping only the most recent data.
if (_state < SYNC2) _state++;
_t_a0 = _t_a1;
_k_a0 = _k_a1;
while (_alsaq->rd_avail ())
{
D = _alsaq->rd_datap ();
// Restart synchronisation in case of
// an error in the ALSA interface.
if (D->_state == Alsathread::WAIT)
{
initwait (_ppsec / 2);
return 0;
}
_t_a1 = D->_timer;
_k_a1 += D->_nsamp;
_alsaq->rd_commit ();
}
}

err = 0;
if (_state >= SYNC2)
{
// Compute the delay error.
d1 = tjack_diff (tj, _t_a0);
d2 = tjack_diff (_t_a1, _t_a0);
rd = _resamp ? _resamp->inpdist () : 0.0;

if (_mode == PLAY)
{
n = _audioq->nwr () - _k_a0; // Must be done as integer as both terms will overflow.
err = n - (_k_a1 - _k_a0) * d1 / d2 + rd * _ratio - _delay;
}
else
{
n = _k_a0 - _audioq->nrd (); // Must be done as integer as both terms will overflow.
err = n + (_k_a1 - _k_a0) * d1 / d2 + rd - _delay ;
}
n = (int)(floor (err + 0.5));
if (_state == SYNC2)
{
// We have the first delay error value. Adjust the audio
// queue to obtain the actually wanted delay, and start
// tracking.
if (_mode == PLAY) _audioq->wr_commit (-n);
else _audioq->rd_commit (n);
err -= n;
setloop (1.0);
_state = PROC1;
}
}

// Switch to lower bandwidth after 4 seconds.
if ((_state == PROC1) && (++_count == 4 * _ppsec))
{
_state = PROC2;
setloop (0.05);
}

if (_state >= PROC1)
{
_z1 += _w0 * (_w1 * err - _z1);
_z2 += _w0 * (_z1 - _z2);
_z3 += _w2 * _z2;
// Check error conditions.
if (fabs (_z3) > 0.05)
{
// Something is really wrong, wait 10 seconds then restart.
initwait (10 * _ppsec);
return 0;
}
// Run loop filter and set resample ratio.
if (_resamp)
{
_rcorr = 1 - (_z2 + _z3);
if (_rcorr > 1.05) _rcorr = 1.05;
if (_rcorr < 0.95) _rcorr = 0.95;
_resamp->set_rratio (_rcorr);
}
sendinfo (_state, err, _rcorr);

// Resample and transfer between audio
// queue and jack ports.
if (_mode == PLAY) playback (nframes);
else capture (nframes);
}
else if (_mode == CAPT) silence (nframes);

return 0;
}

+ 120
- 0
tools/zalsa/jackclient.h View File

@@ -0,0 +1,120 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#ifndef __JACKCLIENT_H
#define __JACKCLIENT_H


#include <zita-resampler/vresampler.h>
#include "jack/jack.h"
#include "lfqueue.h"


class Jackclient
{
public:

Jackclient (jack_client_t*, const char *jserv, int mode, int nchan, bool sync, void *arg);
virtual ~Jackclient (void);
enum { PLAY, CAPT, MAXCHAN = 64 };
enum { INIT, TERM, WAIT, SYNC0, SYNC1, SYNC2, PROC1, PROC2 };

void start (Lfq_audio *audioq,
Lfq_int32 *commq,
Lfq_adata *alsaq,
Lfq_jdata *infoq,
double ratio,
int delay,
int ltcor,
int rqual);

const char *jname (void) const { return _jname; }
int fsamp (void) const { return _fsamp; }
int bsize (void) const { return _bsize; }
int rprio (void) const { return _rprio; }
void *getarg(void) const { return _arg; }

private:

bool init (const char *jserv);
void fini (void);
void initwait (int nwait);
void initsync (void);
void setloop (double bw);
void silence (int nframes);
void playback (int nframes);
void capture (int nframes);
void sendinfo (int state, double error, double ratio);

virtual void thr_main (void) {}

void jack_freewheel (int state);
void jack_latency (jack_latency_callback_mode_t jlcm);
int jack_process (int nframes);

jack_client_t *_client;
jack_port_t *_ports [MAXCHAN];
void *_arg;
const char *_jname;
int _mode;
int _nchan;
int _state;
int _count;
int _fsamp;
int _bsize;
int _rprio;
bool _freew;
float *_buff;
Lfq_audio *_audioq;
Lfq_int32 *_commq;
Lfq_adata *_alsaq;
Lfq_jdata *_infoq;
double _ratio;
int _ppsec;
int _bstat;

jack_nframes_t _ft;
double _t_a0;
double _t_a1;
int _k_a0;
int _k_a1;
double _delay;
int _ltcor;

double _w0;
double _w1;
double _w2;
double _z1;
double _z2;
double _z3;
double _rcorr;
VResampler *_resamp;

static void jack_static_shutdown (void *arg);
static int jack_static_buffsize (jack_nframes_t nframes, void *arg);
static void jack_static_freewheel (int state, void *arg);
static void jack_static_latency (jack_latency_callback_mode_t jlcm, void *arg);
static int jack_static_process (jack_nframes_t nframes, void *arg);
};


#endif

+ 89
- 0
tools/zalsa/lfqueue.cc View File

@@ -0,0 +1,89 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <assert.h>
#include "lfqueue.h"


Lfq_adata::Lfq_adata (int size) :
_size (size),
_mask (size - 1),
_nwr (0),
_nrd (0)
{
assert (!(_size & _mask));
_data = new Adata [_size];
}

Lfq_adata::~Lfq_adata (void)
{
delete[] _data;
}


Lfq_jdata::Lfq_jdata (int size) :
_size (size),
_mask (size - 1),
_nwr (0),
_nrd (0)
{
assert (!(_size & _mask));
_data = new Jdata [_size];
}

Lfq_jdata::~Lfq_jdata (void)
{
delete[] _data;
}


Lfq_int32::Lfq_int32 (int size) :
_size (size),
_mask (size - 1),
_nwr (0),
_nrd (0)
{
assert (!(_size & _mask));
_data = new int32_t [_size];
}

Lfq_int32::~Lfq_int32 (void)
{
delete[] _data;
}


Lfq_audio::Lfq_audio (int nsamp, int nchan) :
_size (nsamp),
_mask (nsamp - 1),
_nch (nchan),
_nwr (0),
_nrd (0)
{
assert (!(_size & _mask));
_data = new float [_nch * _size];
}

Lfq_audio::~Lfq_audio (void)
{
delete[] _data;
}



+ 182
- 0
tools/zalsa/lfqueue.h View File

@@ -0,0 +1,182 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#ifndef __LFQUEUE_H
#define __LFQUEUE_H


#include <stdint.h>
#include <string.h>


class Adata
{
public:

int32_t _state;
int32_t _nsamp;
double _timer;
};


class Lfq_adata
{
public:

Lfq_adata (int size);
~Lfq_adata (void);

void reset (void) { _nwr = _nrd = 0; }
int size (void) const { return _size; }

int wr_avail (void) const { return _size - _nwr + _nrd; }
Adata *wr_datap (void) { return _data + (_nwr & _mask); }
void wr_commit (void) { _nwr++; }

int rd_avail (void) const { return _nwr - _nrd; }
Adata *rd_datap (void) { return _data + (_nrd & _mask); }
void rd_commit (void) { _nrd++; }

private:

Adata *_data;
int _size;
int _mask;
int _nwr;
int _nrd;
};


class Jdata
{
public:

int32_t _state;
double _error;
double _ratio;
int _bstat;
};


class Lfq_jdata
{
public:

Lfq_jdata (int size);
~Lfq_jdata (void);

void reset (void) { _nwr = _nrd = 0; }
int size (void) const { return _size; }

int wr_avail (void) const { return _size - _nwr + _nrd; }
Jdata *wr_datap (void) { return _data + (_nwr & _mask); }
void wr_commit (void) { _nwr++; }

int rd_avail (void) const { return _nwr - _nrd; }
Jdata *rd_datap (void) { return _data + (_nrd & _mask); }
void rd_commit (void) { _nrd++; }

private:

Jdata *_data;
int _size;
int _mask;
int _nwr;
int _nrd;
};


class Lfq_int32
{
public:

Lfq_int32 (int size);
~Lfq_int32 (void);

int size (void) const { return _size; }
void reset (void) { _nwr = _nrd = 0; }

int wr_avail (void) const { return _size - _nwr + _nrd; }
int32_t *wr_datap (void) { return _data + (_nwr & _mask); }
void wr_commit (void) { _nwr++; }

int rd_avail (void) const { return _nwr - _nrd; }
int32_t *rd_datap (void) { return _data + (_nrd & _mask); }
void rd_commit (void) { _nrd++; }

void wr_int32 (int32_t v) { _data [_nwr++ & _mask] = v; }
void wr_uint32 (uint32_t v) { _data [_nwr++ & _mask] = v; }
void wr_float (float v) { *(float *)(_data + (_nwr++ & _mask)) = v; }

int32_t rd_int32 (void) { return _data [_nrd++ & _mask]; }
int32_t rd_uint32 (void) { return _data [_nrd++ & _mask]; }
float rd_float (void) { return *(float *)(_data + (_nrd++ & _mask)); }

private:

int32_t *_data;
int _size;
int _mask;
int _nwr;
int _nrd;
};


class Lfq_audio
{
public:

Lfq_audio (int nsamp, int nchan);
~Lfq_audio (void);

int size (void) const { return _size; }
void reset (void)
{
_nwr = _nrd = 0;
memset (_data, 0, _size * _nch * sizeof (float));
}

int nchan (void) const { return _nch; }
int nwr (void) const { return _nwr; };
int nrd (void) const { return _nrd; };

int wr_avail (void) const { return _size - _nwr + _nrd; }
int wr_linav (void) const { return _size - (_nwr & _mask); }
float *wr_datap (void) { return _data + _nch * (_nwr & _mask); }
void wr_commit (int k) { _nwr += k; }

int rd_avail (void) const { return _nwr - _nrd; }
int rd_linav (void) const { return _size - (_nrd & _mask); }
float *rd_datap (void) { return _data + _nch * (_nrd & _mask); }
void rd_commit (int k) { _nrd += k; }

private:

float *_data;
int _size;
int _mask;
int _nch;
int _nwr;
int _nrd;
};


#endif


+ 78
- 0
tools/zalsa/pxthread.cc View File

@@ -0,0 +1,78 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include "pxthread.h"


Pxthread::Pxthread (void) : _thrid (0)
{
}


Pxthread::~Pxthread (void)
{
}


extern "C" void *Pxthread_entry_point (void *arg)
{
Pxthread *T = (Pxthread *) arg;
T->thr_main ();
return NULL;
}


int Pxthread::thr_start (int policy, int priority, size_t stacksize)
{
int min, max, rc;
pthread_attr_t attr;
struct sched_param parm;

min = sched_get_priority_min (policy);
max = sched_get_priority_max (policy);
priority += max;
if (priority > max) priority = max;
if (priority < min) priority = min;
parm.sched_priority = priority;

pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setschedpolicy (&attr, policy);
pthread_attr_setschedparam (&attr, &parm);
pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setstacksize (&attr, stacksize);

_thrid = 0;
rc = pthread_create (&_thrid,
&attr,
Pxthread_entry_point,
this);

pthread_attr_destroy (&attr);

return rc;
}


void Pxthread::thr_main (void)
{
}


+ 52
- 0
tools/zalsa/pxthread.h View File

@@ -0,0 +1,52 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#ifndef __PXTHREAD_H
#define __PXTHREAD_H


#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <pthread.h>


class Pxthread
{
public:

Pxthread (void);
virtual ~Pxthread (void);
Pxthread (const Pxthread&);
Pxthread& operator=(const Pxthread&);

virtual void thr_main (void) = 0;
virtual int thr_start (int policy, int priority, size_t stacksize = 0);

private:
pthread_t _thrid;
};


#endif

+ 53
- 0
tools/zalsa/timers.h View File

@@ -0,0 +1,53 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#ifndef __TIMERS_H
#define __TIMERS_H


#include <math.h>
#include <sys/time.h>
#include <jack/jack.h>


#define tjack_mod ldexp (1e-6f, 32)



inline double tjack_diff (double a, double b)
{
double d, m;

d = a - b;
m = tjack_mod;
while (d < -m / 2) d += m;
while (d >= m / 2) d -= m;
return d;
}


inline double tjack (jack_time_t t, double dt = 0)
{
int32_t u = (int32_t)(t & 0xFFFFFFFFLL);
return 1e-6 * u;
}


#endif

+ 333
- 0
tools/zalsa/zita-a2j.cc View File

@@ -0,0 +1,333 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <signal.h>
#include "alsathread.h"
#include "jackclient.h"
#include "lfqueue.h"
#include "jack/control.h"

static const char *clopt = "hvLSj:d:r:p:n:c:Q:I:";

static void help (void)
{
jack_info ("%s-%s", APPNAME, VERSION);
jack_info ("(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>");
jack_info ("Use ALSA capture device as a Jack client.");
jack_info ("Options:");
jack_info (" -h Display this text");
jack_info (" -j <jackname> Name as Jack client [%s]", APPNAME);
jack_info (" -d <device> ALSA capture device [none]");
jack_info (" -r <rate> Sample rate [48000]");
jack_info (" -p <period> Period size [256]");
jack_info (" -n <nfrags> Number of fragments [2]");
jack_info (" -c <nchannels> Number of channels [2]");
jack_info (" -S Word clock sync, no resampling");
jack_info (" -Q <quality> Resampling quality, 16..96 [auto]");
jack_info (" -I <samples> Latency adjustment [0]");
jack_info (" -L Force 16-bit and 2 channels [off]");
jack_info (" -v Print tracing information [off]");
}

class zita_a2j
{
Lfq_int32 *commq;
Lfq_adata *alsaq;
Lfq_jdata *infoq;
Lfq_audio *audioq;
bool stop;
bool v_opt;
bool L_opt;
bool S_opt;
char *jname;
char *device;
int fsamp;
int bsize;
int nfrag;
int nchan;
int rqual;
int ltcor;

public:

zita_a2j()
{
commq = new Lfq_int32(16);
alsaq = new Lfq_adata(256);
infoq = new Lfq_jdata(256);
audioq = 0;
stop = false;
v_opt = false;
L_opt = false;
S_opt = false;
jname = strdup(APPNAME);
device = 0;
fsamp = 48000;
bsize = 128;
nfrag = 2;
nchan = 2;
rqual = 0;
ltcor = 0;
A = 0;
C = 0;
J = 0;
}

private:

int procoptions (int ac, const char *av [])
{
int k;
optind = 1;
opterr = 0;
while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1)
{
if (optarg && (*optarg == '-'))
{
jack_error (APPNAME ": Missing argument for '-%c' option.", k);
jack_error (APPNAME ": Use '-h' to see all options.");
return 1;
}
switch (k)
{
case 'h' : help (); return 1;
case 'v' : v_opt = true; break;
case 'L' : L_opt = true; break;
case 'S' : S_opt = true; break;
case 'j' : jname = optarg; break;
case 'd' : device = optarg; break;
case 'r' : fsamp = atoi (optarg); break;
case 'p' : bsize = atoi (optarg); break;
case 'n' : nfrag = atoi (optarg); break;
case 'c' : nchan = atoi (optarg); break;
case 'Q' : rqual = atoi (optarg); break;
case 'I' : ltcor = atoi (optarg); break;
case '?':
if (optopt != ':' && strchr (clopt, optopt))
{
jack_error (APPNAME ": Missing argument for '-%c' option.", optopt);
}
else if (isprint (optopt))
{
jack_error (APPNAME ": Unknown option '-%c'.", optopt);
}
else
{
jack_error (APPNAME ": Unknown option character '0x%02x'.", optopt & 255);
}
jack_error (APPNAME ": Use '-h' to see all options.");
return 1;
default:
return 1;
}
}
return 0;
}

int parse_options (const char* load_init)
{
int argsz;
int argc = 0;
const char** argv;
char* args = strdup (load_init);
char* token;
char* ptr = args;
char* savep;

if (!load_init) {
return 0;
}

argsz = 8; /* random guess at "maxargs" */
argv = (const char **) malloc (sizeof (char *) * argsz);

argv[argc++] = APPNAME;

while (1) {

if ((token = strtok_r (ptr, " ", &savep)) == NULL) {
break;
}

if (argc == argsz) {
argsz *= 2;
argv = (const char **) realloc (argv, sizeof (char *) * argsz);
}

argv[argc++] = token;
ptr = NULL;
}

return procoptions (argc, argv);
}

void printinfo (void)
{
int n, k;
double e, r;
Jdata *J;

n = 0;
k = 99999;
e = r = 0;
while (infoq->rd_avail ())
{
J = infoq->rd_datap ();
if (J->_state == Jackclient::TERM)
{
jack_error (APPNAME ": Fatal error condition, terminating.");
stop = true;
return;
}
else if (J->_state == Jackclient::WAIT)
{
jack_info (APPNAME ": Detected excessive timing errors, waiting 10 seconds.");
n = 0;
}
else if (J->_state == Jackclient::SYNC0)
{
jack_info (APPNAME ": Starting synchronisation.");
}
else if (v_opt)
{
n++;
e += J->_error;
r += J->_ratio;
if (J->_bstat < k) k = J->_bstat;
}
infoq->rd_commit ();
}
if (n) jack_info (APPNAME ": %8.3lf %10.6lf %5d", e / n, r / n, k);
}


Alsa_pcmi *A;
Alsathread *C;
Jackclient *J;

public:

int
jack_initialize (jack_client_t* client, const char* load_init)
{
int k, k_del, opts;
double t_jack;
double t_alsa;
double t_del;

if (parse_options (load_init)) {
jack_error (APPNAME ": parse options failed");
delete this;
return 1;
}

if (device == 0)
{
help ();
delete this;
return 1;
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1))
{
jack_error (APPNAME ": Illegal parameter value(s).");
delete this;
return 1;
}

opts = 0;
if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL;
if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH;
A = new Alsa_pcmi (0, device, 0, fsamp, bsize, nfrag, opts);
if (A->state ())
{
jack_error (APPNAME ": Can't open ALSA capture device '%s'.", device);
delete this;
return 1;
}
if (v_opt) A->printinfo ();
if (nchan > A->ncapt ())
{
nchan = A->ncapt ();
jack_error (APPNAME ": Warning: only %d channels are available.", nchan);
}
C = new Alsathread (A, Alsathread::CAPT);
J = new Jackclient (client, 0, Jackclient::CAPT, nchan, S_opt, this);
usleep (100000);

t_alsa = (double) bsize / fsamp;
if (t_alsa < 1e-3) t_alsa = 1e-3;
t_jack = (double) J->bsize () / J->fsamp ();
t_del = t_alsa + t_jack;
k_del = (int)(t_del * fsamp);
for (k = 256; k < 2 * k_del; k *= 2);
audioq = new Lfq_audio (k, nchan);

if (rqual == 0)
{
k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp ();
if (k < 44100) k = 44100;
rqual = (int)((6.7 * k) / (k - 38000));
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;

C->start (audioq, commq, alsaq, J->rprio () + 10);
J->start (audioq, commq, alsaq, infoq, J->fsamp () / (double) fsamp, k_del, ltcor, rqual);

return 0;
}

void jack_finish (void* arg)
{
commq->wr_int32 (Alsathread::TERM);
usleep (100000);
delete C;
delete A;
delete J;
delete audioq;
}
};

extern "C" {

int
jack_initialize (jack_client_t* client, const char* load_init)
{
zita_a2j *c = new zita_a2j();
return c->jack_initialize(client, load_init);
}

void jack_finish (void* arg)
{
if (!arg) return;
Jackclient *J = (Jackclient *)arg;
zita_a2j *c = (zita_a2j *)J->getarg();
c->jack_finish(arg);
delete c;
}

} /* extern "C" */

+ 331
- 0
tools/zalsa/zita-j2a.cc View File

@@ -0,0 +1,331 @@
// ----------------------------------------------------------------------------
//
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <signal.h>
#include "alsathread.h"
#include "jackclient.h"
#include "lfqueue.h"
#include "jack/control.h"

static const char *clopt = "hvLSj:d:r:p:n:c:Q:O:";

static void help (void)
{
jack_info ("%s-%s", APPNAME, VERSION);
jack_info ("(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>");
jack_info ("Use ALSA playback device as a Jack client.");
jack_info ("Options:");
jack_info (" -h Display this text");
jack_info (" -j <jackname> Name as Jack client [%s]", APPNAME);
jack_info (" -d <device> ALSA playback device [none]");
jack_info (" -r <rate> Sample rate [48000]");
jack_info (" -p <period> Period size [256]");
jack_info (" -n <nfrags> Number of fragments [2]");
jack_info (" -c <nchannels> Number of channels [2]");
jack_info (" -S Word clock sync, no resampling");
jack_info (" -Q <quality> Resampling quality, 16..96 [auto]");
jack_info (" -O <samples> Latency adjustment [0]");
jack_info (" -L Force 16-bit and 2 channels [off]");
jack_info (" -v Print tracing information [off]");
}

class zita_j2a
{
Lfq_int32 *commq;
Lfq_adata *alsaq;
Lfq_jdata *infoq;
Lfq_audio *audioq;
bool stop;
bool v_opt;
bool L_opt;
bool S_opt;
char *jname;
char *device;
int fsamp;
int bsize;
int nfrag;
int nchan;
int rqual;
int ltcor;

public:

zita_j2a()
{
commq = new Lfq_int32(16);
alsaq = new Lfq_adata(256);
infoq = new Lfq_jdata(256);
audioq = 0;
stop = false;
v_opt = false;
L_opt = false;
S_opt = false;
jname = strdup(APPNAME);
device = 0;
fsamp = 48000;
bsize = 128;
nfrag = 2;
nchan = 2;
rqual = 0;
ltcor = 0;
A = 0;
P = 0;
J = 0;
}

private:

int procoptions (int ac, const char *av [])
{
int k;

optind = 1;
opterr = 0;
while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1)
{
if (optarg && (*optarg == '-'))
{
jack_error (APPNAME ": Missing argument for '-%c' option.", k);
jack_error (APPNAME ": Use '-h' to see all options.");
return 1;
}
switch (k)
{
case 'h' : help (); return 1;
case 'v' : v_opt = true; break;
case 'L' : L_opt = true; break;
case 'S' : S_opt = true; break;
case 'j' : jname = optarg; break;
case 'd' : device = optarg; break;
case 'r' : fsamp = atoi (optarg); break;
case 'p' : bsize = atoi (optarg); break;
case 'n' : nfrag = atoi (optarg); break;
case 'c' : nchan = atoi (optarg); break;
case 'Q' : rqual = atoi (optarg); break;
case 'O' : ltcor = atoi (optarg); break;
case '?':
if (optopt != ':' && strchr (clopt, optopt))
{
jack_error (APPNAME ": Missing argument for '-%c' option.", optopt);
}
else if (isprint (optopt))
{
jack_error (APPNAME ": Unknown option '-%c'.", optopt);
}
else
{
jack_error (APPNAME ": Unknown option character '0x%02x'.", optopt & 255);
}
jack_error (APPNAME ": Use '-h' to see all options.");
return 1;
default:
return 1;
}
}

return 0;
}

int parse_options (const char* load_init)
{
int argsz;
int argc = 0;
const char** argv;
char* args = strdup (load_init);
char* token;
char* ptr = args;
char* savep;

if (!load_init) {
return 0;
}

argsz = 8; /* random guess at "maxargs" */
argv = (const char **) malloc (sizeof (char *) * argsz);

argv[argc++] = APPNAME;

while (1) {

if ((token = strtok_r (ptr, " ", &savep)) == NULL) {
break;
}

if (argc == argsz) {
argsz *= 2;
argv = (const char **) realloc (argv, sizeof (char *) * argsz);
}

argv[argc++] = token;
ptr = NULL;
}

return procoptions (argc, argv);
}

void printinfo (void)
{
int n, k;
double e, r;
Jdata *J;

n = 0;
k = 99999;
e = r = 0;
while (infoq->rd_avail ())
{
J = infoq->rd_datap ();
if (J->_state == Jackclient::TERM)
{
jack_info (APPNAME ": Fatal error condition, terminating.");
stop = true;
return;
}
else if (J->_state == Jackclient::WAIT)
{
jack_info (APPNAME ": Detected excessive timing errors, waiting 10 seconds.");
n = 0;
}
else if (J->_state == Jackclient::SYNC0)
{
jack_info (APPNAME ": Starting synchronisation.");
}
else if (v_opt)
{
n++;
e += J->_error;
r += J->_ratio;
if (J->_bstat < k) k = J->_bstat;
}
infoq->rd_commit ();
}
if (n) jack_info ("%8.3lf %10.6lf %5d", e / n, r / n, k);
}

Alsa_pcmi *A;
Alsathread *P;
Jackclient *J;
public:

int jack_initialize (jack_client_t* client, const char* load_init)
{
int k, k_del, opts;
double t_jack;
double t_alsa;
double t_del;

if (parse_options (load_init)) {
delete this;
return 1;
}

if (device == 0)
{
help ();
delete this;
return 1;
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1))
{
jack_error (APPNAME ": Illegal parameter value(s).");
delete this;
return 1;
}

opts = 0;
if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL;
if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH;
A = new Alsa_pcmi (device, 0, 0, fsamp, bsize, nfrag, opts);
if (A->state ())
{
jack_error (APPNAME ": Can't open ALSA playback device '%s'.", device);
delete this;
return 1;
}
if (v_opt) A->printinfo ();
if (nchan > A->nplay ())
{
nchan = A->nplay ();
jack_error (APPNAME ": Warning: only %d channels are available.", nchan);
}
P = new Alsathread (A, Alsathread::PLAY);
J = new Jackclient (client, 0, Jackclient::PLAY, nchan, S_opt, this);
usleep (100000);

t_alsa = (double) bsize / fsamp;
if (t_alsa < 1e-3) t_alsa = 1e-3;
t_jack = (double) J->bsize () / J->fsamp ();
t_del = t_alsa + t_jack;
k_del = (int)(t_del * fsamp);
for (k = 256; k < 2 * k_del; k *= 2);
audioq = new Lfq_audio (k, nchan);

if (rqual == 0)
{
k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp ();
if (k < 44100) k = 44100;
rqual = (int)((6.7 * k) / (k - 38000));
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;

P->start (audioq, commq, alsaq, J->rprio () + 10);
J->start (audioq, commq, alsaq, infoq, (double) fsamp / J->fsamp (), k_del, ltcor, rqual);

return 0;
}

void jack_finish (void* arg)
{
commq->wr_int32 (Alsathread::TERM);
usleep (100000);
delete P;
delete A;
delete J;
delete audioq;
}
};

extern "C" {

int
jack_initialize (jack_client_t* client, const char* load_init)
{
zita_j2a *c = new zita_j2a();
return c->jack_initialize(client, load_init);
}

void jack_finish (void* arg)
{
if (!arg) return;
Jackclient *J = (Jackclient *)arg;
zita_j2a *c = (zita_j2a *)J->getarg();
c->jack_finish(arg);
delete c;
}

} /* extern "C" */

+ 6
- 1
wscript View File

@@ -11,7 +11,7 @@ import sys
from waflib import Logs, Options, Task, Utils from waflib import Logs, Options, Task, Utils
from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext


VERSION='1.9.17'
VERSION='1.9.18'
APPNAME='jack' APPNAME='jack'
JACK_API_VERSION = '0.1.0' JACK_API_VERSION = '0.1.0'


@@ -174,6 +174,11 @@ def options(opt):
help='Use Berkeley DB (metadata)') help='Use Berkeley DB (metadata)')
db.check(header_name='db.h') db.check(header_name='db.h')
db.check(lib='db') db.check(lib='db')
zalsa = opt.add_auto_option(
'zalsa',
help='Build internal zita-a2j/j2a client')
zalsa.check(lib='zita-alsa-pcmi')
zalsa.check(lib='zita-resampler')


# dbus options # dbus options
opt.recurse('dbus') opt.recurse('dbus')


Loading…
Cancel
Save