Open AL Issue

Started by Hardcoal, February 01, 2021, 21:04:17

Previous topic - Next topic

Hardcoal

ive been struggling with this OpenAL
Im trying to record sound..
but the command Device = alcOpenDevice(null) it finds no device..
i even tried to put the device name instead of null.. didnt work..

Im using blitzmax 1.50

here is the code.

SuperStrict

Rem
Use ESC to exit
End Rem

Framework brl.blitz
Import vertex.openal
Import brl.math
Import brl.max2d
Import brl.glmax2d
Import "float_ring_buffer.bmx"

TOpenAL.Open()
TOpenAL.InitAL()
TOpenAL.InitALC()

Global Device:Byte Ptr,  ..
        Context:Byte Ptr

Device = alcOpenDevice(Null)
If Device = Null Then Notify "device is null" ; End
Context = alcCreateContext(Device, Null)
alcMakeContextCurrent(Context)

WriteStdout("List of capture devices:~n")
For Local CaptureDevice:String = EachIn EnumCaptureDevices()
WriteStdout(CaptureDevice + "~n")
Next
WriteStdout("#Default: " + String.FromCString( ..
alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)))

Graphics(800, 600)

Const SAMPLE_RATE:Int = 44100,  ..    'In Hertz [Hz]
      BUFFER_SIZE:Int = SAMPLE_RATE 'In Samples
'Null means using default capture device
Global CaptureDevice:Byte Ptr
CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, BUFFER_SIZE)
If CaptureDevice = Null Then Notify ("CaptureDevice is null") ; End

Global Data:Byte[] = New Byte[BUFFER_SIZE * 2] '16 Bit per sample
Global RingBuffer : TFloatRingBuffer = TFloatRingBuffer.Create(BUFFER_SIZE)
Global Samples : Float[] = New Float[GraphicsWidth()]

alcCaptureStart(CaptureDevice)

While Not KeyDown(KEY_ESCAPE)
' Read Samples
Local NumAvailableSamples:Int
alcGetIntegerv(CaptureDevice , ALC_CAPTURE_SAMPLES, 4, Varptr(NumAvailableSamples))
If NumAvailableSamples > 0 Then
alcCaptureSamples(CaptureDevice, Data, NumAvailableSamples)
For Local I:Int = 0 Until NumAvailableSamples
Local Offset:Int = I*2 ' 16 Bit per sample
Local Sample:Float = DecodeSigned16BitLittleEndian(Data, Offset)
RingBuffer.Put(Sample)
Next
EndIf

If RingBuffer.GetSize() >= GraphicsWidth() Then
RingBuffer.PeakLast(Samples, 0, GraphicsWidth())
EndIf

' Root Mean Square
Local RMS:Float = 0
Local Size:Int = GraphicsWidth()
For Local I:Int = 0 Until Size
RMS = RMS + (Samples[I] * Samples[I])
Next
RMS = Sqr(RMS / Size)
Assert 0 <= RMS And RMS <= 1

Cls()
' Draw Intensity
DrawRect 0, 0, RMS * GraphicsWidth(), 20

' Draw wave form

Local OldX:Int=0 ; Local OldY:Float=GraphicsHeight()/2
For Local X:Int = 0 Until GraphicsWidth()
Local Y:Float = Samples[X] * 100 + GraphicsHeight()/2
DrawLine OldX, OldY, X, Y
OldX = X ; OldY = Y
Next

Flip()
Wend

alcCaptureStop(CaptureDevice)

alcMakeContextCurrent(Null)
alcDestroyContext(Context)
alcCloseDevice(Device)

TOpenAL.Close()
EndGraphics()
End

Function EnumCaptureDevices:String[]()
Local List       : Byte Ptr, ..
      Specifiers : String[], ..
      Specifier  : String

' Null-terminated specifier list
List = alcGetString(Null, ALC_CAPTURE_DEVICE_SPECIFIER)

' Separate specifier by null character
While List[0]
Specifiers = Specifiers[..Specifiers.Length + 1]
Specifiers[Specifiers.Length - 1] = String.FromCString(List)
List :+ Specifiers[Specifiers.Length - 1].Length + 1
Wend

Return Specifiers
End Function

