Help testing ALSA streaming

Linux specific questions.
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Help testing ALSA streaming

Post by angros47 »

I need your help testing this code: on Ubuntu 12.04 32 bit it works well, on other versions it gave me stuttering audio... I would like help fixing it, possibly keeping latency as low as possible

Code: Select all

Dim shared As Any Ptr alsa
dim shared as integer OSS


'ALSA declarations

Const EAGAIN                       = -11 ' Try again
Const EPIPE                        = -32 ' Broken pipe
Const ESTRPIPE                     = -86 ' Streams pipe error

Const BLOCK                        = 0
Const NONBLOCK                     = 1
Const ASYNC                        = 2

Const SND_PCM_STREAM_PLAYBACK      = 0
Const SND_PCM_STREAM_CAPTURE       = 1
Const SND_PCM_FORMAT_S16_LE        = 2
Const SND_PCM_ACCESS_RW_INTERLEAVED= 3

#ifndef NULL
#define NULL 0
#endif

Type snd_pcm_t           As Any Ptr
Type snd_pcm_hw_params_t As Any Ptr
Type snd_output_t        As Any Ptr

' PCM


Dim Shared snd_strerror as Function ( _
Byval ecode As LONG) As Zstring Ptr

Dim Shared snd_pcm_open as Function ( _
Byval pcm          As snd_pcm_t Ptr, _
Byval device       As Zstring Ptr, _
Byval direction    As LONG, _
Byval mode         As LONG) As LONG

Dim Shared snd_pcm_close as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_start as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_drain as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_hw_free as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_nonblock as Function ( _
Byval pcm          As snd_pcm_t, _
Byval nonblock     As LONG) As LONG

Dim Shared snd_pcm_prepare as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_writei as Function ( _
Byval pcm          As snd_pcm_t, _
Byval buffer       As Any Ptr, _
Byval size         As LONG) As LONG

Dim Shared snd_pcm_recover as Function ( _
Byval pcm          As snd_pcm_t, _
Byval err          As LONG, _
Byval silent       As LONG) As LONG

Dim Shared snd_pcm_avail_update as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_avail as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_delay as Function ( _
Byval pcm          As snd_pcm_t, _
Byval delayp       As snd_pcm_t) As LONG

Dim Shared snd_pcm_wait as Function ( _
Byval pcm          As snd_pcm_t, _
Byval msec As LONG) As LONG

Dim Shared snd_pcm_resume as Function ( _
Byval pcm          As snd_pcm_t) As LONG

'hardware
Dim Shared snd_pcm_hw_params_malloc as Function ( _
Byval hw           As snd_pcm_hw_params_t Ptr) As LONG

Dim Shared snd_pcm_hw_params_any as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t) As LONG

Dim Shared snd_pcm_hw_params_set_access as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval mode         As LONG) As LONG

Dim Shared snd_pcm_hw_params_set_format as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval fmt          As LONG) As LONG

Dim Shared snd_pcm_hw_params_set_channels as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval Channels     As LONG) As LONG

Dim Shared snd_pcm_hw_params_get_channels as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpChannels   As ULONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_rate_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpRate       As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG


Dim Shared snd_pcm_hw_params_get_periods as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_periods_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_get_period_size as Function ( _
Byval params       As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_period_size_near as Function ( _
Byval pcm          As snd_pcm_t Ptr, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As Any Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_buffer_size_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr) As LONG

Dim Shared snd_pcm_hw_params_get_buffer_size as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr) As LONG

Dim Shared snd_pcm_hw_params as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t) As LONG

Dim Shared snd_pcm_hw_params_free as Sub ( _
Byval hw           As snd_pcm_hw_params_t)
     

Dim Shared As snd_pcm_t Ptr           hDevice
Dim Shared As snd_pcm_hw_params_t Ptr hw
Dim Shared As LONG                 ret,value,direction,buffer_size,SampleSize
Dim Shared As Integer		   nFrames
Dim Shared As Zstring Ptr             strRet


'OSS declarations

#include "file.bi"


declare function fileno cdecl alias "fileno" (byval as any ptr) as integer
declare function ioctl cdecl alias "ioctl" (byval hDevice as unsigned long,byval io_cmd as unsigned long,byval lpArg as long ptr) as long


#define _IOC_NRBITS    8
#define _IOC_TYPEBITS  8
#define _IOC_SIZEBITS 14
#define _IOC_DIRBITS   2

#define _IOC_NRMASK   ((1 shl _IOC_NRBITS  )-1)
#define _IOC_TYPEMASK ((1 shl _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 shl _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK  ((1 shl _IOC_DIRBITS )-1)

#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT   + _IOC_NRBITS)
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT + _IOC_TYPEBITS)
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT + _IOC_SIZEBITS)

