使用FFMPEG分离mp4/flv文件中的264视频和aac音频

news2024/11/24 8:36:23

 准备

ffmpeg 4.4

一个MP4或flv格式的视频文件

分离流程

大致分为以下几个简单步骤:

1.使用avformat_open_input 函数打开文件并初始化结构AVFormatContext

2.查找是否存在音频和视频信息

3.构建一个h264_mp4toannexb比特流的过滤器,用来给视频avpaket包添加头信息

4.打开2个输出文件(音频, 视频)

5.循环读取视频文件,并将音视频分别写入文件

注意:音频需要手动添加头信息,没有提供aac的adts自动添加的过滤器

 

 源码

#include <stdio.h>
extern "C"
{
#include <libavformat/avformat.h>
}

/* 打印编码器支持该采样率并查找指定采样率下标 */
static int find_sample_rate_index(const AVCodec* codec, int sample_rate)
{
	const int* p = codec->supported_samplerates;
	int sample_rate_index = -1; //支持的分辨率下标
	int count = 0;
	while (*p != 0) {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_rates
		printf("%s 支持采样率: %dhz  对应下标:%d\n", codec->name, *p, count);

		if (*p == sample_rate)
			sample_rate_index = count;
		p++;
		count++;
	}
	return sample_rate_index;
}


/// <summary>
/// 给aac音频数据添加adts头
/// </summary>
/// <param name="header">adts数组</param>
/// <param name="sample_rate">采样率</param>
/// <param name="channals">通道数</param>
/// <param name="prfile">音频编码器配置文件(FF_PROFILE_AAC_LOW  定义在 avcodec.h)</param>
/// <param name="len">音频包长度</param>
void addHeader(char header[], int sample_rate, int channals, int prfile, int len)
{
	

	uint8_t sampleIndex = 0;    
	switch (sample_rate) {
	case 96000: sampleIndex = 0; break;
	case 88200: sampleIndex = 1; break;
	case 64000: sampleIndex = 2; break;
	case 48000: sampleIndex = 3; break;
	case 44100: sampleIndex = 4; break;
	case 32000: sampleIndex = 5; break;
	case 24000: sampleIndex = 6; break;
	case 22050: sampleIndex = 7; break;
	case 16000: sampleIndex = 8; break;
	case 12000: sampleIndex = 9; break;
	case 11025: sampleIndex = 10; break;
	case 8000: sampleIndex = 11; break;
	case 7350: sampleIndex = 12; break;
	default: sampleIndex = 4; break;
	}

	uint8_t audioType = 2;	//AAC LC

	uint8_t channelConfig = 2;	//双通道

	len += 7;
	//0,1是固定的
	header[0] = (uint8_t)0xff;         //syncword:0xfff                          高8bits
	header[1] = (uint8_t)0xf0;         //syncword:0xfff                          低4bits
	header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
	header[1] |= (0 << 1);    //Layer:0                                 2bits 
	header[1] |= 1;           //protection absent:1                     1bit
	//根据aac类型,采样率,通道数来配置
	header[2] = (audioType - 1) << 6;            //profile:audio_object_type - 1                      2bits
	header[2] |= (sampleIndex & 0x0f) << 2; //sampling frequency index:sampling_frequency_index  4bits 
	header[2] |= (0 << 1);                             //private bit:0                                      1bit
	header[2] |= (channelConfig & 0x04) >> 2;           //channel configuration:channel_config               高1bit
	//根据通道数+数据长度来配置
	header[3] = (channelConfig & 0x03) << 6;     //channel configuration:channel_config      低2bits
	header[3] |= (0 << 5);                      //original:0                               1bit
	header[3] |= (0 << 4);                      //home:0                                   1bit
	header[3] |= (0 << 3);                      //copyright id bit:0                       1bit  
	header[3] |= (0 << 2);                      //copyright id start:0                     1bit
	header[3] |= ((len & 0x1800) >> 11);           //frame length:value   高2bits
	//根据数据长度来配置
	header[4] = (uint8_t)((len & 0x7f8) >> 3);     //frame length:value    中间8bits
	header[5] = (uint8_t)((len & 0x7) << 5);       //frame length:value    低3bits
	header[5] |= (uint8_t)0x1f;                    //buffer fullness:0x7ff 高5bits
	header[6] = (uint8_t)0xfc;
}


