600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Java Mp3转化WAV/PCM音频数据 解码详细解析 提取每一帧数据集合/比特流/播放 一行代码!

Java Mp3转化WAV/PCM音频数据 解码详细解析 提取每一帧数据集合/比特流/播放 一行代码!

时间:2024-04-15 16:03:10

相关推荐

Java Mp3转化WAV/PCM音频数据 解码详细解析 提取每一帧数据集合/比特流/播放 一行代码!

导言

大家好!我是原子君

1.因为Java本身只支持,wav,缺少mp3的解码器,所以Java自带的无法对mp3进行处理,这种MPEG-*音频有损压缩标准编码,更不要说使用Java的音频格式和音频流就可以解决。

2.所以本次转换需要使用到colorful1.1这种纯Java-Pc可跨平台的工具框架。

注意:colorful只支持Java19,因为早在之前这就是为了解决Java上遇到的各种麻烦而开发的,所以可以在开发中启到不少帮助。

3.Mp3说白了就是一种压缩技术,其优点是压缩后占用空间小,适用于移动设备的存储和使用。而且还非常好的保持了原来的音质

4.那我们可以开始了:新手安装教程->点击我查看,完成开源,免费,可商用。

解码过程

PCM进行MP3压缩:

封装:以1152个PCM采样值为单位,封装成具有固定长度的MP3数据帧,同时帧是MP3文件的最小组成单位。

利用数据帧:在解码时,利用数据帧中的信息就可以恢复1152个PCM的采样值。

粒度组:而这1152个采样值会被分为2个粒度组,每个粒度组包含576个采样值。

数据帧:一个MP3数据帧分为5个部分,帧头、CRC校验值、边信息、主数据、附加数据。

Mp3结构

MP3 文件一般分为三部分:ID3V2,Frame,ID3V1也属于帧,叫标签帧,Frame 部分叫数据帧,在MP3 文件内不一定有标签帧,但一定有数据帧.

ID3V2

在mp3中的首部-它包含了作者,作曲,专辑,等一些信息,注意长度是不固定的

扩展了ID3V1的信息量.

音频数据Frame:

1.由一系列的数据帧构成,帧的数量由文件大小和帧长决定.

2.每个Frame 的长度可能不相等,也可能相等,由位率决定.

3.每个Frame 分为帧头和数据实体两部分.

4.帧头:记录着mp3 的位率,采样率,版本等信息,

5.每个帧之间相互独立.如果启用CRC校验,则帧头后跟随2字节CRC校验,后面可能会有32字节的附加信息。

ID3V1:包含了作者,作曲,专辑等信息,长度固定为128,标准并不周全,存放的信息少,无法存放歌词,无法录入专辑封面、图片等.

ID3 V2.0 是一个相当完备的标准,但给编写软件带来困难,虽然赞成此格式的人很多,在软件中真正实现的却极少,现在绝大多数MP3 仍使用ID3 V1.0 标准。此标准是将MP3 文件尾的最后128 个字节用来存放ID3 信息.

说明信息

一些mp3可能会携带一些额外的说明信息。

ID3V2解析

开始处,长度为10字节,结构如下:

标签头

头部标识:占3个字节,由字符ID3组成,表示这是一个ID3v2的标签;

主版本号:版本号 ID3V2.3 就记录 3

副版本号:这里记录为0

标签大小:代表的是后面所有标签帧的总大小。一共占四个字节, 但是按照ID3v2 标准的要求,每个字节只用 7 位,最高位不使用,恒为 0,比如:如果后面标签帧的总大小是257,那么在写入时,就必须是:513

数值在写入时:必须以大端格式写入,由于计算结果需要将每个字节的最高位0位丢弃

public static int discard(int num){int result = 0, mask = 0x7F;while ((mask ^ 0x7FFFFFFF)==1){result = num & ~mask;result <<= 1;result |= num & mask;mask = ((mask + 1) << 8) - 1;num = result;}return result;}

恢复:

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”。

帧长计算

计算公式:这取决于位率和频率

Lyaer 1使用公式:

帧长度(字节) = 每帧采样数 / 采样频率 * 比特率/ 8 +填充 * 4

Lyer 2和Lyaer 3使用公式:

帧长度(字节)= 每帧采样数 / 采样频率 * 比特率/ 8 + 填充

2.帧的填充大小就是第23位的帧长调节,不是0就是1。

3.采样个数:MPEG1-3的不同规范,以及同一规范中不同的 Layer1-3,每一帧

对应的采样,都是固定的,具体的值看下表(单位:个/帧):

MPEG帧的采样表

每帧播放时长

每帧播放持续时间 = 帧大小 / 采样率

ID3V1尾部说明

各项信息按顺序存放,没有任何标识将其分开,比如标题信息不足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<String>方式

内容:

保存了每一帧的解码后的二进制数据,随时可以对数据持久化。

也可以对数据音频进行剪辑,等其他变声操作。

它也可以导出成其他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()));};}}

结尾

如果你喜欢的话就点个赞吧。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。