' Direction bits.
#define _IOC_NONE  0U
#define _IOC_WRITE 1U
#define _IOC_READ  2U

#define _IOC(dir,t,nr,size) (((dir) shl _IOC_DIRSHIFT) or ((t) shl _IOC_TYPESHIFT) or ((nr)  shl _IOC_NRSHIFT) or ((size) shl  _IOC_SIZESHIFT))

' used to create numbers
#define _IO(t,nr)        _IOC(_IOC_NONE,(t),(nr),0)
#define _IOR(t,nr,size)  _IOC(_IOC_READ,(t),(nr),sizeof(size))
#define _IOW(t,nr,size)  _IOC(_IOC_WRITE,(t),(nr),sizeof(size))
#define _IOWR(t,nr,size) _IOC(_IOC_READ or _IOC_WRITE,(t),(nr),sizeof(size))

' used to decode ioctl numbers..
#define _IOC_DIR(nr)   (((nr) shr _IOC_DIRSHIFT)  and _IOC_DIRMASK)
#define _IOC_TYPE(nr)  (((nr) shr _IOC_TYPESHIFT) and _IOC_TYPEMASK)
#define _IOC_NR(nr)    (((nr) shr _IOC_NRSHIFT)   and _IOC_NRMASK)
#define _IOC_SIZE(nr)  (((nr) shr _IOC_SIZESHIFT) and _IOC_SIZEMASK)

' ...and for the drivers/sound files...
#define IOC_IN        ( _IOC_WRITE    shl _IOC_DIRSHIFT)
#define IOC_OUT       ( _IOC_READ     shl _IOC_DIRSHIFT)
#define IOC_INOUT     ((_IOC_WRITE    or  _IOC_READ) shl _IOC_DIRSHIFT)
#define IOCSIZE_MASK  ( _IOC_SIZEMASK shl _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT ( _IOC_SIZESHIFT)

' IOCTL commands for /dev/dsp and /dev/audio
#define SNDCTL_DSP_RESET          _IO  (asc("P"), 0)
#define SNDCTL_DSP_SYNC           _IO  (asc("P"), 1)
#define SNDCTL_DSP_SPEED          _IOWR(asc("P"), 2, LONG)
#define SNDCTL_DSP_STEREO         _IOWR(asc("P"), 3, LONG)
#define SNDCTL_DSP_GETBLKSIZE     _IOWR(asc("P"), 4, LONG)
#define SNDCTL_DSP_SETFMT         _IOWR(asc("P"), 5, LONG) ' Selects ONE fmt
#define SNDCTL_DSP_CHANNELS       _IOWR(asc("P"), 6, LONG)
#define SOUND_PCM_WRITE_CHANNELS  SNDCTL_DSP_CHANNELS
#define SOUND_PCM_WRITE_FILTER    _IOWR(asc("P"), 7, LONG)
#define SNDCTL_DSP_POST           _IO  (asc("P"), 8)
#define SNDCTL_DSP_SUBDIVIDE      _IOWR(asc("P"), 9, LONG)
#define SNDCTL_DSP_SETFRAGMENT    _IOWR(asc("P"),10, LONG)
#define SNDCTL_DSP_GETFMTS        _IOR (asc("P"),11, LONG) ' Returns a mask
#define SNDCTL_DSP_SAMPLESIZE     SNDCTL_DSP_SETFMT
#define SNDCTL_DSP_GETODELAY	_IOR (asc("P"), 23, LONG)


'arg for SNDCTL_DSP_SETFMT cmd
const AFMT_MU_LAW    = &H00000001
const AFMT_A_LAW     = &H00000002
const AFMT_IMA_ADPCM = &H00000004
const AFMT_U8        = &H00000008
const AFMT_S16_LE    = &H00000010  ' Little endian signed 
const AFMT_S16_BE    = &H00000020  ' Big endian signed 16 
const AFMT_S8        = &H00000040
const AFMT_U16_LE    = &H00000080  ' Little endian U16 
const AFMT_U16_BE    = &H00000100  ' Big endian U16 
const AFMT_MPEG      = &H00000200  ' MPEG (2) audio 
const AFMT_AC3       = &H00000400  ' Dolby Digital AC3 




