最简单的基于 FFmpeg 的编码器 - 纯净版(不包含 libavformat)

最简单的基于 FFmpeg 的编码器 - 纯净版(不包含 libavformat)

  • 最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))
    • 正文
    • 结果
    • 工程文件下载

最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)

正文

本文记录一个更加“纯净”的基于 FFmpeg 的视频编码器。此前记录过一个基于 FFmpeg 的视频编码器:最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))。

这个视频编码器调用了 FFmpeg 中的 libavformat 和 libavcodec 两个库完成了视频编码工作。但是这不是一个“纯净”的编码器。上述两个库中 libavformat 完成封装格式处理,而 libavcodec 完成编码工作。一个“纯净”的编码器,理论上说只需要使用 libavcodec 就足够了,并不需要使用 libavformat。

本文记录的编码器就是这样的一个“纯净”的编码器,它仅仅通过调用 libavcodec 将 YUV 数据编码为 H.264/HEVC 等格式的压缩视频码流。

仅使用 libavcodec(不使用 libavformat)编码视频的流程如下图所示:

在这里插入图片描述

流程图中关键函数的作用如下所列:

  1. avcodec_register_all():注册所有的编解码器。
  2. avcodec_find_encoder():查找编码器。
  3. avcodec_alloc_context3():为 AVCodecContext 分配内存。
  4. avcodec_open2():打开编码器。
  5. avcodec_encode_video2():编码一帧数据。

两个存储数据的结构体如下所列:

  1. AVFrame:存储一帧未编码的像素数据。
  2. AVPacket:存储一帧压缩编码数据。

简单记录一下这个只使用 libavcodec 的“纯净版”视频编码器和使用 libavcodec+libavformat 的视频编码器的不同。

  1. 下列与libavformat相关的函数在“纯净版”视频编码器中都不存在:
    • av_register_all():注册所有的编解码器,复用/解复用器等等组件。其中调用了 * avcodec_register_all()注册所有编解码器相关的组件。
    • avformat_alloc_context():创建 AVFormatContext 结构体。
    • avformat_alloc_output_context2():初始化一个输出流。
    • avio_open():打开输出文件。
    • avformat_new_stream():创建 AVStream 结构体。avformat_new_stream() 中会调用 avcodec_alloc_context3() 创建 AVCodecContext 结构体。
    • avformat_write_header():写文件头。
    • av_write_frame():写编码后的文件帧。
    • av_write_trailer():写文件尾。
  2. 新增了如下几个函数:
    • avcodec_register_all():只注册编解码器有关的组件。
    • avcodec_alloc_context3():创建 AVCodecContext 结构体。

可以看出,相比于“完整”的编码器,这个纯净的编码器函数调用更加简单,功能相对少一些,相对来说更加的“轻量”。

源代码:

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

/**
* 最简单的基于 FFmpeg 的视频编码器(纯净版)
* Simplest FFmpeg Video Encoder Pure
*
* 源程序:
* 雷霄骅 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
*
* 本程序实现了 YUV 像素数据编码为视频码流(H264,MPEG2,VP8 等等)。
* 它仅仅使用了 libavcodec(而没有使用 libavformat)。
* 是最简单的 FFmpeg 视频编码方面的教程。
* 通过学习本例子可以了解 FFmpeg 的编码流程。
*
* This software encode YUV420P data to video bitstream
* (Such as H.264, H.265, VP8, MPEG2 etc).
* It only uses libavcodec to encode video (without libavformat)
* It's the simplest video encoding software based on FFmpeg.
* Suitable for beginner of FFmpeg
*/

#include "stdafx.h"

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

// 解决报错:fopen() 函数不安全
#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 "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif

// test different codec
#define TEST_H264  0
#define TEST_HEVC  1

