Java 解析 MP3 格式

来源:互联网 发布:windows latex 实时 编辑:程序博客网 时间:2024/06/09 16:44
最近在学习音乐媒体文件的数据结构。首先看的是流行的MP3格式,网上的资料也不少。但是看过后都不能深入理解结构。就抽空时间使用Java写了个简单的Tools  package.当前只能解析MP3格式文件,而且还没有研究出声音压缩流解码的算法。以下代码主要功能:提取文件的标签头(PrivateInfo类)、扩展标签头、标签尾(PublicInfo类)、数据帧(FrameData类)。

music\iface\PrivateInfo.java
----------------------------------------
package iface;

public interface PrivateInfo {
public int getSize();

}
-----------------------------------------
music\iface\PublicInfo.java
-----------------------------------------
package iface;

public interface PublicInfo {

/*
char Header[3];     //标签头必须是"TAG"否则认为没有标签 
char Title[30];     //标题 
char Artist[30];    //作者 
char Album[30];     //专集 
char Year[4];       //出品年代 
char Comment[28];  //备注 
char reserve;       //保留 
char track;        //音轨 
char Genre;         //类型 
*/
public String getTitle();
public String getArtist();
public String getAlbum();
public String getYaer();
public String getComment();
public String getGenre();
}
---------------------------------------------
music\tools\FrameData.java
---------------------------------------------
package tools;

import java.util.Map;

