Autor Beitrag
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8553
Erhaltene Danke: 479

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: So 07.09.03 10:10 
Ich möchte verschiedene Daten aus einer mp3 Datei rauslesen. Zum Beispiel Spieldauer, Artist, Titel, Albumtitel, Bitrate etc.
Spieldauer geht einigermaßen, den id3v1 Tag kann ich auch auslesen (hab ich hier im Forum gefunden). Id3v2 hab ich noch nicht probiert, sollte aber auch zu machen sein.
Problem ist die Bitrate. Die steht nämlich in jedem mpeg-Header des Files.
Aber der mpeg-header muss nicht am Anfang des Files stehen, da kann nämlich auch erst der id3v2-Tag sein (1). Es kann aber auch sein, dass das File erstmal mit nem Haufen 0-Bytes anfängt (2), oder sogar, dass da sich jemand n Spass erlaubt hat, und IRGENDWAS an den Anfang gesetzt hat (3).
Wers nicht glaubt, kann sich mal die Dateien hier mit nem HexEditor anschauen. (Dabei fällt auch auf, dass Winamp die Spiellänge NICHT korrekt berechnet!)
www-public.rz.uni-du...f.de/~gaussman/1.mp3
www-public.rz.uni-du...f.de/~gaussman/2.mp3
www-public.rz.uni-du...f.de/~gaussman/3.mp3

Meine Frage lautet nun:
Wie finde ich den Beginn eines MPEG-Headers in einer mp3-Datei? Ich weiss, dass er mit 11 (oder 12??) 1er Bits anfängt. Wie kann ich danach effektiv suchen?
Auch weiss ich noch nicht, wie ich eine variable Bitrate erkennen kann.
Und: Kennt jemand einen Editor, der ein File BITweise anzeigt? Also in der Form 10010100101010111011110... Das könnte nämlich auch schon etwas helfen...

_________________
We are, we were and will not be.
Gausi Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8553
Erhaltene Danke: 479

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Di 09.09.03 10:55 
Soo...habs mir selber gemacht. Im nachhinein alles ganz einfach ;-)
Wenns jemanden interessiert:
Hier ein Prozedur, die aus einem mp3-File
- den ID3v1-Tag ausliest (den Teil hab ich hier aus dem Forum),
- Teile des ID3v2-Tags ausliest (Artist, Titel, Album), unter der voraussetzung, dass dieser am Anfang des Files steht. Ich hoffe, dass das immer so ist...
- den ersten mpeg-Header sucht und auswertet (teilweise hier gefunden),
- dabei einen evtl. vorhandenen XING-Tag für VBR-Files berücksichtigt,
- die Länge des Stückes in Sekunden ausrechnet, wobei auch vbr-Files in der Regel richtig berechnet werden, und zwar OHNE dass das ganze File untersucht werden muss, und ohne die Verwendung von Zusatzkomponenten wie fmod.

Einschränkungen: Ich habe bisher keine Informationen darüber gefunden, was für Infos LAME (3.92) nach dem letzten Audioframe einfügt. Dadurch KANN die Längenberechnung um einige Sekunden abweichen. In der Regel stimmt sie aber.
Es wird angenommen, dass der erste MPEG-header in den ersten 5800 Bytes beginnt. Das muss nicht immer so sein, da in die id3v2 Tags SEHR lang sein können (es dürfen auch bilder etc dadrin gespeichert werden...)
Allerdings ist mir bisher kein solches mp3 untergekommen.
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
type
  TID3Tag = record
    ID: string[3];
    Titel: string[30];
    Artist: string[30];
    Album: string[30];
    Year: string[4];
    Comment: string[30];
    Genre: byte;
  end;
  TmpegHeader =record
    position:integer;
    version:integer;
    layer:integer;
    protection:boolean;
    bitrate:integer;
    samplerate:integer;
    channelmode:byte;
    extension:byte;
    copyright:boolean;
    original:boolean;
    frames:longint;
    dauer:longint;
    vbr:boolean;
  end;