sub dsp_init()
	alsa = DyLibLoad("asound")
	if alsa=0 then alsa = DyLibLoad("libasound.so.2")

	if alsa<>0 then
		snd_strerror= DyLibSymbol(alsa, "snd_strerror")
		snd_pcm_open= DyLibSymbol(alsa, "snd_pcm_open")
		snd_pcm_close= DyLibSymbol(alsa, "snd_pcm_close")
		snd_pcm_start= DyLibSymbol(alsa, "snd_pcm_start")
		snd_pcm_drain= DyLibSymbol(alsa, "snd_pcm_drain")
		snd_pcm_hw_free= DyLibSymbol(alsa, "snd_pcm_hw_free")
		snd_pcm_nonblock= DyLibSymbol(alsa, "snd_pcm_nonblock")
		snd_pcm_prepare= DyLibSymbol(alsa, "snd_pcm_prepare")
		snd_pcm_writei= DyLibSymbol(alsa, "snd_pcm_writei")
		snd_pcm_recover= DyLibSymbol(alsa, "snd_pcm_recover")
		snd_pcm_avail_update= DyLibSymbol(alsa, "snd_pcm_avail_update")
		snd_pcm_avail= DyLibSymbol(alsa, "snd_pcm_avail")
		snd_pcm_delay= DyLibSymbol(alsa, "snd_pcm_delay")
		snd_pcm_wait= DyLibSymbol(alsa, "snd_pcm_wait")
		snd_pcm_resume= DyLibSymbol(alsa, "snd_pcm_resume")
		snd_pcm_hw_params_malloc= DyLibSymbol(alsa, "snd_pcm_hw_params_malloc")
		snd_pcm_hw_params_any= DyLibSymbol(alsa, "snd_pcm_hw_params_any")
		snd_pcm_hw_params_set_access= DyLibSymbol(alsa, "snd_pcm_hw_params_set_access")
		snd_pcm_hw_params_set_format= DyLibSymbol(alsa, "snd_pcm_hw_params_set_format")
		snd_pcm_hw_params_set_channels= DyLibSymbol(alsa, "snd_pcm_hw_params_set_channels")
		snd_pcm_hw_params_get_channels= DyLibSymbol(alsa, "snd_pcm_hw_params_get_channels")
		snd_pcm_hw_params_set_rate_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_rate_near")
		snd_pcm_hw_params_get_periods= DyLibSymbol(alsa, "snd_pcm_hw_params_get_periods")
		snd_pcm_hw_params_set_periods_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_periods_near")
		snd_pcm_hw_params_get_period_size= DyLibSymbol(alsa, "snd_pcm_hw_params_get_period_size")
		snd_pcm_hw_params_set_period_size_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_period_size_near")
		snd_pcm_hw_params_set_buffer_size_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_buffer_size_near")
		snd_pcm_hw_params_get_buffer_size= DyLibSymbol(alsa, "snd_pcm_hw_params_get_buffer_size")
		snd_pcm_hw_params= DyLibSymbol(alsa, "snd_pcm_hw_params")
		snd_pcm_hw_params_free= DyLibSymbol(alsa, "snd_pcm_hw_params_free")
	end if

	OSS=FileExists("/dev/dsp")

end sub




sub SoundSet(frequency as long, channels as long, bits as long)
	if alsa=0 andalso OSS=0 then dsp_init

	if alsa then
		if bits=16 then SampleSize=2 else SampleSize=1
		SampleSize*=channels

		ret = snd_pcm_open(@hDevice,"default", SND_PCM_STREAM_PLAYBACK, BLOCK)

		snd_pcm_hw_params_malloc(@hw)
		ret = snd_pcm_hw_params_any(hDevice,hw)
		ret = snd_pcm_hw_params_set_access(hDevice, hw, SND_PCM_ACCESS_RW_INTERLEAVED)

		ret = snd_pcm_hw_params_set_format(hDevice,hw,IIF(bits=16,2,1))
		ret = snd_pcm_hw_params_set_channels(hDevice,hw,channels)

		value=frequency 'set speed
		ret = snd_pcm_hw_params_set_rate_near(hDevice,hw,@value,@direction)

		ret = snd_pcm_hw_params(hDevice,hw)
		ret = snd_pcm_hw_params_get_buffer_size(hw, @nFrames)
		nFrames/=SampleSize

		snd_pcm_hw_params_free (hw)
		snd_pcm_prepare(hDevice)

		buffer_size=snd_pcm_avail(hDevice)
	elseif OSS then
		if OSS>0 then close OSS
		OSS=FreeFile

		open "/dev/dsp" for output as #1

		dim as long arg
		arg=IIF(bits=16, AFMT_S16_LE, AFMT_U8)

		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_SETFMT, @arg

		arg=IIF(channels<>0, 1, 0)
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_STEREO, @arg

		arg=channels
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_CHANNELS, @arg

		arg=frequency
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_SPEED, @arg


	end if
end sub

