Signed-off-by: falkTX <falktx@falktx.com>tags/v1.9.18^2
| @@ -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 (); | |||
| } | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||
| { | |||
| } | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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" */ | |||
| @@ -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" */ | |||