@@ -1,6 +1,14 @@ | |||||
ChangeLog | ChangeLog | ||||
######### | ######### | ||||
* 1.9.18 (2021-04-15) | |||||
* Add zalsa_in/out as internal client (based on zita-a2j/j2a and jack1 code) | |||||
* Fix jack_midi_dump deadlock on close after the jack server is restarted | |||||
* Fix interrupt signal for linux futex waits | |||||
* Fix usage of meta-data in official macOS builds (private DB errors) | |||||
* Log error message when cleaning previous DB (macOS and Windows) | |||||
* 1.9.17 (2021-01-15) | * 1.9.17 (2021-01-15) | ||||
* Fix jack_control stopping after first command iteration | * Fix jack_control stopping after first command iteration | ||||
@@ -31,6 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||||
#include <math.h> | #include <math.h> | ||||
#include <string> | #include <string> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <climits> | |||||
using namespace std; | using namespace std; | ||||
@@ -636,7 +637,7 @@ inline int JackClient::CallProcessCallback() | |||||
inline bool JackClient::WaitSync() | inline bool JackClient::WaitSync() | ||||
{ | { | ||||
// Suspend itself: wait on the input synchro | // Suspend itself: wait on the input synchro | ||||
if (GetGraphManager()->SuspendRefNum(GetClientControl(), fSynchroTable, 0x7FFFFFFF) < 0) { | |||||
if (GetGraphManager()->SuspendRefNum(GetClientControl(), fSynchroTable, LONG_MAX) < 0) { | |||||
jack_error("SuspendRefNum error"); | jack_error("SuspendRefNum error"); | ||||
return false; | return false; | ||||
} else { | } else { | ||||
@@ -24,7 +24,7 @@ | |||||
#include "config.h" | #include "config.h" | ||||
#endif | #endif | ||||
#define VERSION "1.9.17" | |||||
#define VERSION "1.9.18" | |||||
#define BUFFER_SIZE_MAX 8192 | #define BUFFER_SIZE_MAX 8192 | ||||
@@ -123,9 +123,11 @@ int JackMetadata::PropertyInit() | |||||
#endif | #endif | ||||
if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) { | if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) { | ||||
#ifdef WIN32 | |||||
#if defined(WIN32) || defined(__APPLE__) | |||||
// new versions of jack2 are built with HAVE_MIXED_SIZE_ADDRESSING, which induces this error, this is expected | // new versions of jack2 are built with HAVE_MIXED_SIZE_ADDRESSING, which induces this error, this is expected | ||||
if (ret == DB_VERSION_MISMATCH) { | if (ret == DB_VERSION_MISMATCH) { | ||||
jack_error ("Failed to open previous DB environment, trying again clean..."); | |||||
// cleanup old stuff | // cleanup old stuff | ||||
snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir); | snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir); | ||||
remove (dbpath); | remove (dbpath); | ||||
@@ -145,7 +147,7 @@ int JackMetadata::PropertyInit() | |||||
if (ret != 0) | if (ret != 0) | ||||
#endif | #endif | ||||
{ | { | ||||
jack_error ("cannot open DB environment: %s", db_strerror (ret)); | |||||
jack_error ("Cannot open DB environment: %s", db_strerror (ret)); | |||||
fDBenv = NULL; | fDBenv = NULL; | ||||
return -1; | return -1; | ||||
} | } | ||||
@@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||||
#include "JackConstants.h" | #include "JackConstants.h" | ||||
#include "JackError.h" | #include "JackError.h" | ||||
#include "promiscuous.h" | #include "promiscuous.h" | ||||
#include <climits> | |||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <sys/mman.h> | #include <sys/mman.h> | ||||
@@ -93,18 +94,24 @@ bool JackLinuxFutex::Wait() | |||||
fFutex->internal = !fFutex->internal; | fFutex->internal = !fFutex->internal; | ||||
} | } | ||||
const int wait_mode = fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; | |||||
for (;;) | for (;;) | ||||
{ | { | ||||
if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) | if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) | ||||
return true; | return true; | ||||
if (::syscall(SYS_futex, fFutex, fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, 0, NULL, NULL, 0) != 0 && errno != EWOULDBLOCK) | |||||
return false; | |||||
if (::syscall(SYS_futex, fFutex, wait_mode, 0, NULL, NULL, 0) != 0) | |||||
if (errno != EAGAIN && errno != EINTR) | |||||
return false; | |||||
} | } | ||||
} | } | ||||
bool JackLinuxFutex::TimedWait(long usec) | bool JackLinuxFutex::TimedWait(long usec) | ||||
{ | { | ||||
if (usec == LONG_MAX) | |||||
return Wait(); | |||||
if (!fFutex) { | if (!fFutex) { | ||||
jack_error("JackLinuxFutex::TimedWait name = %s already deallocated!!", fName); | jack_error("JackLinuxFutex::TimedWait name = %s already deallocated!!", fName); | ||||
return false; | return false; | ||||
@@ -120,14 +127,16 @@ bool JackLinuxFutex::TimedWait(long usec) | |||||
const int nsecs = (usec % 1000000) * 1000; | const int nsecs = (usec % 1000000) * 1000; | ||||
const timespec timeout = { static_cast<time_t>(secs), nsecs }; | const timespec timeout = { static_cast<time_t>(secs), nsecs }; | ||||
const int wait_mode = fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; | |||||
for (;;) | for (;;) | ||||
{ | { | ||||
if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) | if (__sync_bool_compare_and_swap(&fFutex->futex, 1, 0)) | ||||
return true; | return true; | ||||
if (::syscall(SYS_futex, fFutex, fFutex->internal ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, 0, &timeout, NULL, 0) != 0 && errno != EWOULDBLOCK) | |||||
return false; | |||||
if (::syscall(SYS_futex, fFutex, wait_mode, 0, &timeout, NULL, 0) != 0) | |||||
if (errno != EAGAIN && errno != EINTR) | |||||
return false; | |||||
} | } | ||||
} | } | ||||
@@ -112,6 +112,11 @@ process (jack_nframes_t frames, void* arg) | |||||
static void wearedone(int sig) { | static void wearedone(int sig) { | ||||
fprintf(stderr, "Shutting down\n"); | fprintf(stderr, "Shutting down\n"); | ||||
keeprunning = 0; | keeprunning = 0; | ||||
/* main loop might be blocked by data_ready when jack server dies. */ | |||||
if (pthread_mutex_trylock (&msg_thread_lock) == 0) { | |||||
pthread_cond_signal (&data_ready); | |||||
pthread_mutex_unlock (&msg_thread_lock); | |||||
} | |||||
} | } | ||||
static void usage (int status) { | static void usage (int status) { | ||||
@@ -19,8 +19,9 @@ example_tools = { | |||||
} | } | ||||
def configure(conf): | def configure(conf): | ||||
conf.env['BUILD_EXAMPLE_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA'] | |||||
conf.env['BUILD_EXAMPLE_CLIENT_TRANSPORT'] = conf.env['READLINE'] | |||||
conf.env['BUILD_TOOL_ALSA_IO'] = conf.env['SAMPLERATE'] and conf.env['BUILD_DRIVER_ALSA'] | |||||
conf.env['BUILD_TOOL_CLIENT_TRANSPORT'] = conf.env['READLINE'] | |||||
conf.env['BUILD_TOOL_ZALSA'] = conf.env['ZALSA'] | |||||
def build(bld): | def build(bld): | ||||
if bld.env['IS_LINUX']: | if bld.env['IS_LINUX']: | ||||
@@ -50,7 +51,7 @@ def build(bld): | |||||
prog.target = example_tool | prog.target = example_tool | ||||
if bld.env['BUILD_EXAMPLE_CLIENT_TRANSPORT']: | |||||
if bld.env['BUILD_TOOL_CLIENT_TRANSPORT']: | |||||
prog = bld(features = 'c cprogram') | prog = bld(features = 'c cprogram') | ||||
prog.includes = os_incdir + ['../common/jack', '../common'] | prog.includes = os_incdir + ['../common/jack', '../common'] | ||||
prog.source = 'transport.c' | prog.source = 'transport.c' | ||||
@@ -74,7 +75,7 @@ def build(bld): | |||||
prog.target = 'jack_netsource' | prog.target = 'jack_netsource' | ||||
prog.defines = ['HAVE_CONFIG_H'] | prog.defines = ['HAVE_CONFIG_H'] | ||||
if bld.env['IS_LINUX'] and bld.env['BUILD_EXAMPLE_ALSA_IO']: | |||||
if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ALSA_IO']: | |||||
prog = bld(features = 'c cprogram') | prog = bld(features = 'c cprogram') | ||||
prog.includes = os_incdir + ['../common/jack', '../common'] | prog.includes = os_incdir + ['../common/jack', '../common'] | ||||
prog.source = ['alsa_in.c', '../common/memops.c'] | prog.source = ['alsa_in.c', '../common/memops.c'] | ||||
@@ -89,6 +90,25 @@ def build(bld): | |||||
prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M'] | prog.use = ['clientlib', 'ALSA', 'SAMPLERATE', 'M'] | ||||
prog.target = 'alsa_out' | prog.target = 'alsa_out' | ||||
if bld.env['IS_LINUX'] and bld.env['BUILD_TOOL_ZALSA']: | |||||
prog = bld(features = ['cxx', 'cxxshlib']) | |||||
prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_in"','VERSION="0.4.0"'] | |||||
prog.install_path = '${ADDON_DIR}/' | |||||
prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa'] | |||||
prog.source = ['zalsa/zita-a2j.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc'] | |||||
prog.target = 'zalsa_in' | |||||
prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib'] | |||||
prog.env['cxxshlib_PATTERN'] = '%s.so' | |||||
prog = bld(features = ['cxx', 'cxxshlib']) | |||||
prog.defines = ['HAVE_CONFIG_H','SERVER_SIDE','APPNAME="zalsa_out"','VERSION="0.4.0"'] | |||||
prog.install_path = '${ADDON_DIR}/' | |||||
prog.includes = os_incdir + ['../common/jack', '../common', 'zalsa'] | |||||
prog.source = ['zalsa/zita-j2a.cc', 'zalsa/alsathread.cc', 'zalsa/jackclient.cc', 'zalsa/pxthread.cc', 'zalsa/lfqueue.cc'] | |||||
prog.target = 'zalsa_out' | |||||
prog.use = ['ZITA-ALSA-PCMI', 'ZITA-RESAMPLER', 'ALSA', 'M', 'RT', 'serverlib'] | |||||
prog.env['cxxshlib_PATTERN'] = '%s.so' | |||||
if not bld.env['IS_WINDOWS']: | if not bld.env['IS_WINDOWS']: | ||||
bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect') | bld.symlink_as('${PREFIX}/bin/jack_disconnect', 'jack_connect') | ||||
bld.install_files('${PREFIX}/bin', 'jack_control', chmod=0o755) | bld.install_files('${PREFIX}/bin', 'jack_control', chmod=0o755) |
@@ -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, "playback_%d", i + 1); | |||||
_ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE, | |||||
flags | JackPortIsInput, 0); | |||||
} | |||||
else | |||||
{ | |||||
sprintf (s, "capture_%d", i + 1); | |||||
_ports [i] = jack_port_register (_client, s, JACK_DEFAULT_AUDIO_TYPE, | |||||
flags | JackPortIsOutput, 0); | |||||
} | |||||
} | |||||
pthread_getschedparam (jack_client_thread_id (_client), &spol, &spar); | |||||
_rprio = spar.sched_priority - sched_get_priority_max (spol); | |||||
_buff = new float [_bsize * _nchan]; | |||||
return true; | |||||
} | |||||
void Jackclient::fini (void) | |||||
{ | |||||
delete[] _buff; | |||||
delete _resamp; | |||||
} | |||||
void Jackclient::jack_static_shutdown (void *arg) | |||||
{ | |||||
((Jackclient *) arg)->sendinfo (TERM, 0, 0); | |||||
} | |||||
int Jackclient::jack_static_buffsize (jack_nframes_t nframes, void *arg) | |||||
{ | |||||
Jackclient *J = (Jackclient *) arg; | |||||
if (J->_bsize == 0) J->_bsize = nframes; | |||||
else if (J->_bsize != (int) nframes) J->_state = Jackclient::TERM; | |||||
return 0; | |||||
} | |||||
void Jackclient::jack_static_freewheel (int state, void *arg) | |||||
{ | |||||
((Jackclient *) arg)->jack_freewheel (state); | |||||
} | |||||
void Jackclient::jack_static_latency (jack_latency_callback_mode_t jlcm, void *arg) | |||||
{ | |||||
((Jackclient *) arg)->jack_latency (jlcm); | |||||
} | |||||
int Jackclient::jack_static_process (jack_nframes_t nframes, void *arg) | |||||
{ | |||||
return ((Jackclient *) arg)->jack_process (nframes); | |||||
} | |||||
void Jackclient::start (Lfq_audio *audioq, | |||||
Lfq_int32 *commq, | |||||
Lfq_adata *alsaq, | |||||
Lfq_jdata *infoq, | |||||
double ratio, | |||||
int delay, | |||||
int ltcor, | |||||
int rqual) | |||||
{ | |||||
double d; | |||||
_audioq = audioq; | |||||
_commq = commq; | |||||
_alsaq = alsaq; | |||||
_infoq = infoq; | |||||
_ratio = ratio; | |||||
_delay = delay; | |||||
_rcorr = 1.0; | |||||
if (_resamp) | |||||
{ | |||||
_resamp->setup (_ratio, _nchan, rqual); | |||||
_resamp->set_rrfilt (100); | |||||
d = _resamp->inpsize () / 2.0; | |||||
if (_mode == PLAY) d *= _ratio; | |||||
_delay += d; | |||||
} | |||||
_ltcor = ltcor; | |||||
_ppsec = (_fsamp + _bsize / 2) / _bsize; | |||||
initwait (_ppsec / 2); | |||||
jack_recompute_total_latencies (_client); | |||||
} | |||||
void Jackclient::initwait (int nwait) | |||||
{ | |||||
_count = -nwait; | |||||
_commq->wr_int32 (Alsathread::WAIT); | |||||
_state = WAIT; | |||||
if (nwait > _ppsec) sendinfo (WAIT, 0, 0); | |||||
} | |||||
void Jackclient::initsync (void) | |||||
{ | |||||
// Reset all lock-free queues. | |||||
_commq->reset (); | |||||
_alsaq->reset (); | |||||
_audioq->reset (); | |||||
if (_resamp) | |||||
{ | |||||
// Reset and prefill the resampler. | |||||
_resamp->reset (); | |||||
_resamp->inp_count = _resamp->inpsize () / 2 - 1; | |||||
_resamp->out_count = 99999; | |||||
_resamp->process (); | |||||
} | |||||
// Initiliase state variables. | |||||
_t_a0 = _t_a1 = 0; | |||||
_k_a0 = _k_a1 = 0; | |||||
// Initialise loop filter state. | |||||
_z1 = _z2 = _z3 = 0; | |||||
// Activate the ALSA thread, | |||||
_commq->wr_int32 (Alsathread::PROC); | |||||
_state = SYNC0; | |||||
sendinfo (SYNC0, 0, 0); | |||||
} | |||||
void Jackclient::setloop (double bw) | |||||
{ | |||||
double w; | |||||
// Set the loop bandwidth to bw Hz. | |||||
w = 6.28 * bw * _bsize / _fsamp; | |||||
_w0 = 1.0 - exp (-20.0 * w); | |||||
_w1 = w * 2 / _bsize; | |||||
_w2 = w / 2; | |||||
if (_mode == PLAY) _w1 /= _ratio; | |||||
else _w1 *= _ratio; | |||||
} | |||||
void Jackclient::playback (int nframes) | |||||
{ | |||||
int i, j, n; | |||||
float *p, *q; | |||||
float *inp [MAXCHAN]; | |||||
_bstat = _audioq->rd_avail (); | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
inp [i] = (float *)(jack_port_get_buffer (_ports [i], nframes)); | |||||
} | |||||
if (_resamp) | |||||
{ | |||||
// Interleave inputs into _buff. | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
p = inp [i]; | |||||
q = _buff + i; | |||||
for (j = 0; j < _bsize; j++) q [j * _nchan] = p [j]; | |||||
} | |||||
// Resample _buff and write to audio queue. | |||||
// The while loop takes care of wraparound. | |||||
_resamp->inp_count = _bsize; | |||||
_resamp->inp_data = _buff; | |||||
while (_resamp->inp_count) | |||||
{ | |||||
_resamp->out_count = _audioq->wr_linav (); | |||||
_resamp->out_data = _audioq->wr_datap (); | |||||
n = _resamp->out_count; | |||||
_resamp->process (); | |||||
n -= _resamp->out_count; | |||||
_audioq->wr_commit (n); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Interleave inputs into audio queue. | |||||
// The while loop takes care of wraparound. | |||||
while (nframes) | |||||
{ | |||||
q = _audioq->wr_datap (); | |||||
n = _audioq->wr_linav (); | |||||
if (n > nframes) n = nframes; | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
p = inp [i]; | |||||
for (j = 0; j < n; j++) q [j * _nchan] = p [j]; | |||||
inp [i] += n; | |||||
q += 1; | |||||
} | |||||
_audioq->wr_commit (n); | |||||
nframes -= n; | |||||
} | |||||
} | |||||
} | |||||
void Jackclient::capture (int nframes) | |||||
{ | |||||
int i, j, n; | |||||
float *p, *q; | |||||
float *out [MAXCHAN]; | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
out [i] = (float *)(jack_port_get_buffer (_ports [i], nframes)); | |||||
} | |||||
if (_resamp) | |||||
{ | |||||
// Read from audio queue and resample. | |||||
// The while loop takes care of wraparound. | |||||
_resamp->out_count = _bsize; | |||||
_resamp->out_data = _buff; | |||||
while (_resamp->out_count) | |||||
{ | |||||
_resamp->inp_count = _audioq->rd_linav (); | |||||
_resamp->inp_data = _audioq->rd_datap (); | |||||
n = _resamp->inp_count; | |||||
_resamp->process (); | |||||
n -= _resamp->inp_count; | |||||
_audioq->rd_commit (n); | |||||
} | |||||
// Deinterleave _buff to outputs. | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
p = _buff + i; | |||||
q = out [i]; | |||||
for (j = 0; j < _bsize; j++) q [j] = p [j * _nchan]; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Deinterleave audio queue to outputs. | |||||
// The while loop takes care of wraparound. | |||||
while (nframes) | |||||
{ | |||||
p = _audioq->rd_datap (); | |||||
n = _audioq->rd_linav (); | |||||
if (n > nframes) n = nframes; | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
q = out [i]; | |||||
for (j = 0; j < n; j++) q [j] = p [j * _nchan]; | |||||
out [i] += n; | |||||
p += 1; | |||||
} | |||||
_audioq->rd_commit (n); | |||||
nframes -= n; | |||||
} | |||||
} | |||||
_bstat = _audioq->rd_avail (); | |||||
} | |||||
void Jackclient::silence (int nframes) | |||||
{ | |||||
int i; | |||||
float *q; | |||||
// Write silence to all jack ports. | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
q = (float *)(jack_port_get_buffer (_ports [i], nframes)); | |||||
memset (q, 0, nframes * sizeof (float)); | |||||
} | |||||
} | |||||
void Jackclient::sendinfo (int state, double error, double ratio) | |||||
{ | |||||
Jdata *J; | |||||
if (_infoq->wr_avail ()) | |||||
{ | |||||
J = _infoq->wr_datap (); | |||||
J->_state = state; | |||||
J->_error = error; | |||||
J->_ratio = ratio; | |||||
J->_bstat = _bstat; | |||||
_infoq->wr_commit (); | |||||
} | |||||
} | |||||
void Jackclient::jack_freewheel (int state) | |||||
{ | |||||
_freew = state ? true : false; | |||||
if (_freew) initwait (_ppsec / 4); | |||||
} | |||||
void Jackclient::jack_latency (jack_latency_callback_mode_t jlcm) | |||||
{ | |||||
jack_latency_range_t R; | |||||
int i; | |||||
if (_state < WAIT) return; | |||||
if (_mode == PLAY) | |||||
{ | |||||
if (jlcm != JackPlaybackLatency) return; | |||||
R.min = R.max = (int)(_delay / _ratio) + _ltcor; | |||||
} | |||||
else | |||||
{ | |||||
if (jlcm != JackCaptureLatency) return; | |||||
R.min = R.max = (int)(_delay * _ratio) + _ltcor; | |||||
} | |||||
for (i = 0; i < _nchan; i++) | |||||
{ | |||||
jack_port_set_latency_range (_ports [i], jlcm, &R); | |||||
} | |||||
} | |||||
int Jackclient::jack_process (int nframes) | |||||
{ | |||||
int dk, n; | |||||
Adata *D; | |||||
jack_time_t t0, t1; | |||||
jack_nframes_t ft; | |||||
float us; | |||||
double tj, err, d1, d2, rd; | |||||
// Buffer size change or other evil. | |||||
if (_state == TERM) | |||||
{ | |||||
sendinfo (TERM, 0, 0); | |||||
return 0; | |||||
} | |||||
// Skip cylce if ports may not yet exist. | |||||
if (_state < WAIT) return 0; | |||||
// Start synchronisation 1/2 second after entering | |||||
// the WAIT state. This delay allows the ALSA thread | |||||
// to restart cleanly if necessary. Disabled while | |||||
// freewheeling. | |||||
if (_state == WAIT) | |||||
{ | |||||
if (_freew) return 0; | |||||
if (_mode == CAPT) silence (nframes); | |||||
if (++_count == 0) initsync (); | |||||
else return 0; | |||||
} | |||||
// Get the start time of the current cycle. | |||||
jack_get_cycle_times (_client, &ft, &t0, &t1, &us); | |||||
tj = tjack (t0); | |||||
// Check for any skipped cycles. | |||||
if (_state >= SYNC1) | |||||
{ | |||||
dk = ft - _ft - _bsize; | |||||
if (_mode == PLAY) | |||||
{ | |||||
dk = (int)(dk * _ratio + 0.5); | |||||
_audioq->wr_commit (dk); | |||||
} | |||||
else | |||||
{ | |||||
dk = (int)(dk / _ratio + 0.5); | |||||
_audioq->rd_commit (dk); | |||||
} | |||||
} | |||||
_ft = ft; | |||||
// Check if we have timing data from the ALSA thread. | |||||
n = _alsaq->rd_avail (); | |||||
// If the data queue is full restart synchronisation. | |||||
// This can happen e.g. on a jack engine timeout, or | |||||
// when too many cycles have been skipped. | |||||
if (n == _alsaq->size ()) | |||||
{ | |||||
initwait (_ppsec / 2); | |||||
return 0; | |||||
} | |||||
if (n) | |||||
{ | |||||
// Else move interval end to start, and update the | |||||
// interval end keeping only the most recent data. | |||||
if (_state < SYNC2) _state++; | |||||
_t_a0 = _t_a1; | |||||
_k_a0 = _k_a1; | |||||
while (_alsaq->rd_avail ()) | |||||
{ | |||||
D = _alsaq->rd_datap (); | |||||
// Restart synchronisation in case of | |||||
// an error in the ALSA interface. | |||||
if (D->_state == Alsathread::WAIT) | |||||
{ | |||||
initwait (_ppsec / 2); | |||||
return 0; | |||||
} | |||||
_t_a1 = D->_timer; | |||||
_k_a1 += D->_nsamp; | |||||
_alsaq->rd_commit (); | |||||
} | |||||
} | |||||
err = 0; | |||||
if (_state >= SYNC2) | |||||
{ | |||||
// Compute the delay error. | |||||
d1 = tjack_diff (tj, _t_a0); | |||||
d2 = tjack_diff (_t_a1, _t_a0); | |||||
rd = _resamp ? _resamp->inpdist () : 0.0; | |||||
if (_mode == PLAY) | |||||
{ | |||||
n = _audioq->nwr () - _k_a0; // Must be done as integer as both terms will overflow. | |||||
err = n - (_k_a1 - _k_a0) * d1 / d2 + rd * _ratio - _delay; | |||||
} | |||||
else | |||||
{ | |||||
n = _k_a0 - _audioq->nrd (); // Must be done as integer as both terms will overflow. | |||||
err = n + (_k_a1 - _k_a0) * d1 / d2 + rd - _delay ; | |||||
} | |||||
n = (int)(floor (err + 0.5)); | |||||
if (_state == SYNC2) | |||||
{ | |||||
// We have the first delay error value. Adjust the audio | |||||
// queue to obtain the actually wanted delay, and start | |||||
// tracking. | |||||
if (_mode == PLAY) _audioq->wr_commit (-n); | |||||
else _audioq->rd_commit (n); | |||||
err -= n; | |||||
setloop (1.0); | |||||
_state = PROC1; | |||||
} | |||||
} | |||||
// Switch to lower bandwidth after 4 seconds. | |||||
if ((_state == PROC1) && (++_count == 4 * _ppsec)) | |||||
{ | |||||
_state = PROC2; | |||||
setloop (0.05); | |||||
} | |||||
if (_state >= PROC1) | |||||
{ | |||||
_z1 += _w0 * (_w1 * err - _z1); | |||||
_z2 += _w0 * (_z1 - _z2); | |||||
_z3 += _w2 * _z2; | |||||
// Check error conditions. | |||||
if (fabs (_z3) > 0.05) | |||||
{ | |||||
// Something is really wrong, wait 10 seconds then restart. | |||||
initwait (10 * _ppsec); | |||||
return 0; | |||||
} | |||||
// Run loop filter and set resample ratio. | |||||
if (_resamp) | |||||
{ | |||||
_rcorr = 1 - (_z2 + _z3); | |||||
if (_rcorr > 1.05) _rcorr = 1.05; | |||||
if (_rcorr < 0.95) _rcorr = 0.95; | |||||
_resamp->set_rratio (_rcorr); | |||||
} | |||||
sendinfo (_state, err, _rcorr); | |||||
// Resample and transfer between audio | |||||
// queue and jack ports. | |||||
if (_mode == PLAY) playback (nframes); | |||||
else capture (nframes); | |||||
} | |||||
else if (_mode == CAPT) silence (nframes); | |||||
return 0; | |||||
} |
@@ -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,333 @@ | |||||
// ---------------------------------------------------------------------------- | |||||
// | |||||
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org> | |||||
// | |||||
// This program is free software; you can redistribute it and/or modify | |||||
// it under the terms of the GNU General Public License as published by | |||||
// the Free Software Foundation; either version 3 of the License, or | |||||
// (at your option) any later version. | |||||
// | |||||
// This program is distributed in the hope that it will be useful, | |||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
// GNU General Public License for more details. | |||||
// | |||||
// You should have received a copy of the GNU General Public License | |||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
// | |||||
// ---------------------------------------------------------------------------- | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <ctype.h> | |||||
#include <stdio.h> | |||||
#include <signal.h> | |||||
#include "alsathread.h" | |||||
#include "jackclient.h" | |||||
#include "lfqueue.h" | |||||
#include "jack/control.h" | |||||
static const char *clopt = "hvLSj:d:r:p:n:c:Q:I:"; | |||||
static void help (void) | |||||
{ | |||||
jack_info ("%s-%s", APPNAME, VERSION); | |||||
jack_info ("(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>"); | |||||
jack_info ("Use ALSA capture device as a Jack client."); | |||||
jack_info ("Options:"); | |||||
jack_info (" -h Display this text"); | |||||
jack_info (" -j <jackname> Name as Jack client [%s]", APPNAME); | |||||
jack_info (" -d <device> ALSA capture device [none]"); | |||||
jack_info (" -r <rate> Sample rate [48000]"); | |||||
jack_info (" -p <period> Period size [256]"); | |||||
jack_info (" -n <nfrags> Number of fragments [2]"); | |||||
jack_info (" -c <nchannels> Number of channels [2]"); | |||||
jack_info (" -S Word clock sync, no resampling"); | |||||
jack_info (" -Q <quality> Resampling quality, 16..96 [auto]"); | |||||
jack_info (" -I <samples> Latency adjustment [0]"); | |||||
jack_info (" -L Force 16-bit and 2 channels [off]"); | |||||
jack_info (" -v Print tracing information [off]"); | |||||
} | |||||
class zita_a2j | |||||
{ | |||||
Lfq_int32 *commq; | |||||
Lfq_adata *alsaq; | |||||
Lfq_jdata *infoq; | |||||
Lfq_audio *audioq; | |||||
bool stop; | |||||
bool v_opt; | |||||
bool L_opt; | |||||
bool S_opt; | |||||
char *jname; | |||||
char *device; | |||||
int fsamp; | |||||
int bsize; | |||||
int nfrag; | |||||
int nchan; | |||||
int rqual; | |||||
int ltcor; | |||||
public: | |||||
zita_a2j() | |||||
{ | |||||
commq = new Lfq_int32(16); | |||||
alsaq = new Lfq_adata(256); | |||||
infoq = new Lfq_jdata(256); | |||||
audioq = 0; | |||||
stop = false; | |||||
v_opt = false; | |||||
L_opt = false; | |||||
S_opt = false; | |||||
jname = strdup(APPNAME); | |||||
device = 0; | |||||
fsamp = 48000; | |||||
bsize = 128; | |||||
nfrag = 2; | |||||
nchan = 2; | |||||
rqual = 0; | |||||
ltcor = 0; | |||||
A = 0; | |||||
C = 0; | |||||
J = 0; | |||||
} | |||||
private: | |||||
int procoptions (int ac, const char *av []) | |||||
{ | |||||
int k; | |||||
optind = 1; | |||||
opterr = 0; | |||||
while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1) | |||||
{ | |||||
if (optarg && (*optarg == '-')) | |||||
{ | |||||
jack_error (APPNAME ": Missing argument for '-%c' option.", k); | |||||
jack_error (APPNAME ": Use '-h' to see all options."); | |||||
return 1; | |||||
} | |||||
switch (k) | |||||
{ | |||||
case 'h' : help (); return 1; | |||||
case 'v' : v_opt = true; break; | |||||
case 'L' : L_opt = true; break; | |||||
case 'S' : S_opt = true; break; | |||||
case 'j' : jname = optarg; break; | |||||
case 'd' : device = optarg; break; | |||||
case 'r' : fsamp = atoi (optarg); break; | |||||
case 'p' : bsize = atoi (optarg); break; | |||||
case 'n' : nfrag = atoi (optarg); break; | |||||
case 'c' : nchan = atoi (optarg); break; | |||||
case 'Q' : rqual = atoi (optarg); break; | |||||
case 'I' : ltcor = atoi (optarg); break; | |||||
case '?': | |||||
if (optopt != ':' && strchr (clopt, optopt)) | |||||
{ | |||||
jack_error (APPNAME ": Missing argument for '-%c' option.", optopt); | |||||
} | |||||
else if (isprint (optopt)) | |||||
{ | |||||
jack_error (APPNAME ": Unknown option '-%c'.", optopt); | |||||
} | |||||
else | |||||
{ | |||||
jack_error (APPNAME ": Unknown option character '0x%02x'.", optopt & 255); | |||||
} | |||||
jack_error (APPNAME ": Use '-h' to see all options."); | |||||
return 1; | |||||
default: | |||||
return 1; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
int parse_options (const char* load_init) | |||||
{ | |||||
int argsz; | |||||
int argc = 0; | |||||
const char** argv; | |||||
char* args = strdup (load_init); | |||||
char* token; | |||||
char* ptr = args; | |||||
char* savep; | |||||
if (!load_init) { | |||||
return 0; | |||||
} | |||||
argsz = 8; /* random guess at "maxargs" */ | |||||
argv = (const char **) malloc (sizeof (char *) * argsz); | |||||
argv[argc++] = APPNAME; | |||||
while (1) { | |||||
if ((token = strtok_r (ptr, " ", &savep)) == NULL) { | |||||
break; | |||||
} | |||||
if (argc == argsz) { | |||||
argsz *= 2; | |||||
argv = (const char **) realloc (argv, sizeof (char *) * argsz); | |||||
} | |||||
argv[argc++] = token; | |||||
ptr = NULL; | |||||
} | |||||
return procoptions (argc, argv); | |||||
} | |||||
void printinfo (void) | |||||
{ | |||||
int n, k; | |||||
double e, r; | |||||
Jdata *J; | |||||
n = 0; | |||||
k = 99999; | |||||
e = r = 0; | |||||
while (infoq->rd_avail ()) | |||||
{ | |||||
J = infoq->rd_datap (); | |||||
if (J->_state == Jackclient::TERM) | |||||
{ | |||||
jack_error (APPNAME ": Fatal error condition, terminating."); | |||||
stop = true; | |||||
return; | |||||
} | |||||
else if (J->_state == Jackclient::WAIT) | |||||
{ | |||||
jack_info (APPNAME ": Detected excessive timing errors, waiting 10 seconds."); | |||||
n = 0; | |||||
} | |||||
else if (J->_state == Jackclient::SYNC0) | |||||
{ | |||||
jack_info (APPNAME ": Starting synchronisation."); | |||||
} | |||||
else if (v_opt) | |||||
{ | |||||
n++; | |||||
e += J->_error; | |||||
r += J->_ratio; | |||||
if (J->_bstat < k) k = J->_bstat; | |||||
} | |||||
infoq->rd_commit (); | |||||
} | |||||
if (n) jack_info (APPNAME ": %8.3lf %10.6lf %5d", e / n, r / n, k); | |||||
} | |||||
Alsa_pcmi *A; | |||||
Alsathread *C; | |||||
Jackclient *J; | |||||
public: | |||||
int | |||||
jack_initialize (jack_client_t* client, const char* load_init) | |||||
{ | |||||
int k, k_del, opts; | |||||
double t_jack; | |||||
double t_alsa; | |||||
double t_del; | |||||
if (parse_options (load_init)) { | |||||
jack_error (APPNAME ": parse options failed"); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (device == 0) | |||||
{ | |||||
help (); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (rqual < 16) rqual = 16; | |||||
if (rqual > 96) rqual = 96; | |||||
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1)) | |||||
{ | |||||
jack_error (APPNAME ": Illegal parameter value(s)."); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
opts = 0; | |||||
if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL; | |||||
if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH; | |||||
A = new Alsa_pcmi (0, device, 0, fsamp, bsize, nfrag, opts); | |||||
if (A->state ()) | |||||
{ | |||||
jack_error (APPNAME ": Can't open ALSA capture device '%s'.", device); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (v_opt) A->printinfo (); | |||||
if (nchan > A->ncapt ()) | |||||
{ | |||||
nchan = A->ncapt (); | |||||
jack_error (APPNAME ": Warning: only %d channels are available.", nchan); | |||||
} | |||||
C = new Alsathread (A, Alsathread::CAPT); | |||||
J = new Jackclient (client, 0, Jackclient::CAPT, nchan, S_opt, this); | |||||
usleep (100000); | |||||
t_alsa = (double) bsize / fsamp; | |||||
if (t_alsa < 1e-3) t_alsa = 1e-3; | |||||
t_jack = (double) J->bsize () / J->fsamp (); | |||||
t_del = t_alsa + t_jack; | |||||
k_del = (int)(t_del * fsamp); | |||||
for (k = 256; k < 2 * k_del; k *= 2); | |||||
audioq = new Lfq_audio (k, nchan); | |||||
if (rqual == 0) | |||||
{ | |||||
k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp (); | |||||
if (k < 44100) k = 44100; | |||||
rqual = (int)((6.7 * k) / (k - 38000)); | |||||
} | |||||
if (rqual < 16) rqual = 16; | |||||
if (rqual > 96) rqual = 96; | |||||
C->start (audioq, commq, alsaq, J->rprio () + 10); | |||||
J->start (audioq, commq, alsaq, infoq, J->fsamp () / (double) fsamp, k_del, ltcor, rqual); | |||||
return 0; | |||||
} | |||||
void jack_finish (void* arg) | |||||
{ | |||||
commq->wr_int32 (Alsathread::TERM); | |||||
usleep (100000); | |||||
delete C; | |||||
delete A; | |||||
delete J; | |||||
delete audioq; | |||||
} | |||||
}; | |||||
extern "C" { | |||||
int | |||||
jack_initialize (jack_client_t* client, const char* load_init) | |||||
{ | |||||
zita_a2j *c = new zita_a2j(); | |||||
return c->jack_initialize(client, load_init); | |||||
} | |||||
void jack_finish (void* arg) | |||||
{ | |||||
if (!arg) return; | |||||
Jackclient *J = (Jackclient *)arg; | |||||
zita_a2j *c = (zita_a2j *)J->getarg(); | |||||
c->jack_finish(arg); | |||||
delete c; | |||||
} | |||||
} /* extern "C" */ |
@@ -0,0 +1,331 @@ | |||||
// ---------------------------------------------------------------------------- | |||||
// | |||||
// Copyright (C) 2012 Fons Adriaensen <fons@linuxaudio.org> | |||||
// | |||||
// This program is free software; you can redistribute it and/or modify | |||||
// it under the terms of the GNU General Public License as published by | |||||
// the Free Software Foundation; either version 3 of the License, or | |||||
// (at your option) any later version. | |||||
// | |||||
// This program is distributed in the hope that it will be useful, | |||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
// GNU General Public License for more details. | |||||
// | |||||
// You should have received a copy of the GNU General Public License | |||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
// | |||||
// ---------------------------------------------------------------------------- | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <ctype.h> | |||||
#include <stdio.h> | |||||
#include <signal.h> | |||||
#include "alsathread.h" | |||||
#include "jackclient.h" | |||||
#include "lfqueue.h" | |||||
#include "jack/control.h" | |||||
static const char *clopt = "hvLSj:d:r:p:n:c:Q:O:"; | |||||
static void help (void) | |||||
{ | |||||
jack_info ("%s-%s", APPNAME, VERSION); | |||||
jack_info ("(C) 2012-2018 Fons Adriaensen <fons@linuxaudio.org>"); | |||||
jack_info ("Use ALSA playback device as a Jack client."); | |||||
jack_info ("Options:"); | |||||
jack_info (" -h Display this text"); | |||||
jack_info (" -j <jackname> Name as Jack client [%s]", APPNAME); | |||||
jack_info (" -d <device> ALSA playback device [none]"); | |||||
jack_info (" -r <rate> Sample rate [48000]"); | |||||
jack_info (" -p <period> Period size [256]"); | |||||
jack_info (" -n <nfrags> Number of fragments [2]"); | |||||
jack_info (" -c <nchannels> Number of channels [2]"); | |||||
jack_info (" -S Word clock sync, no resampling"); | |||||
jack_info (" -Q <quality> Resampling quality, 16..96 [auto]"); | |||||
jack_info (" -O <samples> Latency adjustment [0]"); | |||||
jack_info (" -L Force 16-bit and 2 channels [off]"); | |||||
jack_info (" -v Print tracing information [off]"); | |||||
} | |||||
class zita_j2a | |||||
{ | |||||
Lfq_int32 *commq; | |||||
Lfq_adata *alsaq; | |||||
Lfq_jdata *infoq; | |||||
Lfq_audio *audioq; | |||||
bool stop; | |||||
bool v_opt; | |||||
bool L_opt; | |||||
bool S_opt; | |||||
char *jname; | |||||
char *device; | |||||
int fsamp; | |||||
int bsize; | |||||
int nfrag; | |||||
int nchan; | |||||
int rqual; | |||||
int ltcor; | |||||
public: | |||||
zita_j2a() | |||||
{ | |||||
commq = new Lfq_int32(16); | |||||
alsaq = new Lfq_adata(256); | |||||
infoq = new Lfq_jdata(256); | |||||
audioq = 0; | |||||
stop = false; | |||||
v_opt = false; | |||||
L_opt = false; | |||||
S_opt = false; | |||||
jname = strdup(APPNAME); | |||||
device = 0; | |||||
fsamp = 48000; | |||||
bsize = 128; | |||||
nfrag = 2; | |||||
nchan = 2; | |||||
rqual = 0; | |||||
ltcor = 0; | |||||
A = 0; | |||||
P = 0; | |||||
J = 0; | |||||
} | |||||
private: | |||||
int procoptions (int ac, const char *av []) | |||||
{ | |||||
int k; | |||||
optind = 1; | |||||
opterr = 0; | |||||
while ((k = getopt (ac, (char **) av, (char *) clopt)) != -1) | |||||
{ | |||||
if (optarg && (*optarg == '-')) | |||||
{ | |||||
jack_error (APPNAME ": Missing argument for '-%c' option.", k); | |||||
jack_error (APPNAME ": Use '-h' to see all options."); | |||||
return 1; | |||||
} | |||||
switch (k) | |||||
{ | |||||
case 'h' : help (); return 1; | |||||
case 'v' : v_opt = true; break; | |||||
case 'L' : L_opt = true; break; | |||||
case 'S' : S_opt = true; break; | |||||
case 'j' : jname = optarg; break; | |||||
case 'd' : device = optarg; break; | |||||
case 'r' : fsamp = atoi (optarg); break; | |||||
case 'p' : bsize = atoi (optarg); break; | |||||
case 'n' : nfrag = atoi (optarg); break; | |||||
case 'c' : nchan = atoi (optarg); break; | |||||
case 'Q' : rqual = atoi (optarg); break; | |||||
case 'O' : ltcor = atoi (optarg); break; | |||||
case '?': | |||||
if (optopt != ':' && strchr (clopt, optopt)) | |||||
{ | |||||
jack_error (APPNAME ": Missing argument for '-%c' option.", optopt); | |||||
} | |||||
else if (isprint (optopt)) | |||||
{ | |||||
jack_error (APPNAME ": Unknown option '-%c'.", optopt); | |||||
} | |||||
else | |||||
{ | |||||
jack_error (APPNAME ": Unknown option character '0x%02x'.", optopt & 255); | |||||
} | |||||
jack_error (APPNAME ": Use '-h' to see all options."); | |||||
return 1; | |||||
default: | |||||
return 1; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
int parse_options (const char* load_init) | |||||
{ | |||||
int argsz; | |||||
int argc = 0; | |||||
const char** argv; | |||||
char* args = strdup (load_init); | |||||
char* token; | |||||
char* ptr = args; | |||||
char* savep; | |||||
if (!load_init) { | |||||
return 0; | |||||
} | |||||
argsz = 8; /* random guess at "maxargs" */ | |||||
argv = (const char **) malloc (sizeof (char *) * argsz); | |||||
argv[argc++] = APPNAME; | |||||
while (1) { | |||||
if ((token = strtok_r (ptr, " ", &savep)) == NULL) { | |||||
break; | |||||
} | |||||
if (argc == argsz) { | |||||
argsz *= 2; | |||||
argv = (const char **) realloc (argv, sizeof (char *) * argsz); | |||||
} | |||||
argv[argc++] = token; | |||||
ptr = NULL; | |||||
} | |||||
return procoptions (argc, argv); | |||||
} | |||||
void printinfo (void) | |||||
{ | |||||
int n, k; | |||||
double e, r; | |||||
Jdata *J; | |||||
n = 0; | |||||
k = 99999; | |||||
e = r = 0; | |||||
while (infoq->rd_avail ()) | |||||
{ | |||||
J = infoq->rd_datap (); | |||||
if (J->_state == Jackclient::TERM) | |||||
{ | |||||
jack_info (APPNAME ": Fatal error condition, terminating."); | |||||
stop = true; | |||||
return; | |||||
} | |||||
else if (J->_state == Jackclient::WAIT) | |||||
{ | |||||
jack_info (APPNAME ": Detected excessive timing errors, waiting 10 seconds."); | |||||
n = 0; | |||||
} | |||||
else if (J->_state == Jackclient::SYNC0) | |||||
{ | |||||
jack_info (APPNAME ": Starting synchronisation."); | |||||
} | |||||
else if (v_opt) | |||||
{ | |||||
n++; | |||||
e += J->_error; | |||||
r += J->_ratio; | |||||
if (J->_bstat < k) k = J->_bstat; | |||||
} | |||||
infoq->rd_commit (); | |||||
} | |||||
if (n) jack_info ("%8.3lf %10.6lf %5d", e / n, r / n, k); | |||||
} | |||||
Alsa_pcmi *A; | |||||
Alsathread *P; | |||||
Jackclient *J; | |||||
public: | |||||
int jack_initialize (jack_client_t* client, const char* load_init) | |||||
{ | |||||
int k, k_del, opts; | |||||
double t_jack; | |||||
double t_alsa; | |||||
double t_del; | |||||
if (parse_options (load_init)) { | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (device == 0) | |||||
{ | |||||
help (); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (rqual < 16) rqual = 16; | |||||
if (rqual > 96) rqual = 96; | |||||
if ((fsamp < 8000) || (bsize < 16) || (nfrag < 2) || (nchan < 1)) | |||||
{ | |||||
jack_error (APPNAME ": Illegal parameter value(s)."); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
opts = 0; | |||||
if (v_opt) opts |= Alsa_pcmi::DEBUG_ALL; | |||||
if (L_opt) opts |= Alsa_pcmi::FORCE_16B | Alsa_pcmi::FORCE_2CH; | |||||
A = new Alsa_pcmi (device, 0, 0, fsamp, bsize, nfrag, opts); | |||||
if (A->state ()) | |||||
{ | |||||
jack_error (APPNAME ": Can't open ALSA playback device '%s'.", device); | |||||
delete this; | |||||
return 1; | |||||
} | |||||
if (v_opt) A->printinfo (); | |||||
if (nchan > A->nplay ()) | |||||
{ | |||||
nchan = A->nplay (); | |||||
jack_error (APPNAME ": Warning: only %d channels are available.", nchan); | |||||
} | |||||
P = new Alsathread (A, Alsathread::PLAY); | |||||
J = new Jackclient (client, 0, Jackclient::PLAY, nchan, S_opt, this); | |||||
usleep (100000); | |||||
t_alsa = (double) bsize / fsamp; | |||||
if (t_alsa < 1e-3) t_alsa = 1e-3; | |||||
t_jack = (double) J->bsize () / J->fsamp (); | |||||
t_del = t_alsa + t_jack; | |||||
k_del = (int)(t_del * fsamp); | |||||
for (k = 256; k < 2 * k_del; k *= 2); | |||||
audioq = new Lfq_audio (k, nchan); | |||||
if (rqual == 0) | |||||
{ | |||||
k = (fsamp < J->fsamp ()) ? fsamp : J->fsamp (); | |||||
if (k < 44100) k = 44100; | |||||
rqual = (int)((6.7 * k) / (k - 38000)); | |||||
} | |||||
if (rqual < 16) rqual = 16; | |||||
if (rqual > 96) rqual = 96; | |||||
P->start (audioq, commq, alsaq, J->rprio () + 10); | |||||
J->start (audioq, commq, alsaq, infoq, (double) fsamp / J->fsamp (), k_del, ltcor, rqual); | |||||
return 0; | |||||
} | |||||
void jack_finish (void* arg) | |||||
{ | |||||
commq->wr_int32 (Alsathread::TERM); | |||||
usleep (100000); | |||||
delete P; | |||||
delete A; | |||||
delete J; | |||||
delete audioq; | |||||
} | |||||
}; | |||||
extern "C" { | |||||
int | |||||
jack_initialize (jack_client_t* client, const char* load_init) | |||||
{ | |||||
zita_j2a *c = new zita_j2a(); | |||||
return c->jack_initialize(client, load_init); | |||||
} | |||||
void jack_finish (void* arg) | |||||
{ | |||||
if (!arg) return; | |||||
Jackclient *J = (Jackclient *)arg; | |||||
zita_j2a *c = (zita_j2a *)J->getarg(); | |||||
c->jack_finish(arg); | |||||
delete c; | |||||
} | |||||
} /* extern "C" */ |
@@ -11,7 +11,7 @@ import sys | |||||
from waflib import Logs, Options, Task, Utils | from waflib import Logs, Options, Task, Utils | ||||
from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext | from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext | ||||
VERSION='1.9.17' | |||||
VERSION='1.9.18' | |||||
APPNAME='jack' | APPNAME='jack' | ||||
JACK_API_VERSION = '0.1.0' | JACK_API_VERSION = '0.1.0' | ||||
@@ -174,6 +174,11 @@ def options(opt): | |||||
help='Use Berkeley DB (metadata)') | help='Use Berkeley DB (metadata)') | ||||
db.check(header_name='db.h') | db.check(header_name='db.h') | ||||
db.check(lib='db') | db.check(lib='db') | ||||
zalsa = opt.add_auto_option( | |||||
'zalsa', | |||||
help='Build internal zita-a2j/j2a client') | |||||
zalsa.check(lib='zita-alsa-pcmi') | |||||
zalsa.check(lib='zita-resampler') | |||||
# dbus options | # dbus options | ||||
opt.recurse('dbus') | opt.recurse('dbus') | ||||