Index  Referenz Richtlinien Bemerkungen 

Die Audiobibliothek aulib++

Das Dokument gibt eine kurze Übersicht über die Audiobibliothek:
  1. Audiodateien in aulib++
  2. AudioCache
  3. Einfache Analysewerkzeuge
  4. Schnelle Fourier-Transformation
  5. Abspielen von Audiodateien

1. Audiodateien in aulib++

Die Klassen: AudioData, AudioFile, WaveFile

AudioData

Die Bibliothek ermöglicht einen transparenten Zugriff auf Audiodateien. Wer mit oder für aulib entwickelt, muß sich um das Format der Audiodaten nicht kümmern. Audiodateien werden in Objekten der Klasse AudioData gekapselt. Die Klasse ist somit eine Art Proxy. AudioData ist eine abstrakte Klasse; sie nennt lediglich die Methoden, mit deren Hilfe Audiodateien geöffnet, bearbeitet und geschlossen werden können. Für jedes Audioformat wird von AudioData eine konkrete Klasse abgeleitet. Diese spezielle Klasse füllt die abstrakten Methoden mit formatabhängiger Funktionalität.

Um völlig formatunabhängig zu arbeiten, sollte an keiner Stelle des Programms der Name einer konkreten Klasse erscheinen. Wer die Audiodateien über eine Schnittstelle von außen her erhält, sollte als Parametertyp AudioData nennen:

Wer selbst AudioData-Objekte erzeugt, sollte zu der AudioFactory greifen. Diese Klasse bietet die static Methode openFile(string) an. Sie nimmt den Namen einer Audiodatei und liefert ein entsprechendes AudioData-Objekt zurück: Zur Zeit erkennt openFile() das Format an der Endung des Dateinamens. Es ist aber denkbar, daß in einer späteren Version der Inhat - sprich der Header - der Datei überprüft wird.

Zur Zeit existieren die konkreten Klassen AudioFile und WaveFile. Die erste ist für SUN-au Dateien vorgesehen, die andere ermoeglicht das Arbeiten mit WAV-Dateien.

AudioFile

Die Klasse AudioFile stellt eine Datei im Sun Audio File-Format dar. Eine solche Datei hat folgenden Aufbau:
 
Offset Bytes Bemerkungen
00H 4 Signatur '.snd'
04H 4 Data Location
08H 4 Data Size
0CH 4 Data Format
10H 4 Sampling Rate (Hz)
14H 4 Channel Count
18H n Char Info (ASCII)
 
Danach folgen die Audiodaten. Data Location ist ein Zeiger auf den Anfang des Datenbereiches. Data Size gibt die Laenge des Datenbereiches in Byte an. Data Format gibt das Format der Daten an, insb. die Groesse der Abtastwerte. Es sind u.a. folgende Formate bekannt:
 
Inhalt von Data Format Bedeutung wird unterstuetzt
1 8 Bit mu-law ja
2 8 Bit linear ja
3 16 Bit linear ja
4 24 Bit linear nein
5 32 Bit linear nein
6 Floating point nein
7 Double precision nein
8 Formated sampled data nein
... ... ...
 
Die Audiodaten sind eine Sequenz von Werten der in Data Format festgelegten Groesse (1 Byte, 2 Byte,... ).

Um mu-law Werte lesen/schreiben zu koennen, verfuegt AudioFile ueber Umrechnungstabellen und Umrechnungsfunktionen, die aus mu-law Samples 8 Bit grosse lineare Werte erzeugen und umgekehrt.

8 Bit grosse Abtastwerte werden beim Kopieren in den AudioCache (intern 16 Bit linear) nicht auf 16 Bit expandiert und beim zurueckschreiben in eine Datei nicht wieder komprimiert. Formate mit Abtastwerten ueber 16 Bit werden zur Zeit nicht unterstuetzt. AudioFile akzeptiert Dateien ohne Header. In solchen Faellen wird 8 Bit mu-law angenommen.

zu Implementation:

Die Transformation der 4 Byte grossen Angaben im Header in einen 'unsigned int' erfolgt mittels expliziter Typkonversion. Damit die Konversion korrekt arbeitet, ist es notwendig zu wissen, ab der Rechner eine "Big endian"- (z.B. IBM 370, Motorola 68000 und Sun Sparc) oder eine "Little endian"- Maschine ist (z.B. 80486, pentium  und VAX). Im zweiten Fall muss beim Uebersetzen von aulib++ das Makro DAMN_INTEL_BYTE_ORDER definiert sein.

noch zu tun:

  1. Die Umrechnung 4 Byte -> unsigned int ohne explizite Typkonversion implementieren (siehe WaveFile).
  2. Mehr Datenformate, insb. Expandieren (bzw. Komprimieren) von Abtastwerten auf 16 Bit linear beim Laden der Samples in den AudioCache.