sub playbuffer (soundBuffer as any ptr, buffersize as long)
	if alsa then
		dim Count as long
		buffersize/=SampleSize
		Count=buffersize
		if buffersize>nFrames then buffersize=nFrames
		do while Count>0 
			ret=snd_pcm_writei(hDevice, soundBuffer, buffersize)
			if ret=EPIPE then 
				ret=snd_pcm_recover(hDevice, ret,1)

				if ret=0 then ret=snd_pcm_writei(hDevice, soundBuffer, buffersize) else exit do
		
			end if
			count -=ret
			soundBuffer += ret*SampleSize
		loop
	elseif OSS>0 then
		put #OSS, ,*cast(ubyte ptr, soundBuffer), buffersize
	end if
end sub

function SoundQueue() as integer
	if alsa then
		dim value as long
		value=snd_pcm_avail(hDevice)
		if value<0 then 
			return 0
		else
			return (buffer_size-value)*SampleSize
		end if
		'snd_pcm_delay(hDevice, @value)
		'return value*SampleSize
	elseif OSS>0 then
		dim value as long
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_GETODELAY, @value
		return value
	end if
end function


SoundSet 44100, 2, 16

	dim as short Buffer(4096)

	do
		for i as integer=0 to 4094 step 2
			Buffer(i)=(i mod 200) * 20
			Buffer(i+1)=(i mod 200) * 20
		next

		
		PlayBuffer @Buffer(0),8192
		do
			sleep 1
		loop until SoundQueue()<8192

	loop until multikey(1)

Last edited by angros47 on Jun 27, 2018 18:47, edited 2 times in total.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

Aborting due to runtime error 6 (out of bounds array access) at line 393 of alsa.bas::()
With fbc32 -w all -exx -mt "alsa.bas"
If I ignore that, it does not sound well. Stuttering on fbc32 & fbc64
System: Ubuntu Mate 16.04 64-bit
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

I understand. The runtime error is caused by the line

Code: Select all

for i as integer=0 to 4096 step 2
a mistake of me, it should have been:

Code: Select all

for i as integer=0 to 4094 step 2
About the stuttering: what happens if in the line:

Code: Select all

loop until SoundQueue()<8192
the value 8192 is replaced with a higher one? Is it possible to fix the stutter? and in case, which value is required on your system?
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

I think that triangle wave is not continues when between 2 buffers. Suggestion:

Code: Select all

const as integer N = 4096
   dim as short Buffer(N-1)

      for i as integer=0 to N-1 step 2
         Buffer(i)=(i mod 128) * 20 'L
         Buffer(i+1)=(i mod 128) * 20 'R
      next
   do
   ...
or

Code: Select all

 for i as integer=0 to N-1 step 2
         Buffer(i) = (sin((i/100)*(2*3.14159)) + 1) * 5000 'L
         Buffer(i+1) = (sin((i/100)*(2*3.14159)) + 1) * 5000 'R
      next
But still not perfect, but maybe my imagination now.
Changing "SoundQueue()<8192" (higher value) does not seem to make a difference.
Better:

Code: Select all

 for i as integer=0 to N-1 step 2
         Buffer(i) = (sin((i/100)*(2*3.14159)) + 1) * 5000 'L
         Buffer(i+1) = (sin((i/100)*(2*3.14159)) + 1) * 5000 'R
      next
Last edited by badidea on Jun 27, 2018 19:51, edited 1 time in total.
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

To make the triangle wave (actually, a sawtooth wave) continue, the solution is just something like:

Code: Select all

	dim z as integer
	do
		for i as integer=0 to 4096 step 2
			Buffer(i)=(z mod 200) * 20
			Buffer(i+1)=(z mod 200) * 20
			z+=2
		next
My concerns are about the SoundQueue part, I am not sure it can work on some systems
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

Check sawtooth, that's what I meant. Anyway, seems to produce sound here, which is nice.
I am reading this for better understanding.
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

So, on your system, does it work well both on 32 and 64 bit?
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

Yes, the sine wave I made sounds pretty good on both FBC 32bit and FBC 64bit. I'll try a different system...

Different laptop (Samsung ultrabook series 5) Ubuntu 16.04 64-bit, FBC 64-bit 1.0.5: OK
And a real beauty: Netbook, Samsung, Xubuntu 14.04 32-bit, FBC 32-bit 1.0.0: OK after short hick-up(?) at start.

Actually, all have this short silence in the beginning "piep....pieeeeeeeeeeeeeeeeeeeeeeeep". It's only a shorter period for the faster laptops.
It happens when EPIPE is returned and snd_pcm_recover() is called. It seems to happen once. Fill the buffer sufficiently before start playing?

This:

Code: Select all