public class FrameData {
private FrameHead frameHead;
private byte[] bitData;
private int bitSize;
public FrameData(byte[] head) throws Exception{
this.frameHead=new FrameData.FrameHead(head);
this.bitSize=this.frameHead.getBitDataSize();
}
public void setBitData(byte[] data){
this.bitData=data;
}
public int getBitSize(){
return this.bitSize;
}
class FrameHead{
/* 4字节(32位)数据*/
/* AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
* 注意以上格式:将解释按照位操作数据
A  31-21 [11bit] Frame sync帧同步
B  20-19 [2 bit] MPEG音频版本 (00 - MPEG Version 2.5)  (01 - 保留)   (10 - MPEG Version 2)   (11 - MPEG Version 1) 
C  18-17 [2 bit] 层描述 (00 - 保留)  (01 - Layer III)  (10 - Layer II)  (11 - Layer I) 
D  16    [1 bit] 保护位 0意味着受CRC保护,帧头后面跟16位的CRC
E  15-12 [4 bit] 比特率
F  11-10 [2 bit] 采样频率 
bits  MPEG1   MPEG2     MPEG2.5
00    44100    22050     11025
01    48000    24000     12000
10    32000    16000     8000
11    reserv   reserv    reserv 
G  9     [1 bit] 意味着帧里包含padding位,仅当采样频率为44.1KHz时发生
H  8      [1 bit] 私有未知位
I  7-6   [2 bit] 声道模式  (00 - 立体声)     (01 - 联合立体声)    (11 - 双通道)    (11 - 单通道)
J  5-4   [2 bit] 扩展模式
bit       强度立体声      MS立体声
00            关闭           关闭
01              开            关
10            关闭            开
11              开            开
K  3      [1 bit] 版权保护位 
L  2      [1 bit] 是否拷贝  0=副本  1=原来的
M  1-0   [2 bit] 重点位   (00 - 不重视)  (01 - 50/15毫秒) (10 - 保留)  (11 - 国际电报电话咨询委员会J.17)
*/
private String MPEGVersion;//MPEG版本
private boolean protectionBit;//true 代表别保护 帧头后有16bit CRC
private int bitrate;//比特率
private int sampling;//采样率
private boolean paddingBit;//
private String channelMode;//声道模式
private int bitDataSize;//实际声音数据大小(字节)

public FrameHead(byte[] head) throws Exception{
if(head.length!=4) {
System.out.println("FrameHead数据长度不足4字节!");
throw  new Exception();
}
this.MPEGVersion=this.getMPEGVersionByBit(head[1]);
this.protectionBit=this.getProtectionBitByBit(head[1]);
this.bitrate=this.getBitrateByBit(head[2]);
this.sampling=this.getSamplingByBit(head[2]);
this.paddingBit=this.getPaddingBitByBit(head[2]);
this.channelMode=this.getChannelModeByBit(head[3]);
this.bitDataSize=this.getBitDataSizeByBit();

}


public int getBitDataSize() {
return bitDataSize;
}


public int getBitrate() {
return bitrate;
}


public String getChannelMode() {
return channelMode;
}


public String getMPEGVersion() {
return MPEGVersion;
}


public boolean isPaddingBit() {
return paddingBit;
}


public boolean isProtectionBit() {
return protectionBit;
}


public int getSampling() {
return sampling;
}


//所有的位操作在private方法进行
private String getMPEGVersionByBit(byte b){
b=(byte) ((b&24)>>>3);//b&00011000
if(b==0) return "MPEG2.5";
else if(b==1) return "保留";
else if(b==2) return "MPEG2";
else if(b==3) return "MPEG1";
else return "ERROR";
}
private boolean getProtectionBitByBit(byte b){
b=(byte) (b&1);//b&00000001
if(b==0) return true;
else return false;
}
private int getBitrateByBit(byte b){
/*
0000 free free free free free free
0001 32 32 32 32 32 8 (8)
0010 64 48 40 64 48 16 (16)
0011 96 56 48 96 56 24 (24)
0100 128 64 56 128 64 32 (32)
0101 160 80 64 160 80 64 (40)
0110 192 96 80 192 96 80 (48)
0111 224 112 96 224 112 56 (56)
1000 256 128 112 256 128 64 (64)
1001 288 160 128 288 160 128 (80)
1010 320 192 160 320 192 160 (96)
1011 352 224 192 352 224 112 (112)
1100 384 256 224 384 256 128 (128)
1101 416 320 256 416 320 256 (144)
1110 448 384 320 448 384 320 (160)
1111 bad bad bad bad bad bad 
*/
b=(byte) ((b&240)>>>4);//b&11110000
if(b==0){
return 0;
}
else if(b==1) return 8000;
else if(b==2) return 16000;
else if(b==3) return 24000;
else if(b==4) return 32000;
else if(b==5) return 40000;
else if(b==6) return 48000;
else if(b==7) return 56000;
else if(b==8) return 64000;
else if(b==9) return 80000;
else if(b==10) return 96000;
else if(b==11) return 112000;
else if(b==12) return 128000;
else if(b==13) return 144000;
else if(b==14) return 160000;
else if(b==15) return -1;
else return -1;


}
private int getSamplingByBit(byte b){
/*
bits  MPEG1    MPEG2     MPEG2.5
00    44100    22050     11025
01    48000    24000     12000
10    32000    16000     8000
11    reserv   reserv    reserv 
*/
b=(byte) ((b&12)>>>2);//00001100
if(b==0){
if(this.MPEGVersion.equals("MPEG1")) return 44100;
else if(this.MPEGVersion.equals("MPEG2")) return 22050;
else if(this.MPEGVersion.equals("MPEG2.5")) return 11025;
else  return -1;
}
else if(b==1){
if(this.MPEGVersion.equals("MPEG1")) return 48000;
else if(this.MPEGVersion.equals("MPEG2")) return 24000;
else if(this.MPEGVersion.equals("MPEG2.5")) return 12000;
else  return -1;
}
else if(b==2){
if(this.MPEGVersion.equals("MPEG1")) return 32000;
else if(this.MPEGVersion.equals("MPEG2")) return 16000;
else if(this.MPEGVersion.equals("MPEG2.5")) return 8000;
else  return -1;
}
else return -1;
}
private boolean getPaddingBitByBit(byte b){
b=(byte) ((b&2)>>>1);//00000010
if(b==1 && this.sampling==44100) return true;//??
else return false;
}
private String getChannelModeByBit(byte b){
/*(00 - 立体声)     (01 - 联合立体声)    (11 - 双通道)    (11 - 单通道)  */
b=(byte) ((b&192)>>>6);//11000000
if(b==0) return "立体声";
else if(b==1) return "联合立体声";
else if(b==2) return "双声道";
else if(b==3) return "单声道";
else return "ERROR";
}
private int getBitDataSizeByBit(){
//(((MpegVersion == MPEG1 ? 144 : 72) * Bitrate) / SamplingRate) + PaddingBit 

int size=(int)((this.MPEGVersion.equals("MPEG1")?144 : 72) *this.bitrate/this.sampling);
if(this.paddingBit) return size+1;
else return size;
}


}//inner Class

}//outer Class
-----------------------------------------------
music\tools\MP3.java
-----------------------------------------------
package tools;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import iface.PrivateInfo;
import iface.PublicInfo;