Function DecodeSigned16BitLittleEndian:Float(Buffer:Byte Ptr, Offset:Int)
Local LowerB:Byte, HigherB:Byte
HigherB = Buffer[offset + 1]
LowerB = Buffer[offset]
Local SampleInt:Int = (HigherB Shl 8) | (LowerB & $ff)
If SampleInt & $8000 Then SampleInt = $FFFF8000 | (SampleInt & $7FFF)
Local Sample:Float = Float(SampleInt) / Float($7fff)
Return Sample
End Function


i also tried vanilla mode.. for openal.. but it uses other sets of commands and there is no Demo.. to learn from
Code

Dabz

#1
Do you have OpenAL installed?


If OpenALInstalled()=False Then
RuntimeError "Please install OpenAl"
EndIf

Global Device%
Device=alcOpenDevice(Null)
If Device=Null Then
RuntimeError "Cannot Open Device"
EndIf


Works on vanilla, not tried on NG!

If its not installed, you can nab the redist package here:-

https://community.pcgamingwiki.com/files/file/10-openal/

Dabz
Intel Core i5 6400 2.7GHz, NVIDIA GeForce GTX 1070 (8GB), 16Gig DDR4 RAM, 256GB SSD, 1TB HDD, Windows 10 64bit

Dabz

I've also spied this:-

https://mojolabs.nz/posts.php?topic=90830

Which should help, but I've never tried to capture sound from a device so sadly... This is all I have!

Dabz
Intel Core i5 6400 2.7GHz, NVIDIA GeForce GTX 1070 (8GB), 16Gig DDR4 RAM, 256GB SSD, 1TB HDD, Windows 10 64bit

Hardcoal

thanks, but ive failed solving it..
some commands in vertex mod are missing..
anyway.. it wont work..
Code

Midimaster

#4
Ok... 10 years ago I did a lot with OpenAl recording. Two of my music apps used OpenAl for recording or listening to the microphon (for real time FFT-analysing).

In this time I wrote this tutorial Dabz found in the old blitzmax forum. I can tell you it still works 100%

Today I testet my old apps, and both work as expected. The only difference to the times of old XP is, that now on Win-10 a real device needs to be connected.  At first I forgot to conncet a microphone and this was the reason why alcCaptureOpenDevice() failt. Without it, my apps will crash!

Now I reduced the code of the tutorial to a very minimum as you can see here: This code runs perfect if a microphone is attached to the computer.

For installation of OpenAL I still use the last "official Installer" oalinst.exe from 2009. It is a 800k-Exe file.

Here is my code:
Code (BlitzMax) Select
If OpenALInstalled()=False
  Notify "OpenAl Driver is missing"
  InstallOpenAl
EndIf
EnableOpenALAudio()

Global Device:Int=alcOpenDevice(Null)
If Device=Null Then RuntimeError "No access to OpenAL Device !"

Local flag%=InitCapture()
If flag=False Then  RuntimeError "No access to OpenAL capture Device !"

Global CaptureDevice:Int
alcCaptureStart(CaptureDevice)

Function InitCapture%()
CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_STEREO16, 44100*5*4);
    If Not(CaptureDevice) Then Return False
Return True
EndFunction

Function InstallOpenAl()
system_ "oalinst.exe"
Notify("Open AL Installer")
End
End Function


with this minimum code it will be more  easy for you to test and perhaps find a solution. I will also contact Brucey, whether he already knows a modern module with recording facilities.
...on the way to Egypt

Derron

I posted some similar piece of code in the discord channel


Enum capture devices:
Code (Blitzmax) Select

SuperStrict
Framework Brl.StandardIO
Import Brl.OpenALAudio
?linux
Import "-ldl" 'fix "undefined reference to "dlopen/dlsym"
?

EnableOpenALAudio()

print "OpenALInstalled: " + OpenALInstalled()
if not OpenALInstalled() then End


For local device:String = EachIn EnumOpenALCaptureDevices()
    print "capture device: " + device
Next


print "DEFAUlT capture device: " + GetOpenALDefaultCaptureDevice()

Function GetOpenALDefaultCaptureDevice:String()
    Return String.FromCString( alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER) )
End Function


Function EnumOpenALCaptureDevices:String[]()
    Local p:Byte Ptr = alcGetString(Null,ALC_CAPTURE_DEVICE_SPECIFIER )
    If Not p Return Null

    Local devices:String[100]
    Local n:Int
    While p[0] And n<100
        Local sz:Int
        Repeat
            sz:+1
        Until Not p[sz]

        devices[n] = String.FromBytes(p, sz)

        n :+ 1
        p :+ sz + 1
    Wend
    Return devices[.. n]