sub playbuffer (soundBuffer as any ptr, buffersize as long)
   if alsa then
      dim Count as long
      buffersize/=SampleSize
      Count=buffersize
      if buffersize>nFrames then buffersize=nFrames
      do while Count>0
         ret=snd_pcm_writei(hDevice, soundBuffer, buffersize)
         print "snd_pcm_writei_1 ="; ret
         if ret=EPIPE then
            ret=snd_pcm_recover(hDevice, ret,1)
            print "snd_pcm_recover = "; ret
            if ret=0 then
               ret=snd_pcm_writei(hDevice, soundBuffer, buffersize)
               print "snd_pcm_writei_2 = "; ret
            else
               exit do
            end if
         end if
         count -=ret
         soundBuffer += ret*SampleSize
      loop
   elseif OSS>0 then
      put #OSS, ,*cast(ubyte ptr, soundBuffer), buffersize
   end if
end sub
prints:

Code: Select all

snd_pcm_writei_1 = 2048
snd_pcm_writei_1 =-32
snd_pcm_recover =  0
snd_pcm_writei_2 =  2048
snd_pcm_writei_1 = 2048
snd_pcm_writei_1 = 2048
snd_pcm_writei_1 = 2048
snd_pcm_writei_1 = 2048
snd_pcm_writei_1 = 2048
snd_pcm_writei_1 = 2048
etc.
Back on the initial laptop, this runs ok (M >=3):

Code: Select all

const as integer M = 3 '3 is OK
const as integer N = 4096 * M
dim as short Buffer(N-1)

for i as integer=0 to N-1 step 2
	Buffer(i) = (sin((i/64)*(2*3.14159)) + 1.0) * 5000 'L
	Buffer(i+1) = (sin((i/64)*(2*3.14159)) + 1.0) * 5000 'R
next

do
	PlayBuffer @Buffer(0),N*2
	do
		sleep 1,1
	loop until SoundQueue()<N*2
loop until multikey(1)
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

I know I could fill the buffer more. But this would increase latency, and since I want to use this code for MIDI synthesis, I prefer to reduce latency as much as possible
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

Here https://stackoverflow.com/questions/146 ... or-speaker they talk about snd_pcm_dump_sw_setup() to show details and tweaking a ~/.asoundrc file.
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

Since the asoundrc file affects all installed applications, it is better not to modify it for a single one (with the risk of breaking sound in the others)
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

Please, test this one, too

Code: Select all

Dim shared As Any Ptr alsa
dim shared as integer OSS


'ALSA declarations

Const EAGAIN                       = -11 ' Try again
Const EPIPE                        = -32 ' Broken pipe
Const ESTRPIPE                     = -86 ' Streams pipe error

Const BLOCK                        = 0
Const NONBLOCK                     = 1
Const ASYNC                        = 2

Const SND_PCM_STREAM_PLAYBACK      = 0
Const SND_PCM_STREAM_CAPTURE       = 1
Const SND_PCM_FORMAT_S16_LE        = 2
Const SND_PCM_ACCESS_RW_INTERLEAVED= 3

#ifndef NULL
#define NULL 0
#endif

Type snd_pcm_t           As Any Ptr
Type snd_pcm_hw_params_t As Any Ptr
Type snd_output_t        As Any Ptr

' PCM


Dim Shared snd_strerror as Function ( _
Byval ecode As LONG) As Zstring Ptr

Dim Shared snd_pcm_open as Function ( _
Byval pcm          As snd_pcm_t Ptr, _
Byval device       As Zstring Ptr, _
Byval direction    As LONG, _
Byval mode         As LONG) As LONG

Dim Shared snd_pcm_close as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_start as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_drain as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_hw_free as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_nonblock as Function ( _
Byval pcm          As snd_pcm_t, _
Byval nonblock     As LONG) As LONG

Dim Shared snd_pcm_prepare as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_writei as Function ( _
Byval pcm          As snd_pcm_t, _
Byval buffer       As Any Ptr, _
Byval size         As LONG) As LONG

Dim Shared snd_pcm_recover as Function ( _
Byval pcm          As snd_pcm_t, _
Byval err          As LONG, _
Byval silent       As LONG) As LONG

Dim Shared snd_pcm_avail_update as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_avail as Function ( _
Byval pcm          As snd_pcm_t) As LONG

Dim Shared snd_pcm_delay as Function ( _
Byval pcm          As snd_pcm_t, _
Byval delayp       As snd_pcm_t) As LONG

Dim Shared snd_pcm_wait as Function ( _
Byval pcm          As snd_pcm_t, _
Byval msec As LONG) As LONG

Dim Shared snd_pcm_resume as Function ( _
Byval pcm          As snd_pcm_t) As LONG

'hardware
Dim Shared snd_pcm_hw_params_malloc as Function ( _
Byval hw           As snd_pcm_hw_params_t Ptr) As LONG

Dim Shared snd_pcm_hw_params_any as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t) As LONG

Dim Shared snd_pcm_hw_params_set_access as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval mode         As LONG) As LONG

