【FFmpeg】调用ffmpeg进行H264软编


====== 示例工程 ======
【FFmpeg】调用FFmpeg库实现264软解

1. FFmpeg的编译

FFmpeg在Windows下的编译参考:http://t.csdnimg.cn/BtAW5
本文使用的FFmpeg版本为7.0

2. 调用FFmpeg实现H264软编

参考FFmpeg当中的/doc/examples当中的video_encode.c文件,进行调用过程的理解和实现。

2.1 基本框架

根据/doc/examples当中的实现进行修改,包括:
1.增加pragma去除fopen的warning
2.增加extern调用ffmpeg库文件
3.将avcodec_find_encoder_by_name修改成avcodec_find_encoder,并将编码器类型设置为AV_CODEC_ID_H264,即264编码器
4.增加error code的打印,可以查阅其对应的错误信息

在编码过程当中,使用了如下的函数

函数名作用
avcodec_find_encoder_by_name根据名称查找编码器,输入为const char*,返回为AVCodec,AVCodec记录了编码器信息
avcodec_find_encoder根据ID查找编码器,输入为AVCodecID,返回为AVCodec,记录了编码器信息
avcodec_alloc_context3创建codec的上下文信息,输入为AVCodec,返回为AVCodecContext,其记录了编码过程上下文的流信息
av_packet_alloc创建数据包packet,并且初始化为默认值,返回为AVPacket;该结构存储压缩之后的数据。通常由解码器导出,然后作为输入传递给解码器,或者作为编码器的输出接收,然后传递给解码器
avcodec_open2打开编码器,输入为AVCodec,返回为ret。该函数初始化了codec线程和配置
av_frame_alloc创建frame,返回为AVFrame。这个结构描述待编码的原始的音频或视频数据
av_frame_get_buffer获取buffer,输入为AVFrame,输出为ret。为音频或视频数据分配新的缓冲区
av_frame_make_writable使得当前帧变为可写状态,输入为AVFrame,输出为ret
avcodec_send_frame将frame送入到编码器当中进行编码,输入为AVCodecContext和AVFrame,输出为ret
avcodec_receive_packet接收编码器已编码信息,输入为AVCodecContext和AVPacket,输出为ret
av_packet_unref释放packet的缓冲区,但不会释放packet结构体本身
av_frame_free释放frame的缓冲区以及结构体本身
av_packet_freeav_packet_free以及结构体本身

从上面这一系列的函数调用来看,大致操作流程和数据走向大约是
1.编码器的创建和初始化(avcodec_find_encoder)
2.编码器上下文的创建和初始化(avcodec_alloc_context3)
3.创建编码器输出信息,使用AVPacket进行存储(av_packet_alloc)
4.打开编码器(avcodec_open2)
5.创建编码器输入信息,使用AVFrame进行存储(av_frame_alloc)
6.获取编码器输入信息的数据Buffer(av_frame_get_buffer)
7.将输入的信息(帧)设置为可写模型(av_frame_make_writable)
8.准备输入信息(帧),这里实现的是使用固定数值,仅做测试(YUV三通道分别是240,128,64)
9.进入内部编码流程(调用encode函数)
10.将帧送入到编码器当中进行编码,数据信息的载体是AVFrame,AVCodecContext记录了编码流信息(avcodec_send_frame)
11.将已编码的帧拿出来,数据信息的载体是AVPacket(avcodec_receive_packet)
另外,unref和free的含义各不同,如果是在程序执行的内部,如encode这种内部编码函数,使用unref仅释放内存但不释放结构体,在最后主函数执行的结尾进行结构体的释放。

2.2 代码实现

// video_implementation.cpp : ���ļ����� "main" ����������ִ�н��ڴ˴���ʼ��������
//
// �������fopen���ֵ�warning
#pragma warning(disable : 4996)
#include "video_encode.h"
#ifdef _WIN32
//Windows
extern "C" // 在C++文件中调用C文件需要使用,ffmpeg是使用C实现的
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#ifdef __cplusplus
};
#endif
#endif