int main(int argc, char* argv[])
{
	AVCodec *pCodec;
	AVCodecContext *pCodecCtx = NULL;
	AVFrame *pFrame;
	AVPacket pkt;

	FILE *fp_in;
	FILE *fp_out;

	int ret;
	int got_output = 0;
	int y_size;
	int i = 0;
	int framecnt = 0;

	char filename_in[] = "ds_480x272.yuv";

#if TEST_HEVC
	AVCodecID codec_id = AV_CODEC_ID_HEVC;
	char filename_out[] = "ds.hevc";
#else
	AVCodecID codec_id = AV_CODEC_ID_H264;
	char filename_out[] = "ds.h264";
#endif

	const int in_width = 480, in_height = 272; // Input data's width and height
	int framenum = 100; // Frames to encode 

	avcodec_register_all();

	pCodec = avcodec_find_encoder(codec_id);
	if (!pCodec)
	{
		// 没有找到合适的编码器
		printf("Can't find encoder.\n");
		return -1;
	}

	pCodecCtx = avcodec_alloc_context3(pCodec);
	if (!pCodecCtx)
	{
		printf("Can't allocate video codec context.\n");
		return -1;
	}

	pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	pCodecCtx->width = in_width;
	pCodecCtx->height = in_height;
	pCodecCtx->bit_rate = 400000;
	pCodecCtx->gop_size = 10;
	pCodecCtx->time_base.num = 1;
	pCodecCtx->time_base.den = 25;
	// H.264
	// pCodecCtx->me_range = 16;
	// pCodecCtx->max_qdiff = 4;
	// pCodecCtx->qcompress = 0.6;

	// pCodecCtx->qmin = 10;
	// pCodecCtx->qmax = 51;

	// Optional Param
	pCodecCtx->max_b_frames = 1;

	if (codec_id == AV_CODEC_ID_H264)
	{
		av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);
	}

	ret = avcodec_open2(pCodecCtx, pCodec, NULL);
	if (ret < 0)
	{
		// 编码器打开失败
		printf("Failed to open encoder.\n");
		return -1;
	}

	pFrame = av_frame_alloc();
	if (!pFrame)
	{
		printf("Can't allocate video frame.\n");
		return -1;
	}

	pFrame->format = pCodecCtx->pix_fmt;
	pFrame->width = pCodecCtx->width;
	pFrame->height = pCodecCtx->height;

	ret = av_image_alloc(pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height,
		pCodecCtx->pix_fmt, 16);
	if (ret < 0)
	{
		printf("Can't allocate raw picture buffer.\n");
		return -1;
	}

	// Input raw data
	fp_in = fopen(filename_in, "rb");
	if (!fp_in)
	{
		printf("Can't open %s.\n", filename_in);
		return -1;
	}
	// Output bitstream
	fp_out = fopen(filename_out, "wb");
	if (!fp_out)
	{
		printf("Can't open %s.\n", filename_out);
		return -1;
	}

	y_size = pCodecCtx->width * pCodecCtx->height;

	// Encode
	for (i = 0; i < framenum; i++)
	{
		av_init_packet(&pkt);
		pkt.data = NULL; // packet data will be allocated by the encoder
		pkt.size = 0;
		// Read raw YUV data
		if (fread(pFrame->data[0], 1, y_size, fp_in) <= 0 ||
			fread(pFrame->data[1], 1, y_size / 4, fp_in) <= 0 ||
			fread(pFrame->data[2], 1, y_size / 4, fp_in) <= 0)
		{
			return -1;
		}
		else if (feof(fp_in))
		{
			break;
		}

		// PTS
		pFrame->pts = i;
		// Encode the frame
		ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_output);
		if (ret < 0)
		{
			printf("Error encoding frame.\n");
			return -1;
		}

		if (got_output)
		{
			printf("Succeed to encode frame: %5d\tsize:%5d.\n", framecnt, pkt.size);
			framecnt++;
			fwrite(pkt.data, 1, pkt.size, fp_out);
			av_free_packet(&pkt);
		}
	}

	// Flush Encoder
	for (got_output = 1; got_output; i++)
	{
		ret = avcodec_encode_video2(pCodecCtx, &pkt, NULL, &got_output);
		if (ret < 0)
		{
			printf("Error encoding frame.\n");
			return -1;
		}

		if (got_output)
		{
			printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d.\n", pkt.size);
			fwrite(pkt.data, 1, pkt.size, fp_out);
			av_free_packet(&pkt);
		}
	}

	fclose(fp_out);
	avcodec_close(pCodecCtx);
	av_free(pCodecCtx);
	av_freep(&pFrame->data[0]);
	av_frame_free(&pFrame);

	system("pause");
	return 0;
}