Dim Shared snd_pcm_hw_params_set_format as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval fmt          As LONG) As LONG

Dim Shared snd_pcm_hw_params_set_channels as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval Channels     As LONG) As LONG

Dim Shared snd_pcm_hw_params_get_channels as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpChannels   As ULONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_rate_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpRate       As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG


Dim Shared snd_pcm_hw_params_get_periods as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_periods_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As ULONG Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_get_period_size as Function ( _
Byval params       As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_period_size_near as Function ( _
Byval pcm          As snd_pcm_t Ptr, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpValue      As Any Ptr, _
Byval lpDir        As LONG Ptr) As LONG

Dim Shared snd_pcm_hw_params_set_buffer_size_near as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr) As LONG

Dim Shared snd_pcm_hw_params_get_buffer_size as Function ( _
Byval hw           As snd_pcm_hw_params_t, _
Byval lpFrames     As Any Ptr) As LONG

Dim Shared snd_pcm_hw_params as Function ( _
Byval pcm          As snd_pcm_t, _
Byval hw           As snd_pcm_hw_params_t) As LONG

Dim Shared snd_pcm_hw_params_free as Sub ( _
Byval hw           As snd_pcm_hw_params_t)
     

Dim Shared As snd_pcm_t Ptr           hDevice
Dim Shared As snd_pcm_hw_params_t Ptr hw
Dim Shared As LONG                 ret,value,direction,buffer_size,SampleSize
Dim Shared As Integer		   nFrames
Dim Shared As Zstring Ptr             strRet


'OSS declarations

#include "file.bi"


declare function fileno cdecl alias "fileno" (byval as any ptr) as integer
declare function ioctl cdecl alias "ioctl" (byval hDevice as unsigned long,byval io_cmd as unsigned long,byval lpArg as long ptr) as long


#define _IOC_NRBITS    8
#define _IOC_TYPEBITS  8
#define _IOC_SIZEBITS 14
#define _IOC_DIRBITS   2

#define _IOC_NRMASK   ((1 shl _IOC_NRBITS  )-1)
#define _IOC_TYPEMASK ((1 shl _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 shl _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK  ((1 shl _IOC_DIRBITS )-1)

#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT   + _IOC_NRBITS)
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT + _IOC_TYPEBITS)
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT + _IOC_SIZEBITS)

' Direction bits.
#define _IOC_NONE  0U
#define _IOC_WRITE 1U
#define _IOC_READ  2U

#define _IOC(dir,t,nr,size) (((dir) shl _IOC_DIRSHIFT) or ((t) shl _IOC_TYPESHIFT) or ((nr)  shl _IOC_NRSHIFT) or ((size) shl  _IOC_SIZESHIFT))

' used to create numbers
#define _IO(t,nr)        _IOC(_IOC_NONE,(t),(nr),0)
#define _IOR(t,nr,size)  _IOC(_IOC_READ,(t),(nr),sizeof(size))
#define _IOW(t,nr,size)  _IOC(_IOC_WRITE,(t),(nr),sizeof(size))
#define _IOWR(t,nr,size) _IOC(_IOC_READ or _IOC_WRITE,(t),(nr),sizeof(size))

' used to decode ioctl numbers..
#define _IOC_DIR(nr)   (((nr) shr _IOC_DIRSHIFT)  and _IOC_DIRMASK)
#define _IOC_TYPE(nr)  (((nr) shr _IOC_TYPESHIFT) and _IOC_TYPEMASK)
#define _IOC_NR(nr)    (((nr) shr _IOC_NRSHIFT)   and _IOC_NRMASK)
#define _IOC_SIZE(nr)  (((nr) shr _IOC_SIZESHIFT) and _IOC_SIZEMASK)

' ...and for the drivers/sound files...
#define IOC_IN        ( _IOC_WRITE    shl _IOC_DIRSHIFT)
#define IOC_OUT       ( _IOC_READ     shl _IOC_DIRSHIFT)
#define IOC_INOUT     ((_IOC_WRITE    or  _IOC_READ) shl _IOC_DIRSHIFT)
#define IOCSIZE_MASK  ( _IOC_SIZEMASK shl _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT ( _IOC_SIZESHIFT)

