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 0-1: Verwenden von .wav-Dateien in Scilab
Bild 0-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 0-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 0-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 0-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 0-5: Wavdateiverarbeitung in C/C++
wav_cpp.zip - Obiges C++ - Projekt.
wav_java.zip - Vergleichbare Realisierung in Java.