结果

通过设定定义在程序开始的宏,确定需要使用的编码器。

// test different codec
#define TEST_H264  0
#define TEST_HEVC  1

输入文件是“ds_480x272.yuv”,如下图所示:

在这里插入图片描述

当 TEST_H264 设置为 1 的时候,编码 H.264 文件“ds.h264”。

程序运行的截图如下所示:

在这里插入图片描述

在这里插入图片描述

输出的 H.264 文件如下图所示:

在这里插入图片描述

当 TEST_HEVC 设置为 1 的时候,解码 HEVC 文件“ds.hevc”。

程序运行的截图如下所示:

在这里插入图片描述

在这里插入图片描述

输出的 HEVC 文件如下图所示:

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Video-Encoder-Pure

CSDN:Simplest FFmpeg Video Encoder - Pure.zip

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

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

相关文章

❤ hexo主题+Gitee搭建个人博客

Hexo的基本使用 1. ​认识 官网 官网地址&#xff1a;https://hexo.io/zh-cn/ 介绍 Hexo是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown&#xff08;或其他渲染引擎&#xff09;解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。即把用…

化学分子Mol2文件格式与使用注意事项

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、Mol2文件示例二、 Mol2文件主要结构解释及注意事项MOLECULE 字段解释ATOM 字段解释BOND 字段解释SUBSTRUCTURE字段解释 总结参考资料 前言 Mol2格式文件是一个ASCII 文件&#xff0c;由Tripos…

Siamfc论文中文翻译(详细!)

Fully-Convolutional Siamese Networks for Object Tracking 用于对象跟踪的Siamese网络 说明 建议对照siamfc&#xff08;2021版&#xff09;原文阅读&#xff0c;翻译软件翻译出来的效果不好&#xff0c;整体阅读体验不佳&#xff0c;所以我对译文重新进行了整理&#xff0…

【ArcGIS】利用高程进行坡度分析:区域面/河道坡度

在ArcGIS中利用高程进行坡度分析 坡度ArcGIS实操案例1&#xff1a;流域面上坡度计算案例2&#xff1a;河道坡度计算2.1 案例数据2.2 操作步骤 参考 坡度 坡度是地表单元陡缓的程度&#xff0c;通常把坡面的垂直高度和水平距离的比值称为坡度。 坡度的表示方法有百分比法、度数…

02|索引优化

