Audio-Recording mit OpenAL

Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Neue Antwort erstellen

Midimaster

Betreff: Audio-Recording mit OpenAL

BeitragDi, Jan 26, 2010 20:58
Antworten mit Zitat
Benutzer-Profile anzeigen
Audio-Recording mit OpenAL

In diesem Tutorial lernst Du, wie Du mit BMax den Audio-In des Computers anzapfen kannst. So kannst Du Eingaben über das Mikrofon machen, aber auch Geräusche anderer Programme abgreifen und als WAV-Datei abspeichern.

(In dieses Tutorial dürfen gerne weitere Beiträge dazugeschrieben werden. auch Fragen von Hilfesuchenden sind hier gern gesehen.)

Lektion I: Installation und Starten Arrow Lektion I

Lektion II: Abholen der Samples Arrow Lektion II

Lektion III: kontinuierliches Abholen der Samples Arrow Lektion III

Lektion IV: Speichern als WAV-Datei Arrow Lektion IV

Anhang: Probleme auf dem Mac OsX Arrow zur Fehlermeldung
  • Zuletzt bearbeitet von Midimaster am Mi, Jan 27, 2010 14:38, insgesamt 7-mal bearbeitet

Midimaster

Betreff: Vorbereitung OpenAL

BeitragDi, Jan 26, 2010 21:00
Antworten mit Zitat
Benutzer-Profile anzeigen
Vorbereitung OpenAL


Zunächst testet diese Zeile, ob auf dem Rechner überhaupt schon OpenAL installiert ist:

BlitzMax: [AUSKLAPPEN]
If OpenALInstalled()=False Then 
RuntimeError "Bitte OpenAL installieren!"
EndIf



Hinweis zur Installation:

OpenAL ist frei verfügbar und wird von CreativLabs auf einer speziellen Homepage zum Download angeboten:

OpenAl-Homepage:
http://connect.creativelabs.co...fault.aspx

Download-Link dort:
http://connect.creativelabs.co...alinst.zip

Die ZIP-Datei enthält nur einen Installer, der die OpenAL32.dll installiert. Nach der Installation steht OpenAL sofort zur Verfügung. Auf dem Mac OsX ist OpenAL bereits standardmäßig vorinstalliert. Hier ist eine Installation (eigentlich) nicht nötig.


Die nächsten Zeilen wählen OpenAL als Audio-Treiber aus:

BlitzMax: [AUSKLAPPEN]
EnableOpenALAudio()
SetAudioDriver("OpenAL")


Als nächster Schritt muss ein Device geöffnet werden, unter dem wir OpenAL später ansprechen:
BlitzMax: [AUSKLAPPEN]
Global Device%
Device=alcOpenDevice(Null)
If Device=Null Then
RuntimeError "Kein Zugriff auf Open-AL-Device möglich!"
EndIf


Innerhalb des OpenAL kann man nun ein weiteres Spezial-Device für Recording öffnen. Allerdings könnte diese Fähigkeit am Computer gar nicht vorhanden sein, deshalb wird zunächst geprüft, ob der Computer bzw. das OpenAL Recording überhaupt beherrscht:

BlitzMax: [AUSKLAPPEN]
Global CaptureDevice%
If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then
RuntimeError "Kein Recording möglich!"
EndIf

CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5);
If Not(CaptureDevice) Then
RuntimeError "Kein Zugriff auf Open-AL-CAPTURE-Device möglich!"
EndIf


Jetzt haben wir das Recording-Device geöffnet. Dabei müssen fünf Parameter übergeben werden:

Null = immer Null

44100 = Sampling Rate. 44100 bedeutet 44100 Samples pro Sekunde (CD-Qualität). Häufig werden auch noch die Werte 22050 oder 11025 verwendet. Der Qualitätsverlust ist bei den Höhen zu hören.

AL_FORMAT_MONO16 = eine spezielle Konstante beschreibt zwei weitere Qualitätsmerkmale. hier: eine MONO-Aufnahme, also nur 1 Kanal. Möglich wäre natürlich auch STEREO. Der zweite Wert definiert 16bit Auflösung (2 Bytes pro Sample). Alternativ kann auch 8bit (1 Byte pro Sample) verwendet werden.

