# MP3 Header auslesen



## Sisko86 (28. Jan 2008)

Hoffe es kann mir jemand helfen.
Ich möchte den MP3 Header auslesen.

Hab mal angefangen, vielleicht bin ich ja auf dem totalen Holzweg.

In Wikipedia steht, das ein MP3 Frame mit 11bits beginnt die mit 1 belegt sind.
So jetzt wollte ich danach suchen, find aber keine elf 1er bits.

Hier mal mein Konstruktor, weiter bin ich noch nicht gekommen:


```
public MP3Header(File file){
		try {
			int sync = 0;
			int i = 0, j = 0;
			byte tmp = 0;
			BufferedInputStream stream = 
				new BufferedInputStream(new FileInputStream(file));

			stream.read(header);
			
			while(i < headersize) {
				tmp = header[i];
				
				j = 0;
				while(j < 8) {
					if ((tmp & -128) == -128) {
						sync++;
						System.out.println("1");
						if (sync == 11) {
							System.out.println("Sync!!!!!!!!");
                                                        // Hier kommt er nicht rein
						}
					}
					else {
						System.out.println("0");
						sync = 0;
					}
					
					tmp <<= 1;
					j++;
				}
				i++;
			}
			//System.out.println(sync);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
```


----------



## Sisko86 (28. Jan 2008)

Hab vergessen:


```
private static final int headersize = 256; 
	private byte[] header = new byte[headersize];
```


----------



## Sisko86 (28. Jan 2008)

Nachtrag:
Hab mal mehr von der Datei ausgelesen.
Nach 187 bytes hab ich die ersten 11 bits gefunden.

Ist das normal, müssen die nicht irgendwo am Anfang stehen?


----------



## Gast (29. Jan 2008)

ja ist normal bei mp3s, der header fängt irgendwo an, oft nicht am Anfang


----------



## The_S (29. Jan 2008)

Wenn der id3-Tag davor steht, dann fängt der "Header" natürlich auch erst später an.


----------



## Sisko86 (29. Jan 2008)

Nachdem ich nun die 11 bits gefunden habe, wollte ich den Header weiter auslesen und auswerten.
Ganz besonders interresiert mich die Bitrate.

Kann es das geben, dass für jeden Frame eine andere Bitrate rauskommt?

Mit anderen Worten die Header unterscheiden sich.

Muss für eine MP3 die Bitrate nicht konstant bleiben?


----------



## Gast (2. Feb 2008)

nein, das nennt sich dann VBR und ist inzwischen standard.


----------



## Guest (10. Feb 2008)

naja, standard würd ich nicht sagen. Es wird nur sehr sehr sehr selten verwendet.
Hab mal eine Klasse geschrieben die das alles erledigt, vielleicht hilft sie dir ja weiter.


