最简单的基于 FFmpeg 的封装格式转换器(无编解码)

最简单的基于 FFmpeg 的封装格式转换器(无编解码)

  • 最简单的基于 FFmpeg 的封装格式转换器(无编解码)
    • 正文
    • 结果
    • 工程文件下载

最简单的基于 FFmpeg 的封装格式转换器(无编解码)

参考雷霄骅博士的文章,链接:最简单的基于FFMPEG的封装格式转换器(无编解码)

正文

本文介绍一个最简单的基于 FFmpeg 的封装格式转换器。所谓的封装格式转换,就是在 AVI,FLV,MKV,MP4 这些格式之间转换(对应 .avi,.flv,.mkv,.mp4 文件)。

需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。

传统的转码程序工作原理如下图所示:

在这里插入图片描述

上图例举了一个举例:FLV(视频:H.264,音频:AAC)转码为 AVI(视频:MPEG2,音频:MP3)的例子。可见视频转码的过程通俗地讲相当于把视频和音频重新“录”了一遍。

本程序的工作原理如下图所示:

在这里插入图片描述

由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:

  1. 处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。
  2. 视音频质量无损。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

下面附上基于 FFmpeg 的 Remuxer 的流程图。图中使用浅红色标出了关键的数据结构,浅蓝色标出了输出视频数据的函数。可见整个程序包含了对两个文件的处理:读取输入文件(位于左边)和写入输出文件(位于右边)。中间使用了一个 avcodec_copy_context() 拷贝输入的 AVCodecContext 到输出的 AVCodecContext。

请添加图片描述

简单介绍一下流程中关键函数的意义:

输入文件操作:

  • avformat_open_input():打开输入文件,初始化输入视频码流的 AVFormatContext。

  • av_read_frame():从输入文件中读取一个 AVPacket。

输出文件操作:

  • avformat_alloc_output_context2():初始化输出视频码流的 AVFormatContext。

  • avformat_new_stream():创建输出码流的 AVStream。

  • avcodec_copy_context():拷贝输入视频码流的 AVCodecContex 的数值 t 到输出视频的 AVCodecContext。

  • avio_open():打开输出文件。

  • avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。

  • av_interleaved_write_frame():将 AVPacket(存储视频压缩码流数据)写入文件。

  • av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。

源代码:

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


/*
* 最简单的基于FFmpeg的封装格式转换器
* Simplest FFmpeg Remuxer
*
* 源程序:
* 雷霄骅 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
*
* 本程序实现了视频封装格式之间的转换。
* 需要注意的是本程序并不改变视音频的编码格式。
*
* This software converts a media file from one container format
* to another container format without encoding/decoding video files.
*/

#include "stdafx.h"

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

// 解决报错:无法解析的外部符号 __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

int main(int argc, char* argv[])
{
	AVOutputFormat *ofmt = NULL;
	// 输入对应一个 AVFormatContext,输出对应一个 AVFormatContext
	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;

	const char *in_filename = "cuc_ieschool.flv"; // 输入文件名
	const char *out_filename = "cuc_ieschool.mp4"; // 输出文件名

	int ret;
	int frame_index;
	int i;

	//if (argc < 3)
	//{
	//	printf("usage: %s input output\n"
	//		"Remux a media file with libavformat and libavcodec.\n"
	//		"The output format is guessed according to the file extension.\n"
	//		, argv[0]);
	//	return 1;
	//}

	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;
	}

	// Print some input information
	av_dump_format(ifmt_ctx, 0, in_filename, 0);

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

	for (i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		// 根据输入流创建输出流
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
		if (out_stream == NULL)
		{
			printf("Failed allocating output stream.\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}

		// 拷贝输入视频码流的 AVCodecContex 的数值 t 到输出视频的 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
	av_dump_format(ofmt_ctx, 0, out_filename, 1);

	// Open output file
	if (!(ofmt->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("Could not open output file '%s'.\n", out_filename);
			goto end;
		}
	}
	// Write file header
	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0)
	{
		printf("Error occurred when opening output file.\n");
		goto end;
	}

	frame_index = 0;

	while (1)
	{
		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];
		out_stream = ofmt_ctx->streams[pkt.stream_index];
		// copy packet
		// 转换 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;

		// 将 AVPacket 写入文件
		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++;
	}

	// Write file trailer
	av_write_trailer(ofmt_ctx);


end:
	avformat_close_input(&ifmt_ctx);
	// close output
	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
	{
		avio_close(ofmt_ctx->pb);
	}
	avformat_free_context(ofmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF)
	{
		printf("Error occurred.\n");
		return -1;
	}

	system("pause");
	return 0;
}

