kramann.info
© Guido Kramann

Login: Passwort:










6.1 Erzeugen und Verarbeiten von .wav-Dateien in C/C++

  • Das .wav-Audio-Format ist ein nicht komprimierendes Speicherformat für Audiodateien.
  • Es ist möglich .wav-Dateien als Array in ein Scilab/C/C++/Java - Programm zu laden, oder umgekehrt die Daten für eine .wav-Datei zu generieren und dann zu speichern.

Eigenschaften der .wav-Dateien

  • Die ersten 44 Byte sind Header und enthalten Informationen über Samplingrate, Bitauflösung, Anzahl der Kanäle (Mono/Stereo) und die Anzahl der Bytes sowohl mit (restlichem), als auch ohne Haeder.
  • Direkt hinter dem Header folgen die unkomprimierten Datenbytes.
  • Bei einer Bitauflösung von 16Bit und einem Stereosignal folgen für jeden Samplingschritt 4 Byte:
  • Byte0: Niederwertiges Byte des rechten Kanals
  • Byte1: Höherwertiges Byte des rechten Kanals
  • Byte2: Niederwertiges Byte des rechten Kanals
  • Byte3: Höherwertiges Byte des rechten Kanals

Scilab - Verwenden von .wav-Dateien in Scilab

  • Das folgende Programm erzeugt zunächst eine Sinusschwingung von 220Hz und 660Hz (Quinte/2. Oberton zu 220Hz) in zwei Arrays.
  • Die Auflösung wird dabei so gewählt, dass sie einer Samplingrate von 44100Hz entspricht.
  • Die Amplitude darf dabei maximal +/-1 betragen und wird hier entsprechend kleiner gewählt.
  • Beide Arrays werden zu einem zusammengefügt und bilden dann ein Stereo-Signal.
  • Dieses wird abgespeichert und kann dann beispielsweise auch mit dem Multimediaplayer abgespielt werden.
  • Im Anschluß wird dieses File wieder in Scilab geladen.
  • Die Stereo-Kanäle werden zu einem Monosignal zusammengefaßt.
  • Vorbereitend auf eine Spektrumsanalyse wird ein Resampling durchgeführt.
  • D.h. die Samplerate wird auf 1/10 herabgesetzt, um den Umfang der zu transformierenden Daten zu reduzieren.
  • Da dann die Samplingrate immer noch bei 4410Hz liegt, können damit immer noch die beiden Tonsignale im Spektrum scharf getrennt werden.
//Stacksize erhöhen, wegen hohen Datenaufkommens:
stacksize(100000000);
//Sinustöne erzeugen und abspeichern:
fs=44100; //Samplingrate
f1=220; //Frequenz Tonsignal 1
f2=660; //Frequenz Tonsignal 2
y1=zeros(1,44100);
y2=zeros(1,44100);
t=0.0;
for i=1:44100
    y1(i) = 0.4*sin(2*%pi*f1*t);    
    y2(i) = 0.4*sin(2*%pi*f2*t);    
    t=t+(1/fs);
end
y = [y1;y2];
wavwrite(y,fs,'toene.wav');
//.wav-Datei laden und analysieren:
z = wavread('toene.wav');
mono = z(1,:)+z(2,:);
fs_neu = 4410;
t     = zeros(1,44100);
N     = 4410;
t_neu = zeros(1,N);
for i=1:44100
    t(i)=(i-1)*(1/fs);
end
for i=1:N
    t_neu(i)=(i-1)*(1/fs_neu);
end
mono_neu = interp1(t,mono,t_neu,'linear');
//plot(t_neu,mono_neu);
spektrum = abs(dft(mono_neu,-1));
//Bestimmung der Frequenzen in der Ordninate zum Spektrum (vergl. DFT in Vorlesung):
frequenzen = zeros(1,4410);
for i=1:N
    frequenzen(i) = ((i-1)*fs_neu)/N;
end
plot(frequenzen,spektrum);

Code 6.1-1: Verwenden von .wav-Dateien in Scilab

Plot des Spektrums

Bild 6.1-1: Plot des Spektrums