const
  MPEG_BIT_RATES : array[1..3of array[1..3of array[0..15of word =
  { Version 1, Layer I }
    (((0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0),
  { Version 1, Layer II }
    (0,32,48,56648096,112,128,160,192,224,256,320,384,0),
  { Version 1, Layer III }
    (0,32,40,4856648096,112,128,160,192,224,256,320,0)),
  { Version 2, Layer I }
    ((0,32,4856648096,112,128,144,160,176,192,224,256,0),
  { Version 2, Layer II }
    (08,16,2432404856648096112,128,144,160,0),
  { Version 2, Layer III }
    (08,16,2432404856648096112,128,144,160,0)),
  { Version 2.5, Layer I }
    ((0,32,4856648096,112,128,144,160,176,192,224,256,0),
  { Version 2.5, Layer II }
    (08,16,2432404856648096112,128,144,160,0),
  { Version 2.5, Layer III }
    (08,16,2432404856648096112,128,144,160,0)));

  sample_rates: array[1..3of array [0..3of word=
    ((44100,48000,32000,0),
    (22050,24000,16000,0),
    (11025,12000,8000,0));
  channel_modes:array[0..3of string=('Stereo','Joint stereo','Dual channel (Stereo)','Single channel (Mono)');
  extensions:array[1..3of array [0..3of string=
    (('bands 4 to 31','bands 8 to 32','bands 12 to 31','bands 16 to 31'),
     ('bands 4 to 31','bands 8 to 32','bands 12 to 31','bands 16 to 31'),
     ('IS:off, MS:off','IS:on, MS:off','IS:off, MS:on','IS:on, MS:on'));

procedure Tform1.mp3Info(filename:string;
    var mpegheader:Tmpegheader;
    var id3v2_artist,id3v2_titel,id3v2_album:string;
    var id3tag:Tid3tag);
(* Diese Prozedur
    - liest Teile des ID3v2-Tags (Version 2.4.0) aus,
    - sucht den ersten MPEG-Header der Datei
    - sucht im ersten MPEG Frame einen Xing-Header, der von vielen Encodern für VBR verwendet wird
    - berchnet aus den Daten des MPEG- und XING-Headers
      die Spieldauer des Files in Sekunden (und bei VBR durchschnittliche Bitrate)
    - liest den id3v1-tag aus, falls dieser vorhanden

    ToDo: evtl. weitere Header erkennen und berücksichtigen, z.b. den LAME-Header
        - dieser kann im Xing-Header eingebettet sein (höhere Bitrate im  Xing-MPEG-Frame-> lägerer Frame)
        - und am Ende eines mp3s kann auch "LAME-Gedöns" (CRC ?) stehen. Leider bisher keine Doku dazu gefunden
    Dies ist wichtig für die korrekte SpielDauer-Berechnung, da man die ganzen Header (=Nicht-Audio-Daten)
    von der Dateigröße abziehen muss.
*)

var
    F: File;
    id3v1_tag:array[1..128of char;
    mp3_header: array[1..4of char;
    buffer:array[0..5999of char;
    bytes:longint;
    i:integer;

    id3v1_size:integer;
    xing_header_size:integer;
    //ID3v2 Variablen
    id3_version, id3_number,id3_flags:byte;
    id3_unsync,id3_extended,id3_footer:boolean;
    id3_frame_id:string[4];
    id3_pos:integer;
    id3_size,id3_extended_size,id3_frame_size:longint;

    //mpeg-header Variablem
    bitrateindex, versionindex: byte;
    erfolg,valid:boolean;
    position:integer;
    padding,samplerateindex:byte;
    framelength:longint;
    Xing_Offset:integer;
    Xing_Flags:byte;

begin
    AssignFile(F, filename);
    FileMode := 0;
    Reset(F,1);
    bytes:=filesize(f);
    blockread(f,buffer,6000); // willkürlich gewählter Wert. In den ersten ca. 6kb sollte ein MPEG-Header stecken!
    Seek(F,FileSize(F)-128);
    BlockRead(F, id3v1_tag, 128);
    CloseFile(F);
    position:=-1;

//******************** id3v1 Tag auslesen*****************
    if (id3v1_tag[1]='T')
        AND (id3v1_tag[2]='A')
        AND (id3v1_tag[3]='G')
    then begin
        id3tag.id:='TAG';
        id3v1_size:=128;
        id3tag.Titel:=trim(copy(id3v1_tag,4,30));
        id3tag.Artist:=trim(copy(id3v1_tag,34,30));
        id3tag.Album:=trim(copy(id3v1_tag,64,30));
        id3tag.Year:=trim(copy(id3v1_tag,94,4));
        id3tag.Comment:=trim(copy(id3v1_tag,98,30));
        id3tag.Genre:=ord(id3v1_tag[128]);
    end else id3v1_size:=0;

//*****************ID3v2 Tag auslesen************************
    if (buffer[0]='I')
       AND (buffer[1]='D')
       AND (buffer[2]='3')
    then begin
        // ID3v2 Tag gefunden
        id3_version:=ord(buffer[3]);
        id3_number:=ord(buffer[4]);
        id3_flags:=ord(buffer[5]);
        // flags: %abcd0000
        id3_unsync:=((id3_flags shr 7AND 1)=1;
        id3_extended:=((id3_flags shr 6AND 1)=1;
        // den experimental-flag ignoriere ich
        id3_footer:=((id3_flags shr 4AND 1)=1;
        // die Grösse wird in Syncsafe-integer gespeichert, daher nicht *256, sondern *128.
        id3_size:=2097152*ord(buffer[6])
            + 16384*ord(buffer[7])
            + 128*ord(buffer[8])
            + ord(buffer[9])+10;
        if id3_footer then inc(id3_size,10);
        // Bei der Suche des mpeg-Headers nach dem id3v2Tag beginnen
    position:=id3_size-1;
        if id3_extended then
        begin
            //in den nächsten 4 Bytes steht die Größe des Extended Headers
            id3_extended_size:=2097152*ord(buffer[10])
            + 16384*ord(buffer[11])
            + 128*ord(buffer[12])
            + ord(buffer[13]);
            //Den überspringe ich einfach. Die Informationen dadrin sind nicht wirklich wichtig für meine Zwecke.
        end else id3_extended_size:=0;
        // Nach dem optionalen Extended Header kommen die einzelnen Frames, in denen die Informationen drin stehn
        // 4 Byte Frame-ID
        // 4 Byte unsync-Integer-Größenangabe, wieder ohne die 10 Bytes im Frame-Header
        // 2 Bytes Flag
        // Daten
        id3_pos:=10+id3_extended_size;
        // hier sollte der erste Frame anfangen
        while (id3_pos<position) do
        begin
            id3_frame_id:=buffer[id3_pos]
                +buffer[id3_pos+1]
                +buffer[id3_pos+2]
                +buffer[id3_pos+3];
            id3_frame_size:=2097152*ord(buffer[id3_pos+4])
                + 16384*ord(buffer[id3_pos+5])
                + 128*ord(buffer[id3_pos+6])
                + ord(buffer[id3_pos+7])+10;
            // Die Flags auf +8, +9 ignoriere ich hier
            // auf +10 ist z.b. der Zeichensatz kodiert, auch den ignoriere ich
            // Einige Frames auslesen:
            if id3_frame_id='TALB' then begin // Album-Name
                if id3_frame_size>11 then setlength(id3v2_album,id3_frame_size-11);
                for i:=id3_pos+11 to id3_pos+id3_frame_size do
                    id3v2_album[i-id3_pos-10]:=buffer[i];
            end;
            if id3_frame_id='TIT2' then begin // Titel
                if id3_frame_size>11 then setlength(id3v2_titel,id3_frame_size-11);
                for i:=id3_pos+11 to id3_pos+id3_frame_size do
                    id3v2_titel[i-id3_pos-10]:=buffer[i];
            end;
            if id3_frame_id='TPE1' then begin // Artist
                if id3_frame_size>11 then setlength(id3v2_artist,id3_frame_size-11);
                for i:=id3_pos+11 to id3_pos+id3_frame_size do
                    id3v2_artist[i-id3_pos-10]:=buffer[i];
            end;
            inc(id3_pos,id3_frame_size);
            // weitere Frame-IDs unter http://www.id3.org
        end;
    end else id3_size:=0;
//******************* ID3v2Tag fertig gelesen*********************

//*******************Start des MPEG-Headers***********************
    erfolg:=false;
    mpegheader.position:=-1;
    // Bei der Suche des mpeg-Headers nach dem id3v2Tag beginnen
    position:=id3_size-1;
    while NOT ((erfolg) or (position>5800)) do
    begin
        inc(position);
        // Ein MPEG Header startet mit 11 gesetzten Bits ($FF E0 = 11111111 11100000)
        if (ord(buffer[position])=$FFAND (ord(buffer[position+1])>=$E0)
        then begin
            valid:=true; // erstmal positiv denken, dann überprüfen ;-)
            // ein mpeg header besteht aus 4 Bytes
            //Byte 1 und 2 AAAAAAAA AAABBCCD
            //A=1 (11 Sync bytes)
            //B: Version, bei Standard-mp3 (=MPEG1, Layer3) sollte BB=11 sein
            //C: Layer, III ist CC=01
            //D: Protection BIT wenn gesetzt, folgt dem mpeg-header 16bit crc, hat aber keinen Einfluss auf die längenberechnung, scheinbar
            Versionindex:=((ord(buffer[position+1]) shr 3and 3);
            case versionindex of
                0: mpegheader.version:=3//eigentlich ist das Version 2.5 aber wegen dem Array-Index...
                1: mpegheader.version:=0//Reserved
                2: mpegheader.version:=2;
                3: mpegheader.version:=1;
            end;
            mpegheader.Layer:=4-((ord(buffer[position+1]) shr 1and 3);
            mpegheader.protection:=((ord(buffer[position+1]) AND 1)=0);

            // Byte 3: EEEEFFGH
            // E: Bitrate-Index
            // F: Samplerate-Index
            // G: Padding Bit
            // H: Private Bit
            bitrateindex:=((ord(buffer[position+2]) shr 4AND $F);
            mpegheader.bitrate:=MPEG_BIT_RATES[mpegheader.version][mpegheader.layer][bitrateindex];
            if bitrateindex=$F then
                valid:=false; // Bad Value !
            samplerateindex:=((ord(buffer[position+2]) shr 2AND 3);
            mpegheader.samplerate:=sample_rates[mpegheader.version][samplerateindex];
            padding:=((ord(buffer[position+2]) shr 1AND 1);

            // Byte 4: IIJJKLMM
            // I: Channel mode
            // J: Mode extension (for Joint Stereo)
            // K: copyright
            // L: original
            // M: Emphasis (??)
            mpegheader.channelmode:=((ord(buffer[position+3]) shr 6AND 3);
            mpegheader.extension:=((ord(buffer[position+3]) shr 4AND 3);
            mpegheader.copyright:=((ord(buffer[position+3]) shr 3AND 1)=1;
            mpegheader.original:=((ord(buffer[position+3]) shr 2AND 1)=1;
            //emphasis lass ich ma weg, keine Ahnung, was das soll
            // Es fehlt: Überprüfung:
            // "For Layer II there are some combinations of bitrate and mode which are not allowed."
//***************damit ist der MPEG Header komplett eingelesen***********************************

// Bei (allen/vielen/einigen ??) VBR-Files ist der erste MPEG-Frame in Wirklichkeit ein "XING-Header"
// wenn dieser fehlt, heisst das aber nicht, dass das File CBR ist. (Oder doch??)
// die vbr-Detektion mache ich auf jeden Fall über diesen XING Header.

//**************Einlesen des XING-Headers***************************
            if mpegheader.version=1 then
                if mpegheader.channelmode<>3 then xing_offset:=32+4
                    else xing_offset:=17+4
            else
                if mpegheader.channelmode<>3 then xing_offset:=17+4
                    else xing_offset:=9+4;
            if (buffer[position+xing_offset]='X')
               AND (buffer[position+xing_offset+1]='i')
               AND (buffer[position+xing_offset+2]='n')
               AND (buffer[position+xing_offset+3]='g')
               then // Xing Tag vorhanden
               begin
                    // Die nächsten 4 Bytes sind die "Flags", wobei eigentlich nur das 4te interessant ist
                    Xing_flags:=ord(buffer[position+xing_offset+7]);
                    if (Xing_flags AND 1)=1 then
                    begin //Anzahl der frames in den nächsten 4 Bytes gespeichert
                        mpegheader.frames:=16777216*ord(buffer[position+xing_offset+8])
                            + 65536*ord(buffer[position+xing_offset+9])
                            + 256*ord(buffer[position+xing_offset+10])
                            + ord(buffer[position+xing_offset+11]);
                    end;
                    // der Rest der Flags interessiert mich nicht,
                    // - die Filesize haben wir schon,
                    // - Der TOC-Flag und die 100 Bytes TOC
                    //   werden zur Errechnung der Sprungstellen verwendet,
                    // - und was vbr_scale sein soll, weiss ich nicht genau.
                    xing_header_size:=trunc(144*(MPEG_BIT_RATES[mpegheader.version][mpegheader.layer][bitrateindex]*1000/mpegheader.samplerate)+padding);
                    mpegheader.bitrate:=trunc((mpegheader.samplerate/1000*(bytes-id3_size-id3v1_size-xing_header_size))/(mpegheader.frames*144));
                    mpegheader.vbr:=true;
               end
               else
               begin
                    framelength:=trunc(144*(MPEG_BIT_RATES[mpegheader.version][mpegheader.layer][bitrateindex]*1000/mpegheader.samplerate)+padding);
                    mpegheader.frames:=trunc((bytes-id3_size-id3v1_size-xing_header_size)/framelength);
                    mpegheader.vbr:=false;
                    xing_header_size:=0;
               end;
//**************XING-Header Ende***************************

//Überprüfen, ob dem ersten MPEG-Frame ein zweiter folgt, um sicherer zu sein, dass es wirklich ein mp3-File ist.
            framelength:=trunc(144*(MPEG_BIT_RATES[mpegheader.version][mpegheader.layer][bitrateindex]*1000/mpegheader.samplerate)+padding);
            if position+framelength>length(buffer)-2 then
            begin
                Reset(F,1);
                Seek(F,position+framelength);
                blockread(f,mp3_header,4);
                CloseFile(F);
            end else
            begin
                mp3_header[1]:=buffer[position+framelength];
                mp3_header[2]:=buffer[position+framelength+1];
            end;
            if (ord(mp3_header[1])<>$FFor (ord(mp3_header[2])<$E0then valid:=false;
            // Besser: neuen header überprüfen und mit den bisher gesammelten Infos vergleichen
            // Einige Daten sollten gleich bleiben, wie z.B. Version, Layer und Samplerate

            // Noch Besser: 3 aufeinanderfolgende Frames suchen und testen
            // Es gibt wohl einige png's, die diesen Test hier bestehen, aber ich belass es mal dabei.
            // btw: Drückt man bei Winamp2.8 bei einem File Alt-3, und lässt sich die Mp3-Infos anzeigen,
            // dann fällt auf, dass Winamp nicht mal nach einem zweiten Frame sucht!!

            if valid then begin
                erfolg:=true;
                mpegheader.dauer:=((bytes-id3_size-id3v1_size-xing_header_size)*8div ((mpegheader.bitrate)*1000);
                mpegheader.position:=position;
            end;
        end;
    end//while
end;

_________________
We are, we were and will not be.
Ex0rzist
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 550

Win XP Prof.
Mandrake 10.0

D6
BeitragVerfasst: Di 09.09.03 15:47 
Gausi hat folgendes geschrieben:
Soo...habs mir selber gemacht.


Ferkel. :wink:


Vielleicht kann man das Ganze in die FAQ schieben.
Gausi Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8553
Erhaltene Danke: 479

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Di 09.09.03 16:45 
Hmm.. ja könnte man vielleicht. Aber um das FAQ-tauglich zu machen, muss ich noch den Fall (ID3v2Tag sehr gross) einarbeiten. Mittlerweile hab ich ein mp3 gefunden, dessen id3v2-header mehr 20kb (!) groß ist. Für den Fall muss ein größerer Block am Anfang eingelesen werden.
Das sollte bis morgen erledigt sein ;-)

_________________
We are, we were and will not be.