最简单的基于 FFmpeg 的视音频分离器

news2025/1/16 15:43:17

最简单的基于 FFmpeg 的视音频分离器

  • 最简单的基于 FFmpeg 的视音频分离器
    • 正文
    • 结果
    • 工程文件下载
    • 参考链接

最简单的基于 FFmpeg 的视音频分离器

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)

正文

本文介绍一个视音频分离器(Demuxer)。

视音频分离器即是将封装格式数据(例如 MKV)中的视频压缩数据(例如 H.264)和音频压缩数据(例如 AAC)分离开,如下图所示。

在这里插入图片描述

在这个过程中并不涉及到编码和解码。

本文记录的程序将一个 MPEG2TS 封装的文件(其中视频编码为 H.264,音频编码为 MP3)分离成为两个文件:一个 H.264 编码的视频码流文件,一个 MP3 编码的音频码流文件。

前一篇文章中,记录一个简单版的视音频分离器:最简单的基于 FFmpeg 的视音频分离器 - 简化版。

相比于前一篇文中的分离器,本篇文章记录的分离器复杂了很多。相比于简单版的分离器,学习的难度大了一些。但是该分离器可以很好地处理 FFmpeg 支持的各种格式(例如分离 AAC 音频流),拥有更好的实用性。

程序的流程如下图所示:

在这里插入图片描述

从流程图中可以看出,一共初始化了 3 个 AVFormatContext,其中 1 个用于输入,另外 2 个分别用于视频输出和音频输出。3 个 AVFormatContext 初始化之后,通过 avcodec_copy_context() 函数可以将输入视频/音频的参数拷贝至输出视频/音频的 AVCodecContext 结构体。最后,通过 av_read_frame() 获取 AVPacket,根据 AVPacket 类型的不同,分别使用 av_interleaved_write_frame() 写入不同的输出文件中即可。

对于某些封装格式(例如 MP4/FLV/MKV 等)中的 H.264,需要用到名称为“h264_mp4toannexb”的 bitstream filter。这一点在前一篇文章《最简单的基于 FFmpeg 的视音频分离器 - 简化版》中,已经有过详细叙述,这里不再重复。

简单介绍一下流程中各个重要函数的意义:

  1. avformat_open_input():打开输入文件。
  2. avcodec_copy_context():赋值 AVCodecContext 的参数。
  3. avformat_alloc_output_context2():初始化输出文件。
  4. avio_open():打开输出文件。
  5. avformat_write_header():写入文件头。
  6. av_read_frame():从输入文件读取一个 AVPacket。
  7. av_interleaved_write_frame():写入一个 AVPacket 到输出文件。
  8. av_write_trailer():写入文件尾。

源程序:

// Simplest FFmpeg Demuxer.cpp : 定义控制台应用程序的入口点。
//

/**
* 最简单的基于 FFmpeg 的视音频分离器
* Simplest FFmpeg Demuxer
*
* 源程序:
* 雷霄骅 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
*
* 本程序可以将封装格式中的视频码流数据和音频码流数据分离出来。
* 在该例子中, 将 MPEG2TS 的文件分离得到 H.264 视频码流文件和 AAC 音频码流文件。
*
* This software split a media file (in Container such as
* MKV, FLV, AVI...) to video and audio bitstream.
* In this example, it demux a MPEG2TS file to H.264 bitstream and AAC bitstream.
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>

// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)

// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
	// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
	FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
// Windows
extern "C"
{
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif

/*
FIX: H.264 in some container formats (FLV, MP4, MKV etc.)
need "h264_mp4toannexb" bitstream filter (BSF).
1. Add SPS,PPS in front of IDR frame
2. Add start code ("0,0,0,1") in front of NALU
H.264 in some containers (such as MPEG2TS) doesn't need this BSF.
*/

// 1: Use H.264 Bitstream Filter 
#define USE_H264BSF 0

