最简单的 AAC 音频码流解析程序

最简单的 AAC 音频码流解析程序

  • 最简单的 AAC 音频码流解析程序
    • 原理
    • 源程序
    • 运行结果
    • 下载链接
    • 参考

最简单的 AAC 音频码流解析程序

参考雷霄骅博士的文章:视音频数据处理入门:AAC音频码流解析

本文中的程序是一个AAC码流解析程序。该程序可以从AAC码流中分析得到它的基本单元ADTS frame,并且可以简单解析ADTS frame首部的字段。通过修改该程序可以实现不同的AAC码流处理功能。

注:本程序在雷博士的基础上多解析了很多ADTS frame首部的字段,比如 ID、Channel Configuration等,并且完善了Profile字段的解析。

原理

AAC原始码流(又称为“裸流”)是由一个一个的ADTS frame组成的。它们的结构如下图所示。

在这里插入图片描述

其中每个ADTS frame之间通过syncword(同步字)进行分隔。同步字为0xFFF(二进制“111111111111”)。AAC码流解析的步骤就是首先从码流中搜索0x0FFF,分离出ADTS frame;然后再分析ADTS frame的header里的各个字段。本文的程序即实现了上述的两个步骤。

ADTS frame header:

在这里插入图片描述

源程序

整个程序位于simplest_aac_parser()函数中。

