前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【从0做项目】Java音缘心动(4)———MP3文件格式解读

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

作者头像
三三是该溜子
发布2025-02-23 08:51:08
发布2025-02-23 08:51:08
7700
代码可运行
举报
文章被收录于专栏:该溜子的专栏该溜子的专栏
运行总次数:0
代码可运行

零:项目结果展示

项目目前已经上线

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

一:上传音乐

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

1:接口设计

2:实体Music类

代码语言:javascript
代码运行次数:0
复制
@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的相关信息保存到数据库中

代码语言:javascript
代码运行次数:0
复制
 @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);//当前没有会话不创建会话,默认为true
        if(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.mp3
        System.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)调用insert
        int 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帧头判断

代码语言:javascript
代码运行次数:0
复制
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字节,前三个字节为TAG
        try {
            // 获取文件的字节数组,读取文件的最后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标签
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零:项目结果展示
  • 一:上传音乐
    • 1:接口设计
    • 2:实体Music类
  • 二:MusicController类
  • 三:实现.MP3文件的检验
    • 1: 文件格式解读
    • 2:结构特点
      • (1)ID3V1标签
      • (2)ID3V2标签
      • (3)帧头Frame
    • 3:上代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档