本程序可以直接在 VS 2015 上运行。

结果

运行程序,将输入 flv 文件转封装为 mp4 文件,以下是其信息:

在这里插入图片描述

下图显示了一个测试的输入文件的视音频参数。

在这里插入图片描述

下图显示了输出文件的视音频参数。

在这里插入图片描述

可以看出除了视频的封装格式从flv转换成了mp4,其他有关视音频编码的参数没有任何变化。

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Remuxer

CSDN:Simplest FFmpeg Remuxer.zip

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

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

相关文章

文件上传漏洞--Upload-labs--Pass03--特殊后缀与::$DATA绕过

方法一&#xff1a;特殊后缀绕过&#xff1a; 一、什么是特殊后缀绕过 源代码中的黑名单禁止一系列后缀名 之外的后缀&#xff0c;称之为‘特殊后缀名’&#xff0c;利用其来绕过黑名单&#xff0c;达到上传含有恶意代码的文件的目的。 二、代码审计 接下来对代码逐条拆解进行…

【精品】关于枚举的高级用法

枚举父接口 public interface BaseEnum {Integer getCode();String getLabel();/*** 根据值获取枚举** param code* param clazz* return*/static <E extends Enum<E> & BaseEnum> E getEnumByCode(Integer code, Class<E> clazz) {Objects.requireNonN…

【点云】生成有凹凸的平面

文章目录 前言高斯函数原理代码保存 测试测试1 &#xff1a;领域曲率代码测试2&#xff1a;高斯曲率代码 加上噪点测试1测试2 总结 前言 尝试用一些数据生成有凹凸面的点云。 我们姑且把z轴当成有凹凸的缺陷&#xff0c;x轴和y轴共同组成一个平面。 高斯函数 原理 高斯函数w…

nba2k23 中国梦之队面补名单

nba2k23 中国梦之队面补名单 提示&#xff1a;本面补为名单形式&#xff0c;内含中国国家队2000、2008、2015、2019面补名单&#xff0c;安装后多队同时存在。 下载地址&#xff1a; https://www.changyouzuhao.cn/12759.html

Python算法题集_验证二叉搜索树

Python算法题集_验证二叉搜索树 题98&#xff1a;验证二叉搜索树1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【DFS递归】2) 改进版一【DFS递归终止检测】3) 改进版二【BFS迭代终止检测】 4. 最优算法 本文为Python算法题集之一的代码示例 题…

【pyopenGL编程手册- 01/20】pyopenGL安装和简要说明

目录 一、说明二、测试系统安装的健康性三、安装64位的openGL四、写给程序员的4. 1 函数库介绍4.2 库内函数的命名 五、常见库的函数介绍5.1 OpenGL 核心库 GL5.2 OpenGL 实用库 GLU5.3 OpenGL 工具库 GLUT5.4 Windows 专用库 WGL 六、错误引发点和异常追踪6.1 错误检查开关6.…

时间序列预测模型:ARIMA模型

1. ARIMA模型原理介绍 ARIMA模型&#xff0c;全称为自回归积分滑动平均模型&#xff08;Autoregressive Integrated Moving Average Model&#xff09;&#xff0c;是一种常用的时间序列预测方法。ARIMA模型通过对时间序列数据的差分化处理&#xff0c;使非平稳时间序列数据变…

CSS之margin塌陷

margin塌陷 CSS中的外边距塌陷&#xff08;Margin Collapse&#xff09;问题是指在垂直方向上&#xff0c;当两个或多个块级元素的边距相遇时&#xff0c;它们之间的距离不是它们各自边距的总和&#xff0c;而是其中的最大值。这种现象主要出现在块级元素的上下外边距之间。 &…

【机器学习笔记】10 人工神经网络

人工神经网络发展史 1943年&#xff0c;心理学家McCulloch和逻辑学家Pitts建立神经网络的数学模型&#xff0c;MP模型 每个神经元都可以抽象为一个圆圈&#xff0c;每个圆圈都附带特定的函数称之为激活函数&#xff0c;每两个神经元之间的连接的大小的加权值即为权重。 1960年…

springboot194基于springboot的医药管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的医药管理系统 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考。 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **…

【STM32 CubeMX】串口编程DMA

文章目录 前言一、DMA方式1.1 DMA是什么1.2 CubeMX配置DMA1.3 DMA方式函数使用DMA的发送接收函数 总结 前言 在嵌入式系统中&#xff0c;串口通信是一项至关重要的功能&#xff0c;它允许单片机与外部设备进行数据交换&#xff0c;如传感器、显示器或其他设备。然而&#xff0…

C++ //练习 7.13 使用istream构造函数重写第229页的程序。

C Primer&#xff08;第5版&#xff09; 练习 7.13 练习 7.13 使用istream构造函数重写第229页的程序。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /******************************************************************…

太阳光模拟器助力于太阳光对金属铝靶材影响

1. 引言 金属铝靶材是一种被广泛应用于薄膜制备领域的金属材料&#xff0c;具有高纯度、均一性好、结构致密等优点。其制备工艺主要包括冶金法、电化学法、物理气相沉积法等&#xff0c;其中电化学法制备的铝靶材品质最佳&#xff0c;价格也比较实惠。 其中包含&#xff1a; …

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱2(附带项目源码)

效果演示 文章目录 效果演示系列目录前言拖放、交换物品绘制拖拽物品插槽UI修改Inventory&#xff0c;控制拖放功能 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中&#xf…

Linux桌面

系统信息的截图 登录界面右下角可以切换 Ubuntu on Wayland &#xff0c;虽然还是测试版&#xff0c;不过体验已经比之前的 Xorg 好多了&#xff0c;最笔记本上使用最影响体验的高分屏适配功能&#xff0c;在 wayland 中也是几乎完美支持的。 卸载 snap 这个 snap 是 Ubuntu …

GEO文章套路,数据下载和批次效应处理

原文链接&#xff1a; SCI文章复现 | GEO文章套路&#xff0c;数据下载和批次效应处理https://mp.weixin.qq.com/s/KBA67EJ7cCK5NDTUzrwJ2Q 一、前言 这是2024年春节后的第一个推送教程&#xff0c;我们也给大家赠送一个福利。将前期的付费教程免费推送给大家。其实&#xff…

springboot集成elk实现日志采集可视化

一、安装ELK 安装ELK组件请参考我这篇博客&#xff1a;windows下安装ELK(踩坑记录)_windows上安装elk教程-CSDN博客 这里不再重复赘述。 二、编写logstash配置 ELK组件均安装好并成功启动&#xff0c;进入到logstash组件下的config文件夹&#xff0c;创建logstash.conf配置…

网络原理-TCP/IP(7)

目录 网络层 路由选择 数据链路层 认识以太网 以太网帧格式 认识MAC地址 对比理解MAC地址和IP地址 认识MTU ARP协议 ARP协议的作用 ARP协议工作流程 重要应用层协议DNS(Domain Name System) DNS背景 NAT技术 NAT IP转换过程 NAPT NAT技术的优缺点 网络层 路由…

JDK8新增的时间

设计更合理&#xff0c;功能更丰富&#xff0c;使用更方便&#xff0c;都是不可变的对象&#xff0c;修改后会返回新的事件对象不会丢失最开始的时间&#xff0c;线程安全&#xff0c;能精确到毫秒、纳秒。 这三个类都有一个静态方法now()&#xff1a;获取系统当前时间对应的该…

Java解决下降路径最小和

Java解决下降路径最小和 01 题目 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列…