/**
* 最简单的 AAC 音频码流解析程序
* Simplest AAC Parser
*
* 原程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本项目是一个 AAC 码流分析程序,可以分离并解析 ADTS 帧。
*
* This project is an AAC stream analysis program.
* It can parse AAC bitstream and analysis ADTS frame of stream.
*
*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 解决报错:fopen() 函数不安全
#pragma warning(disable:4996)int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data, int* data_size)
{int size = 0;if (!buffer || !data || !data_size){return -1;}while (1){if (buf_size < 7){// 一个 ADTS header 最少占 7 字节,如果小于 7 字节,说明不是一个 ADTS frame 或数据不完整return -1;}// 同步字占 12 bit,为 0xfffif ((buffer[0] == 0xFF) && ((buffer[1] & 0xF0) == 0xF0)){// frame_length,13 bit,表示当前 ADTS 帧的长度,存储在第 4 个字节的后两位,第 5 个字节,第 6 个字节的前三位size |= ((buffer[3] & 0x03) << 11); // high 2 bitsize |= buffer[4] << 3; // middle 8 bitsize |= ((buffer[5] & 0xE0) >> 5); // low 3 bitbreak;}--buf_size;++buffer;}if (buf_size < size) {return 1;}memcpy(data, buffer, size);*data_size = size;return 0;
}int simplest_aac_parser(char *url)
{int data_size = 0;int size = 0;int frame_cnt = 0;int offset = 0;// FILE *myout = fopen("output_log.txt", "wb+");FILE *myout = stdout;unsigned char *aacframe = (unsigned char *)malloc(1024 * 5);unsigned char *aacbuffer = (unsigned char *)malloc(1024 * 1024);FILE *ifile = fopen(url, "rb");if (!ifile){printf("Open file error.\n");return -1;}printf("-----+----+-------+-------------------+- ADTS Frame Table --+---------+------+-----------------+--------+\n");printf(" NUM | ID | Layer | Protection Absent | Profile | Frequency | Channel | Size | Buffer Fullness | Blocks |\n");printf("-----+----+-------+-------------------+---------------------+---------+------+-----------------+--------+\n");while (!feof(ifile)){data_size = fread(aacbuffer + offset, 1, 1024 * 1024 - offset, ifile);unsigned char* input_data = aacbuffer;while (1){int ret = getADTSframe(input_data, data_size, aacframe, &size);if (ret == -1)break;else if (ret == 1){memcpy(aacbuffer, input_data, data_size);offset = data_size;break;}char profile_str[50] = { 0 };char frequence_str[10] = { 0 };// ID,1 bit,表示 MPEG 版本,存储在第 2 字节的第五比特// 值为 0 表示 MPEG-4,值为 1 表示 MPEG-2unsigned char id = (aacframe[1] & 0x08) >> 3;// Layer,2 bit,表示音频流所属的层级,存储在第 2 字节的第 6、7 比特unsigned char layer = (aacframe[1] & 0x00) >> 1;// Protection Absent,1 bit,指示是否启用 CRC 错误校验,存储在第 2 字节的最后一个比特// 1 表示没有 CRC,整个 ADST 头为 7 字节;0 表示有 CRC,整个 ADST 头为 9 字节unsigned char protection_absent = aacframe[1] & 0x01;// profile,2 bit,表示 AAC 规格,存储在第 3 字节的头两位unsigned char profile = (aacframe[2] & 0xC0) >> 6;if (id == 1) // MPEG-2{switch (profile){case 0:sprintf(profile_str, "Main");break;case 1:sprintf(profile_str, "LC");break;case 2:sprintf(profile_str, "SSR");break;default:sprintf(profile_str, "unknown");break;}}else // MPEG-4{switch (profile){case 0: sprintf(profile_str, "AAC MAIN"); break;case 1: sprintf(profile_str, "AAC LC"); break;case 2: sprintf(profile_str, "AAC SSR"); break;case 3: sprintf(profile_str, "AAC LTP"); break;case 4: sprintf(profile_str, "SBR"); break;case 5: sprintf(profile_str, "AAC scalable"); break;case 6: sprintf(profile_str, "TwinVQ"); break;case 7: sprintf(profile_str, "CELP"); break;case 8: sprintf(profile_str, "HVXC"); break;case 11: sprintf(profile_str, "TTSI"); break;case 12: sprintf(profile_str, "Main synthetic"); break;case 13: sprintf(profile_str, "Wavetable synthesis"); break;case 14: sprintf(profile_str, "General MIDI"); break;case 15: sprintf(profile_str, "Algorithmic Synthesis and Audio FX"); break;default:sprintf(profile_str, "reversed");break;}}// Sampling Frequency Index,4 bit,表示采样率的索引,存储在第 3 字节的 3~6 位unsigned char sampling_frequency_index = (aacframe[2] & 0x3C) >> 2;switch (sampling_frequency_index){case 0: sprintf(frequence_str, "96000Hz"); break;case 1: sprintf(frequence_str, "88200Hz"); break;case 2: sprintf(frequence_str, "64000Hz"); break;case 3: sprintf(frequence_str, "48000Hz"); break;case 4: sprintf(frequence_str, "44100Hz"); break;case 5: sprintf(frequence_str, "32000Hz"); break;case 6: sprintf(frequence_str, "24000Hz"); break;case 7: sprintf(frequence_str, "22050Hz"); break;case 8: sprintf(frequence_str, "16000Hz"); break;case 9: sprintf(frequence_str, "12000Hz"); break;case 10: sprintf(frequence_str, "11025Hz"); break;case 11: sprintf(frequence_str, "8000Hz"); break;case 12: sprintf(frequence_str, "7350Hz"); break;case 13:case 14:sprintf(frequence_str, "reversed"); break;case 15: sprintf(frequence_str, "escape value"); break;default:sprintf(frequence_str, "unknown"); break;}// Private Bit,1 bit,私有比特,存储在第 3 字节的第七比特unsigned char private_bit = (aacframe[2] & 0x02) >> 1;// Channel Configuration,3 bit,表示音频的通道数,存储在第 3 个字节的最后一比特,第 4 个字节前两个比特unsigned char channel_configuration = 0;channel_configuration |= ((aacframe[2] & 0x01) << 2); // high 1 bitchannel_configuration |= ((aacframe[3] & 0xC0) >> 6); // low 2 bit// Originality,1 bit,存储在第 4 个字节的第三比特unsigned char originality = (aacframe[3] & 0x20) >> 5;// Home,1 bit,存储在第 4 个字节的第四比特unsigned char home = (aacframe[3] & 0x10) >> 4;// Copyright Identification Bit,1 bit,存储在第 4 个字节的第五比特unsigned char copyright_identification_bit = (aacframe[3] & 0x08) >> 3;// Copyright Identification Start,1 bit,存储在第 4 个字节的第六比特unsigned char copyright_identification_start = (aacframe[3] & 0x04) >> 2;// Adts Buffer Fullness,11 bit,存储在第 6 个字节的后五个比特,第 7 个字节的前六个比特// 0x7FF 表示码率可变的码流,0x000 表示固定码率的码流int adts_buffer_fullness = 0;adts_buffer_fullness |= ((aacframe[5] & 0x1F) << 6); // high 5 bitadts_buffer_fullness |= ((aacframe[6] & 0xFC) >> 2); // low 6 bit// 存储在第 7 个字节的最后两比特,该字段表示当前 ADST 帧中所包含的 AAC 帧的个数减一unsigned char number_of_raw_data_blocks_in_frame = aacframe[6] & 0x03;// NUM | ID | Layer | Protection Absent | Profile | Frequency | Channel | Size | Buffer Fullness | Blocksfprintf(myout, "%5d|%4d|%7d|%19d|%9s|%11s|%9d|%6d|            %#X|%8d|\n",frame_cnt, id, layer, protection_absent, profile_str, frequence_str, channel_configuration,size, adts_buffer_fullness, number_of_raw_data_blocks_in_frame);data_size -= size;input_data += size;frame_cnt++;}}fclose(ifile);free(aacbuffer);free(aacframe);return 0;
}int main()
{char in_filename[] = "tdjm.aac";simplest_aac_parser(in_filename);system("pause");return 0;
}

上文中的函数调用方法如下所示。

simplest_aac_parser("nocturne.aac");

运行结果

本程序的输入为一个 AAC 原始码流(裸流)的文件路径,输出为该码流中 ADTS frame 的统计数据,如下图所示。

在这里插入图片描述

下载链接

GitHub:UestcXiye / Simplest-AAC-Parser

CSDN:Simplest AAC Parser.zip

参考

  1. https://blog.csdn.net/leixiaohua1020/article/details/50535042
  2. https://www.cnblogs.com/daner1257/p/10709233.html
  3. https://blog.csdn.net/jay100500/article/details/52955232

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/788822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux(05) Debian 系统修改主机名

查看主机名 方法1&#xff1a;hostname hostname 方法2&#xff1a;cat etc/hostname cat /etc/hostname 如果在创建Linux系统的时候忘记修改主机名&#xff0c;可以采用以下的方式来修改主机名称。 修改主机名 注意&#xff0c;在linux中下划线“_”可能是无效的字符&…

数据结构(初阶)第一节:数据结构概论

本篇文章是对数据结构概念的纯理论介绍&#xff0c;希望系统了解数据结构概念的友友可以看看&#xff0c;对概念要求不高的友友稍做了解后移步下一节&#xff1a; 数据结构&#xff08;初阶&#xff09;第二节&#xff1a;顺序表-CSDN博客 正文 目录 正文 1.数据结构的相关概…

qqqqqqq

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Polardb MySQL 产品架构及特性

一、产品概述; 1、产品族 参考&#xff1a;https://edu.aliyun.com/course/3121700/lesson/341900000?spma2cwt.28120015.3121700.6.166d71c1wwp2px 2、polardb mysql架构优势 1&#xff09;大容量高弹性&#xff1a;最大支持存储100T&#xff0c;最高超1000核CPU&#xff0…

open Gauss 数据库-03 openGauss数据库维护管理指导手册

发文章是为了证明自己真的掌握了一个知识&#xff0c;同时给他人带来帮助&#xff0c;如有问题&#xff0c;欢迎指正&#xff0c;祝大家万事胜意&#xff01; 目录 前言 openGauss数据库维护管理 1 操作系统参数检查 1.1 实验介绍 1.2 场景设置及操作步骤 2 openGauss 运…

认识什么是Webpack

目录 1. 认识Webpack 1.1. 什么是Webpack?&#xff08;定义&#xff09; 1.2. 使用Webpack 1.2.1. 需求 1.2.2. 步骤 1.3. 入口和出口默认值 1.3.1. 需求代码如下 2. 修改Webpack打包入口和出口 2.1. 步骤&#xff1a; 2.2. 注意 3. Webpack自动生成html文件 3.1.…

D-迷恋网游(遇到过的题,做个笔记)

我的代码&#xff1a; #include <iostream> using namespace std; int main() {int a, b, c; //a表示内向&#xff0c;b表示外向&#xff0c;c表示无所谓cin >> a >> b >> c; //读入数 if (b % 3 0 || 3-b % 3 < c) //如果外向的人能够3人组成…

真·面试题总结——JVM虚拟机

JVM虚拟机 JVM虚拟机规范与实现 JVM虚拟机规范 JVM虚拟机实现 JVM的常见实现 JVM虚拟机物理架构 JVM虚拟机的运转流程 JVM类加载过程 JVM类加载器及类加载器类型 JVM类加载器双亲委派机制 JVM运行时数据区的内存模型 JVM运行时数据区的内存模型&#xff1a;程序计数器…

蓝桥杯第八届c++大学B组详解

目录 1.购物单 2.等差素数列 3.承压计算 4.方格分割 5.日期问题 6.包子凑数 7.全球变暖 8.k倍区间 1.购物单 题目解析&#xff1a;就是将折扣字符串转化为数字&#xff0c;进行相加求和。 #include<iostream> #include<string> #include<cmath> usin…

vue2 列表一般不使用索引删除的原因

在 Vue 中使用索引来删除列表项可能会导致一系列问题&#xff0c;尤其是在处理动态列表时。以下是一些可能的问题和相应的例子&#xff1a; 1. 数据不一致问题 当你使用索引来删除列表中的某个项时&#xff0c;如果列表中的其他项发生了变化&#xff08;比如新增或重新排序&a…

编译时提示存在多个默认构造函数的错误怎么解决呢?

c程序中&#xff0c;如果编译器提升存在多个默认构造函数怎么解决呢&#xff1f; class Date { public:Date(){_year 1900;_month 1;_day 1;}Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;} private:int _year;int _month;int _day…

chromium源码学习-调试日志 LOG

在学习 chromium 源码时&#xff0c;我们经常需要增加调试日志&#xff0c;常见的用法一般是 LOG(INFO) << "调试信息";其中 INFO 代表当前这条日志的级别&#xff0c;使用的时候就是输入 INFO 就行。接下来我们在探索下这个宏背后的内容。 一、基本用法 LO…

读所罗门的密码笔记08_共生思想(下)

1. 机器判断 1.1. 在生活的各个领域&#xff0c;机器正在我们无意识的情况下做出更多的决定 1.1.1. 我们看到的新闻会塑造我们的观点和行动&#xff0c;它们是根据我们过去行为中所表达的倾向&#xff0c;或者其他同类人的行为而生成的 1.2. …

K-均值聚类算法

K-均值聚类算法是一种常用的无监督学习算法&#xff0c;用于将数据集分成 K 个簇。该算法的主要思想是通过迭代的方式将数据点分配到离它们最近的簇中&#xff0c;并更新簇的中心点&#xff0c;直到满足某个停止条件为止。 以下是 K-均值聚类算法的基本步骤&#xff1a; 初始化…

【热门话题】WebKit架构简介

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 WebKit架构简介一、引言二、WebKit概览1. 起源与发展2. 模块化设计 三、WebCore…

软考高级架构师:流水线的概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

基于SSM的“任务发布接收平台”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“任务发布接收平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 前台界面 收藏界面 留言管理界面 任务管理界面 订…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑灵活性供需平衡的新型电力系统长短期储能联合规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

file_get_contents(‘php://input‘); 这个postman要如何传参

在 Postman 中传递参数给 file_get_contents(php://input); 是通过请求的 Body 部分来实现的。使用 Postman 进行 API 接口测试时&#xff0c;可以按照以下步骤来传递参数&#xff1a; 打开 Postman 并创建一个新的请求。在请求的 URL 地址栏输入你的 API 地址。选择请求方法为…

蓝桥杯刷题day13——自助餐【算法赛】

一、问题描述 食堂最近推出了自助取餐功能&#xff0c;可以通过盘子的形状自动计算费用。你参与到自助计算价格的项目工作中。视觉组的同学已经帮你通过图像识别把盘子图片转换为了字符串&#xff0c;你只需要计算具体的价格即可。 餐盘的费用如下表所示: 你将会得到n 个字符…