End Function



But trying to capure stuff fails - seems I miss something:
Code (Blitzmax) Select

'        device = alcCaptureOpenDevice( String.FromCString(alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)), 48000, AL_FORMAT_MONO16, 1024)
        device = alcCaptureOpenDevice( "960 Headset Analog Mono", 48000, AL_FORMAT_MONO16, 4*1024 )
...
        alcCaptureStart(device)
        Local numberOfFrames:Int
        alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, 1, Varptr numberOfFrames)
       
        print "captured: " + numberOfFrames
       
        if numberOfFrames
            Local bank:TBank = CreateBank(8*1024)

            alcCaptureSamples(device, bank._buf, numberOfFrames)
        EndIf



bye
Ron

Hardcoal

#6
hi midimaster..

since im using vertex demo to test recording.. i dont have a demo code for brl openaudioal mod

can you please post a full demo that works under this mod?

here is my code..
and the code you have posted wont recognize some of the commands..
it is unrecognized by vertex mod  like EnableOpenALAudio()


thats my corrent code


anyway midimaster, i tried your code and got "No access to OpenAL Device !" error

Strict

Rem
Use ESC to exit
End Rem

Import brl.blitz

Framework pub.openal
Import vertex.openal

Import brl.math
Import brl.max2d
Import brl.glmax2d
Import "float_ring_buffer.bmx"

Import pub.freeaudio

TOpenAL.Open()
TOpenAL.InitAL()
TOpenAL.InitALC()

Global Device:Byte Ptr,  ..
        Context:Byte Ptr

Device = alcOpenDevice(Null)
If Device = Null Then WriteStdout "Device is null when using alcOpenDevice~n"
Context = alcCreateContext(Device, Null)
alcMakeContextCurrent(Context)

WriteStdout("List of capture devices:~n")
For Local CaptureDevice:String = EachIn EnumCaptureDevices()
WriteStdout(CaptureDevice + "~n")
Next
WriteStdout("#Default: " + String.FromCString( ..
alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)))

Graphics(800, 600)

Const SAMPLE_RATE:Int = 44100,  ..    'In Hertz [Hz]
      BUFFER_SIZE:Int = SAMPLE_RATE 'In Samples
'Null means using default capture device
Global CaptureDevice:Byte Ptr
CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, BUFFER_SIZE)
If CaptureDevice = Null Then Notify ("CaptureDevice is null") ; End

Global Data:Byte[] = New Byte[BUFFER_SIZE * 2] '16 Bit per sample
Global RingBuffer : TFloatRingBuffer = TFloatRingBuffer.Create(BUFFER_SIZE)
Global Samples : Float[] = New Float[GraphicsWidth()]

alcCaptureStart(CaptureDevice)

While Not KeyDown(KEY_ESCAPE)
' Read Samples
Local NumAvailableSamples:Int
alcGetIntegerv(CaptureDevice , ALC_CAPTURE_SAMPLES, 4, Varptr(NumAvailableSamples))
If NumAvailableSamples > 0 Then
alcCaptureSamples(CaptureDevice, Data, NumAvailableSamples)
For Local I:Int = 0 Until NumAvailableSamples
Local Offset:Int = I*2 ' 16 Bit per sample
Local Sample:Float = DecodeSigned16BitLittleEndian(Data, Offset)
RingBuffer.Put(Sample)
Next
EndIf

If RingBuffer.GetSize() >= GraphicsWidth() Then
RingBuffer.PeakLast(Samples, 0, GraphicsWidth())
EndIf

' Root Mean Square
Local RMS:Float = 0
Local Size:Int = GraphicsWidth()
For Local I:Int = 0 Until Size
RMS = RMS + (Samples[I] * Samples[I])
Next
RMS = Sqr(RMS / Size)
Assert 0 <= RMS And RMS <= 1

Cls()
' Draw Intensity
DrawRect 0, 0, RMS * GraphicsWidth(), 20

' Draw wave form

Local OldX:Int=0 ; Local OldY:Float=GraphicsHeight()/2
For Local X:Int = 0 Until GraphicsWidth()
Local Y:Float = Samples[X] * 100 + GraphicsHeight()/2
DrawLine OldX, OldY, X, Y
OldX = X ; OldY = Y
Next