```
package at.cbh.jtagger.musicfile;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import at.cbh.jtagger.exceptions.NoFrameHeaderFoundException;
import at.cbh.jtagger.exceptions.NotAMp3FileExcpetion;
import at.cbh.jtagger.util.JTaggerUtils;

/*
 * Diese Klasse erweiter die Klasse File um methoden die
 * relevant sind um Informationen aus einem MP3 File
 * auszulesen. Ein Mp3 File besteht aus vielen Frames die
 * alle einen 4 Byte grossen Header besitzen. In diesem
 * Header ist Information zu dem Mp3 gespeicher wie Bitrate
 * Samplingrate usw. Zusätzlich wurde noch eine Methode hinzugefügt
 * mit der sich die Dauer eines SOngs berechnen lässt. Die
 * Grösse des Files bekommt man über die Methoden der File Klasse.
 * 
 * Eine genaue Spezifikation des MP3 Frame headers ist unter
 * folgendem Link zu finden:
 * 
 * [url]http://www.mp3-tech.org/programmer/frame_header.html[/url]
 * 
 * author:  ******
 * version: 0.7
 * 
 */

/*
 * TODO: - Methode implementieren die prüft ob das Mp3 eine variable
 *         Bitrate hat. 
 * 	     - Einen besseren Weg finden den Frame Header eindeutig zu 
 *         validieren.	 
 *       - Exceptions werfen
 */


@SuppressWarnings("serial")
public class Mp3File extends MusicFile {

	/*
	 * In diesem String ist der Frameheader gespeichert
	 * 
	 * TODO: In einer späteren Verison sollte der Header in 
	 *       einem Byte Array gespeichert werden.
	 */
	
	private String 	firstFrameHeader;				
	
	private int 	bitrate;						
	private long 	duration;						
	private int 	samplingRate;					

	private String MPEGVersion;						
	private String layer;							
	private boolean hasProtectionBit = false;		
	private boolean isPadded = false;				
	private String channelMode;						
	private boolean hasCopyright = false;			
	private boolean isCopyOfOriginal = false;		
	private String emphasis;						
	
	/*
	 * Diese Variablen speicerh layer version und mpeg
	 * versiojn und dienen als Tabellenindex zur Brechnung
	 * der Bitrate und der Samplingrate
	 */
	
	private int l;									
	private int v;									
	
	@SuppressWarnings("unused")
	private long	startPosOfFirstFrameHeader;	
	
	/*
	 * Der Konstruktor erzeugt ein neues Mp3 Objekt. Es wird
	 * geprüft ob es sich um ein Mp3 file hadelt in dem die
	 * dateiendung mit allen variationen von .mp3 verglichen
	 * wird. Danach wird der Header des ersten frames gesucht
	 * und falls gefunden ausgelesen.
	 * 
	 *  Handelt es sich um ein anderes Fileformat oder wird
	 *  kein FrameHeader gefunden so wird eine Exception geworfen.
	 */
	
	public Mp3File(String pathname) throws NotAMp3FileExcpetion, NoFrameHeaderFoundException {
		super(pathname);

		

		if (!this.isMp3File())
			throw new NotAMp3FileExcpetion(pathname
					+ " is not a valid Mp3 File");

		/*
		 * Dies ist ein Zeiger. Er startet bei Position null
		 * im File und dient dem auffinden des Frameheaders
		 */
		
		startPosOfFirstFrameHeader = 0L;

		init();
	}

	/*
	 * Diese Methode prüft ob die Dateiendung MP3 ist und das file
	 * tatsächlich eine Datei ist und man lesend darauf zugreifen
	 * kann. 
	 */
	
	private boolean isMp3File() {

		return (this.getName().endsWith(".mp3")
				|| this.getName().endsWith(".MP3")
				|| this.getName().endsWith(".Mp3") || this.getName().endsWith(
				".mP3"))
				&& this.canRead() && this.isFile();

	}
	
    /*
     * Diese Methode sucht den ersten MP3 Frame Headers
     * im File. Es wird zuerst das Syncwort gesucht. Das
     * Synwortist 0xff dem 3 weiter Bytes folgen die gewisse
     * Kriteren erfüllen müssen um als frame Header durchzugehen.
     * Die Methode sucht einfach das auftreten des ersten Syncwortes
     * und vergleicht die 4 Bytes inkl. Synwort mit einem Muster.
     * Ist es ein gültiger Frameheader so wird er als String in 
     * die Variable firstFrameHeader geschrieben. Wenn nicht
     * so wir weiter im File gesucht.
     */
	
	private void getFirstFrameHeader() throws NoFrameHeaderFoundException {
		
		RandomAccessFile raf = null;
		
		try {
		
			raf = new RandomAccessFile(this,"r");
		
		} catch (FileNotFoundException e) {
		
			System.out.println("File not found");
		}
		
		/*
		 * Im Speicher werden 65536 Bytes für blöcke aus
		 * dem File reserviert die dann mit dem muster
		 * byteweise verglichen werden.
		 */
		
		byte byteBlock[] = new byte[1000];
		
		/*
		 * BlockCounter zählt wieviel blöcke schon gelesen
		 * werden und dient grundsätzlich zur positionsberechnung
		 * des Zeigers.
		 */
		
		long blockCounter = 0;
		
		/*
		 * Wenn der header gefunden wurde wird die schleife verlassen
		 */
		
		boolean headerFound = false;
		
		
		try {
			
			/*
			 * Hier wird der erste 65K Block in den
			 * Hauptspeicher gelesen.
			 */
			
			raf.read(byteBlock);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		/*
		 * Solange kein Header gefunden wurde wird der ByteBlock byteweise
		 * durchlaufen und geprüft ob es sich bei byte[i] + die nächsten 3
		 * Bytes um einen gültiogen Header Handeln kann. Ist dies der Fall
		 * wird die Schleife verlassen. Wenn nicht wird bis zum ende des
		 * Blockes weitergesucht. Da die letzten 3 Bytes nicht geprüft werden
		 * können (da ja noch kein neuer Block von der HD gelesen wurde werden
		 * die letzten 3 Bytes einfach beim neuen Block dazugelesen weshalb
		 * das RAF auch auch blockCounter * 65536 - 3 * blockCounter gesetzt 
		 * wird. 
		 */
		
		while (!headerFound) {

			for (int i = 0; i < byteBlock.length - 3; i++) {

				if (byteBlock[i] == (byte) 0xff) {

					if (isHeader(byteBlock[i + 1], byteBlock[i + 2],
							byteBlock[i + 3])) {
						firstFrameHeader = "11111111"
								+ JTaggerUtils.intArrayToString(JTaggerUtils
										.getBits(byteBlock[i + 1]))
								+ JTaggerUtils.intArrayToString(JTaggerUtils
										.getBits(byteBlock[i + 2]))
								+ JTaggerUtils.intArrayToString(JTaggerUtils
										.getBits(byteBlock[i + 3]));
						headerFound = true;
					
						/*
						 * Da der Header gefunden und in den SPeicher geschrieben
						 * wurde kann die Schleife verlassen werden.
						 */
						System.out.println(i);
						break;
						

					}

				}

			}
			
			blockCounter++;
			
			/*
			 * Da im ersten Block nix gefunden wurde wird ein neuer
			 * Block aus dem File in den Speicher gelesen.
			 */
			
			try {
				raf.seek(blockCounter * 1000 - 3 * blockCounter);
				raf.read(byteBlock);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			/*
			 * Wenn kein Header gefunden wurde wird das RAF geschlossen
			 * und eine Exception geworfen.
			 */
			
			if((blockCounter * 1000 - 3 * blockCounter) > this.length()) {
				try {
					raf.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				throw new NoFrameHeaderFoundException("No frameHeaderFound");
				
			}
			

		}
		
		/*
		 * Wenn alles gepasst hat wird das RAF geschlossen
		 */
		
		try {
			raf.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
			
	
	

	}

	
	/*
	 * Diese Methode prüft ob die 3 Bytes die dem Syncwort folgen
	 * einen gültigen Mp3 Frame Header formen. Welche kriterien
	 * das sind kann unter folgendem Link nachgelesen werden:
	 * 
	 * [url]http://www.mp3-tech.org/programmer/frame_header.html[/url]
	 * 
	 * Die Methode prüft die kriteren Byteweise und macht
	 * AND verknüpfungen mit dem vorherigen Bytewerten.
	 * 
	 */
	
	private boolean isHeader(int byteTwo, int byteThree, int byteFour) {

		

		int two[] = JTaggerUtils.getBits(byteTwo);
		int three[] = JTaggerUtils.getBits(byteThree);
		int four[] = JTaggerUtils.getBits(byteFour);

		/*
		 * Hier wird geprüft ob die ersten 4 Bits des 
		 * zweiten Bytes gesetzt sind.
		 */

		if (two[0] == 0 || two[1] == 0 || two[2] == 0 || two[3] == 0)
			return false;

		/*
		 * Hier wird geprüft ob Bit 4 und Bit 5 
		 * nicht 01 lauten und Bit 6 und Bit 7 des zweiten
		 * Bytes nicht 00 lauten da diese Werte reserviert sind
		 * und auf ein knock out kriterium ist
		 */

		//if (two[3] == 0 && two[4] == 1)
			//return false; 
		
		if(two[5] == 0 && two[6] == 0)
			return false;

		/*
		 * Beim dritten Byte dürfen die ersten 4 Bits nicht
		 * gesetzt sein.
		 */

		if (three[0] == 1 && three[1] == 1 && three[2] == 1 && three[3] == 1)
			return false; // marks a bad header
		
		if (three[0] == 0 && three[1] == 0 && three[2] == 0 && three[3] == 0)
			return false; // marks a bad header
		
		if (three[4] == 1 && three[5] == 1)
			return false; // marks a bad header

		/*
		 * Beim vierten Byte dürfen die letzten 2 Bits nicht
		 * 10 sein.
		 */

		if (four[6] == 1 && four[7] == 0)
			return false;

		return true;
	}
	
	
	/*
	 * Diese Methode ermittelt aus dem übergebenen Code
	 * die MPEG Version. Der Code ist der Index für
	 * folgende Wertetabelle:
	 * 
	 * 00 - MPEG Version 2.5 (later extension of MPEG 2)
     * 01 - reserved
     * 10 - MPEG Version 2 (ISO/IEC 13818-3)
     * 11 - MPEG Version 1 (ISO/IEC 11172-3)
     * 
	 */
	
	private void detectMPEGVersion(String code) {

		final String MPEG_VERSION25 = "00";
		final String MPEG_VERSION2  = "10";
		final String MPEG_VERSION1  = "11";

		if (code.equals(MPEG_VERSION25)) {
			MPEGVersion = "MPEG Version 2.5";

			/*
			 * v = 3 ist der Wert für 2.5
			 */

			v = 3;

		} else if (code.equals(MPEG_VERSION2)) {
			MPEGVersion = "MPEG Version 2";
			v = 2;

		} else if (code.equals(MPEG_VERSION1)) {
			MPEGVersion = "MPEG Version 1";
			v = 1;
		}

	}
	
	/*
	 * Diese Methode ermittelt aus dem übergebenen Code
	 * die MPEG Version. Der Code ist der Index für
	 * folgende Wertetabelle:
	 * 
	 *  00 - reserved
     *  01 - Layer III
     *  10 - Layer II
     *  11 - Layer I
     *  
	 */
	
	private void detectLayerDescription(String code) {

		final String LAYER3 = "01";
		final String LAYER2 = "10";
		final String LAYER1 = "11";

		if (code.equals(LAYER3)) {
			layer = "Layer III";
			l = 3;
			
		} else if (code.equals(LAYER2)) {
			layer = "Layer II";
			l = 2;
			
		} else if (code.equals(LAYER1)) {
			layer = "Layer I";
			l = 1;
		}

	}
	
	/*
	 * Diese Methode prüft ob das Protection
	 * Bit gesetzt ist und setzt bei Bedarf den
	 * Marker auf true.
	 */
	
	private void detectProtectionBit(String code) {
		
		if (code.equals("1"))
			hasProtectionBit = true;
	
	}
	
	/*
	 * Diese Methode schlägt den Wert der Bitrat in einer
	 * Tabell nach. Benötigt werden dafür die Indizes code
	 * version und layer. Die Werte werden aus folgender
	 * Tabelle entnommen:
	 * 
	 * bits  	V1,L1  	V1,L2  	V1,L3  	V2,L1  	V2, L2 & L3
     * 0000 	free 	free 	free 	free 	free
     * 0001 	32 	    32 	    32 	    32 	    8
     * 0010 	64 	    48 	    40 	    48 	    16
     * 0011 	96 	    56 	    48 	    56 	    24
     * 0100 	128 	64 	    56 	    64 	    32
     * 0101 	160 	80 	    64 	    80 	    40
     * 0110 	192 	96 	    80 	    96 	    48
     * 0111 	224 	112 	96 	    112 	56
     * 1000 	256 	128 	112 	128 	64
     * 1001 	288 	160 	128 	144 	80
     * 1010 	320 	192 	160 	160 	96
     * 1011 	352 	224 	192 	176 	112
     * 1100 	384 	256 	224 	192 	128
     * 1101 	416 	320 	256 	224 	144
     * 1110 	448 	384 	320 	256 	160
     * 1111 	bad 	bad 	bad 	bad 	bad
	 */
	
	private void detectBitRate(String code, int vIn, int lIn) {
		
		/*
		 * Hier wird ein 2 Dimensionales Array initialisiert
		 * dass die Werte der BitRate Tabelle aufnimmt.
		 */
		
		System.out.println(code + " vIn " + vIn + " lIn " + lIn);
		
		final int BT[][] = new int[14][5];			
		
		/*
		 * Hier wird die BitRate Tabelle mit Werten gefüllt 
		 */
		
		BT[0][0]=  32;   BT[0][1]= 32;  BT[0][2]=  32;  BT[0][3]=  32;  BT[0][4]=  8;
		BT[1][0]=  64;   BT[1][1]= 48;  BT[1][2]=  40;  BT[1][3]=  48;  BT[1][4]=  16;
		BT[2][0]=  96;   BT[2][1]= 56;  BT[2][2]=  48;  BT[2][3]=  56;  BT[2][4]=  24;
		BT[3][0]=  128;  BT[3][1]= 64;  BT[3][2]=  56;  BT[3][3]=  64;  BT[3][4]=  32;
		BT[4][0]=  160;  BT[4][1]= 80;  BT[4][2]=  64;  BT[4][3]=  80;  BT[4][4]=  40;
		BT[5][0]=  192;  BT[5][1]= 96;  BT[5][2]=  80;  BT[5][3]=  96;  BT[5][4]=  48;
		BT[6][0]=  224;  BT[6][1]= 112; BT[6][2]=  96;  BT[6][3]=  112; BT[6][4]=  56;
		BT[7][0]=  256;  BT[7][1]= 128; BT[7][2]=  112; BT[7][3]=  128; BT[7][4]=  64;
		BT[8][0]=  288;  BT[8][1]= 160; BT[8][2]=  128; BT[8][3]=  144; BT[8][4]=  80;
		BT[9][0]=  320;  BT[9][1]= 192; BT[9][2]=  160; BT[9][3]=  160; BT[9][4]=  96;
		BT[10][0]= 352; BT[10][1]= 224; BT[10][2]= 192; BT[10][3]= 176; BT[10][4]= 112;
		BT[11][0]= 384; BT[11][1]= 256; BT[11][2]= 224; BT[11][3]= 192; BT[11][4]= 128;
		BT[12][0]= 416; BT[12][1]= 320; BT[12][2]= 256; BT[12][3]= 224; BT[12][4]= 144;
		BT[13][0]= 488; BT[13][1]= 384; BT[13][2]= 320; BT[13][3]= 256; BT[13][4]= 160;
		
		int n = 0;
		int m = 0;
		
		/*
		 * Der Version des Layers und des Mpeg Codes entsprechen werden
		 * die Werte aus der Tabelle gelesen
		 */
		
		if (vIn == 1 && lIn == 1)
			n = 0;
		if (vIn == 1 && lIn == 2)
			n = 1;
		if (vIn == 1 && lIn == 3)
			n = 2;
		if ((vIn == 2 || vIn == 3) && lIn == 2)
			n = 3;
		if ((vIn == 2 || vIn == 3) && (lIn == 2 || lIn == 3))
			n = 4;

		
		m = JTaggerUtils.getDecFromBinary(code) - 1;
		
		

		
		/*
		 * TODO: Exception werfen
		 */
		
		if(m > 13 || m < 0 || n < 0) {
			bitrate = 0;
			return;
		}

		
		
		bitrate = BT[m][n]; 
		
		
	}
	
	/*
	 * Diese Methode schlägt die Sampling Rate des Files mittels
	 * der Indizes code und der version in folgender Tabelle nach:
	 * 
	 * 00  	44100 Hz  	22050 Hz  	11025 Hz
     * 01 	48000 Hz 	24000 Hz 	12000 Hz
     * 10 	32000 Hz 	16000 Hz 	8000 Hz
     * 11 	reserv. 	reserv. 	reserv.
     * 
	 */
	
	private void detectSampleRate(String code,int vIn) {
		
		final int SRT[][] = new int[3][3];
		
		SRT[0][0] = 44100; SRT[0][1] = 22050; SRT[0][2] = 11025;
		SRT[1][0] = 48000; SRT[1][1] = 24000; SRT[1][2] = 12000;
		SRT[2][0] = 32000; SRT[2][1] = 16000; SRT[2][2] = 8000;
		
		int index = JTaggerUtils.getDecFromBinary(code);
		
		/*
		 * TODO: Exception werfen
		 */
		
		if(index < 0) {
			samplingRate = 0;
			return;
		}
		
		samplingRate = SRT[index][vIn - 1];
	}
	
	/*
	 * Prüft ob das Padding bit gesetzt ist
	 */
	
	private void detectPaddingBit(String code) {
		
		if (code.equals("1")) isPadded = true;
		
	}
	
	
	/*
	 * Liest den Channel Mode aus dem header
	 */
	
	private void detectChannelMode(String code) {

		final String CM[] = { "Stereo", "Joint Stereo (Stereo)",
				"Dual Channel (Stereo)", "Single Channel (Mono)" };

		int index = JTaggerUtils.getDecFromBinary(code);
		
		/*
		 * TODO: Exception werfen
		 */
		
		if(index < 0 || index > 3) {
			channelMode = "";
			return;
		}
		
		channelMode = CM[index];
	}
	
	/*
	 * Prüft ob das Copyrigth bit gesetzt ist.
	 */
	
	private void detectCopyRight(String code) {
		
		if (code.equals("1"))
			hasCopyright = true;
		
	}
	
	/*
	 * Prüft ob das File ein original ist.
	 */
	
	private void detectOriginal(String code) {
		
		if (code.equals("1"))
			isCopyOfOriginal = true;
		
	}
	
	/*
	 * Setzt das Emphasis des files
	 */
	
	private void detectEmphasis(String code) {

		int index = JTaggerUtils.getDecFromBinary(code);
		
		/*
		 * TODO: Exception werfen
		 */
		
		if(index == 0) emphasis = "none";
		else if(index == 1) emphasis = "50/15 ms";
		else if( index == 3) emphasis = "CCIT J.17";
		else emphasis = "Error";

	}


	public int getBitrate() {
		
		return bitrate;
		
	}


	public String getChannelMode() {
		
		return channelMode;
		
	}


	public long getDuration() {
		
		return duration;
		
	}


	public String getEmphasis() {
		
		return emphasis;
		
	}


	public boolean isHasCopyright() {
		
		return hasCopyright;
		
	}


	public boolean isHasProtectionBit() {
		
		return hasProtectionBit;
		
	}


	public boolean isCopyOfOriginal() {
		
		return isCopyOfOriginal;
		
	}


	public boolean isPadded() {
		
		return isPadded;
		
	}


	public String getLayer() {
		
		return layer;
		
	}


	public String getMPEGVersion() {
		
		return MPEGVersion;
		
	}


	public int getSamplingRate() {
		
		return samplingRate;
		
	}
	
	
	public String getHeader() {
		
		return firstFrameHeader;
		
	}
	
	/*
	 * Sobald der Frame Header im File gefunden wurde können
	 * die Werte initialisiert werden. Die Strings sind gleich
	 * benannt wie die Positionsvariablen im Header:
	 * 
	 * AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
	 * 
	 * Diese Strings werden an die einzelnen Methoden übergeben 
	 * die dann die Information für die Klasse bereitstellen.
	 */
	
	private void init() throws NoFrameHeaderFoundException {

		getFirstFrameHeader();
		
		/*
		 * TODO Exception werfen
		 */

		String B = firstFrameHeader.substring(11, 13);
		String C = firstFrameHeader.substring(13, 15);
		String D = firstFrameHeader.substring(15, 16);
		String E = firstFrameHeader.substring(16, 20);
		String F = firstFrameHeader.substring(20, 22);
		String G = firstFrameHeader.substring(22, 23);
		String I = firstFrameHeader.substring(24, 26);
		String K = firstFrameHeader.substring(28, 29);
		String L = firstFrameHeader.substring(29, 30);
		String M = firstFrameHeader.substring(30, 32);

		detectMPEGVersion(B);
		detectLayerDescription(C);
		detectProtectionBit(D);
		detectBitRate(E, v, l);

		duration = JTaggerUtils.calcDuration(this.length(), bitrate);

		detectSampleRate(F, v);
		detectPaddingBit(G);
		detectChannelMode(I);
		detectCopyRight(K);
		detectOriginal(L);
		detectEmphasis(M);
		
		
	}

	

	@Override
	public String getMusicFileDescription() {
		return "MP3";
	}

	

}
```


----------