static void encode_internal(AVCodecContext* avcodec_ctx, AVFrame* av_frm, AVPacket* av_pkt, FILE* out_file)
{
	int ret;

	/* Send frame to encoder */
	// ret 返回值的几种情况
	// EAGAIN 11 /* Try again */
	// ENOMEM 12 /* Out of memory */
	// EINVAL 22 /* Invalid argument */, 可能是编码器没有打开,或者是初始化时使用的是解码器
	// AVERROR_EOF /* End of File */
	// 0 /* Success */
	ret = avcodec_send_frame(avcodec_ctx, av_frm);
	if (ret < 0) {
		fprintf(stderr, "Error! fail to send frame, error code:%d", ret);
		exit(1);
	}

	while (ret >= 0) {
		/* Receive encoded frame */
		// EAGAIN 11 /* Try again */
		// EINVAL 22 /* Invalid argument */, 可能是编码器没有打开,或者是初始化时使用的是解码器
		// AVERROR_EOF /* End of File */
		ret = avcodec_receive_packet(avcodec_ctx, av_pkt);
		if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
			return;
		}
		else if (ret < 0) {
			fprintf(stderr, "Error! during encoding, error code:%d", ret);
			exit(1);
		}
		// unref将实例对应的内存释放掉
		// packet_unref针对于packet,每个函数都有一个对应的
		printf("Write packet %3" PRId64" (size=%5d)\n", av_pkt->pts, av_pkt->size);
		fwrite(av_pkt->data, 1, av_pkt->size, out_file);
		av_packet_unref(av_pkt);
	}
}