public class MP3 {
private static Map<Integer,String> style=new HashMap<Integer,String>();

private MP3.SongInfo songInfo;
private MP3.DataInfo dataInfo;

private List<FrameData> frameList;
public MP3(String filename) throws Exception{
File file=new File(filename);
byte[] bufLast=new byte[128];//结尾128字节
byte[] bufFront=new byte[10];//开头10字节
byte[] bufFrame=null;//中间数据帧
byte[] headLabel=null;//标签帧(未知长度和帧数)
try {
FileInputStream is=new FileInputStream(file);
try {
is.read(bufFront,0,10);
this.dataInfo=new MP3.DataInfo(bufFront);

headLabel=new byte[this.dataInfo.getSize()];
is.read(headLabel, 0, headLabel.length);
this.ID3ByHeadList(headLabel);

bufFrame=new byte[(int) (file.length()-128-10-headLabel.length)];
is.read(bufFrame, 0, bufFrame.length);
this.analyseFrame(bufFrame);

is.skip(file.length()-(128+10+headLabel.length+bufFrame.length));
is.read(bufLast, 0, 128);
this.songInfo=new MP3.SongInfo(bufLast);



} catch (IOException e) {
System.out.println("文件读取错误:"+file.getAbsolutePath()+" 文件长度不足128。");
e.printStackTrace();
}
} catch (FileNotFoundException e) {
System.out.println("文件路径:"+file.getAbsolutePath()+" 不存在。");
//e.printStackTrace();
}
}
static{
/*Standard genre*/
style.put(0,"Blues");
style.put(1,"ClassicRock");
style.put(2,"Country");
style.put(3,"Dance");
style.put(4,"Disco");
style.put(5,"Funk");
style.put(6,"Grunge");
style.put(7,"Hip-Hop");
style.put(8,"Jazz");
style.put(9,"Metal");
style.put(10,"NewAge");
style.put(11,"Oldies");
style.put(12,"Other");
style.put(13,"Pop");
style.put(14,"R&B");
style.put(15,"Rap");
style.put(16,"Reggae");
style.put(17,"Rock");
style.put(18,"Techno");
style.put(19,"Industrial");
style.put(20,"Alternative");
style.put(21,"Ska");
style.put(22,"DeathMetal");
style.put(23,"Pranks");
style.put(24,"Soundtrack");
style.put(25,"Euro-Techno");
style.put(26,"Ambient");
style.put(27,"Trip-Hop");
style.put(28,"Vocal");
style.put(29,"Jazz+Funk");
style.put(30,"Fusion");
style.put(31,"Trance");
style.put(32,"Classical");
style.put(33,"Instrumental");
style.put(34,"Acid");
style.put(35,"House");
style.put(36,"Game");
style.put(37,"SoundClip");
style.put(38,"Gospel");
style.put(39,"Noise");
style.put(40,"AlternRock");
style.put(41,"Bass");
style.put(42,"Soul");
style.put(43,"Punk");
style.put(44,"Space");
style.put(45,"Meditative");
style.put(46,"InstrumentalPop");
style.put(47,"InstrumentalRock");
style.put(48,"Ethnic");
style.put(49,"Gothic");
style.put(50,"Darkwave");
style.put(51,"Techno-Industrial");
style.put(52,"Electronic");
style.put(53,"Pop-Folk");
style.put(54,"Eurodance");
style.put(55,"Dream");
style.put(56,"SouthernRock");
style.put(57,"Comedy");
style.put(58,"Cult");
style.put(59,"Gangsta");
style.put(60,"Top40");
style.put(61,"ChristianRap");
style.put(62,"Pop/Funk");
style.put(63,"Jungle");
style.put(64,"NativeAmerican");
style.put(65,"Cabaret");
style.put(66,"NewWave");
style.put(67,"Psychadelic");
style.put(68,"Rave");
style.put(69,"Showtunes");
style.put(70,"Trailer");
style.put(71,"Lo-Fi");
style.put(72,"Tribal");
style.put(73,"AcidPunk");
style.put(74,"AcidJazz");
style.put(75,"Polka");
style.put(76,"Retro");
style.put(77,"Musical");
style.put(78,"Rock&Roll");
style.put(79,"HardRock");
/*Extended genre*/
style.put(80,"Folk");
style.put(81,"Folk-Rock");
style.put(82,"NationalFolk");
style.put(83,"Swing");
style.put(84,"FastFusion");
style.put(85,"Bebob");
style.put(86,"Latin");
style.put(87,"Revival");
style.put(88,"Celtic");
style.put(89,"Bluegrass");
style.put(90,"Avantgarde");
style.put(91,"GothicRock");
style.put(92,"ProgessiveRock");
style.put(93,"PsychedelicRock");
style.put(94,"SymphonicRock");
style.put(95,"SlowRock");
style.put(96,"BigBand");
style.put(97,"Chorus");
style.put(98,"EasyListening");
style.put(99,"Acoustic");
style.put(100,"Humour");
style.put(101,"Speech");
style.put(102,"Chanson");
style.put(103,"Opera");
style.put(104,"ChamberMusic");
style.put(105,"Sonata");
style.put(106,"Symphony");
style.put(107,"BootyBass");
style.put(108,"Primus");
style.put(109,"PornGroove");
style.put(110,"Satire");
style.put(111,"SlowJam");
style.put(112,"Club");
style.put(113,"Tango");
style.put(114,"Samba");
style.put(115,"Folklore");
style.put(116,"Ballad");
style.put(117,"PowerBallad");
style.put(118,"RhythmicSoul");
style.put(119,"Freestyle");
style.put(120,"Duet");
style.put(121,"PunkRock");
style.put(122,"DrumSolo");
style.put(123,"Acapella");
style.put(124,"Euro-House");
style.put(125,"DanceHall");
style.put(126,"Goa");
style.put(127,"Drum&Bass");
style.put(128,"Club-House");
style.put(129,"Hardcore");
style.put(130,"Terror");
style.put(131,"Indie");
style.put(132,"BritPop");
style.put(133,"Negerpunk");
style.put(134,"PolskPunk");
style.put(135,"Beat");
style.put(136,"ChristianGangstaRap");
style.put(137,"HeavyMetal");
style.put(138,"BlackMetal");
style.put(139,"Crossover");
style.put(140,"ContemporaryChristian");
style.put(141,"ChristianRock");
style.put(142,"Merengue");
style.put(143,"Salsa");
style.put(144,"TrashMetal");
style.put(145,"Anime");
style.put(146,"JPop");
style.put(147,"Synthpop");
}
class DataInfo implements PrivateInfo{
/*
char Header[3];  字符串 "ID3"  

char Ver;        版本号ID3V2.3 就记录3  

char Revision;  副版本号此版本记录为0  

char Flag;      存放标志的字节,这个版本只定义了三位,很少用到,可以忽略  

char Size[4];  标签大小,除了标签头的10 个字节的标签帧的大小  


*/
private String id3;
private String ver;
private String revision;
private String flag;
private int size;


public DataInfo(byte[] data) throws Exception{
byte[] temp=null;
if(data.length!=10) {
System.out.println("数据不足10字节或者大于10字节!");
throw new Exception();
}
int pos=0;
temp=new byte[3];
System.arraycopy(data,pos,temp,0,3);
this.id3=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data,pos,temp,0,1);
this.ver=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data,pos,temp,0,1);
this.revision=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data,pos,temp,0,1);
this.flag=new String(temp);
pos=pos+temp.length;