C/C++ - Verwenden von .wav-Dateien in C/C++

  • Die folgende Klasse "Ton" erlaubt es, .wav-Dateien zu laden und zu speichern.
  • Die Amplitude wird dabei auf einen double-Wert zwischen -1.0 und +1.0 umgesetzt.
  • Die Klasse ist darauf beschränkt, Stereo-.wav-Dateien mit einer Samplingrate von 44100Hz und 16Bit Bittiefe zu verarbeiten.
  • Anstatt alle Parameter des Headers neu zu schreiben, wird in einen vordefinierten Header lediglich die Filelänge an den beiden notwendigen Stellen hineingeschrieben.
  • Wavdateien speichern:
  • Zum Speichern einer .wav-Datei wird ein Objekt der Klasse Ton unter Angabe der Anzahl der Samples gebildet.
  • Mit den Methoden setLinks(long int i, double l) und setRechts(long int i, double l) können dann zunächst double-Werte in den dem linken bzw. rechten Kanal zugeordneten Array geschrieben werden.
  • Wenn die Audio-Datei vollständig ist, kann sie mit Hilfe der Methode speicher(string filename) abgespeichert werden.
  • Die Methode speicher(string filename) wird nun näher erläutert:
  • Mit folgendem Codeabschnitt wird die Anzahl der notwendigen Bytes bestimmt und in den Header eingetragen:
  • Dabei ist zu beachten, dass ab Index 4 des Headers auch der Rest des Headers als Bytezahl geschrieben werden muß.
  • An Index 40 des Headers (Array kopf) wird die eigentliche Bytezahl der Audiodatei geschrieben.
  • Diese entspricht der vierfachen Anzahl der Samples (Variable anzahl_aktuell).
  • Die Bytezahl wird als 4-Byte-unsigned-Integer-Zahl formatiert.
            z1 = anzahl_aktuell*4+36;
            z2 = anzahl_aktuell*4;
            //KOPF bilden
            for(int i=0;i<4;i++)
            {  
                zahl1[i] = z1%teiler;
                z1/=256;
                kopf[4+i] = zahl1[i];
            }
            for(int i=0;i<4;i++)
            {
                zahl2[i] = z2%teiler;
                z2/=256;
                kopf[40+i] = zahl2[i];
            }

Code 6.1-2: Header anpassen in Methode speicher(string filename) der Klasse Ton.

  • Um die Amplitudenwerte in ihre 16-Bit-Darstellung zu bringen, werden die Ausgangs-double-Werte zunächst in positive Integerzahlen umgerechnet (Variable gesamt).
  • Anschließend wird diese Zahl in ein niederwertiges und ein höherwertiges Byte aufgeteilt (Variablen hi und lo).
  • Schließlich müssen die so gewonnenen unsigned-character-Werte noch in signed character-Werte umgewandelt werden.
  • Diese Umrechnungen für den linken Kanal zeigt folgender Codeausschnitt:
                wert = links[i];
                gesamt = (int)(wert*gross + gross);    
                hi = gesamt/256;
                lo = gesamt - hi*256;
                zh = hi-128;
                if(lo<=127)
                    zl = lo;
                else        
                    zl = lo-256;                  
                file.put(zl);    
                file.put(zh);    

Code 6.1-3: Umwandlung von einer double-Zahl in ein 16-Bit-Wort und Abspeichern des Samples.

  • Wavdateien laden
  • Um eine Wavdatei zu laden wird der leere Konstruktor der Klasse Ton verwendet um ein Objekt zu erzeugen.
  • Bei Aufruf der Methode lade(string filename) passiert folgendes:
  • Aufgrund der Angaben im Header der zu ladenden .wav-Datei, wird die Sample-Anzahl gefunden (Variable anzahl_aktuell) und entsprechender Speicherplatz allokiert.
  • Für jedes Sample verläuft hier der Umrechnungsvorgang umgekehrt zum Speichervorgang:
                 zl = file.get();
                 zh = file.get();
                 if(zl>127)
                     zl-=256;
                 if(zh>127)
                     zh-=256;
                 lo     =  zl;
                 if(lo<0)
                     lo+=256;
                 gesamt = (zh+128)*256;
                 gesamt+=lo;
                 links[i] = ((double)gesamt - gross)/gross;        

Code 6.1-4: Umrechnung der 16-Bit-Worte in double-Werte beim Ladevorgang.

  • Das folgende Beispielprogramm erzeugt in den Objekten ton und ton3 jeweils Sinustöne der Frequenzen 441Hz, bzw. 882Hz.
  • Das ton-Objekt wird unter dem Namen "sinus2.wav" gespeichert, dann erneut in das Objekt ton2 geladen und wiederum unter "sinus3.wav" gespeichert.
#include<math.h>
#include<iostream>
#include<fstream>
using namespace std;
#include "Ton.h"
int main()
{
    long int anzahl_samples = 44100;
    Ton ton(anzahl_samples);
    Ton ton2(anzahl_samples);
    Ton ton3(anzahl_samples);
    double f1 = 441;
    double f2 = 882;
    double dt = 1.0/44100.0;
    double  t = 0.0;
    for(int i=0;i<anzahl_samples;i++)
    {
        ton.setLinks(i,0.0);
        ton.setRechts(i,0.0);
        t+=dt;
    }
    t = 0.0;
    for(int i=0;i<44100;i++)
    {
        ton.setLinks(i,0.9*sin(2.0*M_PI*f1*t));
        ton.setRechts(i,0.9*cos(2.0*M_PI*f2*t));
        ton3.setLinks(i,0.9*sin(2.0*M_PI*f1*t));
        ton3.setRechts(i,0.9*sin(2.0*M_PI*f1*t));
        t+=dt;
    }
    ton.speicher("sinus2.wav");   
    ton2.lade("sinus2.wav");   
    ton2.speicher("sinus3.wav");   
}

Code 6.1-5: Wavdateiverarbeitung in C/C++

wav_cpp.zip - Obiges C++ - Projekt.
wav_java.zip - Vergleichbare Realisierung in Java.