最简单的 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,为 0xfff
		if ((buffer[0] == 0xFF) && ((buffer[1] & 0xF0) == 0xF0))
		{
			// frame_length,13 bit,表示当前 ADTS 帧的长度,存储在第 4 个字节的后两位,第 5 个字节,第 6 个字节的前三位
			size |= ((buffer[3] & 0x03) << 11); // high 2 bit
			size |= buffer[4] << 3; // middle 8 bit
			size |= ((buffer[5] & 0xE0) >> 5); // low 3 bit
			break;
		}
		--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-2
			unsigned 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 bit
			channel_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 bit
			adts_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 | Blocks
			fprintf(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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信息系统项目管理师——第17章项目干系人管理

本章节内容属于10大管理知识领域&#xff0c;选择、案例、论文都会考。 选择题&#xff0c;稳定考1-2分左右&#xff0c;新教材基本考课本原话&#xff0c;这个分不能丢。 案例题&#xff0c;本期考的概率一般。 论文题&#xff0c;202205期考过。 1管理基础 管理的重要性 为…

QT5-qmediaplayer播放视频及进度条控制实例

qmediaplayer是QT5的播放视频的一个模块。它在很多时候还是要基于第三方的解码器。这里以Ubuntu系统为例&#xff0c;记录其用法及进度条qslider的控制。 首先&#xff0c;制作一个简单的界面文件mainwindow.ui&#xff1a; 然后&#xff0c;下载一个mp4或其他格式视频&#x…

爬虫 红网时刻 获取当月指定关键词新闻 并存储到CSV文件

目标网站&#xff1a;红网 爬取目的&#xff1a;为了获取某一地区更全面的在红网已发布的宣传新闻稿&#xff0c;同时也让自己的工作更便捷 环境&#xff1a;Pycharm2021&#xff0c;Python3.10&#xff0c; 安装的包&#xff1a;requests&#xff0c;csv&#xff0c;bs4&…

计算多个元素的累乘结果累乘器start默认初始为1 math.prod()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算多个元素的累乘结果 累乘器start默认初始为1 math.prod() [太阳]选择题 请问题目中的代码最后输出什么? import math list1 [1, 2, 3] print("【显示】list1 ",list1) pri…

如何将本地仓库放到远程仓库中

在我们仓库创建好之后&#xff0c;我们复制好ssh 接着我们需要使用git remote add<shortname><url>这个命令 shortname就是我们远程仓库的别名 接着使用git remote -v这个命令查看一下目前远程仓库的别名和地址 原本还有一个指令git branch -M main 指定分支的名…

智能试卷分析、智能组卷系统

本课题开发一个新型智能试卷分析评价系统&#xff0c;该系统实现了学生试卷的生成与评估以及对各种评估信息的管理和维护。该系统使用SpringBoot MysqlVue搭建的框架为设计平台&#xff0c;以B/S模式开发与设计题库及试卷管理模块。 学生功能&#xff1a;登录&#xff0c;答题考…

RD55UP06-V 三菱iQ-R系列C语言功能模块

RD55UP06-V 三菱iQ-R系列C语言功能模块 RD55UP06-V用户手册&#xff0c;RD55UP06-V功能&#xff0c;RD55UP06-V系统配置 RD55UP06-V参数规格&#xff1a;10BASE-T/100BASE-TX/1000BASE-T 1通道&#xff1b;字节存储次序格式小端模式; 可使用SD存储卡插槽&#xff1b;工作RAM 1…

dddddd

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

vulhub打靶记录——healthcare

文章目录 主机发现端口扫描FTP—21search ProPFTd EXPFTP 匿名用户登录 web服务—80目录扫描search openemr exp登录openEMR 后台 提权总结 主机发现 使用nmap扫描局域网内存活的主机&#xff0c;命令如下&#xff1a; netdiscover -i eth0 -r 192.168.151.0/24192.168.151.1…

更高效、更简洁的 SQL 语句编写丨DolphinDB 基于宏变量的元编程模式详解

元编程&#xff08;Metaprogramming&#xff09;指在程序运行时操作或者创建程序的一种编程技术&#xff0c;简而言之就是使用代码编写代码。通过元编程将原本静态的代码通过动态的脚本生成&#xff0c;使程序员可以创建更加灵活的代码以提升编程效率。 在 DolphinDB 中&#…

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

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

leetCode刷题 25.K 个一组翻转链表

目录 1.思路&#xff1a; 2.解题方法&#xff1a; 3.复杂度&#xff1a; 4.Code 题目&#xff1a; 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不…

补充知识

补充知识1 内存的本质是对数据的临时存储 内存与磁盘进行交互时&#xff0c; 最小单位是4kb叫做页框(内存)和页帧(磁盘) 也就是&#xff0c; 如果我们要将磁盘的内容加载到内存中&#xff0c; 可是文件大小只有1kb&#xff0c; 我们也要拿出4kb来存他&#xff0c; 多余的就直…

简单的弱口令密码字典!!!

将下面的复制到文本文档即可&#xff01;&#xff01;&#xff01; 弱口令密码字典一&#xff1a; %null% %username% !#$ !#$% !#$%^ !#$%^& !#$%^&* 000000 00000000 0123456789 1 101010 111 111111 1111111 11111111 1111111111 111222 112233 11223344 121212 121…

JAVA8 新特性StreamAPI使用(二)

一、使用StreamAPI&#xff0c;&#xff08;基于数据模型——客户、订单和商品&#xff0c;实体关系图如下&#xff0c;客户可以有多个订单&#xff0c;是一对多的关系&#xff0c;而产品和订单的关系是多对多的&#xff09;需求如下&#xff1a; 二、Stream API思维导图 三、需…

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

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

【Python面试题收录】Python的深浅拷贝

一、Python的深浅拷贝的区别 在Python中&#xff0c;深拷贝和浅拷贝是两种不同的对象复制机制&#xff0c;它们的主要区别在于如何处理对象内部所包含的可变或不可变类型的子对象。 浅拷贝 是指创建一个新的对象&#xff0c;但它只复制了原对象的第一层内容&#xff0c;也就是说…

基于模糊PID控制器的的无刷直流电机速度控制simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1无刷直流电机模型与速度控制 4.2 模糊PID控制器设计 5.完整工程文件 1.课题概述 基于模糊PID控制器的的无刷直流电机速度控制simulink建模与仿真。基于模糊PID控制器的无刷直流电机&#xff08;Brus…

并发编程01-深入理解Java并发/线程等待/通知机制

为什么我们要学习并发编程&#xff1f; 最直白的原因&#xff0c;因为面试需要&#xff0c;我们来看看美团和阿里对 Java 岗位的 JD&#xff1a; 从上面两大互联网公司的招聘需求可以看到&#xff0c; 大厂的 Java 岗的并发编程能力属于标配。 而在非大厂的公司&#xff0c; 并…

Redis的高可用和持久化

目录 一、Redis高可用 二、Redis持久化 2.1 持久化的功能 2.2 Redis提供两种方式进行持久化 三、RDB持久化 3.1 触发条件 3.1.1 手动触发 3.1.2 自动触发 3.1.3 其他自动触发机制 四、AOF持久化 4.1 开启AOF 4.2 执行流程 4.2.1 命令追加 (append) 4.2.2 文件写入…