int encode(const char* in_file, const char* out_file)
{
	const AVCodec* avcodec;
	AVCodecContext* avcodec_ctx = NULL;
	AVPacket* av_pkt;
	AVFrame* av_frm;

	//const char* file_name;
	//const char* codec_name;
	uint8_t endcode[] = { 0, 0, 1, 0xb7 };

	int ret = 0;

	//if (argc <= 2) {
	//	fprintf(stderr, "Usage: %s <output file> <codec name>\n", argv[0]);
	//	exit(0);
	//}

	//file_name = argv[1];
	//codec_name = argv[2];

	/* create codec */
	// codec类型设置为264
	avcodec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!avcodec) {
		fprintf(stderr, "Error! can not find encoder");
		exit(1);
	}

	/* create context */
	// 创建编码器上下文信息
	avcodec_ctx = avcodec_alloc_context3(avcodec);
	if (!avcodec_ctx) {
		fprintf(stderr, "Error! can not alloc avcodec");
		exit(1);
	}

	/* create packet */
	// 创建packet数据类型
	av_pkt = av_packet_alloc();
	if (!av_pkt) {
		fprintf(stderr, "Error! alloc pkt failed");
		exit(1);
	}

	/* ctx init */
	avcodec_ctx->width = 1920;
	avcodec_ctx->height = 1200;
	avcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

	/*
		time_base是一种时间戳计量基准,用于在多个结构体当中的time_base当中设置一个基准
		ffmpeg当中不同的结构体所使用的的time_base不统一,通过设置一个time_base,作为各个结构体之间数据转换的标准
	*/
	avcodec_ctx->time_base.den = 1;
	avcodec_ctx->time_base.num = 25;
	avcodec_ctx->framerate.den = 25;
	avcodec_ctx->framerate.num = 1;

	/* set context preset level */
	if (avcodec->id == AV_CODEC_ID_H264) {
		// preset = slow
		// zerolatency are commonly used in conference meeting
		av_opt_set(avcodec_ctx->priv_data, "preset", "slow", 0);
		av_opt_set(avcodec_ctx->priv_data, "tune", "zerolatency", 0);
	}

	/* open codec */
	ret = avcodec_open2(avcodec_ctx, avcodec, NULL);
	if (ret < 0) {
		fprintf(stderr, "Error! open avcodec failed, error code:%d", ret);
		exit(1);
	}

	/* file open */
	FILE* fp_in = fopen(in_file, "rb");
	if (!fp_in) {
		fprintf(stderr, "Error on opening input file\n");
		exit(1);
	}

	FILE* fp_out = fopen(out_file, "wb");
	if (!fp_out) {
		fprintf(stderr, "Error: open file failed");
		exit(1);
	}

	av_frm = av_frame_alloc();
	if (!av_frm) {
		fprintf(stderr, "Error: alloc frame failed");
		exit(1);
	}

	av_frm->width = avcodec_ctx->width;
	av_frm->height = avcodec_ctx->height;
	av_frm->format = avcodec_ctx->pix_fmt;

	/* get buffer */
	ret = av_frame_get_buffer(av_frm, 0);
	if (ret < 0) {
		fprintf(stderr, "Error: alloc frame data failed, error code:%d", ret);
		exit(1);
	}

	int x = 0;
	int y = 0;
	int frame_number = 10;
	int picture_size = 0;
	uint8_t* picture_buf;
	picture_size = av_image_get_buffer_size(avcodec_ctx->pix_fmt, avcodec_ctx->width, avcodec_ctx->height, 1);
	picture_buf = (uint8_t*)av_malloc(picture_size);
	// av_image_fill_arrays(av_frm->data, av_frm->linesize, picture_buf, avcodec_ctx->pix_fmt, avcodec_ctx->width, avcodec_ctx->height, 1);

	int y_size = avcodec_ctx->width * avcodec_ctx->height;

	for (int i = 0; i < frame_number; i++) {
		fflush(stdout);
		/* Make sure the frame data is writable.
		   On the first round, the frame is fresh from av_frame_get_buffer()
		   and therefore we know it is writable.
		   But on the next rounds, encode() will have called
		   avcodec_send_frame(), and the codec may have kept a reference to
		   the frame in its internal structures, that makes the frame
		   unwritable.
		   av_frame_make_writable() checks that and allocates a new buffer
		   for the frame only if necessary.
		 */
		ret = av_frame_make_writable(av_frm);
		if (ret < 0) {
			fprintf(stderr, "Error: can not make frame writable");
			exit(1);
		}

		/* Prepare dummy image */
		/* Componet Y */
		//for (y = 0; y < avcodec_ctx->height; y++) {
		//	for (x = 0; x < avcodec_ctx->width; x++) {
		//		av_frm->data[0][y * av_frm->linesize[0] + x] = 240;
		//	}
		//}

		///* Component Cb and Cr */
		//for (y = 0; y < avcodec_ctx->height / 2; y++) {
		//	for (x = 0; x < avcodec_ctx->width / 2; x++) {
		//		av_frm->data[1][y * av_frm->linesize[1] + x] = 128;
		//		av_frm->data[2][y * av_frm->linesize[2] + x] = 64;

		//	}
		//}
		if (fread(picture_buf, 1, y_size * 3 / 2, fp_in) <= 0)
		{
			printf("Failed to read raw data.\n");
			return -1;
		}
		else if (feof(fp_in))
		{
			break;
		}

		av_frm->data[0] = picture_buf; // Y
		av_frm->data[1] = picture_buf + y_size; // U 
		av_frm->data[2] = picture_buf + y_size * 5 / 4; // V

		av_frm->pts = i;

		/* encode the img */
		encode_internal(avcodec_ctx, av_frm, av_pkt, fp_out);
		fprintf(stdout, "encoded frame num:%d\n", i);
	}

	/* flush the encoder */
	encode_internal(avcodec_ctx, NULL, av_pkt, fp_out);

	// Add end code if use MEPG format
	//if (avcodec->id == AV_CODEC_ID_MPEG1VIDEO || avcodec->id == AV_CODEC_ID_MPEG2VIDEO || avcodec->id == AV_CODEC_ID_H264)
	//	fwrite(endcode, 1, sizeof(endcode), fp_out);
	//fclose(fp_out);

	// free memory
	avcodec_free_context(&avcodec_ctx);
	av_frame_free(&av_frm);
	av_packet_free(&av_pkt);
	return 0;
}



2.3 测试结果

