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

本文介绍了如何使用FFmpeg的libavcodec库实现一个简单的纯净视频编码器,不依赖libavformat,仅编码YUV数据为H.264或HEVC格式。详细步骤包括初始化AVCodecContext、编码帧数据和输出编码后的码流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

最简单的基于 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.youkuaiyun.com/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.youkuaiyun.com/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

优快云:Simplest FFmpeg Video Encoder - Pure.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值