int main() {
	AVFormatContext* ifmt_ctx = NULL;
	AVPacket pkt;
	int ret, i;
	int videoindex = -1, audioindex = -1;
	const char* in_filename = "D:/测试工程/sound/beautlWorld.mp4";
	const char* out_filename_v = "D:/测试工程/sound/ffmpeg_demo.h264";
	const char* out_filename_a = "D:/测试工程/sound/ffmpeg_demo.aac";

	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
		printf("Could not open input file.");
		return -1;
	}

	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		printf("Failed to retrieve input stream information");
		return -1;
	}

	videoindex = -1;
	for (i = 0; i < ifmt_ctx->nb_streams; i++) { //nb_streams:视音频流的个数
		if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
			videoindex = i;
		else if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
			audioindex = i;
	}

	printf("\nInput Video===========================\n");
	av_dump_format(ifmt_ctx, 0, in_filename, 0);  // 打印信息
	printf("\n======================================\n");

	FILE* fp_audio = fopen(out_filename_a, "wb+");
	FILE* fp_video = fopen(out_filename_v, "wb+");


	AVBSFContext* bsf_ctx = NULL;
	const AVBitStreamFilter* pfilter = av_bsf_get_by_name("h264_mp4toannexb");
	if (pfilter == NULL) {
		printf("Get bsf failed!\n");
	}

	if ((ret = av_bsf_alloc(pfilter, &bsf_ctx)) != 0) {
		printf("Alloc bsf failed!\n");

	}

	ret = avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
	if (ret < 0) {
		printf("Set Codec failed!\n");

	}
	ret = av_bsf_init(bsf_ctx);
	if (ret < 0) {
		printf("Init bsf failed!\n");

	}

	//这里遍历音频编码器打印支持的采样率,并找到当前音频采样率所在的下表,用于后面添加adts头
    //本程序并没有使用,只是测试,如果为了程序健壮性可以采用此方式
	AVCodec* codec = nullptr;
	codec  = avcodec_find_encoder(ifmt_ctx->streams[audioindex]->codecpar->codec_id);
	int sample_rate_index = find_sample_rate_index(codec, ifmt_ctx->streams[audioindex]->codecpar->sample_rate);
	printf("分辨率数组下表:%d\n", sample_rate_index);



	while (av_read_frame(ifmt_ctx, &pkt) >= 0) {
		if (pkt.stream_index == videoindex) {

			av_bsf_send_packet(bsf_ctx, &pkt);

			while (true)
			{
				ret = av_bsf_receive_packet(bsf_ctx, &pkt);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
					break;
				else if (ret < 0) {
					printf("Receive Pkt failed!\n");
					break;
				}

				printf("Write Video Packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);

				fwrite(pkt.data, 1, pkt.size, fp_video);
			}
			
		}
		else if (pkt.stream_index == audioindex) {
			printf("Write Audio Packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
			char adts[7] = { 0 };
			addHeader(adts, ifmt_ctx->streams[audioindex]->codecpar->sample_rate, 
				ifmt_ctx->streams[audioindex]->codecpar->channels, 
				ifmt_ctx->streams[audioindex]->codecpar->profile,
				pkt.size);
			fwrite(adts, 1, 7, fp_audio);
			fwrite(pkt.data, 1, pkt.size, fp_audio);
		}
		av_packet_unref(&pkt);
	}

	av_bsf_free(&bsf_ctx);


	fclose(fp_video);
	fclose(fp_audio);

	avformat_close_input(&ifmt_ctx);
	return 0;


	if (ifmt_ctx)
		avformat_close_input(&ifmt_ctx);
	if (fp_audio)
		fclose(fp_audio);
	if (fp_video)
		fclose(fp_video);
	if (bsf_ctx)
		av_bsf_free(&bsf_ctx);
	return -1;
}

小记

1.av_read_frame 就是读取问价并返回下一帧

2.视频的头信息不在avpacket中需要使用bsf过滤器来添加

3.aac音频头信息需要手动添加

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

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

相关文章

Hudi 数据湖技术之集成Flink

目录 1 安装Flink2 快速入门2.1 集成Flink概述2.2 环境准备2.3 创建表2.4 插入数据2.5 查询数据2.6 更新数据 3 Streaming query3.1 创建表3.2 查询数据3.3 插入数据 4 Flink SQL Writer4.1 Flink SQL集成Kafka4.2 Flink SQL写入Hudi4.2.1 创建Maven Module4.2.2 消费Kafka数据…

【C++】了解设计模式、 stackqueue的使用与模拟实现

文章目录 1.设计模式2.stack1.stack的使用1.stack的结构2.stack的接口 2.stack的模拟实现1.stack的结构2.接口实现 3.queue1.queue的使用1.queue的结构3.queue的接口 2.queue的模拟实现1.queue的结构2.接口实现 4.了解deque1.deque的原理介绍2.deque的底层结构3.deque的迭代器设…

Codeforces Round 861 (Div. 2)(A~D)

A. Lucky Numbers 给出边界l和r&#xff0c;在区间[l, r]之间找到幸运值最大的数字。一个数字的幸运值被定义为数位差的最大值&#xff0c;即数字中最大的数位和最小的数位的差。 思路&#xff1a;因为涉及到至少两位&#xff0c;即个位和十位变化最快&#xff0c;最容易得到相…

Android四大组件之广播接收者BroadcastReceiver

一、全局广播 Android中的广播可以分为两种类型&#xff1a;标准广播和有序广播 标准广播&#xff1a;一种完全异步执行的广播&#xff0c;在广播发出之后&#xff0c;所有的广播接收器几乎都会同一时刻接收到这条广播消息&#xff0c;因此它们之间没有任何先后顺序。无法进行…

Vector-常用CAN工具 - 入门到精通 - 专栏链接

一、CANoe篇 1、CANoe入门到精通_软件安装 2、CANoe入门到精通_硬件及环境搭建 3、CANoe入门到精通_软件环境配置 4、CANoe入门到精通_Network Node CAPL开发 5、CANoe入门到精通_Node节点开发基本数据类型 6、CANoe入门到精通_Test Node节点开发设置 7、CANoe入门到精通…

《Cocos Creator游戏实战》AIGC之将草稿内容转为真实内容

目录 前言 训练AI 从识别结果中提取必要数据 发送图片并生成最终代码 总结与提高 资源下载 前言 当创作灵感来的时候&#xff0c;我们可能会先把灵感记录在草稿上&#xff0c;之后再去实现它。比方说有一天&#xff0c;我突然来了游戏创作灵感&#xff0c;想着那可以先把…

gpt 怎么用-免费gpt下载使用方法

gpt 怎么用 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer的神经网络模型&#xff0c;用于自然语言处理任务&#xff0c;例如文本生成、摘要生成、翻译、问答等。以下是使用GPT进行文本生成的一般步骤&#xff1a; 首先&#xff0c;您…

编译预处理

编译预处理 1、宏定义1.1、 无参宏定义1.2、使用宏定义的优点1.3、宏定义注意点1.4、带参数的宏(重点)1.5、条件编译1.6、宏定义的一些巧妙用法(有用)1.7、结构体占用字节数的计算原则&#xff08;考题经常考&#xff0c;要会画图&#xff09;1.8、#在宏定义中的作用&#xff0…

转型产业互联网,新氧能否再造辉煌?

近年来&#xff0c;“颜值经济”推动医美行业快速发展&#xff0c;在利润驱动下&#xff0c;除了专注医美赛道的企业之外&#xff0c;也有不少第三方互联网平台正强势进入医美领域&#xff0c;使以新氧为代表的医美企业面对不小发展压力&#xff0c;同时也展现出强大的发展韧性…

六、CANdelaStudio入门-通信参数编辑

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio的通信参数编辑,欢迎各位朋友订阅、评论,…

Kubernetes 笔记(16)— 集群管理、使用名字空间分隔系统资源、给名字空间设置资源限额、默认资源配额的使用

1. 为什么要有名字空间 首先要明白&#xff0c;Kubernetes 的名字空间并不是一个实体对象&#xff0c;只是一个逻辑上的概念。它可以把集群切分成一个个彼此独立的区域&#xff0c;然后我们把对象放到这些区域里&#xff0c;就实现了类似容器技术里 namespace 的隔离效果&…

MATLAB符号运算(七) 更新中...

目录 1、实验目的&#xff1a; 2、实验内容&#xff1a; 1、实验目的&#xff1a; 1&#xff09;掌握定义符号对象和创建符号表达式的方法&#xff1b; 2&#xff09;掌握符号运算基本命令和规则&#xff1b; 3&#xff09;掌握符号表达式的运算法则以及符号矩阵运算&#xf…

93、Dehazing-NeRF: Neural Radiance Fields from Hazy Images

简介 论文&#xff1a;https://arxiv.org/pdf/2304.11448.pdf 从模糊图像输入中恢复清晰NeRF 使用大气散射模型模拟有雾图像的物理成像过程&#xff0c;联合学习大气散射模型和干净的NeRF模型&#xff0c;用于图像去雾和新视图合成 通过将NeRF 3D场景的深度估计与大气散射模…

【牛客刷题专栏】23:JZ22 链表中倒数最后k个结点(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

LeetCode-344. 反转字符串

题目链接 LeetCode-344. 反转字符串 题目描述 题解 题解一&#xff08;Java&#xff09; 作者&#xff1a;仲景 直接双指针前后一直交换即可 class Solution {public void reverseString(char[] s) {if (s.length 1)return;// 双指针int lp 0, rp s.length - 1;while (lp…

【百度智能云】基于http3的xcdn 开放直播方案设计与实践

大神:柯老师 现有的融合CDN 0 需要集成sdksdk 是集成在端侧缺点 sdk 对端侧有影响多云模式下,sdk不互通、 XCDN 设计目标 :保持现有cdn的优势 承载各种业务:直播点播让各家的cdn互通cdn 厂家屏蔽了差异性,集成起来比较简单,对接简单开发的互联网生态。使用统一的http3标…

理解缓冲区

文章目录 一.缓冲区1.什么是缓冲区2.缓冲区的意义3.缓冲区的刷新策略4.我们目前谈论的缓冲区在哪里5.仿写FILE5.1myStdio.h5.2myStdio.c 6.操作系统的缓冲区 一.缓冲区 int main() {printf("hello linux");sleep(2);return 0; }对于这样的代码&#xff0c;首先可以肯…

C++11 unique_ptr智能指针

#include<iostream> using namespace std;class test { public:test() {cout << "调用构造函数" << endl;}~test() {cout << "调用析构函数" << endl;} };int main(void) {//1.构造函数unique_ptr<test>t1;unique_ptr…

数据结构之KMP算法:彻底搞懂kmp算法

数据结构的学习&#xff0c;kmp匹配算法困扰我许久&#xff0c;此处来一个总结&#xff08;仅供自己复习了解参考使用&#xff09;&#xff0c;如果有不对的地方请多多指点。好了废话不多说我们直接开始好吧。 目录 关于暴力匹配原理的讲解&#xff1a; kmp算法&#xff1a; …

ChatGPT - 如何高效的调教ChatGPT (指令建构模型-LACES问题模型)

文章目录 定义1. Limitation&#xff08;限定条件&#xff09;2. Assignment&#xff08;分配角色&#xff09;3. Context&#xff08;背景或上下文&#xff09;4. Example&#xff08;示例&#xff09;5. Step by Step&#xff08;拆分任务&#xff09; 小Demo 定义 LACES问题…