MeMP - Mein einfacher Mp3-Player
Kapitel 2. Informationen in Audiodateien
Bevor wir mit dem eigentlichen Player beginnen, erstmal etwas Grundlegendes. Wenn ich eine mp3-Datei (oder etwas ähnliches) in einem Player öffne, dann erwarte ich, dass mir der Player anzeigt, wer da gerade singt, und wie das Lied heißt. Auch die Länge des Liedes möchte ich angezeigt bekommen.
Mal ehrlich: Würdet ihr einen Player benutzen, der nichts als den Dateinamen anzeigt? Eben.
Mp3-Dateien
Eine Mp3-Datei besteht aus vielen einzelnen MPEG-Frames – in etwa so, wie ein Film aus vielen Einzelbildern besteht. Jeder dieser Frames besitzt einen 4 Byte großen Header, und in diesen 4 Bytes stecken unter anderem Informationen über Bitrate (z.B. 192 kbit/s), Channelmode (z.B. Stereo) und Samplerate (z.B. 44.1 kHz). Über diese Daten kann man dann die Dauer eines Stückes berechnen.
Informationen wie Titel und Interpret sind im Mp3-Standard eigentlich gar nicht vorgesehen. Hier haben sich aber die so genannten ID3-Tags etabliert und gelten als informeller Standard. In einer ersten Version wurde der ID3v1-Tag ans Ende der Datei geschrieben. Der ID3v1-Tag hat eine feste Größe, kann sehr leicht gefunden und ausgewertet werden, ist aber stark eingeschränkt.
Später wurde der ID3v2-Tag entwickelt, der am Anfang der Datei zu finden ist, eine variable Größe besitzt und nahezu beliebige Informationen aufnehmen kann.
Da Version 1 am Ende und Version 2 am Anfang einer Datei zu finden ist, können natürlich auch beide Versionen in einer Datei auftauchen. Und da die beiden unabhängig voneinander sind, müssen die enthaltenen Informationen auch nicht unbedingt zueinander passen.
ID3-Tags in anderen Formaten
ID3-Tags in anderen Formaten gibt es eigentlich nicht. Bei Ogg Vorbis findet man Vorbis Kommentare, in Wma-Dateien Wma-Tags und bei den Formaten von Apple findet man wieder was anderes. Das schöne an Standards ist ja, dass es so viele davon gibt. Das macht leider ein unkompliziertes Auslesen dieser Daten für alle Formate unmöglich.
Den Anwender interessiert es eigentlich nicht, ob er nun eine mp3-Datei oder eine wma-Datei abspielt. Er möchte einfach wissen, wie das Lied heißt, und wir sollten als Programmierer diesen Wunsch beachten. Und wir werden das so erledigen, dass wir uns einmal darüber Gedanken machen, und in der weiteren Entwicklung nicht mehr. Zu diesem Zweck entwerfen wir unsere erste Klasse, die ein wichtiges Basiselement unseres Players bilden wird.
Die Klasse TAudioFile
Unsere Audiodatei-Klasse enthält Properties für Interpret, Titel, Dauer und einige andere Daten, die wir hier der Übersichtlichkeit wegen weglassen. Die Bitrate, Samplerate oder auch das Album wäre sicherlich noch interessant, hat aber zur Klärung des Prinzips keinerlei Bedeutung.
TAudioFile = class
private
fInterpret: String;
fTitel : String;
fPfad : String;
fDauer : Integer;
// ...
procedure GetMp3Info;
procedure GetWmaInfo;
procedure SetUnknown;
function GetPlaylistTitel: String;
public
property Interpret: String read fInterpret write fInterpret;
property Titel : String read fTitel write fTitel ;
property Pfad : String read fPfad write fPfad ;
property Dauer : Integer read fDauer write fDauer ;
property PlaylistTitel: String read GetPlaylistTitel ;
procedure GetAudioInfo(filename: String);
end;
Die vorerst einzige öffentliche Methode GetAudioInfo ermittelt aus einer Datei diese Informationen – oder versucht es zumindest. Sie ruft dabei je nach Dateityp die passende private Methode auf:
procedure TAudioFile.GetAudioInfo(filename: String);
begin
fPfad := filename;
if (AnsiLowerCase(ExtractFileExt(filename)) = '.mp3') then
GetMp3Info
else
if AnsiLowerCase(ExtractFileExt(filename)) = '.wma' then
GetWMAInfo
else
SetUnknown;
end;
Die einzelnen Prozeduren für das Auslesen der Daten bei einem bestimmten Dateityp greifen schließlich auf die benutzten Units zurück und übertragen anschließend die enthaltenen Informationen auf unser Gerüst. Bei mp3-Dateien sieht das dann zum Beispiel so aus:
procedure TAudioFile.GetMp3Info;
var mpegInfo: TMpegInfo;
ID3v2Tag: TID3V2Tag;
ID3v1tag: TID3v1Tag;
Stream: TFileStream;
begin
// Daten mit MP3FileUtils auslesen
mpeginfo:=TMpegInfo.Create;
ID3v2Tag:=TID3V2Tag.Create;
ID3v1tag:=TID3v1Tag.Create;
stream := TFileStream.Create(fPfad, fmOpenRead or fmShareDenyWrite);
id3v1tag.ReadFromStream(stream);
stream.Seek(0, sobeginning);
id3v2tag.ReadFromStream(stream);
if Not id3v2Tag.exists then
stream.Seek(0, sobeginning)
else
stream.Seek(id3v2tag.size, soFromBeginning);
mpeginfo.LoadFromStream(Stream);
stream.free;
// Daten auf unser Geruest uebertragen
if mpeginfo.FirstHeaderPosition >- 1 then
begin
if id3v2tag.artist <> '' then
fInterpret := id3v2tag.artist
else
fInterpret := id3v1tag.artist;
if id3v2tag.title <> '' then
fTitel := id3v2tag.title
else
if id3v1tag.title <> '' then
fTitel := id3v1tag.title
else
fTitel := ExtractFileName(fPfad);
fDauer := mpeginfo.dauer;
end else
SetUnknown;
MpegInfo.Free;
Id3v2Tag.Free;
Id3v1Tag.Free;
end;
Der Vorteil an diesem Konstrukt ist recht einfach. Wir können die Ermittlung der Audio-Informationen zu einem späteren Zeitpunkt sehr einfach erweitern, indem wir z.B. eine weitere private Methode GetOggInfo schreiben, und die Methode GetAudioInfo um einen else-Zweig erweitern. Das Prinzip bei diesen Methoden ist immer dasselbe: Suche die Format-spezifischen Informationen aus der Datei zusammen und fülle damit sinnvoll die Felder, die den Anwender am Ende interessieren. Wenn eine Information nicht gefunden werden kann, wird ein Standardwert dafür genommen – z.B. der Dateiname als Titel.
Wir können die Klasse auch durch weitere Eigenschaften erweitern. Zum Beispiel einen String der Form „Interpret – Titel“, wie man ihn häufig in Playlisten vorfindet. Anstatt dies immer an Ort und Stelle zu erledigen, schreiben wir einmal einen entsprechenden Getter für diese Property, der auch noch ein paar Fehler ausbügeln kann.
function TAudioFile.GetPlaylistTitel: String;
begin
if Trim(fInterpret) = '' then
result := fTitel
else
result := fInterpret + ' - ' + fTitel;
end;