Browse Source

Merge pull request #747 from jackaudio/zalsa

Add zita-a2j/j2a as internal client
tags/v1.9.18^2
Filipe Coelho GitHub 4 years ago
parent
commit
8f4880518b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2094 additions and 4 deletions
  1. +24
    -4
      tools/wscript
  2. +218
    -0
      tools/zalsa/alsathread.cc
  3. +68
    -0
      tools/zalsa/alsathread.h
  4. +549
    -0
      tools/zalsa/jackclient.cc
  5. +120
    -0
      tools/zalsa/jackclient.h
  6. +89
    -0
      tools/zalsa/lfqueue.cc
  7. +182
    -0
      tools/zalsa/lfqueue.h
  8. +78
    -0
      tools/zalsa/pxthread.cc
  9. +52
    -0
      tools/zalsa/pxthread.h
  10. +53
    -0
      tools/zalsa/timers.h
  11. +329
    -0
      tools/zalsa/zita-a2j.cc
  12. +327
    -0
      tools/zalsa/zita-j2a.cc
  13. +5
    -0
      wscript

+ 24
- 4
tools/wscript View File

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

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):
if bld.env['IS_LINUX']:
@@ -50,7 +51,7 @@ def build(bld):

prog.target = example_tool

if bld.env['BUILD_EXAMPLE_CLIENT_TRANSPORT']:
if bld.env['BUILD_TOOL_CLIENT_TRANSPORT']:
prog = bld(features = 'c cprogram')
prog.includes = os_incdir + ['../common/jack', '../common']
prog.source = 'transport.c'
@@ -74,7 +75,7 @@ def build(bld):
prog.target = 'jack_netsource'
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.includes = os_incdir + ['../common/jack', '../common']
prog.source = ['alsa_in.c', '../common/memops.c']
@@ -89,6 +90,25 @@ def build(bld):
prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M']
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 = 'zita-a2j'
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 = 'zita-j2a'
prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib']
prog.env['cxxshlib_PATTERN'] = '%s.so'

if not bld.env['IS_WINDOWS']:
bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect')
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, "USB_Audio_Playback_%d", i + 1);
_ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE,
flags | JackPortIsInput, 0);
}
else
{
sprintf (s, "USB_Audio_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

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

@@ -0,0 +1,329 @@
// ----------------------------------------------------------------------------
//
// 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"

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

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

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 == '-'))
{
fprintf (stderr, " Missing argument for '-%c' option.\n", k);
fprintf (stderr, " Use '-h' to see all options.\n");
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))
{
fprintf (stderr, " Missing argument for '-%c' option.\n", optopt);
}
else if (isprint (optopt))
{
fprintf (stderr, " Unknown option '-%c'.\n", optopt);
}
else
{
fprintf (stderr, " Unknown option character '0x%02x'.\n", optopt & 255);
}
fprintf (stderr, " Use '-h' to see all options.\n");
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)
{
printf ("Fatal error condition, terminating.\n");
stop = true;
return;
}
else if (J->_state == Jackclient::WAIT)
{
printf ("Detected excessive timing errors, waiting 10 seconds.\n");
n = 0;
}
else if (J->_state == Jackclient::SYNC0)
{
printf ("Starting synchronisation.\n");
}
else if (v_opt)
{
n++;
e += J->_error;
r += J->_ratio;
if (J->_bstat < k) k = J->_bstat;
}
infoq->rd_commit ();
}
if (n) printf ("%8.3lf %10.6lf %5d\n", 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)) {
fprintf (stderr, "parse options failed\n");
return 1;
}

if (device == 0)
{
help ();
return 1;
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1))
{
fprintf (stderr, "Illegal parameter value(s).\n");
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 ())
{
fprintf (stderr, "Can't open ALSA capture device '%s'.\n", device);
return 1;
}
if (v_opt) A->printinfo ();
if (nchan > A->ncapt ())
{
nchan = A->ncapt ();
fprintf (stderr, "Warning: only %d channels are available.\n", 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();
c->jack_initialize(client, load_init);
return 0;
}

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

} /* extern "C" */

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

@@ -0,0 +1,327 @@
// ----------------------------------------------------------------------------
//
// 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 <iostream>

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

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

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

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 == '-'))
{
fprintf (stderr, " Missing argument for '-%c' option.\n", k);
fprintf (stderr, " Use '-h' to see all options.\n");
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))
{
fprintf (stderr, " Missing argument for '-%c' option.\n", optopt);
}
else if (isprint (optopt))
{
fprintf (stderr, " Unknown option '-%c'.\n", optopt);
}
else
{
fprintf (stderr, " Unknown option character '0x%02x'.\n", optopt & 255);
}
fprintf (stderr, " Use '-h' to see all options.\n");
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)
{
printf ("Fatal error condition, terminating.\n");
stop = true;
return;
}
else if (J->_state == Jackclient::WAIT)
{
printf ("Detected excessive timing errors, waiting 10 seconds.\n");
n = 0;
}
else if (J->_state == Jackclient::SYNC0)
{
printf ("Starting synchronisation.\n");
}
else if (v_opt)
{
n++;
e += J->_error;
r += J->_ratio;
if (J->_bstat < k) k = J->_bstat;
}
infoq->rd_commit ();
}
if (n) printf ("%8.3lf %10.6lf %5d\n", 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)) {
return 1;
}

if (device == 0)
{
help ();
return 1;
}
if (rqual < 16) rqual = 16;
if (rqual > 96) rqual = 96;
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1))
{
fprintf (stderr, "Illegal parameter value(s).\n");
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 ())
{
fprintf (stderr, "Can't open ALSA playback device '%s'.\n", device);
return 1;
}
if (v_opt) A->printinfo ();
if (nchan > A->nplay ())
{
nchan = A->nplay ();
fprintf (stderr, "Warning: only %d channels are available.\n", 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();
c->jack_initialize(client, load_init);
return 0;
}

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

} /* extern "C" */

+ 5
- 0
wscript View File

@@ -174,6 +174,11 @@ def options(opt):
help='Use Berkeley DB (metadata)')
db.check(header_name='db.h')
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
opt.recurse('dbus')


Loading…
Cancel
Save