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

news2024/11/24 10:43:39

最简单的 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

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

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

相关文章

Lafida多目数据集实测

Lafida 数据集 paper&#xff1a;J. Imaging | Free Full-Text | LaFiDa—A Laserscanner Multi-Fisheye Camera Dataset 官网数据&#xff1a;https://www.ipf.kit.edu/english/projekt_cv_szenen.php 官网&#xff1a;KIT-IPF-Software and Datasets - LaFiDa 标定数据下载&…

解析Flutter应用在iOS环境中的性能优化技巧

本文探讨了使用Flutter开发的iOS应用能否上架&#xff0c;以及上架的具体流程。苹果提供了App Store作为正式上架渠道&#xff0c;同时也有TestFlight供开发者进行内测。合规并通过审核后&#xff0c;Flutter应用可以顺利上架。但上架过程可能存在一些挑战&#xff0c;因此可能…

Javascript/Node.JS中如何用多种方式避免属性为空(cannot read property of undefined ERROR)

>>>>>>问题 "cannot read property of undefined" 是一个常见的 JavaScript 错误&#xff0c;包含我在内很多人都会遇到&#xff0c;表示你试图访问一个未定义&#xff08;undefined&#xff09;对象的属性。这通常是因为你在访问一个不存在的对象…

【QT+QGIS跨平台编译】056:【pdal_kazhdan+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdal_kazhdan介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdal_kazhdan介绍 pdal_kazhdan 是 PDAL(Point Data Abstraction Library)相关的 Kazhdan 算法的实现。PDAL 是一个用于处理和分析点云数据的开源库,而 Kazhdan 算法通常…

MySQL之索引详细总结

索引简介 索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用(指向)数据&#xff0c;这样就可以在这些数据结构上实现高级查法&#xff0c;这种数据结构就是索引 为什…

Java零基础入门-java8新特性(中篇)

一、概述 ​上几期&#xff0c;我们是完整的学完了java异常类的学习及实战演示、以及学习了线程进程等基础概念&#xff0c;而这一期&#xff0c;我们要来玩点好的东西&#xff0c;那就是java8&#xff0c;我们都知道java8是自2004年发布java5之后最重要且一次重大的版本更新&…

电商技术揭秘五:电商平台的个性化营销与数据分析

文章目录 引言1. 个性化营销的概念与价值1.1 个性化营销的定义1.1.1 个性化营销的基本概念1.1.2 个性化营销在电商领域的重要性 1.2 个性化营销的核心价值1.2.1 提升用户体验1.2.2 增加转化率和客户忠诚度1.2.3 优化营销资源配置 2. 用户画像与行为分析2.1 用户画像的构建2.1.1…

二百二十九、离线数仓——离线数仓Hive从Kafka、MySQL到ClickHouse的完整开发流程

一、目的 为了整理离线数仓开发的全流程&#xff0c;算是温故知新吧 离线数仓的数据源是Kafka和MySQL数据库&#xff0c;Kafka存业务数据&#xff0c;MySQL存维度数据 采集工具是Kettle和Flume&#xff0c;Flume采集Kafka数据&#xff0c;Kettle采集MySQL数据 离线数仓是Hi…

Topaz Video AI for Mac v5.0.0激活版 视频画质增强软件

Topaz Video AI for Mac是一款功能强大的视频处理软件&#xff0c;专为Mac用户设计&#xff0c;旨在通过人工智能技术为视频编辑和增强提供卓越的功能。这款软件利用先进的算法和深度学习技术&#xff0c;能够自动识别和分析视频中的各个元素&#xff0c;并进行智能修复和增强&…

llama.cpp运行qwen0.5B

编译llama.cp 参考 下载模型 05b模型下载 转化模型 创建虚拟环境 conda create --prefixD:\miniconda3\envs\llamacpp python3.10 conda activate D:\miniconda3\envs\llamacpp安装所需要的包 cd G:\Cpp\llama.cpp-master pip install -r requirements.txt python conver…

docker容器环境安装记录(MAC M1)(完善中)