44100*2*1*5 = legt die Größe des Ringbuffers fest.
Fausregel: Sampling-Rate * Auflösung * Kanäle * Sekunden

Aus diesen Werten erstellt OpenAL nun einen Ringbuffer in den die Daten des Audio-In fließen werden.
Das Eintragen der Daten in den Ringbuffer geschieht später im Hintergrund und völlig unabhängig von deinem Programm in einem zweiten Prozess. Die Daten werden kontinuierlich in den Ringbuffer eingetragen, auch wenn es in deinem Programm oder anderen Anwendungen zu Wartezeiten kommt. Dadurch ist gewährleistet, dass es keine "Aussetzer" im Audio-Material hörbar sein werden.

OpenAL wertet den AudioEingang aus und speichert jedes Ereignis dort als Sample in den Ringbuffer.
Jedes "Sample" hat immer die gleiche Größe von 1 (MONO8), 2 (MONO16 oder STEREO8) oder 4 Bytes (STEREO16). Solange der Buffer noch nicht voll belegt ist (hier 5 Sekunden Zeit), füllt OpenAL ihn an. Wenn er überzulaufen droht, werden einfach die ältesten Samples überschrieben. Man stellt sich den Speicher wie einen Ring vor, bei dem der Schreibvorgang die Daten immer Kreis herum auf den Ring schreibt. Der Lesezeiger rennt später dem Schreibzeiger immer hinterher. Ist der Schreibzeiger schneller, dann überrennt er den Lesezeiger, aber es werden eben keine Daten versehentlich außerhalb des Rings geschrieben. So kommt es auch zu keinem "Überlauf" oder einem "Schreibstopp".

In Lektion II wirst du lernen, wie man die Daten aus dem Ringbuffer abholt. Doch zunächst ist es wichtig, dass Du niemals vergisst, dass man Programme mit OpenAL nicht einfach beenden kann. Der Ringbuffer würde immer weiter laufen.

Deshalb darfst du am Ende des Programms nicht vergessen, beide Device zuerst zu schließen, bevor du das Programm beendest:

BlitzMax: [AUSKLAPPEN]
alcCloseDevice(CaptureDevice)
alcCloseDevice(Device)



Hier nun der komplette Code für Start und Ende einer OpenAL-Recording-Maschine:

BlitzMax: [AUSKLAPPEN]
If OpenALInstalled()=False Then 
RuntimeError "Bitte OpenAL installieren!"
EndIf

EnableOpenALAudio()
SetAudioDriver("OpenAL")

Global Device%
Device=alcOpenDevice(Null)
If Device=Null Then
RuntimeError "Kein Zugriff auf Open-AL-Device möglich!"
EndIf

Global CaptureDevice%
If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then
RuntimeError "Kein Recording möglich!"
EndIf

CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5);
If Not(CaptureDevice) Then
RuntimeError "Kein Zugriff auf Open-AL-CAPTURE-Device möglich!"
EndIf

'--------------------------------------------
' Main-Recording Schleife hier:
........
'--------------------------------------------

alcCloseDevice(CaptureDevice)
alcCloseDevice(Device)
End




Daten holen

Fortsetzung folgt....

Midimaster

Betreff: Abholen der Sample-Daten

BeitragMi, Jan 27, 2010 3:01
Antworten mit Zitat
Benutzer-Profile anzeigen
Abholen der Sample-Daten

Das Device ist nun für die Aufnahme vorbereitet. Allerdings arbeitet es noch nicht, d.h. der Ringbuffer ist reseviert, aber es werden noch keine Samples hineingeschrieben. Also starten wir den Hintergrundprozess des Schreibens in den Ringbuffers:
BlitzMax: [AUSKLAPPEN]
alcCaptureStart(CaptureDevice)

und so stoppst du ihn wieder
BlitzMax: [AUSKLAPPEN]
alcCaptureStop(CaptureDevice)