WaveFile

Die Klasse WaveFile stellt Dateien im Windows WAVE-Format dar. Eine solche Datei hat folgenden Aufbau:
 
RIFF-Chunk
Format-Chunk
Data-Chunks...

Der RIFF-Chunk und der Format-Chunk bilden den WAVE-Header. Der RIFF-Chunk hat die Struktur:
 
Offset Bytes Bemerkungen
00H 4 Signatur 'RIFF'
04H 4 Chunk-Laenge
08H 4 Signatur 'WAVE'

Der Format-Chunk besitzt folgenden Aufbau:
 
Offset Bytes Bemerkungen
00H 4 Signatur 'FMT '
04H 4 Chunk-Laenge
08H 2 Format Typ:
0 = Mono
1 = Stereo
0AH 2 Kanalzahl
0CH 4 Sample Rate [Hz]
10H 4 Bytes pro Sekunde
14H 2 Bytes pro Sample
1 = 8 Bit Mono
2 = 8 Bit Stereo oder 16 Bit Mono
4 = 16 Bit Stereo
16H 2 Bits pro Sample

Aus 'Format Typ' und 'Bytes pro Sample' wird die in aulib++ verwendete Codierung von Datenformat errechnet:
 
Format Typ Bytes pro Sample Format Code
0 (Mono) 1 8 Bit linear (lin8)
0 2 16 Bit linear (lin16)
1 (Stereo) 2 8 Bit linear
1 4 16 Bit linear

Es sind folgende Abtastfrequenzen moeglich: 11025 Hz (11 2B 00 00H), 22050 Hz (22 56 00 00H), 44100 Hz (44 AC 00 00H). 'Bytes pro Sekunde' ist der Wert, der sich aus der Kanalzahl, Abtastrate und der Anzahl von Bytes pro Sample ergibt. 'Bits pro Sample' kann 8 im Falle von 1 Byte grossen Abtastwerten bzw. 12 oder 16 bei 2 Byte grosse Abtastwerten betragen.

An den WAVE-Header schliessen sich einer oder mehrere Data-Chunks an. Jeder hat die Struktur:
 
Offset Bytes Bemerkung
00H 4 Signatur 'data'
04H 4 Chunk-Laenge
08H n Daten

Die Daten sind als eine Sequenz von Werten des im Header spezifizierten Formats abgelegt. Bei Stereo-Aufnahmen werden die Werte fuer den linken und rechten Kanal wechselweise gespeichert.

Es ist zu beachten, dass die Daten wortweise organisiert sind. Besitzt ein Chunk (insb. ein Data-Chunk) eine ungerade Anzahl von Bytes, so wird er mit einem Nullbyte aufgefuellt. Die Laengenangabe umfasst ihn allerdings nicht!

WaveFile kann nur auf den ersten Chunk einer WAVE-Datei zugreifen. In Normallfall duerfte es ausreichen, denn die meisten Dateien weisen nur einen solchen auf.

2. AudioCache

Die Klasse AudioCache

Eine Implementierung von AudioData mit freiem Zugriff auf einzelne Abtastwerte waere ineffizient. Ausserdem arbeiten die meisten Analysewerkzeuge nicht mit einzelnen Werten sondern mit ganzen Signalausschnitten. Jedes AudioData-Objekt verfuegt deshalb ueber einen sog. Audiocache.

Die Klasse AudioCache stellt einen sequentiellen Container dar, in dem eine beliebige Anzahl von Abtastwerten gespeichert werden kann. Das Datenformat der gespeicherten Werte ist 16 Bit linear. Es sind mono-Daten. Die Abtastfrequenz wird bei der erzeugung des AudioCaches festgelegt und kann danach nicht mehr veraendert werden. Es ist insbesondere nicht moeglich den Zuweisungsoperator (operator=) fuer zwei AudioCache-Objekte mit unterschiedlichen Abtastraten aufzurufen, da eine Umrechnungsfunktion der Art: "Daten bei Abtastfrequenz 1 -> dieselben Daten bei Abtastfrequenz 2" fehlt.

Die Kapazitaet des Caches kann ebelfalls bei der Erzeugung angegeben werden, muss aber nicht. Vereingestellt ist die groesse 0. Es ist moeglich die Groesse des Containers nachtraeglich zu veraendern. Die Veraenderungen sind destruktiv, d.h. der Inhalt des Containers geht dabei verloren.

Der Zugriff auf den Inhalt des Containers erfolgt mittels operator[] oder mit Hilfe der Elementfunktion at(size_t). Im ersten Fall wird der Index nicht ueberprueft, at() wirft bei falscher Indizierung eine ausnahme aus.