temp=new byte[4];
System.arraycopy(data,pos,temp,0,4);
this.size=MP3.getSizeByByte(temp);

}
public int getSize(){
return this.size;
}
}
class SongInfo implements PublicInfo{
private String tag;

private String title;
private String artist;
private String album;
private String year;
private String comment;
private String genre;

private String reserve;
private String track;

public SongInfo(byte[] data) throws Exception{
byte[] temp=null;
if(data.length!=128){
System.out.println("数据不足128字节或者大于128字节!");
throw new Exception();
}
int pos=0;
temp=new byte[3];
System.arraycopy(data,pos,temp,0,3);
this.tag=new String(temp);
pos=pos+temp.length;

if(!this.tag.equals("TAG")){
return;
}

temp=new byte[30];
System.arraycopy(data, pos, temp, 0, 30);
this.title=new String(temp);
pos=pos+temp.length;

temp=new byte[30];
System.arraycopy(data, pos, temp, 0, 30);
this.artist=new String(temp);
pos=pos+temp.length;

temp=new byte[30];
System.arraycopy(data, pos, temp, 0, 30);
this.album=new String(temp);
pos=pos+temp.length;

temp=new byte[4];
System.arraycopy(data, pos, temp, 0, 4);
this.year=new String(temp);
pos=pos+temp.length;

temp=new byte[28];
System.arraycopy(data, pos, temp, 0, 28);
this.comment=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data, pos, temp, 0, 1);
this.reserve=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data, pos, temp, 0, 1);
this.track=new String(temp);
pos=pos+temp.length;

