带你写一个Mp文件解析器-Mp3文件结构全解析(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 每个FRAME 都有一个帧头FRAMEHEADER,长度是4BYTE(32bit),帧头后面可能有两个字节的CRC 校验,这两个字节的是否存在决定于FRAMEHEADER 信息的第16bit, 为0 则帧头后面无校验,为1 则有校验,校验值长度为2 个字节,紧跟在FRAMEHEADER 后面,接着就是帧的实体数据了

上一带你写一个Mp文件解析器-Mp3文件结构全解析(一)中分析了MP3的Tag解析,这篇接着分析MP3的音频内容解析


音频数据解析


每个FRAME 都有一个帧头FRAMEHEADER,长度是4BYTE(32bit),帧头后面可能有两个字节的CRC 校 验,这两个字节的是否存在决定于FRAMEHEADER 信息的第16bit, 为0 则帧头后面无校验,为1 则有校验, 校验值长度为2 个字节,紧跟在FRAMEHEADER 后面,接着就是帧的实体数据了,格式如下:


FRAMEHEADER CRC(free) 通道信息 MAIN_DATA
4 BYTE 0 OR 2 BYTE 32 长度由帧头计算得出


帧头FRAMEHEADER 格式


帧头长4字节,结构如下:


typedef FrameHeader
{
unsigned int sync:11;                        //同步信息
unsigned int version:2;                      //版本
unsigned int layer: 2;                           //层
unsigned int error protection:1;           // CRC校验
unsigned int bitrate_index:4;              //位率
unsigned int sampling_frequency:2;         //采样频率
unsigned int padding:1;                    //帧长调节
unsigned int private:1;                       //保留字
unsigned int mode:2;                         //声道模式
unsigned int mode extension:2;        //扩充模式
unsigned int copyright:1;                           // 版权
unsigned int original:1;                      //原版标志
unsigned int emphasis:2;                  //强调模式
}HEADER, *LPHEADER;


详细说明:



帧长度与帧大小


帧大小即每帧的采样数,表示一帧数据中采样的个数,该值是恒定的,如下表所示:


MPEG1 MPEG2(LSF) MPEG2.5(LSF)
Layer1 384 384 384
Layer2 1152 1152 1152
Layer3 1152 576 576


帧长度是压缩时每一帧的长度,包括帧头的4个字节。它将填充的空位也计算在内。Layer 1的一个空位长4字节,Layer 2和Layer 3的空位是1字节。当读取MPEG文件时必须计算该值以便找到相邻的帧。注意:因为有填充和比特率变换,帧长度可能变化


计算公式如下:


  • Layer 1:Len(字节) = ((每帧采样数/8比特率)/采样频率)+填充4
  • Layer2/3:Len(字节) = ((每帧采样数/8*比特率)/采样频率)+填充


例:MPEG1 Layer3 比特率128000,采样率44100,填充0,帧长度为:((1152/8*128K)/44.1K+0=417字节


帧持续时间


计算公式:


每帧持续时间(毫秒) = 每帧采样数 / 采样频率 * 1000


例:1152/441000*1000=26ms


帧数据


在帧头后边是Side Info(姑且称之为通道信息)。对标准的立体声MP3文件来说其长度为32字节。当解码器在读到上述信息后,就可以进行解码了。


对于mp3来说现在有两种编码方式,一种是CBR,也就是固定位率,固定位率的帧的大小在整个文件中都是是固定的(公式如上所述),只要知道文件总长度,和从第一帧帧头读出的信息,就都可以通过计算得出这个mp3文件的信息,比如总的帧数,总的播放时间等等,要定位到某一帧或某个时间点也很方便,这种编码方式不需要文件头,第一帧开始就是音频数据。另一种是VBR,就是可变位率,VBR是XING公司推出的算法,所以在MP3的FRAME里会有“Xing"这个关键字(也有用"Info"来标识的,现在很多流行的小软件也可以进行VBR压缩,它们是否遵守这个约定,那就不得而知了),它存放在MP3文件中的第一个有效帧的数据区里,它标识了这个MP3文件是VBR的。同时第一个帧里存放了MP3文件的帧的总个数,这就很容易获得了播放总时间,同时还有100个字节存放了播放总时间的100个时间分段的帧索引,假设4分钟的MP3歌曲,240S,分成100段,每两个相邻INDEX的时间差就是2.4S,所以通过这个INDEX,只要前后处理少数的FRAME,就能快速找出我们需要快进的帧头。其实这第一帧就相当于文件头了。不过现在有些编码器在编码CBR文件时也像VBR那样将信息记入第一帧,比如著名的lame,它使用"Info"来做CBR的标记。


VBR头文件


VBR文件头位于MP3文件中第一个有效帧的数据区,详细结构如下:



实现一个数据解析器


主要步骤:


  1. 读取一个字节,判断是否是FF
  2. 读取一个字节与上0xF0,判断是否等于0xF0 或者0xE0
  3. 如果是说明找到一帧音频,再依次读取两个字节中计算位率,采样率等信息
  4. 通过公式计算音频内容大小:(144 * (float)nFrameBitRate /(float)nFrameSamplingFrequency ) + nFramePadded
  5. 跳过音频内容大小个字节,继续读取下一帧.


具体代码实现:


position = lseek(fd, tagsize-10, 0);
  printf("seek to get audio frame , position = %d\n", position);
  int nFrames, nFileSampleRate;
  unsigned char ucHeaderByte1, ucHeaderByte2, ucHeaderByte3, ucHeaderByte4;
  float fBitRateSum=0;
syncWordSearch:
  while( position < file_size)
  {
    if (read(fd, &ucHeaderByte1, sizeof(ucHeaderByte1)) < 0) {
      perror("Read File: ");
      exit(1);
    }
    position ++;
    //printf("111:%d\n", ucHeaderByte1);
    if( ucHeaderByte1 == 0xFF )
    {
      if (read(fd, &ucHeaderByte2, sizeof(ucHeaderByte2)) < 0) {
        perror("Read File: ");
        exit(1);
      }
      position ++;
      unsigned char ucByte2LowerNibble = ucHeaderByte2 & 0xF0;
      if( ucByte2LowerNibble == 0xF0 || ucByte2LowerNibble == 0xE0 )
      {
          ++nFrames;
          printf("Found frame %d at offset = %ld B\n",nFrames, position);
          //printf("Header Bits:\n");
          //get the rest of the header:
          if (read(fd, &ucHeaderByte3, sizeof(ucHeaderByte3)) < 0) {
            perror("Read File: ");
            exit(1);
          }
          position ++;
          if (read(fd, &ucHeaderByte4, sizeof(ucHeaderByte4)) < 0) {
            perror("Read File: ");
            exit(1);
          }
          position ++;
          //print the header:
          //printBits(sizeof(ucHeaderByte1),&ucHeaderByte1);
          //printBits(sizeof(ucHeaderByte2),&ucHeaderByte2);
          //printBits(sizeof(ucHeaderByte3),&ucHeaderByte3);
          //printBits(sizeof(ucHeaderByte4),&ucHeaderByte4);
          //get header info:
          int nFrameSamplingFrequency = findFrameSamplingFrequency(ucHeaderByte3);
          int nFrameBitRate = findFrameBitRate(ucHeaderByte3);
          int nMpegVersionAndLayer = findMpegVersionAndLayer(ucHeaderByte2);
          if( nFrameBitRate==0 || nFrameSamplingFrequency == 0 || nMpegVersionAndLayer==0 )
          {//if this happens then we must have found the sync word but it was not actually part of the header
              --nFrames;
              printf("Error: not a header\n\n");
              goto syncWordSearch;
          }
          fBitRateSum += nFrameBitRate;
          if(nFrames==1){ nFileSampleRate = nFrameSamplingFrequency; }
          int nFramePadded = findFramePadding(ucHeaderByte3);
          //calculate frame size:
          int nFrameLength = (144 * (float)nFrameBitRate /
                                            (float)nFrameSamplingFrequency ) + nFramePadded;
          printf("\tFrame Length: %d Bytes \n\n", nFrameLength);
          //lnPreviousFramePosition=ftell(ifMp3)-4; //the position of the first byte of this frame
          //move file position by forward by frame length to bring it to next frame:
          position = lseek(fd, position + nFrameLength-4, 0);
      }
    }
  }
  float fFileAveBitRate= fBitRateSum/nFrames;
  printmp3details(nFrames,nFileSampleRate,fFileAveBitRate);


很多文章都是抄来抄去,犯了很多相同的错误,遇到问题,最权威的还是官方文档,需要很耐心的阅读学习,边阅读边实现,加深自己的理解和记忆.


示例代码地址:git@github.com:qingkouwei/mp3parser.git


如果对你有帮助的话点个赞吧!!!

目录
相关文章
|
9天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
33 2
|
28天前
|
人工智能
歌词结构的巧妙安排:写歌词的方法与技巧解析,妙笔生词AI智能写歌词软件
歌词创作是一门艺术,关键在于巧妙的结构安排。开头需迅速吸引听众,主体部分要坚实且富有逻辑,结尾则应留下深刻印象。《妙笔生词智能写歌词软件》提供多种 AI 功能,帮助创作者找到灵感,优化歌词结构,写出打动人心的作品。
|
1月前
|
Java
Java“解析时到达文件末尾”解决
在Java编程中,“解析时到达文件末尾”通常指在读取或处理文件时提前遇到了文件结尾,导致程序无法继续读取所需数据。解决方法包括:确保文件路径正确,检查文件是否完整,使用正确的文件读取模式(如文本或二进制),以及确保读取位置正确。合理设置缓冲区大小和循环条件也能避免此类问题。
|
1月前
|
自然语言处理 数据处理 Python
python操作和解析ppt文件 | python小知识
本文将带你从零开始,了解PPT解析的工具、工作原理以及常用的基本操作,并提供具体的代码示例和必要的说明【10月更文挑战第4天】
336 60
|
15天前
|
存储
文件太大不能拷贝到U盘怎么办?实用解决方案全解析
当我们试图将一个大文件拷贝到U盘时,却突然跳出提示“对于目标文件系统目标文件过大”。这种情况让人感到迷茫,尤其是在急需备份或传输数据的时候。那么,文件太大为什么会无法拷贝到U盘?又该如何解决?本文将详细分析这背后的原因,并提供几个实用的方法,帮助你顺利将文件传输到U盘。
|
1月前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
20天前
|
机器学习/深度学习 自然语言处理 数据管理
GraphRAG核心组件解析:图结构与检索增强生成
【10月更文挑战第28天】在当今数据科学领域,自然语言处理(NLP)和图数据管理技术的发展日新月异。GraphRAG(Graph Retrieval-Augmented Generation)作为一种结合了图结构和检索增强生成的创新方法,已经在多个应用场景中展现出巨大的潜力。作为一名数据科学家,我对GraphRAG的核心组件进行了深入研究,并在此分享我的理解和实践经验。
42 0
|
26天前
光纤电缆(FOC)的结构深度解析
【10月更文挑战第21天】
41 0
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
70 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0

推荐镜像

更多