数据准备 创建联合索引 KEY idx_name_age_position (name,age,position) USING BTREE CREATE TABLE employees (id int(11) NOT NULL AUTO_INCREMENT,name varchar(24) NOT NULL DEFAULT COMMENT 姓名,age int(11) NOT NULL DEFAULT 0 COMMENT 年龄,position varchar(20) NO…

在线程调用的函数中使用pthread_exit同样会将线程退出

如上图所示&#xff0c;在func()函数中调用pthread_exit&#xff0c;同样可以退出当前线程&#xff1b; 类似的&#xff0c;如果func&#xff08;&#xff09;函数中调用exit&#xff0c;可以直接退出整个进程。 return 是返回到函数调用处&#xff1b; pthread_exit是退出…

解决easyExcel模板填充时转义字符\{xxx\}失效

正常我们在使用easyExcel进行模板填充时&#xff0c;定义的变量会填充好对应的实际数据&#xff0c;未定义的变量会被清空&#xff0c;但是如果这个未定义的变量其实是模板的一部分&#xff0c;那么清空了就出错了。 在这张图里&#xff0c;上面的是模板填充后导出的文件&…

Java基础之lambda表达式(五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【Java程序员面试专栏 数据结构】六 高频面试算法题:字符串

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,本篇主要聊聊数组,包括数组合并,滑动窗口解决最长无重复子数组问题,图形法解下一个排列问题,以及一些常见的二维矩阵问题,所以放到一篇Blog中集中练习 题目…

联想开天昭阳N4620Z笔记本如何恢复出厂麒麟操作系统(图解)

联想开天昭阳N4620Z笔记本简单参数&#xff1a; 中央处理器&#xff1a;KX-6640MA G2 内存&#xff1a;8GB 固态硬盘&#xff1a;512GB SSD 显示器&#xff1a;14.0”FHD 电池&#xff1a;4Cell 操作系统&#xff1a;麒麟KOS中文RTM&#xff08;试用版&#xff09; 此款笔…

关于Arrays类中asList(T... a)泛型参数辨析

前提 我们需要知道两点 &#xff08;1&#xff09;T指的是泛型类型&#xff0c;它只能是引用类型&#xff0c;何为引用类型&#xff1f;在java中除了基本数据类型&#xff08;如byte、short、int、long、float、double、boolean、char&#xff09;之外的所有类型都是引用类型…

【Flutter/Android】运行到安卓手机上一直卡在 Running Gradle task ‘assembleDebug‘... 的终极解决办法

方法步骤简要 查看你的Flutter项目需要什么版本的 Gradle 插件&#xff1a; 下载这个插件&#xff1a; 方法一&#xff1a;浏览器输入&#xff1a;https://services.gradle.org/distributions/gradle-7.6.3-all.zip 方法二&#xff1a;去Gradle官网找对应的版本&#xff1a;h…

Uniapp小程序开发-底部tabbar的开发思路

文章目录 前言一、uniapp 实现 tabbar二、图标使用网络图片后端返回tabbar信息uniapp方式中的setTabBarItem 总结 前言 记录uniapp 开发小程序的底部tabbar &#xff0c;这里讨论的不是自定义tabbar的情况。而是使用wx.setTabBarItem(Object object) 这个api的情况。关于custo…

IT廉连看——C语言——分支语句

IT廉连看—分支语句 一、什么是语句 C语句可分为以下五类&#xff1a; 表达式语句 函数调用语句 控制语句 复合语句 空语句 本周后面介绍的是控制语句。 控制语句用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&…

OT 安全解决方案:探索技术

IT 和 OT 安全的融合&#xff1a;更好的防御方法 OT 安全解决方案下一个时代&#xff1a; 为了应对不断升级的威胁形势&#xff0c;组织认识到迫切需要采用统一的信息技术 (IT) 和运营技术 (OT) 安全方法。IT 和 OT 安全的融合代表了一种范式转变&#xff0c;承认这些传统孤立领…

了解 JavaScript 中的重放攻击和复现攻击

在网络安全领域&#xff0c;重放攻击&#xff08;Replay Attack&#xff09;和复现攻击&#xff08;Playback Attack&#xff09;是一些可能导致安全漏洞的攻击形式。这两种攻击类型涉及在通信过程中再次发送已经捕获的数据&#xff0c;以达到欺骗系统的目的。本文将介绍 JavaS…

vue3 实现 el-pagination页面分页组件的封装以及调用

示例图 一、组件代码 <template><el-config-provider :locale"zhCn"><el-pagination background class"lj-paging" layout"prev, pager, next, jumper" :pager-count"5" :total"total":current-page"p…

leetcode单调栈

739. 每日温度 请根据每日 气温 列表&#xff0c;重新生成一个列表。对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, …

计算机组成原理(14)----总线

目录 一.总线的物理实现 二.总线概述 三.总线的特性 四.总线的分类 &#xff08;1&#xff09;按数据传输格式分类 •串行总线 •并行总线 &#xff08;2&#xff09;按总线功能分类 •片内总线 •系统总线 系统总线的结构 •通信总线 &#xff08;3&#xff09;按…

激光雷达反光板算法总结

1 高反特征提取 首先,从雷达原始数据,提取到高反点;根据雷达的规格书提供的不同材料的强度,设定合适的阈值;;更优的方法是根据距离设定不同的阈值 2 反光板及反光柱的聚类 根据高反点是否连续进行聚类,同时结合距离及雷达的角度分辨率,计算出针对不同尺寸的反光板或反…