public static int recovery(int num) {
byte[] D = new byte[4];
D[0] = (byte) (num & 0xff);
D[1] = (byte) (num >> 8 & 0xff);
D[2] = (byte) (num >> 16 & 0xff);
D[3] = (byte) (num >> 24 & 0xff);
int Result = 0x0;
Result = Result | D[0];
Result = Result | (D[1] << 7);
Result = Result | (D[2] << 14);
Result = Result | (D[3] << 21);
return Result;
如果不懂大小端可以看我这一篇文章:
点击我查看 标签帧
数据结构定义:
TIT2 = 标题 表示内容为这首歌的标题
TPE1 = 作者
TALB = 专集
TRCK = 音轨 格式:N/M 其中 N 为专集中的第 N 首,M 为专集中共 M 首,N 和 M 为 ASCII 码表示的数字
TYER = 年代 是用 ASCII 码表示的数字
TCON = 类型 直接用字符串表示
COMM = 备注 格式:"eng/0 备注内容",其中 eng 表示备注所使用的自然语言
Size = 代表帧标记暂时不清楚有什么实际含义。
flags = 代表帧内容的大小,这里要注意,写入时也必须为大端格式。
Frame解析-标签帧
帧头:长4字节, 帧头后面可能有两个字节的CRC 校验,这两个字节的是否存在决定于帧头信息的第16bit, 为0 则帧头后面无校验,为1 则有校验,校验值长度为2 个字节.
(后面是可变长度的附加信息,对于标准的MP3文件来说,其长度是32字节,本括号内的文字内容有待商榷,暂时没见到这样的文件),紧接其后的 是压缩的声音数据,当解码器读到此处时就进行解码了。
所有的Mp3文件的数据帧开始的两个字节都必需是“FF FA”或者 “FF FB”。
名称 | 位长 | 说 明 |
同步信息 | 11 | 第2字节 | 所有位均为1,第1字节恒为FF。 |
版本 | 2 | 00-MPEG 2.5 ,01-未定义,10-MPEG 2 ,11-MPEG 1 |
层 | 2 | 00-未定义 ,01-Layer 3,10-Layer 2 ,11-Layer 1 |
CRC校验 | 1 | 0-校验 ,1-不校验 |
位率 | 4 | 第3字节 | 取样率,单位是kbps,如:采用MPEG-1 Layer 3,64kbps是,值为0101。 bits V1,L1---V1,L2---V1,L3---V2,L1---V2,L2---V2,L3 0000--free--free--free--free--free--free 0001--32--32--32--32(32)--32(8)--8 (8) 0010--64--48--40--64(48)--48(16)--16 (16) 0011--96--56--48--96(56)--56(24)--24 (24) 0100--128--64--56--128(64)--64(32)--32 (32) 0101--160--80--64--160(80)--80(40)--64 (40) 0110--192--96--80--192(96)--96(48)--80 (48) 0111--224--112--96--224(112)--112(56)--56 (56) 1000--256--128--112--256(128)--128(64)--64 (64) 1001--288--160--128--288(144)--160(80)--128 (80) 1010--320--192--160--320(160)--192(96)--160 (96) 1011--352--224--192--352(176)--224(112)--112 (112) 1100--384--256--224--384(192)--256(128)--128 (128) 1101--416--320--256--416(224)--320(144)--256 (144) 1110--448--384--320--448(256)--384(160)--320 (160) 1111--bad--bad--bad--bad--bad--bad V1 - MPEG 1,V2 - MPEG 2 and MPEG 2.5 L1 - Layer 1 ,L2 - Layer 2 , L3 - Layer 3 "free" :位率可变 "bad" :不允许值 |
采样频率 | 2 | MPEG-1: 00-44.1kHz ,01-48kHz ,10-32kHz ,11-未定义 MPEG-2: 00-22.05kHz , 01-24kHz ,10-16kHz ,11 MPEG-2.5: 00-11.025kHz ,01-12kHz ,10-8kHz ,11-未定义 |
帧长调节 | 1 | 用来调整文件头长度,0-无需调整,1-调整 |
保留字 | 1 | 没有使用 |
声道模式 | 2 | 第 4字节 | 00-立体声 ,01-联合立体声(是基于帧与帧完成的), |
扩充模式 | 2 | 声道是01时用:Value强度立体声,MS立体声 00 off off 01 on off 10 off on 11 on on |
版权 | 1 | 0-不合法 1-合法 |
原版标志 | 1 | 0-非原版 1-原版 |
强调方式 | 2 | 用于声音经降噪压缩后再补偿的分类,很少用到,今后也可能不会用。 00-未定义 01-50/15ms 10-保留 11-CCITT J.17 |
帧长计算
计算公式:这取决于 位率和
Lyaer 1使用公式:
帧长度(字节) = 每帧采样数 / 采样频率 * 比特率/ 8 +填充 * 4
Lyer 2和Lyaer 3使用公式:
帧长度(字节)= 每帧采样数 / 采样频率 * 比特率/ 8 + 填充
2.帧的填充大小就是第23位的帧长调节,不是0就是1。
3.采样个数:MPEG1-3的不同规范,以及同一规范中不同的 Layer1-3,每一帧
对应的采样,都是固定的,具体的值看下表(单位:个/帧):
MPEG帧的采样表
| MPEG 1 | MPEG 2(LSF) | MPEG 2.5(LSF) |
Layer 1 | 384 | 384 | 384 |
Layer 2 | 1152 | 1152 | 1152 |
Layer 3 | 1152 | 576 | 576 |
每帧播放时长
每帧播放持续时间 = 帧大小 / 采样率
ID3V1尾部说明
字节 | 长度-bytes | 内容 |
1-3(A) | 3 | 存储了“TAG”字符,表示ID3V1标准,后面歌曲信息。 |
4-33(B) | 30 | 歌名称 |
34-63(C) | 30 | 作者名称 |
64-93(D) | 30 | 专辑名称 |
94-97(E) | 4 | 年份 |
98-125(F) | 28 | 附注 |
126(G) | 1 | 保留位 |
127(H) | 1 | 音轨号 |
127(I) | 1 | MP3音乐类别一共147种 |
各项信息按顺序存放,没有任何标识将其分开,比如标题信息不足30 个字节,会使用”\0”填充。
Mp3解码还原流程
MP3解码经MP3编码方式压缩后的音频数据还原成原始PCM数据的过程。
MP3解码的整个工作流程见图下图,当预处理操作把MP3帧中的帧头和边信息解码后,解码器对经预处理后的信息进行缩放因子解码和哈夫曼解码,得出的结果再经反量化、重排序、立体声解码、混叠消除、逆离散余弦变换、频率反转和子带合成滤波等操作后,得到左右声道PCM音频数据,完成整个解码过程。

解码导言
这些个复杂的解码过程,我已经为大家封装好了,大家直接调用就可以导出左右PCM数据,
和对Mp3的播放。
上代码
前言
我提供了多种方法,共大家使用。
最适合新手的,最快捷的
如果你需要直接播放,我们为你封装好了,此方式,__response是一个工具框架响应快捷类。

代码见下即可:
import IOS_SHOGUN_Component.__response;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import javax.sound.sampled.SourceDataLine;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException {
try {
try (SourceDataLine Mp3 = __response.Debug_PlayMp3("Mp3地址")) {
} catch (Mp3DecodeException e) {
throw new RuntimeException(e);
}
直接导出数据
如果你要直接导出PCM用于缓存或者其他,持久性性存储
两种存储方式
一种是Base64这种二进制存储方式占用内存小,转换后的大小比例大概为1/3,降低了资源服务器的消耗;
base64编码的字符串,更适合不同平台、不同语言的传输
一种是流存储的方式,不过这种大概只能用于暂时性的缓存,不推荐全部转化为了字节数组,因为
存在丢失的风险,通俗来讲就是,你的音频就变成一段乱音了。
TaskList方式
内容:
保存了每一帧的解码后的二进制数据,随时可以对数据持久化。
也可以对数据音频进行剪辑,等其他变声操作。
它也可以导出成其他list集合,以及提供了非常的API方式,
原始API与Java自带的是一致的线程安全集合。
注意:
在将每一帧的提出并且缓存时,我们需要将它转化为,音频数据
import IOS_SHOGUN_Component.TaskList;
import IOS_SHOGUN_Component.__response;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException {
try {
TaskList<String> Data=__response._mp3_extract_mode_Base64("路径");
//保存了每一帧的PCM解码数据
byte[] PCM=__response._base64_T_X2(Data.get(0));
} catch (Mp3DecodeException e) {
throw new RuntimeException(e);
}
流方式
两种方式,一种是ByteArrayOutputStream,一种是ByteArrayInputStream两种方式
import IOS_SHOGUN_Component.__response;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException {
try {
ByteArrayInputStream I=__response._mp3_extract_mode_IStream("路径");
ByteArrayOutputStream O=__response._mp3_extract_mode_OStream("路径");
} catch (Mp3DecodeException e) {
throw new RuntimeException(e);
}
快捷方式还有很多

你也可以直接导出成pcm格式文件
同样可以使用快捷方式
你可以使用常用的本地导出,和缓存的TaskList<String>,流方式

Mp3是缓存数据不是-本地数据怎么提取转换成Pcm数据?
在Colorful1.1中提供了流读取的支持,比如如果是客户端发送来的音频数据,我们就可以使用它。
翻译成ByteArrayInputStream

翻译成ByteArrayOutputStream

非快捷方式
它同时准备了非快捷的接口,看下图这些快捷方式只是对原本开放的API做了一次完成的封装。
_mp3_extract_mode_Decode

Debug_PlayMp3

同样我们可以直接复制
参数Audio,音频输出类,存储方式,是否是本地引入/缓存引入(CacheData),缓存引入时,它只能执行转化程序。
import IOS_SHOGUN_Component.decodeAean.AudioBuffer;
import IOS_SHOGUN_Component.decodeAean.DecodeSuperclasses;
import IOS_SHOGUN_Component.decodeAean.Header;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import IOS_SHOGUN_Component.mp3_Decode;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException, Mp3DecodeException {
mp3_Decode Create = new mp3_Decode(new mp3_Decode.Audio(), AudioBuffer.STREAM, mp3_Decode.LocalData);
Create.open("路径/流的方式", false);
if (Create.onCreateAndStart()) {
DecodeSuperclasses DECODE = Create.getPCM_DecodeSuperclasses();
Header Head = DECODE.getRecording();
AudioFormat af = new AudioFormat((float) Head.getSamplingRate(), 16, Head.getChannels(), true, false);
SourceDataLine DataLineSource;
try {
DataLineSource = AudioSystem.getSourceDataLine(af);
} catch (LineUnavailableException var8) {
throw new RuntimeException(var8);
try {
DataLineSource.open(af, 8 * Head.getPcmSize());
} catch (LineUnavailableException var7) {
throw new RuntimeException(var7);
DataLineSource.start();
ByteArrayInputStream D = DECODE.getAudioBuffer().getPcmDataExportIStream();
byte[] DD = new byte[DECODE.getAudioBuffer().getOffset()];
while (D.read(DD) != -1) {
DataLineSource.write(DD, 0, DD.length);
}
为啥没有TaskList<String>?,因为TaskList数据引入只是我们的一个有备而来的接口,它最后还是会变成流的方式进入mp3_Decode进行解码流程。并且它是线程安全的。
其实这里我写复杂了,可以更简单
参数Audio,音频输出类,存储方式,是否是本地引入/缓存引入(CacheData),缓存引入时,它只能执行转化程序。
->几个参数Audio,音频输出类,存储方式,是否是本地引入/缓存引入(CacheData),缓存引入时,它只能执行转化程序。
import IOS_SHOGUN_Component.*;
import IOS_SHOGUN_Component.decodeAean.AudioBuffer;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException, Mp3DecodeException {
//参数Audio,音频输出类,存储方式,是否是本地引入/缓存引入(CacheData),缓存引入时,它只能执行转化程序。
mp3_Decode.Audio a=new mp3_Decode.Audio();
mp3_Decode Create = new mp3_Decode(a, AudioBuffer.STREAM, mp3_Decode.LocalData);
Create.open("流/路径", true);
if (Create.onCreateAndStart()){
//创建解码向导
}
我们再添加一点操作,因为在创建(onCreateAndStart)时,程序是阻塞的
import IOS_SHOGUN_Component.*;
import IOS_SHOGUN_Component.decodeAean.AudioBuffer;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import java.io.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
public class Java {
public static void main(String[] X) throws IOException, Mp3DecodeException {
SequenceCachedPool C=new SequenceCachedPool(1,2,100, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(10));//创建一个池做操作控制
//参数Audio,音频输出类,存储方式,是否是本地引入/缓存引入(CacheData),缓存引入时,它只能执行转化程序。
mp3_Decode.Audio a=new mp3_Decode.Audio();
mp3_Decode Create = new mp3_Decode(a, AudioBuffer.STREAM, mp3_Decode.LocalData);
Create.open("流/路径", true);
C.submit(()->{
try {
TimeUnit.SECONDS.sleep(5);
Create.close();
//5秒后退出
} catch (InterruptedException e) {
throw new RuntimeException(e);
if (Create.onCreateAndStart()){
//创建解码向导
}
更推荐这样做
我更加的推荐把它当作一个转化程序,去做,因为它本身的任务不是播放。
这些是额外附加的。
getBase64Statistics(专为PCM-Base64数据做统计),像这样的专项API还有很多

代码
import IOS_SHOGUN_Component.*;
import IOS_SHOGUN_Component.decodeAean.AudioBuffer;
import IOS_SHOGUN_Component.decodeAean.Mp3DecodeException;
import java.io.*;
public class Java {
public static void main(String[] X) throws IOException, Mp3DecodeException {
//注意这里必须是AudioBuffer.BASE64,不然不管以任何方式获取base64的PCM纯音频数据都将为空!
mp3_Decode Create = new mp3_Decode(new mp3_Decode.Audio(), AudioBuffer.BASE64, mp3_Decode.LocalData);
Create.open("D:\\WindowsDataStorageFolder\\CSDN2.mp3", false);//为false只做转化
if (Create.onCreateAndStart()){
TaskList<String> Data=Create.getPCM_dataLine().getPcmDataTaskList();
//TaskList<String> Data=Create.getPCM_DecodeSuperclasses().getAudioBuffer().getPcmDataTaskList();
console.success("数据总长%s".formatted("PCM数据段总长->"+Data.getBase64Statistics()));
}
结尾
如果你喜欢的话就点个赞吧。