Nun kommmt der wichtigste Teil. Wir fragen zunächst OpenAL, wieviele Samples zum Abholen bereit stehen:
BlitzMax: [AUSKLAPPEN]
Global Anzahl%
alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Anzahl))
DebugLog Anzahl + " Samples stehen bereit"

dazu übergeben wir dieser Funktion einen Pointer zu einer 4-Byte langen Integervariable, hier: Anzahl%. Nach dem Aufruf der Funktion steht in Anzahl%, wieviele Samples bereitstehen.


Im letzten Schritt holen wir nun genau diese Anzahl Samples ab, indem wir sie in ein (BlitzMax)-TAudioSample kopieren lassen:
BlitzMax: [AUSKLAPPEN]
Global Klang:TAudioSample=CreateAudioSample(44100*5*2 , 44100 , SF_MONO16LE)
alcCaptureSamples(CaptureDevice, Klang.Samples, Anzahl)
Die Adresse Klang.Samples zeigt genau auf das erste Byte des TAudioSample-Speichers


Nun können wir die Aufnahme abspielen:

BlitzMax: [AUSKLAPPEN]
Global Musik:TSound=LoadSound(Klang)
PlaySound Musik



Hier nun die gesamte Recording-Schleife am Beispiel einer 2 Sekunden-Aufnahme:

BlitzMax: [AUSKLAPPEN]
'--------------------------------------------
' Main-Recording Schleife:

alcCaptureStart(CaptureDevice)
Delay 2*1000
alcCaptureStop(CaptureDevice)

Global Anzahl%
alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Anzahl))
DebugLog Anzahl + " Samples stehen bereit"

Global Klang:TAudioSample=CreateAudioSample(44100*5*2 , 44100 , SF_MONO16LE)
alcCaptureSamples(CaptureDevice, Klang.Samples, Anzahl)

Global Musik:TSound=LoadSound(Klang)
PlaySound Musik
'--------------------------------------------

Füge diesen Code in den Code von Lektion 1 ein

Midimaster

Betreff: Kontinuierliches Abholen

BeitragMi, Jan 27, 2010 4:16
Antworten mit Zitat
Benutzer-Profile anzeigen
Kontinuierliches Abholen

Im der letzten Lektion hast du gesehen, wie du eine Aufnahme in einem Stück erstellst. Natürlich ist es auch möglich die Daten laufend aus dem Ringbuffer zu holen, um sie sofort zu verarbeiten oder nach und nach zusammenzusetzen:

Der Code aus Lektion I bleibt fast unverändert. Nur die Main-Recording-Schleife wird angepasst:

BlitzMax: [AUSKLAPPEN]
alcCaptureStart(CaptureDevice)
Global Anzahl%, Summe%, HolZeit%
Global Spuren=1
Global Aufloesung=2
Global GesamtSpeicherPlatz%= 44100 * Spuren * Aufloesung% * 240
Global Klang:TAudioSample=CreateAudioSample(GesamtSpeicherPlatz , 44100 , SF_MONO16LE)

Wir benötigen einige neue Variablen:

Spuren% enthält eine 1 (MONO) oder eine 2 (STEREO)
Aufloesung% enthält eine 1 (8bit) oder eine 2 (16bit)
Summe% wird sich den bereits für die Aufnahme verbrauchten Speicher (in Bytes) merken
Außerdem erschaffen wir ein TAudioSample, das für 240 Sekunden Aufnahme reicht.

Wir rufen die Audio-Funktion nur noch alle 500msec auf:
BlitzMax: [AUSKLAPPEN]
Repeat
If HolZeit<MilliSecs()
HolZeit=MilliSecs() +500
alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Anzahl))
....


und übergeben dieses Mal nicht den Anfang des Klang-Speichers, sondern zählen kontinuierlich die bereits geholten Bytes dazu:
BlitzMax: [AUSKLAPPEN]

.....
If (Summe + (Anzahl*Spuren*Aufloesung)) < GesamtSpeicherPlatz Then
alcCaptureSamples(CaptureDevice, Klang.Samples+Summe, Anzahl)
Summe= Summe + (Anzahl * Spuren* Aufloesung)
EndIf
EndIf
....

