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 <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@lists.linuxaudio.org > http://lists.linuxaudio.org/listinfo/linux-audio-dev _______________________________________________ Linux-audio-dev mailing list Linux-audio-dev@lists.linuxaudio.org http://lists.linuxaudio.org/listinfo/linux-audio-dev