' IOCTL commands for /dev/dsp and /dev/audio
#define SNDCTL_DSP_RESET          _IO  (asc("P"), 0)
#define SNDCTL_DSP_SYNC           _IO  (asc("P"), 1)
#define SNDCTL_DSP_SPEED          _IOWR(asc("P"), 2, LONG)
#define SNDCTL_DSP_STEREO         _IOWR(asc("P"), 3, LONG)
#define SNDCTL_DSP_GETBLKSIZE     _IOWR(asc("P"), 4, LONG)
#define SNDCTL_DSP_SETFMT         _IOWR(asc("P"), 5, LONG) ' Selects ONE fmt
#define SNDCTL_DSP_CHANNELS       _IOWR(asc("P"), 6, LONG)
#define SOUND_PCM_WRITE_CHANNELS  SNDCTL_DSP_CHANNELS
#define SOUND_PCM_WRITE_FILTER    _IOWR(asc("P"), 7, LONG)
#define SNDCTL_DSP_POST           _IO  (asc("P"), 8)
#define SNDCTL_DSP_SUBDIVIDE      _IOWR(asc("P"), 9, LONG)
#define SNDCTL_DSP_SETFRAGMENT    _IOWR(asc("P"),10, LONG)
#define SNDCTL_DSP_GETFMTS        _IOR (asc("P"),11, LONG) ' Returns a mask
#define SNDCTL_DSP_SAMPLESIZE     SNDCTL_DSP_SETFMT
#define SNDCTL_DSP_GETODELAY	_IOR (asc("P"), 23, LONG)


'arg for SNDCTL_DSP_SETFMT cmd
const AFMT_MU_LAW    = &H00000001
const AFMT_A_LAW     = &H00000002
const AFMT_IMA_ADPCM = &H00000004
const AFMT_U8        = &H00000008
const AFMT_S16_LE    = &H00000010  ' Little endian signed 
const AFMT_S16_BE    = &H00000020  ' Big endian signed 16 
const AFMT_S8        = &H00000040
const AFMT_U16_LE    = &H00000080  ' Little endian U16 
const AFMT_U16_BE    = &H00000100  ' Big endian U16 
const AFMT_MPEG      = &H00000200  ' MPEG (2) audio 
const AFMT_AC3       = &H00000400  ' Dolby Digital AC3 




sub dsp_init()
	alsa = DyLibLoad("asound")
	if alsa=0 then alsa = DyLibLoad("libasound.so.2")

	if alsa<>0 then
		snd_strerror= DyLibSymbol(alsa, "snd_strerror")
		snd_pcm_open= DyLibSymbol(alsa, "snd_pcm_open")
		snd_pcm_close= DyLibSymbol(alsa, "snd_pcm_close")
		snd_pcm_start= DyLibSymbol(alsa, "snd_pcm_start")
		snd_pcm_drain= DyLibSymbol(alsa, "snd_pcm_drain")
		snd_pcm_hw_free= DyLibSymbol(alsa, "snd_pcm_hw_free")
		snd_pcm_nonblock= DyLibSymbol(alsa, "snd_pcm_nonblock")
		snd_pcm_prepare= DyLibSymbol(alsa, "snd_pcm_prepare")
		snd_pcm_writei= DyLibSymbol(alsa, "snd_pcm_writei")
		snd_pcm_recover= DyLibSymbol(alsa, "snd_pcm_recover")
		snd_pcm_avail_update= DyLibSymbol(alsa, "snd_pcm_avail_update")
		snd_pcm_avail= DyLibSymbol(alsa, "snd_pcm_avail")
		snd_pcm_delay= DyLibSymbol(alsa, "snd_pcm_delay")
		snd_pcm_wait= DyLibSymbol(alsa, "snd_pcm_wait")
		snd_pcm_resume= DyLibSymbol(alsa, "snd_pcm_resume")
		snd_pcm_hw_params_malloc= DyLibSymbol(alsa, "snd_pcm_hw_params_malloc")
		snd_pcm_hw_params_any= DyLibSymbol(alsa, "snd_pcm_hw_params_any")
		snd_pcm_hw_params_set_access= DyLibSymbol(alsa, "snd_pcm_hw_params_set_access")
		snd_pcm_hw_params_set_format= DyLibSymbol(alsa, "snd_pcm_hw_params_set_format")
		snd_pcm_hw_params_set_channels= DyLibSymbol(alsa, "snd_pcm_hw_params_set_channels")
		snd_pcm_hw_params_get_channels= DyLibSymbol(alsa, "snd_pcm_hw_params_get_channels")
		snd_pcm_hw_params_set_rate_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_rate_near")
		snd_pcm_hw_params_get_periods= DyLibSymbol(alsa, "snd_pcm_hw_params_get_periods")
		snd_pcm_hw_params_set_periods_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_periods_near")
		snd_pcm_hw_params_get_period_size= DyLibSymbol(alsa, "snd_pcm_hw_params_get_period_size")
		snd_pcm_hw_params_set_period_size_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_period_size_near")
		snd_pcm_hw_params_set_buffer_size_near= DyLibSymbol(alsa, "snd_pcm_hw_params_set_buffer_size_near")
		snd_pcm_hw_params_get_buffer_size= DyLibSymbol(alsa, "snd_pcm_hw_params_get_buffer_size")
		snd_pcm_hw_params= DyLibSymbol(alsa, "snd_pcm_hw_params")
		snd_pcm_hw_params_free= DyLibSymbol(alsa, "snd_pcm_hw_params_free")
	end if

	OSS=FileExists("/dev/dsp")

