[LAD] RME madi latency

Fokke de Jong fokkedejong at gmail.com
Thu Feb 4 15:23:38 UTC 2016


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 at 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 at lists.linuxaudio.org
> http://lists.linuxaudio.org/listinfo/linux-audio-dev



More information about the Linux-audio-dev mailing list