欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > 【从0做项目】Java音缘心动(4)———MP3文件格式解读

【从0做项目】Java音缘心动(4)———MP3文件格式解读

2025/2/25 6:17:21 来源:https://blog.csdn.net/2301_80133875/article/details/145786293  浏览:    关键词:【从0做项目】Java音缘心动(4)———MP3文件格式解读

阿华代码,不是逆风,就是我疯

你们的点赞收藏是我前进最大的动力!!

希望本文内容能够帮助到你!!

目录

 

零:项目结果展示

一:上传音乐

1:接口设计

2:实体Music类

二:MusicController类

三:实现.MP3文件的检验

1: 文件格式解读

2:结构特点

(1)ID3V1标签

(2)ID3V2标签

(3)帧头Frame

3:上代码


零:项目结果展示

项目目前已经上线 

音乐播放器登录页面铁子们可以后台私信获取管理员用户和密码

一:上传音乐

上文我们把用户登录模块设计实现完毕,本文将实现上传音乐的模块

1:接口设计

2:实体Music类

@Data
public class Music {private int id;private String title;private String singer;private String url;private String time;private int userid;
}

二:MusicController类

因为这个类比较复杂,我就单独拿出来给大家分析了

此类用于接待前端用户上传的音乐 ,总体实现框架如我注解分出的四步骤

①检查是否登录(这一步比较简单,就略过了哈,不清楚的可以看之前几篇文章讲解)

②校验是否是.mp3文件(这里需要实现FileVerify.isMP3File()方法,重点!!)

③把music上传到服务器(这里去业务逻辑层,也就是Service类中实现音乐的上传)