end sub




sub SoundSet(frequency as long, channels as long, bits as long)
	if alsa=0 andalso OSS=0 then dsp_init

	if alsa then
		if bits=16 then SampleSize=2 else SampleSize=1
		SampleSize*=channels

		ret = snd_pcm_open(@hDevice,"default", SND_PCM_STREAM_PLAYBACK, BLOCK)

		snd_pcm_hw_params_malloc(@hw)
		ret = snd_pcm_hw_params_any(hDevice,hw)
		ret = snd_pcm_hw_params_set_access(hDevice, hw, SND_PCM_ACCESS_RW_INTERLEAVED)

		ret = snd_pcm_hw_params_set_format(hDevice,hw,IIF(bits=16,2,1))
		ret = snd_pcm_hw_params_set_channels(hDevice,hw,channels)

		value=frequency 'set speed
		ret = snd_pcm_hw_params_set_rate_near(hDevice,hw,@value,@direction)

		ret = snd_pcm_hw_params(hDevice,hw)
		ret = snd_pcm_hw_params_get_buffer_size(hw, @nFrames)
		nFrames/=SampleSize

		snd_pcm_hw_params_free (hw)
		snd_pcm_prepare(hDevice)

		buffer_size=snd_pcm_avail(hDevice)
	elseif OSS then
		if OSS>0 then close OSS
		OSS=FreeFile

		open "/dev/dsp" for output as #1

		dim as long arg
		arg=IIF(bits=16, AFMT_S16_LE, AFMT_U8)

		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_SETFMT, @arg

		arg=IIF(channels<>0, 1, 0)
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_STEREO, @arg

		arg=channels
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_CHANNELS, @arg

		arg=frequency
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_SPEED, @arg


	end if
end sub

sub playbuffer (soundBuffer as any ptr, buffersize as long)
	if alsa then
		dim Count as long
		buffersize/=SampleSize
		Count=buffersize
		if buffersize>nFrames then buffersize=nFrames
		do while Count>0 
			ret=snd_pcm_writei(hDevice, soundBuffer, buffersize)
			if ret=EPIPE then 
				ret=snd_pcm_recover(hDevice, ret,1)

				if ret=0 then ret=snd_pcm_writei(hDevice, soundBuffer, buffersize) else exit do
		
			end if
			count -=ret
			soundBuffer += ret*SampleSize
		loop
	elseif OSS>0 then
		put #OSS, ,*cast(ubyte ptr, soundBuffer), buffersize
	end if
end sub

function SoundQueue() as integer
	if alsa then
		dim value as long
		value=snd_pcm_avail(hDevice)
		if value<0 then 
			return 0
		else
			return (buffer_size-value)*SampleSize
		end if
		'snd_pcm_delay(hDevice, @value)
		'return value*SampleSize
	elseif OSS>0 then
		dim value as long
		ioctl fileno(cast (any ptr,fileattr(1,2))), SNDCTL_DSP_GETODELAY, @value
		return value
	end if
end function


SoundSet 44100, 2, 16

	dim as short Buffer(4096)

	dim z as integer
	do
		static t as integer=8192
		for i as integer=0 to 4094 step 2
			Buffer(i)=(z mod 200) * 20
			Buffer(i+1)=(z mod 200) * 20
			z+=2
		next

		
		PlayBuffer @Buffer(0),8192
		do
			sleep 1
		loop until SoundQueue()<t
		if SoundQueue()=0 then t+=8192:?t


	loop until multikey(1)

badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Help testing ALSA streaming

Post by badidea »

I hope someone else can test, not near a computer for the next 10 days.
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: Help testing ALSA streaming

Post by srvaldez »

tested in a VM Ubuntu 16.04 x86
sound OK, exit program by pressing the esc key.
16384
24576
32768
40960
49152
57344
65536
73728
81920
90112
98304
106496
114688
122880
131072
VM Ubuntu 18.04 x64
sound OK, had to press control-c to exit the program, esc would not work.
16384
24576
32768
40960
49152
57344
65536
73728
81920
90112
98304
106496
114688
122880
131072
139264
147456
155648
angros47
Posts: 2323
Joined: Jun 21, 2005 19:04

Re: Help testing ALSA streaming

Post by angros47 »

So, looks like latency becomes much higher, on virtual machines. Looks like I must leave it self-adapting
Post Reply