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

简介: 每个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


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

目录
相关文章
|
12月前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
2572 65
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
9月前
|
数据采集 JSON 数据可视化
JSON数据解析实战:从嵌套结构到结构化表格
在信息爆炸的时代,从杂乱数据中提取精准知识图谱是数据侦探的挑战。本文以Google Scholar为例,解析嵌套JSON数据,提取文献信息并转换为结构化表格,通过Graphviz制作技术关系图谱,揭示文献间的隐秘联系。代码涵盖代理IP、请求头设置、JSON解析及可视化,提供完整实战案例。
551 4
JSON数据解析实战:从嵌套结构到结构化表格
|
9月前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
348 5
|
10月前
|
Java API 数据处理
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
245 4
|
自然语言处理 算法 Python
再谈递归下降解析器:构建一个简单的算术表达式解析器
本文介绍了递归下降解析器的原理与实现,重点讲解了如何使用Python构建一个简单的算术表达式解析器。通过定义文法、实现词法分析器和解析器类,最终实现了对基本算术表达式的解析与计算功能。
330 52
|
11月前
|
Serverless 对象存储 人工智能
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
415 4
智能文件解析:体验阿里云多模态信息提取解决方案
|
12月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
12月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
12月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
9月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
847 29

热门文章

最新文章

推荐镜像

更多
  • DNS