Wichtig is dabei, dass nicht die Anzahl der Samples summiert wird, sondern die Anzahl der Bytes. Da ein Sample 1, 2 oder 4 Bytes groß sein kann muss die Summe unter Berücksichtigung von Spuren und Auflösung errechnet werden. Wichtig: Achte peinlich darauf, dass die Summe plus die Samples nicht über den reservierten Klang-Speicher hinauszeigen.



Hier nun das gesamte Recording-Programm

BlitzMax: [AUSKLAPPEN]
Graphics 800,600
If OpenALInstalled()=False Then
RuntimeError "Bitte OpenAL installieren!"
EndIf

EnableOpenALAudio()
SetAudioDriver("OpenAL")

Global Device%
Device=alcOpenDevice(Null)
If Device=Null Then
RuntimeError "Kein Zugriff auf Open-AL-Device möglich!"
EndIf

Global CaptureDevice%
If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then
RuntimeError "Kein Recording möglich!"
EndIf

CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5);
If Not(CaptureDevice) Then
RuntimeError "Kein Zugriff auf Open-AL-CAPTURE-Device möglich!"
EndIf

' Main-Recording Schleife:
Global Anzahl%, Summe%, HolZeit%, AufnahmeAn%=1
Global Spuren=1
Global Aufloesung=2
Global GesamtSpeicherPlatz%= 44100 * Spuren * Aufloesung% * 240
Global Klang:TAudioSample=CreateAudioSample(GesamtSpeicherPlatz , 44100 , SF_MONO16LE)
Global Musik:TSound

alcCaptureStart(CaptureDevice)
Repeat
If (HolZeit<MilliSecs()) And (AufnahmeAn=1)
HolZeit=MilliSecs() +500
alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Anzahl))
Print Anzahl + " Samples stehen bereit"
If (Summe + (Anzahl*Spuren*Aufloesung)) < GesamtSpeicherPlatz Then
alcCaptureSamples(CaptureDevice, Klang.Samples+Summe, Anzahl)
Print "Samples kopiert auf " + Summe

Summe= Summe + (Anzahl * Spuren* Aufloesung)
EndIf
EndIf
If KeyHit(Key_R) Then
If AufnahmeAn=0
AufnahmeAn=1
alcCaptureStart(CaptureDevice)
Summe=0
EndIf
Else If KeyHit(Key_P) Then
If AufnahmeAn=0
Musik = LoadSound(Klang)
PlaySound Musik
EndIf
Else If KeyHit(Key_S) Then
If AufnahmeAn=1
AufnahmeAn=0
alcCaptureStop(CaptureDevice)
EndIf
EndIf
' Grafiken zeichnen, etc...
Flip 0
Until KeyHit(Key_Escape)

alcCaptureStop(CaptureDevice)
alcCloseDevice(CaptureDevice)
alcCloseDevice(Device)
End


In diesem Beispiel beginnt die Aufnahme sofort. Du kannst Sie aber mit "S" stoppen und mit "R" wieder starten. Das Aufgenommene lässt sich mit "P" abspielen.

Midimaster

Betreff: Speichern als WAV-Datei

BeitragMi, Jan 27, 2010 12:15
Antworten mit Zitat
Benutzer-Profile anzeigen
Speichern als WAV-Datei

Hier lernst du eine Möglichkeit kennen, die Aufnahme als WAV-Datei abzuspeichern.

Zunächst bieten wir dem User an, den Dateinamen in einer FileSelectBox festzulegen:
BlitzMax: [AUSKLAPPEN]
Local Datei$ = RequestFile$("Song speichern...", "Audio:wav", True)
Local SndBank:TBank, FileStream:TStream
Local BitRate = 16

SndBank = CreateStaticBank(Klang.samples, Summe)

Dann reservieren wir uns einen Speicherbereich als TBank und füllen ihn mit den Aufnahme-Daten.