④把music的相关信息保存到数据库中

 @RequestMapping("/upload")public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam("fileName")MultipartFile file , HttpServletRequest request, HttpServletResponse response) throws IOException {//1:检查是否登录了HttpSession httpSession = request.getSession(false);//当前没有会话不创建会话,默认为trueif(httpSession == null ||httpSession.getAttribute(Constants.USERINFO_SESSION_KEY) == null){return new ResponseBodyMessage<>(-1,"没有登陆",false);}//2:检验是否是mp3文件格式Boolean result = FileVerify.isMP3File(file);if(result == false){return new ResponseBodyMessage<>(-1,"上传的文件格式错误",false);}String fileNameAndType = file.getOriginalFilename();//获取文件的完整名称,包括文件名称和文件拓展名,xxx.mp3System.out.println("fileNameAndType----->" + fileNameAndType);String path = SAVE_PATH + "/" + fileNameAndType;//文件的存储路径File dest = new File(path);//用这个存储路径new 一个file , dest现在就相当于 前端传进来的file可以这么理解//3:music上传到服务器Boolean result2 = musicService.uploadServer(file, path);if(!result2){return new ResponseBodyMessage<>(-1,"服务器上传失败",false);}//4:进行数据库的上传// (1)准备数据 (2)调用insertint ret = musicService.uploadDatebase(fileNameAndType,httpSession,singer);if(ret == 1){response.sendRedirect("/list.html");//前端实现完毕,上传成功进行跳转页面(重定向)} else if (ret == -1) {//已经存在该音乐了return new ResponseBodyMessage<>(-1,"数据库中已经存在该音乐",false);} else {dest.delete();//上传数据库失败return new ResponseBodyMessage<>(-1,"数据库上传失败",false);}return new ResponseBodyMessage<>(0,"数据库上传成功",true);}

三:实现.MP3文件的检验

思考:我们常规思维判定一个文件是否是.mp3音乐文件,是怎么样的?大部分人看到文件名后缀是.mp3默认就把这个文件当成音乐文件了。但是我们作为一名合格的程序猿,这么思考肯定是不行的,万一用户把一个图片文件后缀名改为.mp3,冒充上传,那不就g了。其次我们也要规定你上传的文件大小不能超过一定的范围(这里在配置中已经设置过了)一首歌也就十几二十兆

1: 文件格式解读

2:结构特点

我们的.mp3文件一定会包含Frame帧和ID3V1标签,那我们逐层分析

(1)ID3V1标签

ID3V1标签永远处于文件的末128个字节,这128个字节的前三位为TAG(有些ID3V1标签不讲武德,没有把这三个标志符放到前三位,少数,别问我怎么知道的!!)

有些歌曲,文件格式中TAG不在末尾,GG。

总结:判断.mp3文件后128字节中,前三个字节是否为TAG可以作为我们的判断依据之一

(2)ID3V2标签

这个标签一般是放在整个.mp3文件的头部(也可能在文件的中部),因为我们V1标签的长度有限,存储不了更多的信息,所以V2标签就是用来扩展信息内容的!

如果在头部:这个标签的特点就是前三个字节为ID3

总结:判断.mp3文件前三个字节是否为ID3,也可以作为我们的判断依据之一

(3)帧头Frame

前面说了,我们mp3文件的头部不是v2标签就是Frame帧头,帧头有什么特点呢,先看看它的结构组成吧!

mp3文件的帧头前四个字节,也就是16位,其实是固定的

①看同步信息——11位固定为1——即红色字体

②版本——2位——这里我们要的是mp3音频格式——所以是MPEG——看蓝色圈圈——固定为11

③层——2位——看绿色圈圈——我们要的是Layer——那就是01

④CRC校验——这个没有强制要求——所以0和1都存在

综上所述:那我们的帧头4个字节,16位,只有两种可能

1111  1111  1111  1010   或者  1111  1111  1111  1011

换算为16进制表示就是 FFFA 或者 FFFB 

总结:判断.mp3文件前四个字节是否为FFFA或者FFFB,也可以作为我们的判断依据之一

3:上代码

注意点:这1+3个判断依据是有先后顺序的

第一步:先判断文件名后缀是否是.mp3(这一步正常的音乐文件名后缀都有)

第二步:用ID3V1标签中TAG字节的特点判断(因为ID3V1一直在末尾,也是我们mp3文件中一定包含的)

第三步:用ID3V2标签中ID3字节(因为文件头部可能是ID3V2也可能是Frame,并且ID3V2还可能会在文件中部或者压根mp3文件中就没有v2标签)

第四步:用Frame帧头判断

package com.example.musicserver.tools;import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class FileVerify {/*** 判断上传的文件是否为MP3文件* @param file 上传的文件* @return true 如果是MP3文件,false 如果不是*/public static boolean isMP3File(MultipartFile file) throws IOException {
//         第一种判断:判断文件名是否以.mp3结尾String fileName = file.getOriginalFilename();if (file == null || fileName == null || !fileName.toLowerCase().endsWith(".mp3")) {return false;}
//        第二种判断:尾部ID3v1标签128字节,前三个字节为TAGtry {// 获取文件的字节数组,读取文件的最后128字节byte[] fileBytes = file.getBytes();int fileLength = fileBytes.length;// 如果文件小于128字节,无法包含ID3v1标签if (fileLength < 128) {return false;}// 获取文件的最后128字节byte[] last128Bytes = new byte[3];/*** 数组复制:源数组,起始位置,目标数组,目标数组起始位置,复制的元素个数*/System.arraycopy(fileBytes, fileLength - 128, last128Bytes, 0, 3);// 检查ID3v1标签的前三个字节是否为 "TAG"if (last128Bytes[0] == 'T' && last128Bytes[1] == 'A' && last128Bytes[2] == 'G') {return true;  // 包含ID3v1标签,认为是有效的MP3文件}} catch (IOException e) {e.printStackTrace();}//            第三种判断:判断头部,mp3有一些特定的文件头(ID3v2标签的标识符,它可能在头部也可能在中间)或(MP3的帧头信息)try (InputStream inputStream = file.getInputStream()) {byte[] header = new byte[3]; // 用于读取文件头的前三个字节inputStream.read(header);// 检查文件头部是否包含 "ID3" 标识符(ID3v2标签)if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') {// ID3v2标签return true;}// 读取MP3帧头byte[] frameHeader = new byte[4]; // 用来读取MP3帧头inputStream.read(frameHeader);//             第四种判断:检查文件是否是一个有效的MP3帧(音频数据通常以 "FF FB" 或者 "FF FA"开头)if (frameHeader[0] == (byte) 0xFF && (frameHeader[1] == (byte) 0xFA || frameHeader[1] == (byte) 0xFB)) {// MP3帧头的标志位,表示这是一个有效的MP3音频帧return true;}} catch (IOException e) {e.printStackTrace();}return false;  // 如果没有找到ID3v1标签}}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词