Die meisten Analyse- und Bearbeitungswerkzeuge arbeiten mit Daten, die in einem AudioCache-Objekt gespeichert sind. AudioData verwendet AudioCache als Puffer, um den Zugriff auf den Inhalt von Audiodateien zu beschleunigen. Mittels der Methode AudioData::load(int) koennen Samples aus der Audiodatei in den Audiocache geladen werden. Die Methode AudioData::seek(int, AudioData::SeekMode) dient zum Positionieren des Lesezeigers in der Audiodatei.


Die Schnittstelle von AudioCache erinnert an die Containerklassen der STL, insb. die Klasse vector. Es fehlt allerdings ein Iterator. Es ist deshalb nicht moeglich, die Algorithmen aus der Standardbibliothek zu benutzen.

noch zu tun:

  1. Iterator-Klasse schreiben.
  2. Umrechnungsfunktion zum Veraendern der Abtastrate der gespeicherten Daten.
  3. Nichtdestruktive Veraenderung der Cache-Groesse (wenn ueberhaupt noetig!).

3. Einfache Analysewerkzeuge

Die Klassen: AuAmplitude, AuEnergy, AuFuF, AuLength, AuThreshold

4. Schnelle Fourier-Transformation

Die Klassen: ComplexFFT, LinearFFT

ComplexFFT

aulib++ bietet zwei Werkzeuge fuer die schnelle Fourier-Transformation. ComplexFFT implementiert die "klassische" FFT, d.h. die Transformation mit komplexwertigen Zeit- und Frequenzdomaene.

Die Benutzung von ComplexFFT ist einfach. Bei der Erzeugung eines Werkzeugs werden die gewuenschte Fenstergroesse sowie die Fensterfunktion angegeben. Die Funktion fft(ComplexWave&) transformiert das Signal aus dem Zeit- in das Frequenzbereich, invfft(ComplexWave&) bewirkt das Gegenteil. Das Signal wird jeweils als ein Vektor von komplexen Zahlen uebergeben. Die Groesse des Vektors muss mit der voreingestellten Fenstergroesse exakt uebereinstimmen. Die Transformation ist destruktiv, d.h. die Daten gehen verloren und nach der Operation befindet sich in dem Vektor das Ergebnis.

Die Fenstergroesse und die Fensterfunktion koennen nachtraeglich veraendert werden. Obwohl die FFT ausschliesslich Fenstergroessen zulaesst, die 2-Potenzen sind, kann ein beliebiger Wert angegeben werden. ComplexFFT passt den Wert automatisch an:

    benutzter Wert = Max { i | i <= angegebener Wert, i = 2^k }.

Folgende Fensterfunktionen stehen zur Verfuegung: Hanning, Hamming und Rechteck (= keine).

zu Implementation:

Da von ComplexFFT die Klasse LinearFFT abgeleitet wurde, besteht die 'public' Schnittstelle aus virtuellen Funktionen. Die wesentlichen Teile der Transformation wurden dagegen als nicht-virtuelle Funktionen implementiert und als 'private' deklariert. Das duerfte die Leistungseinbussen im Vergleich zu der originalen C-Implementation in Grenzen halten.

noch zu tun:

  1. Der Copy-Konstruktor und der Zuweisungsoperator fehlen noch.
  2. Eventuell waeren noch weitere Fensterfunktionen zu implementieren.

LinearFFT

Da die Abtastwerte keine komplexen Zahlen sind, ist die Verwendung von ComplexFFT fuer Audiodaten umstaendlich. Jedes Sample muss zunaechst in einen komplexen Wert umgerechnet werden, und das komplexwertige Ergebnis der Transformation muss dann wieder auf eine reelle (oder ganze) Zahl abgebildet werden. Die Klasse LinearFFT implementiert eine spezielle Art der Fourier-Transformation. LinearFFT fuehrt diese Umrechnungen selbst durch. Die Funktion fft(const AudioCache&, FFTData&) nimmt das in AudioCache gespeicherte Signal und schreibt sein Frequenzspektrum in FFTData. invfft(const FFTData&, AudioCache) transformiert das Frequenzspektrum aus FFTData in die Zeitdomaene und speichert das Ergebnis in AudioCache ab.  FFTData ist dabei ein vector.

LinearFFT ist eine Template-Klasse. Der Template-Parameter bestimmt, welchen Typ die Werte von FFTData haben. So ist es moeglich, ja nach Bedarf z.B. int oder double zu verwenden.

Wie die Umrechnung von komplexen zu reellen Werten erfolgt, bestimmt die Konversionsmethode. Sie kann bei der Erzeugung eines Werkzeugs angegeben werden. Es stehen folgende Umrechnungsarten zur Verfuegung: AMP, DB, SQR, PHASE und ADD.

PerformFFT

5. Abspielen von Audiodateien

Die Klasse AFPlayer  

Last modified: $Id: overview.htm,v 1.1 1998/02/06 09:51:52 tomczyk Exp tomczyk $