Prototype using additional ALSA devices (w/resampling).
This commit is contained in:
parent
128a985361
commit
8337982766
@ -23,12 +23,17 @@
|
||||
|
||||
#include <glibmm.h>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include "alsa_audiobackend.h"
|
||||
|
||||
#include "pbd/compose.h"
|
||||
#include "pbd/convert.h"
|
||||
#include "pbd/error.h"
|
||||
#include "pbd/file_utils.h"
|
||||
#include "pbd/pthread_utils.h"
|
||||
|
||||
#include "ardour/filesystem_paths.h"
|
||||
#include "ardour/port_manager.h"
|
||||
#include "ardouralsautil/devicelist.h"
|
||||
@ -668,6 +673,11 @@ AlsaAudioBackend::set_midi_device_enabled (std::string const device, bool enable
|
||||
nfo->enabled = enable;
|
||||
|
||||
if (_run && prev_enabled != enable) {
|
||||
// XXX actually we should not change system-ports while running,
|
||||
// because iterators in main_process_thread will become invalid.
|
||||
//
|
||||
// Luckily the engine dialog does not call this while the engine is running,
|
||||
// This code is currently not used.
|
||||
if (enable) {
|
||||
// add ports for the given device
|
||||
register_system_midi_ports(device);
|
||||
@ -940,6 +950,34 @@ AlsaAudioBackend::_start (bool for_latency_measurement)
|
||||
return ProcessThreadStartError;
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (NULL != getenv ("ALSAEXT")) {
|
||||
boost::char_separator<char> sep (";");
|
||||
boost::tokenizer<boost::char_separator<char> > devs (std::string(getenv ("ALSAEXT")), sep);
|
||||
BOOST_FOREACH (const std::string& tmp, devs) {
|
||||
std::string dev (tmp);
|
||||
std::string::size_type n = dev.find ('@');
|
||||
unsigned int sr = _samplerate;
|
||||
unsigned int spp = _samples_per_period;
|
||||
unsigned int duplex = 3; // TODO parse 1: play, 2: capt, 3:both
|
||||
if (n != std::string::npos) {
|
||||
std::string opt (dev.substr (n + 1));
|
||||
sr = PBD::atoi (opt);
|
||||
dev = dev.substr (0, n);
|
||||
std::string::size_type n = opt.find ('/');
|
||||
if (n != std::string::npos) {
|
||||
spp = PBD::atoi (opt.substr (n + 1));
|
||||
}
|
||||
}
|
||||
if (add_slave (dev.c_str(), sr, spp, duplex)) {
|
||||
PBD::info << string_compose (_("ALSA slave '%1' added"), dev) << endmsg;
|
||||
} else {
|
||||
PBD::error << string_compose (_("ALSA failed to add '%1' as slave"), dev) << endmsg;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return NoError;
|
||||
}
|
||||
|
||||
@ -970,6 +1008,12 @@ AlsaAudioBackend::stop ()
|
||||
delete m;
|
||||
}
|
||||
|
||||
while (!_slaves.empty ()) {
|
||||
AudioSlave* s = _slaves.back ();
|
||||
_slaves.pop_back ();
|
||||
delete s;
|
||||
}
|
||||
|
||||
unregister_ports();
|
||||
delete _pcmi; _pcmi = 0;
|
||||
_midi_ins = _midi_outs = 0;
|
||||
@ -1819,8 +1863,14 @@ AlsaAudioBackend::main_process_thread ()
|
||||
{
|
||||
AudioEngine::thread_init_callback (this);
|
||||
_active = true;
|
||||
bool reset_dll = true;
|
||||
int last_n_periods = 0;
|
||||
_processed_samples = 0;
|
||||
|
||||
double dll_dt = (double) _samples_per_period / (double) _samplerate;
|
||||
double dll_w1 = 2 * M_PI * 0.1 * dll_dt;
|
||||
double dll_w2 = dll_w1 * dll_w1;
|
||||
|
||||
uint64_t clock1;
|
||||
_pcmi->pcm_start ();
|
||||
int no_proc_errors = 0;
|
||||
@ -1829,18 +1879,70 @@ AlsaAudioBackend::main_process_thread ()
|
||||
manager.registration_callback();
|
||||
manager.graph_order_callback();
|
||||
|
||||
const double sr_norm = 1e-6 * (double) _samplerate / (double)_samples_per_period;
|
||||
|
||||
while (_run) {
|
||||
long nr;
|
||||
bool xrun = false;
|
||||
bool drain_slaves = false;
|
||||
|
||||
if (_freewheeling != _freewheel) {
|
||||
_freewheel = _freewheeling;
|
||||
engine.freewheel_callback (_freewheel);
|
||||
for (AudioSlaves::iterator s = _slaves.begin (); s != _slaves.end (); ++s) {
|
||||
(*s)->freewheel (_freewheel);
|
||||
}
|
||||
if (!_freewheel) {
|
||||
_pcmi->pcm_stop ();
|
||||
_pcmi->pcm_start ();
|
||||
drain_slaves = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_freewheel) {
|
||||
nr = _pcmi->pcm_wait ();
|
||||
|
||||
/* update DLL */
|
||||
uint64_t clock0 = g_get_monotonic_time();
|
||||
if (reset_dll || last_n_periods != 1) {
|
||||
reset_dll = false;
|
||||
drain_slaves = true;
|
||||
dll_dt = 1e6 * (double) _samples_per_period / (double)_samplerate;
|
||||
_t0 = clock0;
|
||||
_t1 = clock0 + dll_dt;
|
||||
} else {
|
||||
const double er = clock0 - _t1;
|
||||
_t0 = _t1;
|
||||
_t1 = _t1 + dll_w1 * er + dll_dt;
|
||||
dll_dt += dll_w2 * er;
|
||||
}
|
||||
|
||||
for (AudioSlaves::iterator s = _slaves.begin (); s != _slaves.end (); ++s) {
|
||||
if ((*s)->dead) {
|
||||
continue;
|
||||
}
|
||||
if ((*s)->halt) {
|
||||
/* slave died, unregister its ports (not rt-safe, but no matter) */
|
||||
PBD::error << _("ALSA Slave device halted") << endmsg;
|
||||
for (std::vector<AlsaPort*>::const_iterator it = (*s)->inputs.begin (); it != (*s)->inputs.end (); ++it) {
|
||||
unregister_port (*it);
|
||||
}
|
||||
for (std::vector<AlsaPort*>::const_iterator it = (*s)->outputs.begin (); it != (*s)->outputs.end (); ++it) {
|
||||
unregister_port (*it);
|
||||
}
|
||||
(*s)->inputs.clear ();
|
||||
(*s)->outputs.clear ();
|
||||
(*s)->active = false;
|
||||
(*s)->dead = true;
|
||||
continue;
|
||||
}
|
||||
(*s)->active = (*s)->running () && (*s)->state () >= 0;
|
||||
if (!(*s)->active) {
|
||||
continue;
|
||||
}
|
||||
(*s)->cycle_start (_t0, (_t1 - _t0) * sr_norm, drain_slaves);
|
||||
}
|
||||
|
||||
if (_pcmi->state () > 0) {
|
||||
++no_proc_errors;
|
||||
xrun = true;
|
||||
@ -1858,6 +1960,7 @@ AlsaAudioBackend::main_process_thread ()
|
||||
break;
|
||||
}
|
||||
|
||||
last_n_periods = 0;
|
||||
while (nr >= (long)_samples_per_period && _freewheeling == _freewheel) {
|
||||
uint32_t i = 0;
|
||||
clock1 = g_get_monotonic_time();
|
||||
@ -1869,6 +1972,16 @@ AlsaAudioBackend::main_process_thread ()
|
||||
}
|
||||
_pcmi->capt_done (_samples_per_period);
|
||||
|
||||
for (AudioSlaves::iterator s = _slaves.begin (); s != _slaves.end (); ++s) {
|
||||
if (!(*s)->active) {
|
||||
continue;
|
||||
}
|
||||
i = 0;
|
||||
for (std::vector<AlsaPort*>::const_iterator it = (*s)->inputs.begin (); it != (*s)->inputs.end (); ++it, ++i) {
|
||||
(*s)->capt_chan (i, (float*)((*it)->get_buffer(_samples_per_period)), _samples_per_period);
|
||||
}
|
||||
}
|
||||
|
||||
/* de-queue incoming midi*/
|
||||
i = 0;
|
||||
for (std::vector<AlsaPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
|
||||
@ -1924,6 +2037,18 @@ AlsaAudioBackend::main_process_thread ()
|
||||
_pcmi->clear_chan (i, _samples_per_period);
|
||||
}
|
||||
_pcmi->play_done (_samples_per_period);
|
||||
|
||||
for (AudioSlaves::iterator s = _slaves.begin (); s != _slaves.end (); ++s) {
|
||||
if (!(*s)->active) {
|
||||
continue;
|
||||
}
|
||||
i = 0;
|
||||
for (std::vector<AlsaPort*>::const_iterator it = (*s)->outputs.begin (); it != (*s)->outputs.end (); ++it, ++i) {
|
||||
(*s)->play_chan (i, (float*)((*it)->get_buffer(_samples_per_period)), _samples_per_period);
|
||||
}
|
||||
(*s)->cycle_end ();
|
||||
}
|
||||
|
||||
nr -= _samples_per_period;
|
||||
_processed_samples += _samples_per_period;
|
||||
|
||||
@ -1931,10 +2056,12 @@ AlsaAudioBackend::main_process_thread ()
|
||||
_dsp_load_calc.set_start_timestamp_us (clock1);
|
||||
_dsp_load_calc.set_stop_timestamp_us (g_get_monotonic_time());
|
||||
_dsp_load = _dsp_load_calc.get_dsp_load ();
|
||||
++last_n_periods;
|
||||
}
|
||||
|
||||
if (xrun && (_pcmi->capt_xrun() > 0 || _pcmi->play_xrun() > 0)) {
|
||||
engine.Xrun ();
|
||||
reset_dll = true;
|
||||
#if 0
|
||||
fprintf(stderr, "ALSA x-run read: %.2f ms, write: %.2f ms\n",
|
||||
_pcmi->capt_xrun() * 1000.0, _pcmi->play_xrun() * 1000.0);
|
||||
@ -1980,6 +2107,7 @@ AlsaAudioBackend::main_process_thread ()
|
||||
}
|
||||
|
||||
_dsp_load = 1.0;
|
||||
reset_dll = true;
|
||||
Glib::usleep (100); // don't hog cpu
|
||||
}
|
||||
|
||||
@ -2021,6 +2149,119 @@ AlsaAudioBackend::main_process_thread ()
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
bool
|
||||
AlsaAudioBackend::add_slave (const char* device,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_spp,
|
||||
unsigned int duplex)
|
||||
{
|
||||
AudioSlave* s = new AudioSlave (device, duplex,
|
||||
_samplerate, _samples_per_period,
|
||||
slave_rate, slave_spp, 2);
|
||||
|
||||
if (s->state ()) {
|
||||
// TODO parse error status
|
||||
PBD::error << string_compose (_("Failed to create slave device '%1' error %2\n"), device, s->state ()) << endmsg;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0, n = 1; i < s->ncapt (); ++i) {
|
||||
char tmp[64];
|
||||
do {
|
||||
snprintf(tmp, sizeof(tmp), "extern:capture_%d", n);
|
||||
if (find_port (tmp)) {
|
||||
++n;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
|
||||
if (!p) goto errout;
|
||||
AlsaPort *ap = static_cast<AlsaPort*>(p);
|
||||
s->inputs.push_back (ap);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0, n = 1; i < s->nplay (); ++i) {
|
||||
char tmp[64];
|
||||
do {
|
||||
snprintf(tmp, sizeof(tmp), "extern:playback_%d", n);
|
||||
if (find_port (tmp)) {
|
||||
++n;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
PortHandle p = add_port(std::string(tmp), DataType::AUDIO, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
|
||||
if (!p) goto errout;
|
||||
AlsaPort *ap = static_cast<AlsaPort*>(p);
|
||||
s->outputs.push_back (ap);
|
||||
}
|
||||
|
||||
if (!s->start ()) {
|
||||
PBD::error << string_compose (_("Failed to start slave device '%1'\n"), device) << endmsg;
|
||||
goto errout;
|
||||
}
|
||||
s->UpdateLatency.connect_same_thread (s->latency_connection, boost::bind (&AlsaAudioBackend::update_latencies, this));
|
||||
_slaves.push_back (s);
|
||||
return true;
|
||||
|
||||
errout:
|
||||
delete s; // releases device
|
||||
return false;
|
||||
}
|
||||
|
||||
AlsaAudioBackend::AudioSlave::AudioSlave (
|
||||
const char* device,
|
||||
unsigned int duplex,
|
||||
unsigned int master_rate,
|
||||
unsigned int master_samples_per_period,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_samples_per_period,
|
||||
unsigned int periods_per_cycle)
|
||||
: AlsaDeviceReservation (device)
|
||||
, AlsaAudioSlave (
|
||||
(duplex & 1) ? device : NULL /* playback */,
|
||||
(duplex & 2) ? device : NULL /* capture */,
|
||||
master_rate, master_samples_per_period,
|
||||
slave_rate, slave_samples_per_period, periods_per_cycle)
|
||||
, active (false)
|
||||
, halt (false)
|
||||
, dead (false)
|
||||
{
|
||||
Halted.connect_same_thread (_halted_connection, boost::bind (&AudioSlave::halted, this));
|
||||
}
|
||||
|
||||
AlsaAudioBackend::AudioSlave::~AudioSlave ()
|
||||
{
|
||||
stop ();
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioBackend::AudioSlave::halted ()
|
||||
{
|
||||
// Note: Halted() is emitted from the Slave's process thread.
|
||||
release_device ();
|
||||
halt = true;
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioBackend::AudioSlave::update_latencies (uint32_t play, uint32_t capt)
|
||||
{
|
||||
LatencyRange lr;
|
||||
lr.min = lr.max = (capt);
|
||||
for (std::vector<AlsaPort*>::const_iterator it = inputs.begin (); it != inputs.end (); ++it) {
|
||||
(*it)->set_latency_range (lr, false);
|
||||
}
|
||||
|
||||
lr.min = lr.max = play;
|
||||
for (std::vector<AlsaPort*>::const_iterator it = outputs.begin (); it != outputs.end (); ++it) {
|
||||
(*it)->set_latency_range (lr, true);
|
||||
}
|
||||
printf (" ----- SLAVE LATENCY play=%d capt=%d\n", play, capt); // XXX DEBUG
|
||||
UpdateLatency (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "zita-alsa-pcmi.h"
|
||||
#include "alsa_rawmidi.h"
|
||||
#include "alsa_sequencer.h"
|
||||
#include "alsa_slave.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
@ -397,6 +398,9 @@ class AlsaAudioBackend : public AudioBackend {
|
||||
framecnt_t _processed_samples;
|
||||
pthread_t _main_thread;
|
||||
|
||||
/* DLL, track main process callback timing */
|
||||
double _t0, _t1;
|
||||
|
||||
/* process threads */
|
||||
static void* alsa_process_thread (void *);
|
||||
std::vector<pthread_t> _threads;
|
||||
@ -480,6 +484,46 @@ class AlsaAudioBackend : public AudioBackend {
|
||||
void update_systemic_audio_latencies ();
|
||||
void update_systemic_midi_latencies ();
|
||||
|
||||
/* additional re-sampled I/O */
|
||||
bool add_slave (const char* slave_device,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_spp,
|
||||
unsigned int duplex = 3);
|
||||
|
||||
class AudioSlave : public AlsaDeviceReservation, public AlsaAudioSlave {
|
||||
public:
|
||||
AudioSlave (
|
||||
const char* device,
|
||||
unsigned int duplex,
|
||||
unsigned int master_rate,
|
||||
unsigned int master_samples_per_period,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_samples_per_period,
|
||||
unsigned int periods_per_cycle);
|
||||
|
||||
~AudioSlave ();
|
||||
|
||||
bool active; // set in sync with process-cb
|
||||
bool halt;
|
||||
bool dead;
|
||||
|
||||
std::vector<AlsaPort *> inputs;
|
||||
std::vector<AlsaPort *> outputs;
|
||||
|
||||
PBD::Signal0<void> UpdateLatency;
|
||||
PBD::ScopedConnection latency_connection;
|
||||
|
||||
protected:
|
||||
void update_latencies (uint32_t, uint32_t);
|
||||
|
||||
private:
|
||||
PBD::ScopedConnection _halted_connection;
|
||||
void halted ();
|
||||
};
|
||||
|
||||
typedef std::vector<AudioSlave*> AudioSlaves;
|
||||
AudioSlaves _slaves;
|
||||
|
||||
}; // class AlsaAudioBackend
|
||||
|
||||
} // namespace
|
||||
|
523
libs/backends/alsa/alsa_slave.cc
Normal file
523
libs/backends/alsa/alsa_slave.cc
Normal file
@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Robin Gareus <robin@gareus.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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <glibmm.h>
|
||||
|
||||
#include "pbd/compose.h"
|
||||
#include "pbd/error.h"
|
||||
#include "pbd/pthread_utils.h"
|
||||
|
||||
#include "alsa_slave.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
AlsaAudioSlave::AlsaAudioSlave (
|
||||
const char *play_name,
|
||||
const char *capt_name,
|
||||
unsigned int master_rate,
|
||||
unsigned int master_samples_per_period,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_samples_per_period,
|
||||
unsigned int periods_per_cycle)
|
||||
: _pcmi (play_name, capt_name, 0, slave_rate, slave_samples_per_period, periods_per_cycle, 2, /* Alsa_pcmi::DEBUG_ALL */ 0)
|
||||
, _run (false)
|
||||
, _active (false)
|
||||
, _samples_since_dll_reset (0)
|
||||
, _ratio (1.0)
|
||||
, _slave_speed (1.0)
|
||||
, _draining (1)
|
||||
, _rb_capture (4 * /* AlsaAudioBackend::_max_buffer_size */ 8192 * _pcmi.ncapt ())
|
||||
, _rb_playback (4 * /* AlsaAudioBackend::_max_buffer_size */ 8192 * _pcmi.nplay ())
|
||||
, _samples_per_period (master_samples_per_period)
|
||||
, _capt_buff (0)
|
||||
, _play_buff (0)
|
||||
, _src_buff (0)
|
||||
{
|
||||
if (0 != _pcmi.state()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* from alsa-slave to master */
|
||||
_ratio = (double) master_rate / (double) _pcmi.fsamp();
|
||||
|
||||
#ifndef NDEBUG
|
||||
fprintf (stdout, " --[[ ALSA Slave %s/%s ratio: %.4f\n", play_name, capt_name, _ratio);
|
||||
_pcmi.printinfo ();
|
||||
fprintf (stdout, " --]]\n");
|
||||
#endif
|
||||
|
||||
_src_capt.setup (_ratio, _pcmi.ncapt (), /*quality*/ 32); // save capture to master
|
||||
_src_play.setup (1.0 / _ratio, _pcmi.nplay (), /*quality*/ 32); // master to slave play
|
||||
|
||||
_src_capt.set_rrfilt (100);
|
||||
_src_play.set_rrfilt (100);
|
||||
|
||||
_capt_buff = (float*) malloc (sizeof(float) * _pcmi.ncapt () * _samples_per_period);
|
||||
_play_buff = (float*) malloc (sizeof(float) * _pcmi.nplay () * _samples_per_period);
|
||||
_src_buff = (float*) malloc (sizeof(float) * std::max (_pcmi.nplay (), _pcmi.ncapt ()));
|
||||
}
|
||||
|
||||
AlsaAudioSlave::~AlsaAudioSlave ()
|
||||
{
|
||||
stop ();
|
||||
free (_capt_buff);
|
||||
free (_play_buff);
|
||||
free (_src_buff);
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioSlave::reset_resampler (ArdourZita::VResampler& src)
|
||||
{
|
||||
src.reset ();
|
||||
src.inp_count = src.inpsize () - 1;
|
||||
src.out_count = 200000;
|
||||
src.process ();
|
||||
}
|
||||
|
||||
bool
|
||||
AlsaAudioSlave::start ()
|
||||
{
|
||||
if (_run) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_run = true;
|
||||
if (pbd_realtime_pthread_create (PBD_SCHED_FIFO, -20, 100000,
|
||||
&_thread, _process_thread, this))
|
||||
{
|
||||
if (pthread_create (&_thread, NULL, _process_thread, this)) {
|
||||
_run = false;
|
||||
PBD::error << _("AlsaAudioBackend: failed to create slave process thread.") << endmsg;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int timeout = 5000;
|
||||
while (!_active && --timeout > 0) { Glib::usleep (1000); }
|
||||
|
||||
if (timeout == 0 || !_active) {
|
||||
_run = false;
|
||||
PBD::error << _("AlsaAudioBackend: failed to start slave process thread.") << endmsg;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioSlave::stop ()
|
||||
{
|
||||
void *status;
|
||||
if (!_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
_run = false;
|
||||
if (pthread_join (_thread, &status)) {
|
||||
PBD::error << _("AlsaAudioBackend: slave failed to terminate properly.") << endmsg;
|
||||
}
|
||||
_pcmi.pcm_stop ();
|
||||
}
|
||||
|
||||
void*
|
||||
AlsaAudioSlave::_process_thread (void* arg)
|
||||
{
|
||||
AlsaAudioSlave* aas = static_cast<AlsaAudioSlave*> (arg);
|
||||
return aas->process_thread ();
|
||||
}
|
||||
|
||||
void*
|
||||
AlsaAudioSlave::process_thread ()
|
||||
{
|
||||
_active = true;
|
||||
|
||||
bool reset_dll = true;
|
||||
int last_n_periods = 0;
|
||||
int no_proc_errors = 0;
|
||||
const int bailout = 2 * _pcmi.fsamp () / _pcmi.fsize ();
|
||||
|
||||
double dll_dt = (double) _pcmi.fsize () / (double)_pcmi.fsamp ();
|
||||
double dll_w1 = 2 * M_PI * 0.1 * dll_dt;
|
||||
double dll_w2 = dll_w1 * dll_w1;
|
||||
|
||||
const double sr_norm = 1e-6 * (double) _pcmi.fsamp () / (double) _pcmi.fsize ();
|
||||
|
||||
_pcmi.pcm_start ();
|
||||
|
||||
while (_run) {
|
||||
bool xrun = false;
|
||||
long nr = _pcmi.pcm_wait ();
|
||||
|
||||
/* update DLL */
|
||||
uint64_t clock0 = g_get_monotonic_time();
|
||||
|
||||
if (reset_dll || last_n_periods != 1) {
|
||||
reset_dll = false;
|
||||
dll_dt = 1e6 * (double) _pcmi.fsize () / (double) _pcmi.fsamp();
|
||||
_t0 = clock0;
|
||||
_t1 = clock0 + dll_dt;
|
||||
_samples_since_dll_reset = 0;
|
||||
} else {
|
||||
const double er = clock0 - _t1;
|
||||
_t0 = _t1;
|
||||
_t1 = _t1 + dll_w1 * er + dll_dt;
|
||||
dll_dt += dll_w2 * er;
|
||||
_samples_since_dll_reset += _pcmi.fsize ();
|
||||
}
|
||||
|
||||
_slave_speed = (_t1 - _t0) * sr_norm; // XXX atomic
|
||||
|
||||
if (_pcmi.state () > 0) {
|
||||
++no_proc_errors;
|
||||
xrun = true;
|
||||
}
|
||||
|
||||
if (_pcmi.state () < 0) {
|
||||
PBD::error << _("AlsaAudioBackend: Slave I/O error.") << endmsg;
|
||||
break;
|
||||
}
|
||||
|
||||
if (no_proc_errors > bailout) {
|
||||
PBD::error << _("AlsaAudioBackend: Slave terminated due to continuous x-runs.") << endmsg;
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t spp = _pcmi.fsize ();
|
||||
const bool drain = g_atomic_int_get (&_draining);
|
||||
last_n_periods = 0;
|
||||
|
||||
while (nr >= (long)spp) {
|
||||
no_proc_errors = 0;
|
||||
|
||||
_pcmi.capt_init (spp);
|
||||
if (drain) {
|
||||
/* do nothing */
|
||||
} else if (_rb_capture.write_space () >= _pcmi.ncapt () * spp) {
|
||||
#if 0 // failsafe: write interleave sample by sample
|
||||
for (uint32_t s = 0; s < spp; ++s) {
|
||||
for (uint32_t c = 0; c < _pcmi.ncapt (); ++c) {
|
||||
float d;
|
||||
_pcmi.capt_chan (c, &d, 1);
|
||||
_rb_capture.write (&d, 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
unsigned int nchn = _pcmi.ncapt ();
|
||||
PBD::RingBuffer<float>::rw_vector vec;
|
||||
_rb_capture.get_write_vector (&vec);
|
||||
if (vec.len[0] >= nchn * spp) {
|
||||
for (uint32_t c = 0; c < nchn; ++c) {
|
||||
_pcmi.capt_chan (c, vec.buf[0] + c, spp, nchn);
|
||||
}
|
||||
} else {
|
||||
uint32_t c;
|
||||
/* first copy continuous area */
|
||||
uint32_t k = vec.len[0] / nchn;
|
||||
for (c = 0; c < nchn; ++c) {
|
||||
_pcmi.capt_chan (c, vec.buf[0] + c, k, nchn);
|
||||
}
|
||||
|
||||
/* possible samples at end of first buffer chunk,
|
||||
* incomplete audio-frame */
|
||||
uint32_t s = vec.len[0] - k * nchn;
|
||||
assert (s < nchn);
|
||||
|
||||
for (c = 0; c < s; ++c) {
|
||||
_pcmi.capt_chan (c, vec.buf[0] + k * nchn + c, 1, nchn);
|
||||
}
|
||||
/* cont'd audio-frame at second ringbuffer chunk */
|
||||
for (; c < nchn; ++c) {
|
||||
_pcmi.capt_chan (c, vec.buf[1] + c - s, spp - k, nchn);
|
||||
}
|
||||
/* remaining data in 2nd area */
|
||||
for (c = 0; c < s; ++c) {
|
||||
_pcmi.capt_chan (c, vec.buf[1] + c + nchn - s, spp - k, nchn);
|
||||
}
|
||||
}
|
||||
_rb_capture.increment_write_idx (spp * nchn);
|
||||
#endif
|
||||
} else {
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
}
|
||||
_pcmi.capt_done (spp);
|
||||
|
||||
|
||||
if (drain) {
|
||||
_rb_playback.increment_read_idx (_rb_playback.read_space ());
|
||||
}
|
||||
|
||||
_pcmi.play_init (spp);
|
||||
if (_rb_playback.read_space () >= _pcmi.nplay () * spp) {
|
||||
#if 0 // failsafe: read sample by sample de-interleave
|
||||
for (uint32_t s = 0; s < spp; ++s) {
|
||||
for (uint32_t c = 0; c < _pcmi.nplay (); ++c) {
|
||||
float d;
|
||||
_rb_playback.read (&d, 1);
|
||||
_pcmi.play_chan (c, (const float*)&d, 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
unsigned int nchn = _pcmi.nplay ();
|
||||
PBD::RingBuffer<float>::rw_vector vec;
|
||||
_rb_playback.get_read_vector (&vec);
|
||||
if (vec.len[0] >= nchn * spp) {
|
||||
for (uint32_t c = 0; c < nchn; ++c) {
|
||||
_pcmi.play_chan (c, vec.buf[0] + c, spp, nchn);
|
||||
}
|
||||
} else {
|
||||
uint32_t c;
|
||||
uint32_t k = vec.len[0] / nchn;
|
||||
for (c = 0; c < nchn; ++c) {
|
||||
_pcmi.play_chan (c, vec.buf[0] + c, k, nchn);
|
||||
}
|
||||
|
||||
uint32_t s = vec.len[0] - k * nchn;
|
||||
assert (s < nchn);
|
||||
|
||||
for (c = 0; c < s; ++c) {
|
||||
_pcmi.play_chan (c, vec.buf[0] + k * nchn + c, 1, nchn);
|
||||
}
|
||||
|
||||
for (; c < nchn; ++c) {
|
||||
_pcmi.play_chan (c, vec.buf[1] + c - s, spp - k, nchn);
|
||||
}
|
||||
for (c = 0; c < s; ++c) {
|
||||
_pcmi.play_chan (c, vec.buf[1] + c + nchn - s, spp - k, nchn);
|
||||
}
|
||||
}
|
||||
_rb_playback.increment_read_idx (spp * nchn);
|
||||
#endif
|
||||
} else {
|
||||
if (!drain) {
|
||||
printf ("Slave Process: Playback Buffer Underflow, have %u want %lu\n", _rb_playback.read_space (), _pcmi.nplay () * spp); // XXX DEBUG
|
||||
_play_latency += spp * _ratio;
|
||||
update_latencies (_play_latency, _capt_latency);
|
||||
}
|
||||
/* silence outputs */
|
||||
for (uint32_t c = 0; c < _pcmi.nplay (); ++c) {
|
||||
_pcmi.clear_chan (c, spp);
|
||||
}
|
||||
}
|
||||
_pcmi.play_done (spp);
|
||||
|
||||
nr -= spp;
|
||||
++last_n_periods;
|
||||
}
|
||||
|
||||
if (xrun && (_pcmi.capt_xrun() > 0 || _pcmi.play_xrun() > 0)) {
|
||||
reset_dll = true;
|
||||
_samples_since_dll_reset = 0;
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_pcmi.pcm_stop ();
|
||||
_active = false;
|
||||
|
||||
if (_run) {
|
||||
Halted (); /* Emit Signal */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioSlave::cycle_start (double tme, double mst_speed, bool drain)
|
||||
{
|
||||
//printf ("SRC %f / %f = %f\n", mst_speed, _slave_speed, mst_speed / _slave_speed);
|
||||
//printf ("DRIFT (mst) %11.1f - (slv) %11.1f = %.1f us = %.1f spl\n", tme, _t0, tme - _t0, (tme - _t0) * _pcmi.fsamp () * 1e-6);
|
||||
//printf ("Slave capt: %u play: %u\n", _rb_capture.read_space (), _rb_playback.read_space ());
|
||||
|
||||
// TODO LPF filter ratios, atomic _slave_speed
|
||||
const double slave_speed = _slave_speed;
|
||||
|
||||
_src_capt.set_rratio (mst_speed / slave_speed);
|
||||
_src_play.set_rratio (slave_speed / mst_speed);
|
||||
|
||||
memset (_capt_buff, 0, sizeof(float) * _pcmi.ncapt () * _samples_per_period);
|
||||
|
||||
if (drain) {
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_atomic_int_get (&_draining)) {
|
||||
_rb_capture.increment_read_idx (_rb_capture.read_space());
|
||||
return;
|
||||
}
|
||||
|
||||
/* resample slave capture data from ringbuffer */
|
||||
unsigned int nchn = _pcmi.ncapt ();
|
||||
_src_capt.out_count = _samples_per_period;
|
||||
_src_capt.out_data = _capt_buff;
|
||||
|
||||
/* estimate required samples */
|
||||
const double rratio = _ratio * mst_speed / slave_speed;
|
||||
if (_rb_capture.read_space() < ceil (nchn * _samples_per_period / rratio)) {
|
||||
printf ("--- UNDERFLOW --- have %u want %.1f\n", _rb_capture.read_space(), ceil (nchn * _samples_per_period / rratio)); // XXX DEBUG
|
||||
_capt_latency += _samples_per_period;
|
||||
update_latencies (_play_latency, _capt_latency);
|
||||
return;
|
||||
}
|
||||
|
||||
bool underflow = false;
|
||||
while (_src_capt.out_count && _active) {
|
||||
if (_rb_capture.read_space() < nchn) {
|
||||
underflow = true;
|
||||
break;
|
||||
}
|
||||
unsigned int n;
|
||||
PBD::RingBuffer<float>::rw_vector vec;
|
||||
_rb_capture.get_read_vector (&vec);
|
||||
if (vec.len[0] < nchn) {
|
||||
_rb_capture.read (_src_buff, nchn);
|
||||
_src_capt.inp_count = 1;
|
||||
_src_capt.inp_data = _src_buff;
|
||||
_src_capt.process ();
|
||||
} else {
|
||||
_src_capt.inp_count = n = vec.len[0] / nchn;
|
||||
_src_capt.inp_data = vec.buf[0];
|
||||
_src_capt.process ();
|
||||
n -= _src_capt.inp_count;
|
||||
_rb_capture.increment_read_idx (n * _pcmi.ncapt ());
|
||||
}
|
||||
}
|
||||
|
||||
if (underflow) {
|
||||
std::cerr << "ALSA Slave: Capture Ringbuffer Underflow\n"; // XXX
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
}
|
||||
|
||||
if (!_active || underflow) {
|
||||
memset (_capt_buff, 0, sizeof(float) * _pcmi.ncapt () * _samples_per_period);
|
||||
}
|
||||
|
||||
memset (_play_buff, 0, sizeof(float) * _pcmi.nplay () * _samples_per_period);
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioSlave::cycle_end ()
|
||||
{
|
||||
bool drain_done = false;
|
||||
bool overflow = false;
|
||||
|
||||
if (g_atomic_int_get (&_draining)) {
|
||||
if (_rb_capture.read_space() == 0 && _rb_playback.read_space() == 0 && _samples_since_dll_reset > _pcmi.fsamp ()) {
|
||||
reset_resampler (_src_capt);
|
||||
reset_resampler (_src_play);
|
||||
memset (_src_buff, 0, sizeof (float) * _pcmi.nplay());
|
||||
/* prefill ringbuffers, resampler variance */
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_rb_playback.write (_src_buff, _pcmi.nplay());
|
||||
}
|
||||
memset (_src_buff, 0, sizeof (float) * _pcmi.ncapt());
|
||||
// It's safe to write here, process-thread NO-OPs while draining.
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_rb_capture.write (_src_buff, _pcmi.ncapt());
|
||||
}
|
||||
_capt_latency = 16;
|
||||
_play_latency = 16 + _ratio * _pcmi.fsize () * (_pcmi.play_nfrag () - 1);
|
||||
update_latencies (_play_latency, _capt_latency);
|
||||
drain_done = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* resample collected playback data into ringbuffer */
|
||||
unsigned int nchn = _pcmi.nplay ();
|
||||
_src_play.inp_count = _samples_per_period;
|
||||
_src_play.inp_data = _play_buff;
|
||||
|
||||
while (_src_play.inp_count && _active) {
|
||||
unsigned int n;
|
||||
PBD::RingBuffer<float>::rw_vector vec;
|
||||
_rb_playback.get_write_vector (&vec);
|
||||
if (vec.len[0] < nchn) {
|
||||
_src_play.out_count = 1;
|
||||
_src_play.out_data = _src_buff;
|
||||
_src_play.process ();
|
||||
if (_rb_playback.write_space() < nchn) {
|
||||
overflow = true;
|
||||
break;
|
||||
} else if (_src_play.out_count == 0) {
|
||||
_rb_playback.write (_src_buff, nchn);
|
||||
}
|
||||
} else {
|
||||
_src_play.out_count = n = vec.len[0] / nchn;
|
||||
_src_play.out_data = vec.buf[0];
|
||||
_src_play.process ();
|
||||
n -= _src_play.out_count;
|
||||
if (_rb_playback.write_space() < n * nchn) {
|
||||
overflow = true;
|
||||
break;
|
||||
}
|
||||
_rb_playback.increment_write_idx (n * nchn);
|
||||
}
|
||||
}
|
||||
|
||||
if (overflow) {
|
||||
std::cerr << "ALSA Slave: Playback Ringbuffer Overflow\n"; // XXX
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
return;
|
||||
}
|
||||
if (drain_done) {
|
||||
g_atomic_int_set(&_draining, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AlsaAudioSlave::freewheel (bool onoff)
|
||||
{
|
||||
if (onoff) {
|
||||
g_atomic_int_set(&_draining, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* master read slave's capture.
|
||||
* resampled at cycle_start, before master can call this
|
||||
*/
|
||||
uint32_t
|
||||
AlsaAudioSlave::capt_chan (uint32_t chn, float* dst, uint32_t n_samples)
|
||||
{
|
||||
uint32_t nchn = _pcmi.ncapt ();
|
||||
assert (chn < nchn && n_samples == _samples_per_period);
|
||||
float* src = &_capt_buff[chn];
|
||||
for (uint32_t s = 0; s < n_samples; ++s) {
|
||||
dst[s] = src[s * nchn];
|
||||
}
|
||||
return n_samples;
|
||||
}
|
||||
|
||||
/* write from master to slave output,
|
||||
* resampled at cycle_end, after master called this.
|
||||
*/
|
||||
uint32_t
|
||||
AlsaAudioSlave::play_chan (uint32_t chn, float* src, uint32_t n_samples)
|
||||
{
|
||||
uint32_t nchn = _pcmi.nplay ();
|
||||
assert (chn < nchn && n_samples == _samples_per_period);
|
||||
float* dst = &_play_buff[chn];
|
||||
for (uint32_t s = 0; s < n_samples; ++s) {
|
||||
dst[s * nchn] = src[s];
|
||||
}
|
||||
return n_samples;
|
||||
}
|
103
libs/backends/alsa/alsa_slave.h
Normal file
103
libs/backends/alsa/alsa_slave.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Robin Gareus <robin@gareus.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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __libbackend_alsa_slave_h__
|
||||
#define __libbackend_alsa_slave_h__
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "pbd/ringbuffer.h"
|
||||
#include "zita-resampler/vresampler.h"
|
||||
#include "zita-alsa-pcmi.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class AlsaAudioSlave
|
||||
{
|
||||
public:
|
||||
AlsaAudioSlave (
|
||||
const char *play_name,
|
||||
const char *capt_name,
|
||||
unsigned int master_rate,
|
||||
unsigned int master_samples_per_period,
|
||||
unsigned int slave_rate,
|
||||
unsigned int slave_samples_per_period,
|
||||
unsigned int periods_per_cycle);
|
||||
|
||||
virtual ~AlsaAudioSlave ();
|
||||
|
||||
bool start ();
|
||||
void stop ();
|
||||
|
||||
void cycle_start (double, double, bool);
|
||||
void cycle_end ();
|
||||
|
||||
uint32_t capt_chan (uint32_t chn, float* dst, uint32_t n_samples);
|
||||
uint32_t play_chan (uint32_t chn, float* src, uint32_t n_samples);
|
||||
|
||||
bool running () const { return _active; }
|
||||
void freewheel (bool);
|
||||
|
||||
int state (void) const { return _pcmi.state (); }
|
||||
uint32_t nplay (void) const { return _pcmi.nplay (); }
|
||||
uint32_t ncapt (void) const { return _pcmi.ncapt (); }
|
||||
|
||||
PBD::Signal0<void> Halted;
|
||||
|
||||
protected:
|
||||
virtual void update_latencies (uint32_t, uint32_t) = 0;
|
||||
|
||||
private:
|
||||
Alsa_pcmi _pcmi;
|
||||
|
||||
static void* _process_thread (void *);
|
||||
void* process_thread ();
|
||||
pthread_t _thread;
|
||||
|
||||
bool _run; /* keep going or stop, ardour thread */
|
||||
bool _active; /* is running, process thread */
|
||||
|
||||
/* DLL, track slave process callback */
|
||||
double _t0, _t1;
|
||||
uint64_t _samples_since_dll_reset;
|
||||
|
||||
double _ratio;
|
||||
uint32_t _capt_latency;
|
||||
double _play_latency;
|
||||
|
||||
volatile double _slave_speed;
|
||||
volatile gint _draining;
|
||||
|
||||
PBD::RingBuffer<float> _rb_capture;
|
||||
PBD::RingBuffer<float> _rb_playback;
|
||||
|
||||
size_t _samples_per_period; // master
|
||||
|
||||
float* _capt_buff;
|
||||
float* _play_buff;
|
||||
float* _src_buff;
|
||||
|
||||
ArdourZita::VResampler _src_capt;
|
||||
ArdourZita::VResampler _src_play;
|
||||
|
||||
static void reset_resampler (ArdourZita::VResampler&);
|
||||
|
||||
}; // class AlsaAudioSlave
|
||||
|
||||
} // namespace
|
||||
#endif /* __libbackend_alsa_slave_h__ */
|
@ -23,13 +23,14 @@ def build(bld):
|
||||
'alsa_midi.cc',
|
||||
'alsa_rawmidi.cc',
|
||||
'alsa_sequencer.cc',
|
||||
'alsa_slave.cc',
|
||||
'zita-alsa-pcmi.cc',
|
||||
]
|
||||
obj.includes = ['.']
|
||||
obj.name = 'alsa_audiobackend'
|
||||
obj.target = 'alsa_audiobackend'
|
||||
obj.use = 'libardour libpbd ardouralsautil'
|
||||
obj.uselib = 'ALSA GLIBMM XML'
|
||||
obj.use = 'libardour libpbd ardouralsautil zita-resampler'
|
||||
obj.uselib = 'ALSA GLIBMM XML LIBZRESAMPLER'
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'backends')
|
||||
obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"',
|
||||
'ARDOURBACKEND_DLL_EXPORTS'
|
||||
|
Loading…
Reference in New Issue
Block a user