So erzeugt man eine Waveform mit PHP


Vorbereiten der Waveform

Angenommen wir haben 10 verschiedene Audioformate, welche wir alle unterstützen und mit einer Waveform visualisieren wollen. Nun wollen wir aber nicht 10 Codecs implementieren. Also benötigen wir ein einheitliches Format, welches es uns ermöglicht die Waveform zu generieren. Hier kommt das gute alte WAV Format ins Spiel. Da das WAV Format unkomprimiert und ausreichend dokumentiert ist, werden wir versuchen jede Audiodatei in dieses Format zu konvertieren. Somit umgehen wir das nervige Decoden der Audiodateien und wir müssen nur einen Standard unterstützen. In linux habe ich mir für das Konvertieren diverse Codecs und den MPlayer installiert.

sudo apt-get install faac faad flac lame libmad0 libmpcdec6 mppenc vorbis-tools wavpack mplayer

Der MPlayer konvertiert für uns so ziemlich jedes Format erst mal in eine MP3.



Wir wandeln die Audiodateien erst in eine MP3, da man in der Regel eine Audiodatei auch wiedergeben möchte. Somit haben wir auf jeden Fall ein platzsparendes Format, welches so gut wie überall unterstützt wird. Hier könnte man jetzt, wie in dem Artikel über die MPEG Frames beschrieben, die Spielzeit und andere Informationen auslesen bevor man anfängt die Waveform zu erzeugen.


Nun gebe ich euch hier meine BinaryReader Klasse mit, welche euch hilft die Audiodateien zu lesen.

Lets Wave ... Wir generieren die Waveform

Nun konvertieren wir die zuvor erstellte MP3 Datei in eine WAV Datei. Um den gesamten Prozess zu beschleunigen können wir hier ein paar Daten fallen lassen. Sprich wir nehmen nur einen Kanal und Resamplen auf 8 bit herunter. Würden wir das nicht machen wäre unsere WAV Datei ziemlich groß und das Erzeugen der Waveform würde, da wir uns bis zum Ende durch kämpfen, ewig dauern.


Nun validieren wir die Existenz der Datei und ob diese auch wirklich im WAV Format ist. Indem wir die ersten 4 bytes auf ihren Inhalt prüfen, welcher den Text RIFF darstellen sollte, stellen wir fest ob die Datei im WAV Format ist. Nun beginnen wir mit dem Analysieren der Datei.

Die ersten 22 bytes sind für uns nicht relevant. Um den Samples die richtige Größe zuordnen zu können, benötigen wir die Anzahl der Kanäle. Vom Header benötigen wir nun nur noch die Bitrate um die Waveform generieren zu können.


Für das analysieren müssen wir die Länge eines Samples kennen.

Also berechnen wir die Bytes pro Frame und das Verhältnis um die Streuung zu erhöhen und die Leistung zu verbessern.

Sprich wir analysieren, obwohl wir die Datei bereits verlustbehaftet konvertiert haben, immer noch nicht jedes Sample.


PHP: Ermitteln der Frameanzahl und Framelänge
  1. // Calculate the amount of bytes per frame
  2. $bytesPerFrame = $bitRate / 8;
  3. // Analyze per channel every 16th byte
  4. $ratio = $channels == 2 ? 32 : 16;
  5. // Remove header size and divide by bytesPerFrame + channelRatio
  6. $frameAmount = ($binary->dataSize - 44) / ($ratio + $bytesPerFrame) + 1;

Lets paint it - Wir Zeichen die Waveform

Wir definieren nun unsere Variablen und Initialisieren das Bild worin wir Zeichnen wollen. Das Bild wird mit der Höhe von 255 Pixeln und der Breite eines Fünftels der Frameanzahl erstellt. Die Höhe wird auf 255 gesetzt da dies hier der maximale Wert eines Samples ist und wir so unsere Werte nicht in ein Verhältnis setzen müssen. Nun iterieren wir so lange bis wir entweder das Ende des Streams oder die Anzahl der Frames erreicht haben. Auch jetzt versuchen wir die Performance zu verbessern indem wir nur jedes 5te Frame analysieren. Jetzt merkt ihr vielleicht wieso unser Bild die Breite von einem Fünftel der Frameanzahl hat. Wenn ihr eine detailliertere Waveform haben wollt, müsst ihr diesen Wert runter setzen. Wenn ihr das macht, wird das Erzeugen der Waveform aber auch dementsprechend länger dauern.


Bei jeder Iteration ermitteln wir den Wert des aktuellen Samples um ihn später in die Waveform zeichnen zu können. Dafür bilden wir aus den Bytes des Samples einen Wert welchen wir dann auf ein byte reduzieren. Bei einem 16 Bit Sample (2 byte) bilden wir aus 2 bytes einen unsigned short int Wert und reduzieren diesen wieder auf ein byte. Nun haben wir einen Wert zwischen 0 und 255, also die Größe eines unsigned char oder auch byte gennant, welcher den Endpunkt einer vertikalen Linie in der Waveform darstellt. Dieser Wert wird nun umgekehrt und schon haben wir die zweite Y Koordinate für unsere Waveform. Als Beispiel haben wir bei einem ursprünglichen Wert von 249, die Y Koordinate 6 als Startpunkt und die Y Koordinate 249 als Endpunkt. Nun zeichnen wir diese Linie auf unser Bild, überspringen 5 Frames und fangen wieder an den Wert des Samples zu ermitteln.

Bei jeder Iteration wird die X Koordinate inkrementiert und irgendwann sind wir am Ende der Audiodatei.

Das Ergebnis

Nun haben wir von Anfang bis Ende viele Samples gelesen und unsere Waveform gezeichnet. Diese muss nur noch gespeichert werden und wir sind fertig. Das ganze kann man natürlich auch nach belieben anpassen. Z.B. kann man die Performance verbessern in dem man die $ratio Variable erhöht und weniger Samples (z.B. jedes 10te) analysiert. Im Gegenzug könnte man die Waveform detaillierter zeichnen wenn man mehr Samples analysiert. Man könnte die Darstellung der Waveform ändern in dem man nicht von einer zentrierten Linie, sondern von z.B. einem Grafen ausgeht. Wenn ihr hier im Dateisystem eine Audio Datei hoch ladet, werdet ihr das Ergebnis dieser Funktion sehen.


    Share

    Comments