If anyone knows how to get the roundtrip latency down, i would highly appreciate it!
Ive pasted my full code below (the important stuff is in AlsaDevice::setParam and
AlsaDevice::run_thread).
The problem is that I only start getting input samples after o have already played an
entire buffer full of samples. Which results in a latency of more ore less 1 whole buffer
rather than (just) 2 periods.
I have looked at alsa_driver.c from jack but I can’t really see what exactly they do
different in that regard.
I hope the alsa-experts here, it will be immediately obvious what i’m missing :-)
fokke
////////////////////////////////////// AlsaDevice.h
#ifndef AlsaDevice_H
#define AlsaDevice_H
#include <alsa/asoundlib.h>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <vector>
#include <cmath>
typedef std::vector<std::vector<float>> ChannelBuffers;
class AudioEngine
{
public:
virtual void process(const ChannelBuffers& inputs, ChannelBuffers& outputs) =
0;
};
class AlsaError: public std::runtime_error
{
std::string _file, _function;
int _line;
public:
AlsaError(int err )
:std::runtime_error(snd_strerror(err)) {}
AlsaError(int err, const std::string& file, const std::string& function, int line
)
:std::runtime_error(snd_strerror(err))
,_file(file)
,_function(function)
,_line(line)
{
std::cerr << "throw: " << snd_strerror(err) <<
"\n";;
}
std::string where()
{
std::stringstream w;
w << " function: '" << _function << "' file:
'" << _file << "' line: " << _line;
return w.str();
}
};
#define THROW_ALSA_ERROR(e) throw AlsaError(e, __FILE__, __FUNCTION__, __LINE__ )
class AlsaDevice
{
private:
unsigned int numInChannels = 0;
unsigned int numOutChannels = 0;
unsigned int samplerate = 48000;
snd_pcm_uframes_t buffer_size = 0;
snd_pcm_uframes_t period_size = 0;
//
snd_pcm_t* pcm_in_handle = nullptr;
snd_pcm_t* pcm_out_handle = nullptr;
const snd_pcm_format_t format = SND_PCM_FORMAT_FLOAT_LE;
ChannelBuffers inBuffers;
ChannelBuffers outBuffers; // de-interleaved float buffers;
int numXruns = 0;
static int xrun_recovery(snd_pcm_t *handle, int err)
{
if (err == -EPIPE)
{ /* under-run */
err = snd_pcm_prepare(handle);
if (err < 0)
printf("Can't recovery from underrun, prepare failed: %s\n",
snd_strerror(err));
return 0;
}
else if (err == -ESTRPIPE)
{
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1); /* wait until the suspend flag is released */
if (err < 0)
{
err = snd_pcm_prepare(handle);
if (err < 0)
printf("Can't recovery from suspend, prepare failed: %s\n",
snd_strerror(err));
}
return 0;
}
return err;
}
int run_thread(AudioEngine*);
void setParams(snd_pcm_t* handle, int requestBuffersize, unsigned int
&requestNumChannels, int mode);
public:
AlsaDevice(){}
void process(float **outs, int n);
void print()
{
std::cout << "samplerate: " << samplerate <<
"Hz\n";
std::cout << "in channels: " << numInChannels <<
"\n";
std::cout << "out channels: " << numOutChannels <<
"\n";
std::cout << "buffersize: " << buffer_size<<
"\n";
std::cout << "period_size: " << period_size <<
"\n";
}
void open(const std::string& deviceName, int requestPeriodSize, unsigned int
requestNumInChannels=0, unsigned int requestNumOutChannels=0);
int run(AudioEngine*);
void stop();
int getNumXRuns() const
{
return numXruns;
}
~AlsaDevice();
};
#endif
////////////////////////////////AlsaDevice.cpp
#include "AlsaDevice.h"
#include <thread>
#include <strings.h>
#include <iomanip>
#include <fstream>
#define THROW_ALSA_ERROR(e) throw AlsaError(e, __FILE__, __FUNCTION__, __LINE__ )
void AlsaDevice::open(const std::string& deviceName,
int requestPeriodSize,
unsigned int requestNumInChannels,
unsigned int requestNumOutChannels)
{
int err = 0;
if (requestNumInChannels > 0)
{
err = snd_pcm_open (&pcm_in_handle, deviceName.c_str(), SND_PCM_STREAM_CAPTURE,
0);
if (err < 0)
{
pcm_in_handle = nullptr;
THROW_ALSA_ERROR(err);
}
std::cout << "opened capture device '" << deviceName <<
"'\n";
unsigned int numHWChannels=0;
setParams(pcm_in_handle, requestPeriodSize, numHWChannels, SND_PCM_STREAM_CAPTURE);
if(numHWChannels < requestNumInChannels)
{
throw std::runtime_error("num hw out channels too small");
}
numInChannels = numHWChannels;
inBuffers.resize(requestNumInChannels);
for (int i=0;i<requestNumInChannels;i++)
{
inBuffers[i].resize(period_size);
}
}
else
{
numInChannels = 0;
}
if (requestNumOutChannels > 0)
{
err = snd_pcm_open (&pcm_out_handle, deviceName.c_str(), SND_PCM_STREAM_PLAYBACK,
0);
if (err < 0)
{
pcm_out_handle = nullptr;
THROW_ALSA_ERROR(err);
}
std::cout << "opened playback device '" << deviceName
<< "'\n";
unsigned int numHWChannels=0;
setParams(pcm_out_handle, requestPeriodSize, numHWChannels, SND_PCM_STREAM_PLAYBACK);
if(numHWChannels < requestNumOutChannels)
{
throw std::runtime_error("num hw out channels too small");
}
numOutChannels = numHWChannels;
outBuffers.resize(requestNumOutChannels);
for (int i=0;i<requestNumOutChannels;i++)
{
outBuffers[i].resize(period_size);
for (int j=0;j<period_size;j++)
{
outBuffers[i][j] = 0.0f;
}
}
}
else
{
numOutChannels = 0;
}
}
void AlsaDevice::setParams(snd_pcm_t* pcm_handle, int requestPeriodSize, unsigned
int& numChannels, int dir)
{
snd_pcm_hw_params_t *params = nullptr;
snd_pcm_hw_params_alloca(¶ms);
int err = 0;
err = snd_pcm_hw_params_any(pcm_handle, params);
if (err != 0 ) THROW_ALSA_ERROR(err);
err = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_MMAP_COMPLEX);
if (err != 0 )
{
std::cerr << snd_strerror(err) << "\n";
THROW_ALSA_ERROR(err);
}
err = snd_pcm_hw_params_set_format(pcm_handle, params, format);
if (err != 0 ) THROW_ALSA_ERROR(err);
unsigned int maxChannels = 0;
err = snd_pcm_hw_params_get_channels_max(params, &maxChannels);
if (err != 0 ) THROW_ALSA_ERROR(err);
numChannels = maxChannels;
err = snd_pcm_hw_params_set_channels(pcm_handle, params, numChannels);
if (err != 0 ) THROW_ALSA_ERROR(err);
unsigned int realNumChannels = 0;
err = snd_pcm_hw_params_get_channels(params, &realNumChannels);
if (err != 0 ) THROW_ALSA_ERROR(err);
numChannels = realNumChannels;
err = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &samplerate, 0);
if (err != 0 ) THROW_ALSA_ERROR(err);
snd_pcm_uframes_t min_bufsize=0;
err = snd_pcm_hw_params_get_buffer_size_min(params, &min_bufsize);
if (err < 0) THROW_ALSA_ERROR(err);
buffer_size = min_bufsize;
err = snd_pcm_hw_params_set_buffer_size(pcm_handle, params, buffer_size);
if (err < 0) THROW_ALSA_ERROR(err);
err = snd_pcm_hw_params_set_period_size(pcm_handle, params, requestPeriodSize, 0);
if (err < 0) THROW_ALSA_ERROR(err);
err = snd_pcm_hw_params_get_period_size(params, &period_size, 0);
if (err < 0) THROW_ALSA_ERROR(err);
err = snd_pcm_hw_params(pcm_handle, params);
if (err != 0 ) THROW_ALSA_ERROR(err);
snd_pcm_sw_params_t *sw_params = nullptr;
snd_pcm_sw_params_alloca(&sw_params);
err = snd_pcm_sw_params_current(pcm_handle, sw_params);
if (err != 0 ) THROW_ALSA_ERROR(err);
err = snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, period_size);
if (err != 0 ) THROW_ALSA_ERROR(err);
err = snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period_size);
if (err != 0 ) THROW_ALSA_ERROR(err);
err = snd_pcm_sw_params(pcm_handle, sw_params);
if (err != 0 ) THROW_ALSA_ERROR(err);
}
std::thread audioThred;
int AlsaDevice::run(AudioEngine* engine)
{
audioThred = std::thread([this, engine]()
{
try
{
// ProcessThread::setCPUCore(0);
// ProcessThread::setThreadRealtime(32/ 48000.);
run_thread(engine);
}
catch (AlsaError& e)
{
std::cerr <<"ERROR: " << e.what() << " in "
<< e.where() << "\n";
}
});
return 0;
}
int AlsaDevice::run_thread(AudioEngine* engine)
{
pthread_setname_np(pthread_self(), "audio thread");
snd_pcm_link(pcm_in_handle, pcm_out_handle);
int err, first = 1;
bool firstCapture = true;
while (1)
{
snd_pcm_state_t state = snd_pcm_state(pcm_out_handle);
if (state == SND_PCM_STATE_XRUN)
{
numXruns++;
err = xrun_recovery(pcm_out_handle, -EPIPE);
if (err < 0)
{
THROW_ALSA_ERROR(err);
return err;
}
first = 1;
}
else if (state == SND_PCM_STATE_SUSPENDED)
{
err = xrun_recovery(pcm_out_handle, -ESTRPIPE);
if (err < 0)
{
printf("SUSPEND recovery failed: %s\n", snd_strerror(err));
return err;
}
}
snd_pcm_sframes_t availCapture = snd_pcm_avail_update(pcm_in_handle);
snd_pcm_sframes_t availPlayback = snd_pcm_avail_update(pcm_out_handle);
if (availPlayback < 0)
{
err = xrun_recovery(pcm_out_handle, availPlayback);
if (err < 0)
{
err = xrun_recovery(pcm_out_handle, -EPIPE);
if (err < 0)
{
printf("XRUN recovery failed: %s\n", snd_strerror(err));
return err;
}
first = 1;
}
{
printf("avail update failed: %s\n", snd_strerror(err));
return err;
}
first = 1;
continue;
}
if (availPlayback < period_size)
{
if (first)
{
first = 0;
err = snd_pcm_start(pcm_out_handle);
if (err < 0) {
printf("Start error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
}
else
{
err = snd_pcm_wait(pcm_out_handle, -1);
if (err < 0)
{
if ((err = xrun_recovery(pcm_out_handle, err)) < 0)
{
printf("snd_pcm_wait error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
}
continue;
}
if (availCapture > 0)
{
const snd_pcm_channel_area_t *capt_areas = nullptr;
snd_pcm_uframes_t offset=0, frames=period_size;
err = snd_pcm_mmap_begin(pcm_in_handle, &capt_areas, &offset, &frames);
if (err < 0)
{
exit(EXIT_FAILURE);
//first = 1;
}
for (int chnl=0;chnl<inBuffers.size();chnl++)
{
float *ptr = (float*)capt_areas[chnl].addr;
unsigned first = capt_areas[chnl].first / 32;
unsigned step = capt_areas[chnl].step / 32;
ptr += first;
for (int i=0;i<frames;i++)
{
inBuffers[chnl][i] = ptr[(i+offset)*step];
}
}
snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(pcm_in_handle, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames)
{
//if ((err = xrun_recovery(pcm_out_handle, commitres >= 0 ? -EPIPE : commitres))
< 0)
{
//printf("MMAP commit error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
//first = 1;
}
}
snd_pcm_uframes_t size = period_size;
while (size > 0)
{
snd_pcm_uframes_t offset, frames;
const snd_pcm_channel_area_t *play_areas = nullptr;
//
frames = size;
err = snd_pcm_mmap_begin(pcm_out_handle, &play_areas, &offset, &frames);
if (err < 0)
{
if ((err = xrun_recovery(pcm_out_handle, err)) < 0)
{
printf("MMAP begin avail error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
{
engine->process(inBuffers, outBuffers);
static const float kScale = float(1<<31);
// copy outBuffers
for (int chnl=0;chnl<outBuffers.size();chnl++)
{
float *ptr = (float*)play_areas[chnl].addr;
unsigned first = play_areas[chnl].first / 32;
unsigned step = play_areas[chnl].step / 32;
ptr += first;
for (int i=0;i<frames;i++)
{
ptr[(i+offset)*step] = outBuffers[chnl][i];
}
}
// zero remaining buffers
for (int chnl=outBuffers.size();chnl<numOutChannels;chnl++)
{
float *ptr = (float*)play_areas[chnl].addr;
unsigned first = play_areas[chnl].first / 32;
unsigned step = play_areas[chnl].step / 32;
ptr += first;
for (int i=0;i<frames;i++)
{
ptr[(i+offset)*step] = 0.0f; //
}
}
}
snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(pcm_out_handle, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames)
{
if ((err = xrun_recovery(pcm_out_handle, commitres >= 0 ? -EPIPE : commitres))
< 0)
{
printf("MMAP commit error: %s\n", snd_strerror(err));
exit(EXIT_FAILURE);
}
first = 1;
}
size -= frames;
assert(size == 0);
}
}
}
void AlsaDevice::stop()
{
snd_pcm_drain(pcm_out_handle);
snd_pcm_drop(pcm_out_handle);
}
AlsaDevice::~AlsaDevice()
{
if (pcm_out_handle)
{
snd_pcm_close (pcm_out_handle);
std::cout << "closed device\n";
}
}
class TestAudioEngine: public AudioEngine
{
// just copy in to out for now:
void process(const ChannelBuffers& inputs, ChannelBuffers& outputs) override
{
const int numInChannels = inputs.size();
const int numOutChannels = outputs.size();
int n = outputs[0].size();
assert(numInChannels <= numOutChannels);
for ( int j=0;j<numOutChannels;j++)
{
for (int i=0;i<n;i++)
{
outputs[j][i] = inputs[j][i];
}
}
}
};
int main ()
{
try
{
std::cout << "alsa test..\n";
AlsaDevice device;
device.open("hw:0,0", 32, 32, 32);
device.print();
TestAudioEngine engine;
std::cout << "starting device.\n";
device.run(&engine);
std::cout << "running..\n";
while (1)
{
sleep(1);
}
std::cout << "done.\n";
}
catch (AlsaError& e)
{
std::cerr << "ERROR: " << e.what() << " in "
<< e.where() << "\n";
}
catch (...)
{
std::cerr << "uncaught exception!!..\n";
}
return 0;
}
On Feb 3, 2016, at 14:21 , Adrian Knoth
<adi(a)drcomp.erfurt.thur.de> wrote:
On 02/03/16 14:04, Fokke de Jong wrote:
Hi all,
Hi!
I have a RME madi fx card, but i found out that
the minimal buffersize
for those cards is 8192 samples, which is way to big for my use.
Hardware buffer size. This buffer is divided into sub-buffers (periods)
depending on what you configure.
If you choose 32 samples, then it's 8192/32 == 256 periods that fit into
one buffer. You get an interrupt every 32 samples, and ALSA reads the
just completed sub-buffer.
The HW buffer size really has zero relation to your RTT.
Spin up jackd and then use Fons' jack_delay. Maybe we have to add
buffer_size_min to the driver, but I'm pretty sure returning the card is
the entirely wrong approach.
HTH
_______________________________________________
Linux-audio-dev mailing list
Linux-audio-dev(a)lists.linuxaudio.org
http://lists.linuxaudio.org/listinfo/linux-audio-dev