I just did this module for myself and maybe it is of use for someone else. No idea if everything is correctly declared or done, but for me it works fine here. I thought this also might be a good starting point for someone else to enhance this...
Some test and example code is on the bottom of the code.
Restrictions:
* Only for getting the level, no WAV recording or playback implemented
* Returns only RMS, PEAK and DB levels for left and right signal
* Always acquiring stereo, 44100Khz and 16 bit values
* Only Linux
* Only tested on Kubuntu 14.04 and Ubuntu 14.04. If it works for you on other systems, please mention it.
Changes:
26 July 2016: Added DB level calculation
Code:
Code: Select all
; Small module to simply calculate the level of signals
; on a specific audio device.
;
; PB 5.24 LTS or newer
;
; Find the user documentation here:
; https://freedesktop.org/software/pulseaudio/doxygen/index.html#simple_sec
;
; (w) 2016 / V. Schmid
DeclareModule pulse
;{ PulseAudio Stuff
Enumeration pa_sample_format
#PA_SAMPLE_U8
#PA_SAMPLE_ALAW
#PA_SAMPLE_ULAW
#PA_SAMPLE_S16LE
#PA_SAMPLE_S16BE
#PA_SAMPLE_FLOAT32LE
#PA_SAMPLE_FLOAT32BE
#PA_SAMPLE_S32LE
#PA_SAMPLE_S32BE
#PA_SAMPLE_S24LE
#PA_SAMPLE_S24BE
#PA_SAMPLE_S24_32LE
#PA_SAMPLE_S24_32BE
#PA_SAMPLE_MAX
#PA_SAMPLE_INVALID = -1
EndEnumeration
Enumeration pa_stream_direction
#PA_STREAM_NODIRECTION
#PA_STREAM_PLAYBACK
#PA_STREAM_RECORD
#PA_STREAM_UPLOAD
EndEnumeration
Enumeration pa_channel_position
#PA_CHANNEL_POSITION_INVALID = -1
#PA_CHANNEL_POSITION_MONO = 0
#PA_CHANNEL_POSITION_FRONT_LEFT
#PA_CHANNEL_POSITION_FRONT_RIGHT
#PA_CHANNEL_POSITION_FRONT_CENTER
#PA_CHANNEL_POSITION_LEFT = #PA_CHANNEL_POSITION_FRONT_LEFT
#PA_CHANNEL_POSITION_RIGHT = #PA_CHANNEL_POSITION_FRONT_RIGHT
#PA_CHANNEL_POSITION_CENTER = #PA_CHANNEL_POSITION_FRONT_CENTER
#PA_CHANNEL_POSITION_REAR_CENTER
#PA_CHANNEL_POSITION_REAR_LEFT
#PA_CHANNEL_POSITION_REAR_RIGHT
#PA_CHANNEL_POSITION_LFE
#PA_CHANNEL_POSITION_SUBWOOFER = #PA_CHANNEL_POSITION_LFE
#PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER
#PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER
#PA_CHANNEL_POSITION_SIDE_LEFT
#PA_CHANNEL_POSITION_SIDE_RIGHT
#PA_CHANNEL_POSITION_AUX0
#PA_CHANNEL_POSITION_AUX1
#PA_CHANNEL_POSITION_AUX2
#PA_CHANNEL_POSITION_AUX3
#PA_CHANNEL_POSITION_AUX4
#PA_CHANNEL_POSITION_AUX5
#PA_CHANNEL_POSITION_AUX6
#PA_CHANNEL_POSITION_AUX7
#PA_CHANNEL_POSITION_AUX8
#PA_CHANNEL_POSITION_AUX9
#PA_CHANNEL_POSITION_AUX10
#PA_CHANNEL_POSITION_AUX11
#PA_CHANNEL_POSITION_AUX12
#PA_CHANNEL_POSITION_AUX13
#PA_CHANNEL_POSITION_AUX14
#PA_CHANNEL_POSITION_AUX15
#PA_CHANNEL_POSITION_AUX16
#PA_CHANNEL_POSITION_AUX17
#PA_CHANNEL_POSITION_AUX18
#PA_CHANNEL_POSITION_AUX19
#PA_CHANNEL_POSITION_AUX20
#PA_CHANNEL_POSITION_AUX21
#PA_CHANNEL_POSITION_AUX22
#PA_CHANNEL_POSITION_AUX23
#PA_CHANNEL_POSITION_AUX24
#PA_CHANNEL_POSITION_AUX25
#PA_CHANNEL_POSITION_AUX26
#PA_CHANNEL_POSITION_AUX27
#PA_CHANNEL_POSITION_AUX28
#PA_CHANNEL_POSITION_AUX29
#PA_CHANNEL_POSITION_AUX30
#PA_CHANNEL_POSITION_AUX31
#PA_CHANNEL_POSITION_TOP_CENTER
#PA_CHANNEL_POSITION_TOP_FRONT_LEFT
#PA_CHANNEL_POSITION_TOP_FRONT_RIGHT
#PA_CHANNEL_POSITION_TOP_FRONT_CENTER
#PA_CHANNEL_POSITION_TOP_REAR_LEFT
#PA_CHANNEL_POSITION_TOP_REAR_RIGHT
#PA_CHANNEL_POSITION_TOP_REAR_CENTER
#PA_CHANNEL_POSITION_MA
EndEnumeration
;}
Structure vu_result
peakLeft.f
peakRight.f
rmsLeft.f
rmsRight.f
dbLeft.f
dbRight.f
EndStructure
#PA_BUFSIZE = 1024 ; rec buffer / turned out to be best here / about 160 calls a second
Declare.i initLibrary()
Declare shutdown()
Declare.s getVersion()
Declare.i closeStream()
Declare.i initStream(appName.s, streamName.s = "", deviceName.s = "")
Declare.s errToStr(error.i)
Declare.i getVU(*values.vu_result)
EndDeclareModule
Module pulse
Structure pa_buffer_attr
maxlength.l
tlength.l
prebuf.l
minreq.l
fragsize.l
EndStructure
Structure pa_sample_spec
pa_sample_format.l
rate.l
channels.a
EndStructure
Structure pa_channel_map
channels.a
pa_channel_position.l
EndStructure
Global pulseLib.i
Global *libBuf
Global stream.i
Global *ss.pa_sample_spec
; prototypes for pulseAudio
Prototype.l pa_simple_new(*server, name.p-ascii, dir.l, *dev, stream_name.p-ascii, ss.l, mp.l, attr.l, error.l)
Prototype.l pa_simple_read(*s, *Data, bytes.l, error.l)
Prototype.l pa_simple_free(*s)
Prototype.l pa_get_library_version()
Prototype.l pa_strerror(error.l)
Prototype.l pa_simple_flush(*s, error.l)
Procedure.i initLibrary()
; Open libPulse library for "simple" interface
pulseLib.i = OpenLibrary(#PB_Any, "libpulse-simple.so")
If pulseLib.i = 0
Debug "No pulse lib loaded. Check for 'libpulse-simple.so'."
ProcedureReturn #False
EndIf
; assign prototype functions
Global pa_get_library_version.pa_get_library_version = GetFunction(pulseLib, "pa_get_library_version")
Global pa_simple_new.pa_simple_new = GetFunction(pulseLib, "pa_simple_new")
Global pa_strerror.pa_strerror = GetFunction(pulseLib, "pa_strerror")
Global pa_simple_read.pa_simple_read = GetFunction(pulseLib, "pa_simple_read")
Global pa_simple_free.pa_simple_free = GetFunction(pulseLib, "pa_simple_free")
Global pa_simple_flush.pa_simple_flush = GetFunction(pulseLib, "pa_simple_flush")
ProcedureReturn #True
EndProcedure
Procedure shutdown()
closeStream()
If IsLibrary(pulseLib): CloseLibrary(pulseLib): EndIf
EndProcedure
Procedure.s errToStr(error.i)
Protected p.i = pa_strerror(error.i)
ProcedureReturn "Error " + Str(error.i) + ": " + PeekS(p.i, -1, #PB_Ascii)
EndProcedure
Procedure.s getVersion()
Protected p.i = pa_get_library_version()
ProcedureReturn "V" + PeekS(p.i, -1, #PB_Ascii)
EndProcedure
Procedure.i closeStream()
If stream.i <> 0
pa_simple_free(stream.i)
stream.i = 0
EndIf
If *libBuf <> 0
FreeMemory(*libBuf)
*libBuf = 0
EndIf
ProcedureReturn #True
EndProcedure
; Initialize a new recording stream
; appName and streamName are free to choose (eg use your app name)
; deviceName must be a PulseAudio input devide like "alsa_input.pci-0000_00_1b.0.analog-stereo"
; You can get a list on your PC using this command-line:
; pactl list | grep Name
Procedure.i initStream(appName.s, streamName.s = "", deviceName.s = "")
If IsLibrary(pulseLib) = 0
Debug "Need to initialize the lib first using initLibrary() function."
ProcedureReturn 0
EndIf
If streamName.s = "": streamName.s = appName.s: EndIf
; close previous stream
closeStream()
Protected error.l = 0
; open record stream
*ss.pa_sample_spec = AllocateMemory(SizeOf(pa_sample_spec))
*ss\channels = 2
*ss\rate = 44100
*ss\pa_sample_format = #PA_SAMPLE_S16LE
If deviceName.s = ""
stream.i = pa_simple_new(0, appName.s, #PA_STREAM_RECORD, 0, streamName.s, *ss, 0, 0, @error.l)
Else
Protected p.s = Space(Len(deviceName))
PokeS(@p.s, deviceName.s, -1, #PB_Ascii)
stream.i = pa_simple_new(0, appName.s, #PA_STREAM_RECORD, @p, streamName.s, *ss, 0, 0, @error.l)
EndIf
If error.l <> 0 Or stream.i = 0
ProcedureReturn error.l
EndIf
*libBuf = AllocateMemory(#PA_BUFSIZE)
ProcedureReturn 0 ; success
EndProcedure
; Get a buffer of audio data and calcumate PEAK and RMS values of it
Procedure.i getVU(*values.vu_result)
If IsLibrary(pulseLib) = 0
Debug "Need to initialize the lib first using initLibrary() function."
ProcedureReturn -1
EndIf
If stream.i = 0
Debug "Need to open a stream first using initStream() function."
ProcedureReturn -1
EndIf
Protected error.l = 0
If pa_simple_read(stream.i, *libBuf, #PA_BUFSIZE, @error.l) < 0
Debug "pa_simple_read() failed: " + errToStr(error.l)
ProcedureReturn error.l
EndIf
Protected levLeft.i = 0, levRight.i = 0
Protected peakLeft.i = 0, peakRight.i = 0
Protected rmsLeft.q = 0, rmsRight.q = 0
Protected x.i = 0
For x = 0 To #PA_BUFSIZE - 1 Step 4
levLeft = Abs(PeekW(*libBuf + x))
levRight = Abs(PeekW(*libBuf + x + 2))
If levLeft > peakLeft: peakLeft = levLeft: EndIf
If levRight > peakRight: peakRight = levRight: EndIf
rmsLeft = rmsLeft + (levLeft * levLeft)
rmsRight = rmsRight + (levRight * levRight)
Next
Protected dbFact.i = 4096
*values\rmsLeft = Sqr(rmsLeft / (#PA_BUFSIZE / 4))
*values\rmsRight= Sqr(rmsRight / (#PA_BUFSIZE / 4))
*values\peakLeft = peakLeft
*values\peakRight = peakRight
*values\dbLeft = dbFact.i * Log10(*values\rmsLeft)
*values\dbRight = dbFact.i * Log10(*values\rmsRight)
ProcedureReturn 0
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
; PulseAudio test and example code
If pulse::initLibrary()
Debug "Initialized PulseAudio " + pulse::getVersion()
Define err.i = pulse::initStream("PulseAudio Test", "record")
Define values.pulse::vu_result ; define return value
If err.i <> 0
Debug pulse::errToStr(err.i)
Else
; get signals for the next 5 seconds (this is demo only!)
Define start.i = ElapsedMilliseconds()
Repeat
pulse::getVU(values)
; output the values
Debug Str(values\peakLeft) + #TAB$ +
Str(values\peakRight) + #TAB$ +
Str(values\rmsLeft) + #TAB$ +
Str(values\rmsRight)
Until ElapsedMilliseconds() > start.i + 1000
pulse::closeStream()
EndIf
EndIf
pulse::shutdown()
CompilerEndIf