diff options
author | Filipe Coelho <falktx@falktx.com> | 2021-04-14 16:16:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-14 16:16:11 +0100 |
commit | 8f4880518b2d1aeed7381feb84ccffea8800b4f4 (patch) | |
tree | 82170e2354bed86af0a5f8cd8241c7cafc2a6436 /tools/zalsa/jackclient.cc | |
parent | 62b07487dc76b5d94d118e7926d216a2cbb8bb5d (diff) | |
parent | 5a1c0f98d55c8c97a178f21fa6617034411c38af (diff) | |
download | jack2-8f4880518b2d1aeed7381feb84ccffea8800b4f4.tar.gz |
Merge pull request #747 from jackaudio/zalsa
Add zita-a2j/j2a as internal client
Diffstat (limited to 'tools/zalsa/jackclient.cc')
-rw-r--r-- | tools/zalsa/jackclient.cc | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/tools/zalsa/jackclient.cc b/tools/zalsa/jackclient.cc new file mode 100644 index 00000000..826f706e --- /dev/null +++ b/tools/zalsa/jackclient.cc @@ -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; +} |