[libx264 @ 000001e7b1fc6000] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 000001e7b1fc6000] profile High, level 5.0, 4:2:0, 8-bit
[libx264 @ 000001e7b1fc6000] frame I:1     Avg QP: 5.00  size:  1063
[libx264 @ 000001e7b1fc6000] frame P:24    Avg QP: 0.00  size:    77
[libx264 @ 000001e7b1fc6000] mb I  I16..4: 100.0%  0.0%  0.0%
[libx264 @ 000001e7b1fc6000] mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  0.0%  0.0%  0.0%  0.0%  0.0%    skip:100.0%
[libx264 @ 000001e7b1fc6000] 8x8 transform intra:0.0%
[libx264 @ 000001e7b1fc6000] coded y,uvDC,uvAC intra: 0.0% 0.0% 0.0% inter: 0.0% 0.0% 0.0%
[libx264 @ 000001e7b1fc6000] i16 v,h,dc,p: 99%  0%  1%  0%
[libx264 @ 000001e7b1fc6000] i8c dc,h,v,p: 100%  0%  0%  0%
[libx264 @ 000001e7b1fc6000] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 000001e7b1fc6000] kb/s:0.04

Github: https://github.com/DoFulangChen/video_implementation.git

  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ffmpeg是一个开源的音视频处理工具,可以用于对音视频进行编解码、转码、剪辑等操作。它支持多种编码器和解码器,包括H264编码器。 要使用ffmpeg进行硬编H264,需要使用支持硬件加速的编码器,例如NVIDIA的NVENC编码器。可以通过调用ffmpeg库中的函数来实现。 以下是一个使用ffmpeg进行硬编H264的示例代码: ```c #include <stdio.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> int main() { // 注册所有的编解码器和格式 av_register_all(); // 打开输入文件 AVFormatContext *input_ctx = NULL; if (avformat_open_input(&input_ctx, "input.h264", NULL, NULL) != 0) { printf("无法打开输入文件\n"); return -1; } // 查找视频流 int video_stream_index = -1; for (int i = 0; i < input_ctx->nb_streams; i++) { if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { printf("找不到视频流\n"); return -1; } // 获取输入视频流的编解码器参数 AVCodecParameters *input_codecpar = input_ctx->streams[video_stream_index]->codecpar; // 查找NVENC编码器 AVCodec *encoder = avcodec_find_encoder_by_name("h264_nvenc"); if (!encoder) { printf("找不到NVENC编码器\n"); return -1; } // 创建编码器上下文 AVCodecContext *encoder_ctx = avcodec_alloc_context3(encoder); if (!encoder_ctx) { printf("无法创建编码器上下文\n"); return -1; } // 设置编码器参数 encoder_ctx->width = input_codecpar->width; encoder_ctx->height = input_codecpar->height; encoder_ctx->pix_fmt = input_codecpar->format; encoder_ctx->time_base = input_ctx->streams[video_stream_index]->time_base; // 打开编码器 if (avcodec_open2(encoder_ctx, encoder, NULL) < 0) { printf("无法打开编码器\n"); return -1; } // 创建输出文件 AVFormatContext *output_ctx = NULL; if (avformat_alloc_output_context2(&output_ctx, NULL, NULL, "output.mp4") < 0) { printf("无法创建输出文件\n"); return -1; } // 添加视频流到输出文件 AVStream *output_stream = avformat_new_stream(output_ctx, NULL); if (!output_stream) { printf("无法创建输出视频流\n"); return -1; } // 复制编解码器参数到输出视频流 avcodec_parameters_copy(output_stream->codecpar, input_codecpar); // 打开输出文件 if (avio_open(&output_ctx->pb, "output.mp4", AVIO_FLAG_WRITE) < 0) { printf("无法打开输出文件\n"); return -1; } // 写文件头 if (avformat_write_header(output_ctx, NULL) < 0) { printf("无法写文件头\n"); return -1; } // 编码并写入输出文件 AVPacket packet; while (av_read_frame(input_ctx, &packet) >= 0) { if (packet.stream_index == video_stream_index) { // 发送原始数据到编码器 avcodec_send_packet(encoder_ctx, &packet); // 接收编码后的数据 while (avcodec_receive_packet(encoder_ctx, &packet) == 0) { // 写入输出文件 av_interleaved_write_frame(output_ctx, &packet); av_packet_unref(&packet); } } av_packet_unref(&packet); } // 写文件尾 av_write_trailer(output_ctx); // 释放资源 avformat_close_input(&input_ctx); avcodec_free_context(&encoder_ctx); avformat_free_context(output_ctx); return 0; } ``` 这段代码使用ffmpeg库将H264裸流文件读取到内存中,然后使用NVENC编码器将其封装为MP4文件并保存到本地。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值