Flip()
Wend

alcCaptureStop(CaptureDevice)

alcMakeContextCurrent(Null)
alcDestroyContext(Context)
alcCloseDevice(Device)

TOpenAL.Close()
EndGraphics()
End

Function EnumCaptureDevices:String[]()
Local List       : Byte Ptr, ..
      Specifiers:String[]

' Null-terminated specifier list
List = alcGetString(Null, ALC_CAPTURE_DEVICE_SPECIFIER)

' Separate specifier by null character
While List[0]
Specifiers = Specifiers[..Specifiers.Length + 1]
Specifiers[Specifiers.Length - 1] = String.FromCString(List)
List :+ Specifiers[Specifiers.Length - 1].Length + 1
Wend

Return Specifiers
End Function

Function DecodeSigned16BitLittleEndian:Float(Buffer:Byte Ptr, Offset:Int)
Local LowerB:Byte, HigherB:Byte
HigherB = Buffer[offset + 1]
LowerB = Buffer[offset]
Local SampleInt:Int = (HigherB Shl 8) | (LowerB & $ff)
If SampleInt & $8000 Then SampleInt = $FFFF8000 | (SampleInt & $7FFF)
Local Sample:Float = Float(SampleInt) / Float($7fff)
Return Sample
End Function
Code

Midimaster

#7
My code should run as stand alone app. If not... the OpenAl maybe installed not correct. I will send you the original installer by CreativeLabs from 2009 in a pm
...on the way to Egypt

Derron

Think I got it working with pub.openal too.

I am not sure - but I assume it would be better to kick off audio recording in a new thread (dunno if openal can work in threads). And then to either define a "record X seconds" or a "record until stopped" ...
Another option is to call a callback each time a "second" is recorded (so you can fetch the audio data) or everytime the "buffer" is full.

Plenty of options.


bye
Ron

Hardcoal

#9
i never messed with threads , i have no idea how to do that..
anyway i keep testing..
eventually i must make it work or im stuck in the specific project :)

Derron if you got an example code.. ill be glad to see it because i only have example code for the vertex version.

Code

Derron

#10
You want to create some advanced stuff (your project).
I do not have any more clues than you about OpenAL - it is googling what the commands do (in C) and what samples do (in C)
and then trying to use these commands in Blitzmax.

Experiment with the code, look what others did with openal - and try to adopt the code, bit by bit.


bye
Ron

Hardcoal

#11
Ive experimented for hours.. Its not that i come here and expect people to feed me.
And ill keep trying, until ill succeed..

Thanks
Code

Midimaster

#12
also news from me. also my code works and my apps too. I simply forgot to connect a microphone to the computer. Without any input device the code fails. With a microphone connected it works. I updated my reply #4.
...on the way to Egypt

Derron

Here is another approach - just using "pub.openal":


Code (Blitzmax) Select

SuperStrict
Framework Brl.StandardIO
Import Brl.OpenALAudio
Import Brl.Bank
Import Brl.Threads
Import "-ldl" 'fix "undefined reference to "dlopen/dlsym"


EnableOpenALAudio()

print "OpenALInstalled: " + OpenALInstalled()
if not OpenALInstalled() then End


For local device:String = EachIn TOpenALCapture.EnumDevices()
print "capture device: " + device
Next

print "DEFAUlT capture device: " + TOpenALCapture.GetDefaultDevice()


local openALCapture:TOpenALCapture = new TOpenALCapture( TOpenALCapture.GetDefaultDevice() )
openALCapture.StartCapture()
For local i:int = 1 to 10
Delay(100)
print "waited "+(i*100)+"ms"
Next
openALCapture.StopCapture()
Local result:TBank = openALCapture.GetCapturedData()
SaveBank(result, "record.dat")


Type TOpenALCapture
Field deviceName:String
Field device:Byte Ptr
Field context:Byte Ptr
Field captureThread:TThread
Field captureThreadExit:Int
Field captureRunning:Int
Field capturedData:TBank
Field capturedDataFrames:Int = 0

Method New(deviceName:String)
SetDeviceName(deviceName)
End Method


Method SetDeviceName:TOpenALCapture(deviceName:String)
self.deviceName = deviceName
rem
context = alcCreateContext(device, Null)
If not context
print "cannot create context"
alcCloseDevice( device )
Return Null
EndIf

alcMakeContextCurrent(context)
endrem
Return Self
End Method