temp=new byte[1];
System.arraycopy(data, pos, temp, 0, 1);
this.genre=style.get(new Byte(temp[0]).intValue());

}

public String getAlbum() {
// TODO 自动生成方法存根
return this.album;
}

public String getArtist() {
// TODO 自动生成方法存根
return this.artist;
}

public String getComment() {
// TODO 自动生成方法存根
return this.comment;
}

public String getGenre() {
// TODO 自动生成方法存根
return this.genre;
}

public String getTitle() {
// TODO 自动生成方法存根
return this.title;
}

public String getYaer() {
// TODO 自动生成方法存根
return this.year;
}

}
private static int getSizeByByte(byte[] temp){
//return   (int)(temp[0] & 0x7F) << 21| (int)(temp[1] & 0x7F) << 14| (int)(temp[2] & 0x7F) << 7| (int)(temp[3] & 0x7F) + 10; 
int r=1;
if(temp[0]+temp[1]+temp[2]+temp[3]==0) return 0;
for(int x=0;x<4;x++)
if(temp[x]!=0) r=r*temp[x];
return r;
}
private Map<String,String> ID3ByHeadList(byte[] buf){
/*
* 标签帧: | 数据名 | 数据长度 | 标记 | [真实数据] | ..... 
*/
Map<String,String> map=new HashMap<String,String>();
int pix=0;
byte[] head;//4
byte[] size;//4
byte[] flag;//2
int dataLeng=0;
byte[] dataBuf;//n

for(;pix<buf.length;){
head=new byte[4];
size=new byte[4];
flag=new byte[2];
System.arraycopy(buf, pix, head, 0, 4);
pix=pix+4;
System.arraycopy(buf, pix, size, 0, 4);
pix=pix+4;
System.arraycopy(buf, pix, flag, 0, 2);
pix=pix+2;
dataLeng=getSizeByByte(size);
if(dataLeng<=0) return map;
System.out.println(dataLeng);
dataBuf=new byte[dataLeng];
System.arraycopy(buf, pix, dataBuf, 0, dataLeng);
pix=pix+dataLeng;
map.put(new String(head), new String(dataBuf));
System.out.println(new String(head)+" \t| "+new String(dataBuf)+" \t| "+pix);

}


return map;

}
private void analyseFrame(byte[] frameByte) throws Exception{
frameList=new LinkedList<FrameData>();
int pix=0;
int size=0;
byte[] frameHead;//帧头
byte[] frameData;//数据帧
for(;pix<frameByte.length;){
frameHead=new byte[4];
System.arraycopy(frameByte, pix, frameHead, 0, 4);
pix=pix+4;
if(pix>=frameByte.length) break;

FrameData frame=new FrameData(frameHead);
size=frame.getBitSize();
if(size<=0) continue;
//if(size<0) break;//是否注释
frameData=new byte[size];
System.out.println("SIZE:"+size+"  获取源数组开始位置:"+pix+"  新数组大小:"+frameData.length+"  长度: "+frame.getBitSize()+"  总大小:"+frameByte.length);
System.arraycopy(frameByte, pix, frameData, 0, frame.getBitSize());
pix=pix+frame.getBitSize();
System.out.println("PIX:"+pix+"   SIZE:"+size);
if(pix>=frameByte.length) break;
frame.setBitData(frameData);
this.frameList.add(frame);

//if (pix>500) break;

}
}
static public void main(String[] str) throws Exception{
MP3 mp3=new MP3("D:/D/音乐/我可以抱你吗.mp3");
System.out.println(mp3.songInfo.getTitle());
System.out.println(mp3.songInfo.getGenre());
System.out.println(mp3.dataInfo.getSize());
}

}

保证你的歌曲文件路径,编译MP3.java。
原创粉丝点击