Re: [LAD] RME madi latency

From: Fokke de Jong <fokkedejong@email-addr-hidden>
Date: Thu Feb 04 2016 - 17:23:38 EET

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(&params);
                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@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
Received on Thu Feb 4 20:15:01 2016

This archive was generated by hypermail 2.1.8 : Thu Feb 04 2016 - 20:15:01 EET