Method StartCapture:Int()
If captureThread Then Return False

capturedData = CreateBank(2048)
capturedDataFrames = 0
captureRunning = True

'device = alcCaptureOpenDevice( String.FromCString(alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)), 48000, AL_FORMAT_MONO16, 1024)
'device = alcCaptureOpenDevice( Null, 48000, AL_FORMAT_MONO16, 1024)
device = alcCaptureOpenDevice( deviceName, 48000, AL_FORMAT_MONO16, 1024)
If Not device
captureRunning = False
Throw "StartCapture: failed opening device"
Return False
EndIf

captureThread = CreateThread( RunCaptureThread, self )
if not captureRunning Then Return False

Return True
End Method


Method StopCapture:Int()
If not captureThread then Return False

captureThreadExit = True
WaitThread(captureThread)

captureThread = Null
_Close()

print "captured: " + capturedDataFrames + " frames."

Return True
End Method


'returns a window (static memory) to the captured data
Method GetCapturedDataWindow:TBank()
if not capturedData then return Null
Return capturedData.Window(0, capturedDataFrames)
End Method


'creates a new bank containing only the valid record data
Method GetCapturedData:TBank()
if not capturedData then return new TBank()
Local result:TBank = CreateBank(capturedDataFrames)
?bmxng
CopyBank(capturedData, 0:Size_T, result, 0:Size_T, Size_T(capturedDataFrames))
?not bmxng
CopyBank(capturedData, 0, result, 0, capturedDataFrames)
?
return result
End Method


Method OnCaptureData(data:TBank, capturedFrames:Int)
'print "OnCaptureData: could read " + capturedFrames +" captured frames."
if not capturedData then capturedData = CreateBank(4096)

if capturedData.Size() < capturedDataFrames + capturedFrames
'plus some space to avoid resizing it over and over
capturedData.Resize(capturedDataFrames + capturedFrames + 4096)
endif

capturedDataFrames :+ capturedFrames
End Method


Function RunCaptureThread:object( data:object )
local openALCapture:TOpenALCapture = TOpenALCapture(data)

alcCaptureStart(openALCapture.device)


Local availableFrames:Int
Local bank:TBank = CreateBank(4096)
Repeat
'how much is available to fetch?
alcGetIntegerv(openALCapture.device, ALC_CAPTURE_SAMPLES, 1, Varptr availableFrames)

if availableFrames > 0
'print "Capture size: " + availableFrames
if bank.Size() < availableFrames * 1 '* 2 if stereo
bank.Resize(availableFrames * 1 + 1024) 'with a little space on top
endif
alcCaptureSamples(openALCapture.device, bank.Lock(), availableFrames)
bank.Unlock()

openALCapture.OnCaptureData(bank, availableFrames)
endif

delay(10) 'sleep a bit

'received command to exit the thread
if openALCapture.captureThreadExit then exit
Forever

alcCaptureStop(openALCapture.device)

openALCapture.captureRunning = False
End Function


Method _Close()
If context Then alcDestroyContext(context)
'dunno, still get an "AL lib: (EE) alc_cleanup: 1 device not closed"
If device Then alcCloseDevice(device)
End Method


Method Delete()
_Close()
End Method


Function GetDefaultDevice:String()
Return String.FromCString( alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER) )
End Function


Function EnumDevices:String[]()
Local p:Byte Ptr = alcGetString(Null,ALC_CAPTURE_DEVICE_SPECIFIER )
If Not p Return Null

Local devices:String[100]
Local n:Int
While p[0] And n<100
Local sz:Int
Repeat
sz:+1
Until Not p[sz]

devices[n] = String.FromBytes(p, sz)

n :+ 1
p :+ sz + 1
Wend
Return devices[.. n]
End Function
End Type



What it does is recording (here for ... 1 second) and saving it to a "record.dat" file.

I assume one might also load that into a TSound to play it back or so.

Using threads allows to process other stuff in your app without being blocked by the capture / data processing in the capture.


bye
Ron

Hardcoal

#14
your example produced this error

Compile Error: Expression of type '<unknown>' cannot be invoked

when it reached this code line

local openALCapture:TOpenALCapture = new TOpenALCapture( TOpenALCapture.GetDefaultDevice() )

also i had to copy the 'Type TThread' Type from brl.mod threads.mod file.. because it didnt find this type for some reason..

although ive built the mod
Code