Java Mp3 は、WAV/PCM オーディオ データを変換し、デコードして詳細に分析し、データ収集/ビット ストリーム/再生の各フレームを抽出する、1 行のコードです!

序文


みなさんこんにちは! Atom Jun 1 です. Java 自体は wav しかサポートしておらず、mp3 デコーダーがないため、Java のビルトインでは mp3 を処理できません. この MPEG-* オーディオ 非可逆圧縮標準エンコーディング は 、Java のオーディオ形式とオーディオ ストリームを使用することはもちろんのこと、解決されます。
2. したがって、この変換では、純粋な Java-PC クロスプラットフォーム ツール フレームワークである、colorful1.1 を使用する必要があります。
注意: カラフルは Java で発生するさまざまな問題を解決するために開発されたため、Java19 のみをサポートしているため、開発に大いに役立ちます。
3. 率直に言えば、Mp3 は圧縮技術であり、 その 利点は、圧縮後の容量が少なくモバイル デバイスでの保存と使用に適していることです。また、元の音質も非常によく維持されています 4.
次に 、開始できます

デコード処理

PCM は MP3 圧縮を行います。
カプセル化: 1152 個の PCM サンプリング値を使用して、固定長の MP3 データ フレームにカプセル化し、同時にフレームは MP3 ファイルの最小構成単位です。
データ フレームを利用する:デコード時に、データ フレーム内の情報を使用して 1152 PCM サンプリング値を復元します。
粒度グループ: 1152 のサンプリング値は 2 つの粒度グループに分割され、各粒度グループには 576 のサンプリング値が含まれます。
データ フレーム: MP3 データ フレームは、フレーム ヘッダー、CRC チェック値、サイド情報、メイン データ、追加データの 5 つの部分に分割されます。

MP3 構造

MP3ファイルは一般的に3つの部分に分けられます.ID3V2,フレーム,ID3V1もタグフレームと呼ばれるフレームに属し,フレーム部分はデータフレームと呼ばれます.MP3ファイルにはタグフレームが存在しない場合がありますが,データが存在する必要があります.フレーム。

mp3 のID3V2
の最初の部分- 作成者、作曲者、アルバムなどの情報が含まれています。長さは固定されておらず、
ID3V1 の情報量が拡張されていることに注意してください。
オーディオ データ フレーム :
1. 一連のデータ フレームで構成され、フレーム数はファイル サイズとフレーム長によって決定されます.
2. 各フレームの長さは、ビット レートによって決定され、等しくない場合も、等しくない場合もあります.
3. 各フレーム フレーム ヘッダーとデータ エンティティの 2 つの部分に分けられます.
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 として記録されます
ラベル サイズ: 以下を表します の合計サイズすべてのタグ付きフレーム。合計 4 バイトを占有します 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;
    }
大小を理解していない場合は、私の記事を読むことができます: Click me to view

ラベルフレーム

データ構造の定義:
TIT2 = タイトルは曲のタイトルを示します
TPE1 = 作者
TALB = アルバム
TRCK = トラック フォーマット: N/M (N はアルバムの N 番目の曲、M はアルバムの合計 M 曲、N と M) TYER = ASCII コードで表現された数値
TYER = 年は ASCII コードで表現された数値
TCON = 型は文字列で直接表現
COMM = コメント形式: "eng/0 remark content"、ここで eng は使用される自然言語を表す注釈
サイズ = フレームを表す マークの実際の意味が何であるかは明確ではありません。
flags = フレーム コンテンツのサイズを表します。ここで、書き込み時にはビッグ エンディアン形式である必要があることに注意してください。

フレーム分析 - ラベル フレーム

フレーム ヘッダー: 4バイトの長さ。フレーム ヘッダーの後に 2 バイトの CRC チェックがある場合があります。これらの 2 バイトの存在は、フレーム ヘッダー情報の 16 番目のビットに依存します。0 の場合、フレーム ヘッダーの後にチェックサムはありません。 、1 はチェックサムがあることを意味し、チェックサム値の長さは 2 バイトです
(以下は可変長の追加情報です。標準の MP3 ファイルの場合、長さは 32 バイトです。この括弧内のテキストの内容は、このようなファイルはしばらく見たことがありません)、圧縮された音声データが続き、デコーダーがここを読み取るときにデコードされます。

すべての Mp3 ファイルのデータ フレームの最初の 2 バイトは、 "FF FA" または "FF FB" でなければなりません

名前

ビット長

例証する

同期情報

11

2バイト目

すべてのビットが 1 で、最初のバイトは常に FF です。

バージョン

2

00-MPEG 2.5、01-未定義、10-MPEG 2、11-MPEG 1

2

00-未定義、01-レイヤー 3、10-レイヤー 2、11-レイヤー 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-联合立体声(是基于帧与帧完成的), 10-双声道 ,11-单声道

扩充模式

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<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()));
        };
    }
}

结尾

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

おすすめ

転載: blog.csdn.net/m0_61267721/article/details/129345078