0、背景 在MAC M1中搭建商城项目环境时&#xff0c;采用docker统一管理开发工具&#xff0c;期间碰到了许多环境安装问题&#xff0c;做个总结。 1、安装redis 在宿主机新建redis.conf文件运行创建容器命令&#xff0c;进行容器创建、端口映射、文件挂载、以指定配置文件启动…

《QT实用小工具·八》数据库通用翻页类

1、概述 源码放在文章末尾 该项目实现数据库通用翻页类&#xff0c;主要包含如下功能&#xff1a; 1:自动按照设定的每页多少行数据分页 2:只需要传入表名/字段集合/每页行数/翻页指示按钮/文字指示标签 3:提供公共静态方法绑定字段数据到下拉框 4:建议条件字段用数字类型的主…

37.HarmonyOS鸿蒙系统 App(ArkUI) 创建第一个应用程序hello world

HarmonyOS App(ArkUI) 创建第一个应用程序helloworld 线性布局 1.鸿蒙应用程序开发app_hap开发环境搭建 3.DevEco Studio安装鸿蒙手机app本地模拟器 打开DevEco Studio,点击文件-》新建 双击打开index.ets 复制如下代码&#xff1a; import FaultLogger from ohos.faultL…

SpringBoot+ECharts+Html 字符云/词云案例详解

1. 技术点 SpringBoot、MyBatis、thymeleaf、MySQL、ECharts 等 2. 准备条件 在mysql中创建数据库echartsdb&#xff0c;数据库中创建表t_comment表&#xff0c;表中设置两个字段word与count&#xff0c;添加表中的数据。如&#xff1a;附件中的 echartsdb.sql 3. SpringBoot…

将excel数据拆分成多个excel文件

一、背景&#xff1a; 平时在日常工作中&#xff0c;经常需要将excel的文件数据进行拆分&#xff0c;拆分成多个excel文件&#xff0c;然而用人工来处理这个既耗时&#xff0c;又费精力&#xff0c;眼睛会疲劳&#xff0c;时间长了操作上会出现失误&#xff0c;导致数据拆分错…

BetterZip2024Mac上一款功能强大的Mac平台解压压缩软件

一、软件概述 BetterZip是一款Mac平台上的压缩解压缩工具&#xff0c;它为用户提供了一个方便的方式来处理各种压缩文件&#xff0c;包括但不限于ZIP、TAR、GZIP等格式。除了基本的压缩解压缩功能外&#xff0c;BetterZip还具备文件预览、文件加密、分卷压缩等高级功能&#x…

JUC:double-checked locking(DCL) 懒汉单例模式

文章目录 double-checked locking(DCL) 问题解决方法 volatile作用 double-checked locking(DCL) 问题 第一个if用于后续进入的线程&#xff0c;不用再获取锁来判断是否已经创建了对象。第二个if&#xff0c;为的是第一个进入的线程创建对象&#xff0c;以及防止卡在第一个if之…

nodeJs 实现视频的转换(超详细教程)

前段时间拿到一个视频是4k的&#xff0c;没法播放&#xff0c;于是通过 node.js 和 ffmpeg 实现了视频的转换。在win10 系统下实现。 所需工具 node 16.19 直接安装 ffmpeg-5.1.1-essentials_build 解压后重名 ffmpeg 放到C盘 然后配置下环境变量 Git-2.42.0.2-64-bit 直接…

【HTML】注册页面制作 案例二

&#xff08;大家好&#xff0c;今天我们将通过案例实战对之前学习过的HTML标签知识进行复习巩固&#xff0c;大家和我一起来吧&#xff0c;加油&#xff01;&#x1f495;&#xff09; 案例复习 通过综合案例&#xff0c;主要复习&#xff1a; 表格标签&#xff0c;可以让内容…

linux通过进程pid查询容器docker

我遇到的问题是在docker中启动了进行&#xff0c;占用显卡&#xff0c;如下nvidis-smi查看&#xff1a; 现在要查询pid16325属于哪个容器ID&#xff0c;指令&#xff1a; ps -e -o pid,cmd,comm,cgroup | grep 16325查到如下结果&#xff0c;其中12:cpuset:/docker/ 后面的 8…