Jetzt erstellen wir den Header der WAV-Datei:
BlitzMax: [AUSKLAPPEN]
FileStream = WriteStream(Datei$)
If FileStream Then
fileStream.WriteString("RIFF") ' "RIFF" fester Header ( immer 4 bytes)
fileStream.WriteInt(Summe + 40) ' Dateigröße
fileStream.WriteString("WAVE") ' "WAVE" fester Header ( immer 4 bytes)
fileStream.WriteString("fmt ") ' "fmt " fester Header ( immer 4 bytes)
fileStream.WriteInt(16) 'size of WAVE section chunk
fileStream.WriteShort(1) 'WAVE type format
fileStream.WriteShort(Spuren) 'mono/stereo
fileStream.WriteInt(Klang.Hertz) 'Sample-Rate: 44100
fileStream.WriteInt(Klang.Hertz * Spuren*Aufloesung) 'Bytes/sec
fileStream.WriteShort(Spuren*Aufloesung)' Bytes/Sample
fileStream.WriteShort(BitRate) ' Aufloesung: 8 oder 16
fileStream.WriteString("data") ' "data" fester Header ( immer 4 bytes)
fileStream.WriteInt(Summe) 'Größe der Sample-Daten)

Viele der Werte sind fixe Buchstabenkombinationen.

Danach fügen wir die eigentlichen Klangdaten ein:

BlitzMax: [AUSKLAPPEN]
    SndBank.Write(FileStream, 0, Summe)
CloseStream FileStream



Die komplette Funktion:

BlitzMax: [AUSKLAPPEN]
Function SaveAufnahme()
Local Datei$ = RequestFile$("Song speichern...", "Audio:wav", True)
Local SndBank:TBank, FileStream:TStream
Local BitRate = 16
SndBank = CreateStaticBank(Klang.samples, Summe)

FileStream = WriteStream(Datei$)
If FileStream Then
fileStream.WriteString("RIFF") ' "RIFF" fester Header ( immer 4 bytes)
fileStream.WriteInt(Summe + 40) ' Dateigröße
fileStream.WriteString("WAVE") ' "WAVE" fester Header ( immer 4 bytes)
fileStream.WriteString("fmt ") ' "fmt " fester Header ( immer 4 bytes)
fileStream.WriteInt(16) 'size of WAVE section chunk
fileStream.WriteShort(1) 'WAVE type format
fileStream.WriteShort(Spuren) 'mono/stereo
fileStream.WriteInt(Klang.Hertz) 'Sample-Rate: 44100
fileStream.WriteInt(Klang.Hertz * Spuren*Aufloesung) 'Bytes/sec
fileStream.WriteShort(Spuren*Aufloesung)' Bytes/Sample
fileStream.WriteShort(BitRate) ' Aufloesung: 8 oder 16
fileStream.WriteString("data") ' "data" fester Header ( immer 4 bytes)
fileStream.WriteInt(Summe) 'Größe der Sample-Daten)
SndBank.Write(FileStream, 0, Summe)
CloseStream FileStream
EndIf
End Function

Midimaster

BeitragMi, Jan 27, 2010 14:36
Antworten mit Zitat
Benutzer-Profile anzeigen
[reserviert für Erweiterungen]
 

danielos

BeitragSa, Feb 27, 2010 21:56
Antworten mit Zitat
Benutzer-Profile anzeigen
Super Tutorial, vielen Dank, das interessiert mich sehr! Very Happy

tft

BeitragSo, Feb 28, 2010 15:19
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo.

das Tut ist super ..... Ich werde mal versuchen die sache in B3D umzusetzen.

Gruss TFT
TFT
https://www.sourcemagic.ch
Monkey,HTML5,CSS3,W 10 64 Bit, 32 GB Ram, GTX Titan, W8 ist Müll !!!!!!
 

barratator

BeitragDi, März 30, 2010 12:50
Antworten mit Zitat
Benutzer-Profile anzeigen
Super Tutorial! Vielen Dank Smile


Hat vielleicht jemand eine Idee, wie man richtig über OpenAL Sound wieder ausgibt? Vorzugsweise das, was man grade von wenigen Mikrosekunden aufgenommen hat? Also praktisch als Echo?


Danke im Voraus Smile

Gruß
Bastian

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group