int main(int argc, char* argv[])
{
	AVOutputFormat *ofmt_audio = NULL, *ofmt_video = NULL;
	// Input AVFormatContext
	AVFormatContext *ifmt_ctx = NULL;
	// Output AVFormatContext
	AVFormatContext *ofmt_ctx_audio = NULL, *ofmt_ctx_video = NULL;
	AVPacket pkt;

	int ret;
	int videoindex = -1, audioindex = -1;
	int frame_index = 0;

	// Input file URL
	const char *in_filename = "cuc_ieschool.ts";
	// Output video file URL
	const char *out_video_filename = "cuc_ieschool.h264";
	// Output audio file URL
	const char *out_audio_filename = "cuc_ieschool.aac";

	av_register_all();

	// 输入
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		printf("Could not open input file.\n");
		goto end;
	}

	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0)
	{
		printf("Failed to retrieve input stream information.\n");
		goto end;
	}

	// 输出
	avformat_alloc_output_context2(&ofmt_ctx_video, NULL, NULL, out_video_filename);
	if (ofmt_ctx_video == NULL)
	{
		printf("Could not create output video context.\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt_video = ofmt_ctx_video->oformat;

	avformat_alloc_output_context2(&ofmt_ctx_audio, NULL, NULL, out_audio_filename);
	if (ofmt_ctx_audio == NULL)
	{
		printf("Could not create output audio context.\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt_audio = ofmt_ctx_audio->oformat;

	// Print some input information
	printf("\n============== Input Video =============\n");
	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	for (size_t i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		// Create output AVStream according to input AVStream
		AVFormatContext *ofmt_ctx;
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVStream *out_stream = NULL;

		if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoindex = i;
			out_stream = avformat_new_stream(ofmt_ctx_video, in_stream->codec->codec);
			ofmt_ctx = ofmt_ctx_video;
		}
		else if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			audioindex = i;
			out_stream = avformat_new_stream(ofmt_ctx_audio, in_stream->codec->codec);
			ofmt_ctx = ofmt_ctx_audio;
		}
		else
			break;

		if (out_stream == NULL)
		{
			printf("Failed allocating output stream.\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}

		// Copy the settings of AVCodecContext
		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
		if (ret < 0)
		{
			printf("Failed to copy context from input to output stream codec context.\n");
			goto end;
		}
		out_stream->codec->codec_tag = 0;

		if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		{
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
		}
	}

	// // Print some output information
	printf("\n============== Output Video ============\n");
	av_dump_format(ofmt_ctx_video, 0, out_video_filename, 1);
	printf("\n============== Output Audio ============\n");
	av_dump_format(ofmt_ctx_audio, 0, out_audio_filename, 1);

	// Open output file
	if (!(ofmt_video->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&ofmt_ctx_video->pb, out_video_filename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("Could not open output file '%s'.\n", out_video_filename);
			goto end;
		}
	}

	if (!(ofmt_audio->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&ofmt_ctx_audio->pb, out_audio_filename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("Could not open output file '%s'.\n", out_audio_filename);
			goto end;
		}
	}

	// Write file header
	ret = avformat_write_header(ofmt_ctx_video, NULL);
	if (ret < 0)
	{
		printf("Error occurred when opening video output file.\n");
		goto end;
	}
	ret = avformat_write_header(ofmt_ctx_audio, NULL);
	if (ret < 0)
	{
		printf("Error occurred when opening audio output file.\n");
		goto end;
	}

#if USE_H264BSF
	AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif

	while (1)
	{
		AVFormatContext *ofmt_ctx;
		AVStream *in_stream, *out_stream;
		// 获取一个 AVPacket
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
		{
			break;
		}

		in_stream = ifmt_ctx->streams[pkt.stream_index];

		if (pkt.stream_index == videoindex)
		{
			out_stream = ofmt_ctx_video->streams[0];
			ofmt_ctx = ofmt_ctx_video;
			printf("Write a video packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);

#if USE_H264BSF
			av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
		}
		else if (pkt.stream_index == audioindex)
		{
			out_stream = ofmt_ctx_audio->streams[0];
			ofmt_ctx = ofmt_ctx_audio;
			printf("Write an audio packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
		}
		else
			continue;

		// 转换 PTS/DTS
		pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,
			out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base,
			out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;
		pkt.stream_index = 0;

		// Write
		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
		if (ret < 0)
		{
			printf("Error muxing packet.\n");
			break;
		}

		printf("Write %8d frames to output file.\n", frame_index);
		av_free_packet(&pkt);
		frame_index++;
	}


#if USE_H264BSF
	av_bitstream_filter_close(h264bsfc);
#endif

	av_write_trailer(ofmt_ctx_audio);
	av_write_trailer(ofmt_ctx_video);


end:
	// Close input
	avformat_close_input(&ifmt_ctx);
	// Close output
	if (ofmt_ctx_audio && !(ofmt_audio->flags & AVFMT_NOFILE))
	{
		avio_close(ofmt_ctx_audio->pb);
	}
	if (ofmt_ctx_video && !(ofmt_video->flags & AVFMT_NOFILE))
	{
		avio_close(ofmt_ctx_video->pb);
	}

	avformat_free_context(ofmt_ctx_audio);
	avformat_free_context(ofmt_ctx_video);

	if (ret < 0 && ret != AVERROR_EOF)
	{
		printf("Error occurred.\n");
		return -1;
	}


	system("pause");
	return 0;
}

结果

运行程序,输出如下:

在这里插入图片描述

输入文件为:

cuc_ieschool.ts:MPEG2TS 封装格式数据。

在这里插入图片描述

输出文件为:

cuc_ieschool.aac:AAC 音频码流数据。

在这里插入图片描述

cuc_ieschool.h264:H.264 视频码流数据。

在这里插入图片描述

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Demuxer

CSDN:Simplest FFmpeg Demuxer.zip

参考链接

  1. 最简单的基于 FFmpeg 的视音频分离器 - 简化版

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

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

相关文章

五种多目标优化算法(MOFA、NSWOA、MOJS、MOAHA、MOPSO)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 多目标优化算法是用于解决具有多个目标函数的优化问题的一类算法。其求解流程通常包括以下几个步骤&#xff1a; 1. 定义问题&#xff1a;首先需要明确问题的目标函数和约束条件。多目标优化问题通常涉及多个目标函数&#xff0c;这些目标函数可能…

电商风控系统(flink+groovy+flume+kafka+redis+clickhouse+mysql)

一.项目概览 电商的防止薅羊毛的风控系统 需要使用 groovy 进行风控规则引擎的编写 然后其它技术进行各种数据的 存储及处理 薅羊毛大致流程 如果单纯使用 if else在业务代码中进行风控规则的编写 那么 维护起来会比较麻烦 并且跟业务系统强绑定不合适 所以一般独立成一个单…

2024年2月20日v1.0.5更新·优雅草便民工具youyacao-tools

2024年2月20日v1.0.5更新优雅草便民工具youyacao-tools apk下载 https://fenfacun.youyacao.com/tools105.apk 介绍 优雅草便民工具是一款由成都市一颗优雅草科技有限公司打造的便民查询公益工具&#xff0c;2024年1月17日正式发布v1.0.0版本&#xff0c;本工具为了方便大众免…

贪心算法---前端问题

1、贪心算法—只关注于当前阶段的局部最优解,希望通过一系列的局部最优解来推出全局最优----但是有的时候每个阶段的局部最优之和并不是全局最优 例如假设你需要找给客户 n 元钱的零钱&#xff0c;而你手上只有若干种面额的硬币&#xff0c;如 1 元、5 元、10 元、50 元和 100…

基于相位的运动放大:如何检测和放大难以察觉的运动(01/2)

基于相位的运动放大&#xff1a;如何检测和放大难以察觉的运动 目录 一、说明二、结果的峰值三、金字塔背景3.1 可操纵金字塔3.2 亚倍频程复数可控金字塔 四、基本方针4.1 1D 问题陈述4.2 一维方法4.3 实际实施说明 五、放大倍率的限制5.1 空间支持的影响5.2 频带的影响 六、推…

SpringBoot3整合Swagger3,访问出现404错误问题(未解决)

秉承着能用就用新的的理念&#xff0c;在JDK、SpringBoot、SpringCloud版本的兼容性下&#xff0c;选择了Java17、SpringBoot3.0.2整合Swagger3。 代码编译一切正常&#xff0c;Swagger的Bean也能加载&#xff0c;到了最后访问前端页面swagger-ui的时候出现404。 根据网上资料…

【计算机网络】传输层——TCP和UDP详解

文章目录 一. TCP和UDP简介二. UDP 协议详解1. UDP报文格式2. UDP的使用场景 三. TCP 协议详解1. TCP报文格式2. TCP协议的重要机制确认应答&#xff08;保证可靠传输的最核心机制&#xff09;超时重传连接管理&#xff08;三次握手、四次挥手&#xff09;&#xff01;&#xf…

React组件通讯

组件通讯 组件是一个独立的单元&#xff0c;默认情况下组件只能自己使用自己的数据。在组件化过程中&#xff0c;我们将一个完整的功能拆分成多个组件&#xff0c;便于更好的完成整个应用的功能。 Props 组件本来是封闭的&#xff0c;要接受外部数据应该可以通过Props来实现…

Jmeter学习系列之八:控制器Controllers 的入门介绍

一、Controllers 简介 Jmeter有两种类型的控制器&#xff1a;Samplers&#xff08;取样器&#xff09;和Logical Controllers&#xff08;逻辑控制器&#xff09;&#xff1b;它们驱动着测试的进行取样器&#xff1a;让jmeter发送请求到服务器以及接收服务器的响应数据逻辑控制…

三种方法用c语言求最大公约数以及最小公倍数

学习目标&#xff1a; 掌握求最大公约数&#xff08;最小公倍数&#xff09;的三种基本方法 学习内容&#xff1a; 1.一大一小取其小&#xff0c;剖根问底取公约 意思是从一大一小两个数当中&#xff0c;我们取较小的那个数&#xff08;min&#xff09;进行剖析&#xff0c;试…

Socket、UDP、TCP协议和简单实现基于UDP的客户端服务端

目录 Socket TCP和UDP区别 UDP&#xff1a;无连接&#xff0c;不可靠传输&#xff0c;面向数据报&#xff0c;全双工 TCP&#xff1a;有连接&#xff0c;可靠传输&#xff0c;面向字节流&#xff0c;全双工 无连接和有连接 可靠传输和不可靠传输 面向数据报和面向字节流…

pclpy 最小二乘法拟合平面

pclpy 最小二乘法拟合平面 一、算法原理二、代码三、结果1.左边原点云、右边最小二乘法拟合平面后点云投影 四、相关数据 一、算法原理 平面方程的一般表达式为&#xff1a; A x B y C z D 0 ( C ≠ 0 ) Ax By Cz D 0 \quad (C\neq0) AxByCzD0(C0) 即&#xff1a; …

【深度学习笔记】 3_13 丢弃法

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.13 丢弃法 除了前一节介绍的权重衰减以外&#xff0c;深度学习模型常常使用丢弃法&#xff08;dropout&#xff09;[1] 来应对过拟合…

HDL FPGA 学习 - Quartus II 工程搭建,ModelSim 仿真,时序分析,IP 核使用,Nios II 软核使用,更多技巧和规范总结

目录 工程搭建、仿真与时钟约束 一点技巧 ModelSim 仿真 Timing Analyzer 时钟信号约束 SignalTap II 使用 In-System Memory Content Editor 使用 记录 QII 的 IP 核使用 记录 Qsys/Nios II 相关 记录 Qsys 的 IP 核使用 封装 Avalon IP 更多小技巧教程文章 更多好…

【C语言】linux内核ipoib模块 - ipoib_tx_poll

一、中文注释 这段代码是 Linux 内核网络栈中与 InfiniBand 协议相关的一个部分&#xff0c;特别是与 IP over InfiniBand (IPoIB)相关。该函数负责去处理IPoIB的发送完成队列&#xff08;发送CQ&#xff09;上的工作请求&#xff08;work completions&#xff09;。以下是对这…

前后端分离Vue+ElementUI+nodejs蛋糕甜品商城购物网站95m4l

本文主要介绍了一种基于windows平台实现的蛋糕购物商城网站。该系统为用户找到蛋糕购物商城网站提供了更安全、更高效、更便捷的途径。本系统有二个角色&#xff1a;管理员和用户&#xff0c;要求具备以下功能&#xff1a; &#xff08;1&#xff09;用户可以修改个人信息&…

YOLO系列论文阅读(v1--v3)

搞目标检测&#xff0c;绕不开的一个框架就是yolo&#xff0c;而且更糟糕的是&#xff0c;随着yolo的发展迭代&#xff0c;yolo网络可以做的事越来越多&#xff0c;语义分割&#xff0c;关键点检测&#xff0c;3D目标检测。。。这几天决定把YOLO系列彻底梳理一下&#xff0c;在…

奇异递归模板模式应用6-类模板enable_shared_from_this

异步编程中存在一种场景&#xff0c;需要在类中将该类的对象注册到某个回调类或函数中&#xff0c;不能简单地将this传递给回调类中&#xff0c;很可能因为回调时该对象不存在而导致野指针访问&#xff08;也有可能在析构函数解注册时被回调&#xff0c;造成对象不完整&#xf…

【变压器故障诊断分类及预测】基于GRNN神经网络

课题名称&#xff1a;基于GRNN神经网络的变压器故障诊断分类及预测 版本日期&#xff1a;2024-02-10 运行方式&#xff1a;直接运行GRNN0507.m文件 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 对变压器油中溶解气体进行分析是变压器…

基于springboot+vue的精准扶贫管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…