ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频相关的知识,希望对你有一定的参考价值。

参考文章:【FFmpeg编码实战】(1)将YUV420P图片集编码成H.264视频文件

文章目录

第二个项目:将YUV图片序列作为流读入,编码封装成x264 MP4视频

将YUV图片序列编码成.h264文件

直接把博主的代码拷到我们继承的上一个项目中,感觉应该有一些地方需要修改,果不其然

#pragma warning(disable : 4996)
#include <iostream>
#include <string>       // 字符串操作
#include <direct.h>     // 文件夹

extern "C"             // ffmpeg 相关头文件
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include "libavutil/imgutils.h"


using namespace std;


#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "avdevice.lib")

// C++ 中 ffmpeg有些地方报错,修改这样后 OK
//
static char* av_ts_make_string1(char* buf, int64_t ts)

	if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS");
	else                      snprintf(buf, AV_TS_MAX_STRING_SIZE, "%" PRId64, ts);
	return buf;


static char* av_ts_make_time_string1(char* buf, int64_t ts, AVRational* tb)

	if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS");
	else                      snprintf(buf, AV_TS_MAX_STRING_SIZE, "%.6g", av_q2d(*tb) * ts);
	return buf;

//



/// <summary>
/// 输入输出文件信息
/// </summary>
const char* Out_File_Name = "video.h264";

// 源文件名:video/video_out.yuv420p.0.yuv 
const char* Folder = "video";
const char* YUV_File_Name = "video_out.yuv420p";
const char* YUV = "yuv";			// 图片文件后缀
#define YUV_Width	1050			// 宽
#define YUV_Height	540				// 高
#define Stream_Frame_Rate	25		// 帧率:每秒25帧

// 参考:doc\\examples\\encode_video.c、doc\\examples\\muxing.c

int main(int* argc, char* argv[])


	// 1. 初始化视频文件格式上下文
	AVFormatContext* p_FormatCtx = NULL;

	avformat_alloc_output_context2(&p_FormatCtx, NULL, "h264", Out_File_Name);
	if (!p_FormatCtx) 
		cout << "无法从输出文件后缀判断视频格式,默认使用h264" << endl;
		avformat_alloc_output_context2(&p_FormatCtx, NULL, "h264", Out_File_Name);
	

	// 2. 获得输出格式 Format信息
	AVOutputFormat* p_OutputFmt = NULL;
	p_OutputFmt = p_FormatCtx->oformat;


	bool have_video = false, have_audio = false, encode_video = false, encode_audio = false;
	AVCodec* p_Video_Enc = NULL, * p_Vudio_Enc = NULL;
	AVCodecContext* p_Video_Enc_Ctx = NULL, * p_Audio_Enc_Ctx = NULL;
	AVStream* p_Video_st = NULL, * p_Audio_st = NULL;
	AVFrame* p_Video_Frame = NULL;

	// 3. 添加 视频输出流,查找并打开视频解码器
	if (p_OutputFmt->video_codec != AV_CODEC_ID_NONE) 
		// 3.1 找到视频解码器
		p_Video_Enc = avcodec_find_encoder(p_OutputFmt->video_codec);

		// 3.2 创建视频流
		p_Video_st = avformat_new_stream(p_FormatCtx, p_Video_Enc);
		p_Video_st->id = p_FormatCtx->nb_streams - 1;

		// 3.3 创建编码器上下文
		p_Video_Enc_Ctx = avcodec_alloc_context3(p_Video_Enc);

		// 3.4 配置视频格式
		p_Video_Enc_Ctx->codec_type = AVMediaType::AVMEDIA_TYPE_VIDEO;
		p_Video_Enc_Ctx->codec_id = p_OutputFmt->video_codec;	// Code id
		p_Video_Enc_Ctx->pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P;			// 图片格式

		p_Video_Enc_Ctx->bit_rate = 2000000;	// 码率越高,画面质量越好,相应视频越大	// 采样器码率
		p_Video_Enc_Ctx->width = YUV_Width;						// 宽
		p_Video_Enc_Ctx->height = YUV_Height;					// 高

		p_Video_st->time_base.num = 1;					// 音位 1s
		p_Video_st->time_base.den = 25;					// 帧率,每秒25帧


		p_Video_Enc_Ctx->framerate.num = 25;
		p_Video_Enc_Ctx->framerate.den = 1;
		p_Video_Enc_Ctx->time_base = p_Video_st->time_base;

		p_Video_Enc_Ctx->gop_size = 12;							// 连续画面组大小

		if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_H264)
		
			p_Video_Enc_Ctx->qmin = 10;				// 最小的量化因子
			p_Video_Enc_Ctx->qmax = 51;				// 最大的量化因子
			p_Video_Enc_Ctx->qcompress = 0.6;
		


		if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
			p_Video_Enc_Ctx->max_b_frames = 2;
		if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
			p_Video_Enc_Ctx->mb_decision = 2;

		if (p_OutputFmt->flags & AVFMT_GLOBALHEADER)
			p_Video_Enc_Ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

		// 3.5 打开编码器
		avcodec_open2(p_Video_Enc_Ctx, p_Video_Enc, NULL);
		avcodec_parameters_from_context(p_Video_st->codecpar, p_Video_Enc_Ctx);

		// 3.6 分配视频帧内存
		p_Video_Frame = av_frame_alloc();
		p_Video_Frame->format = p_Video_Enc_Ctx->pix_fmt;
		p_Video_Frame->width = p_Video_Enc_Ctx->width;
		p_Video_Frame->height = p_Video_Enc_Ctx->height;
		av_frame_get_buffer(p_Video_Frame, 0);

		have_video = true;
		encode_video = true;
	

	// 预留
	// 4. 添加 音频输出流,查找并打开音频解码器
	if (p_OutputFmt->audio_codec != AV_CODEC_ID_NONE) 
		have_audio = true;
		encode_video = true;
	

	// 5. 打印输出信息
	av_dump_format(p_FormatCtx, 0, Out_File_Name, 1);

	// 6. 打开输出文件
	if (!(p_OutputFmt->flags & AVFMT_NOFILE))
		avio_open(&p_FormatCtx->pb, Out_File_Name, AVIO_FLAG_WRITE);

	// 7. 写头信息
	avformat_write_header(p_FormatCtx, NULL);

	char pic_file_name[50] = "";
	int pic_index = 0;
	const int pic_size = YUV_Height * YUV_Width * 3 / 2;
	FILE* pic_file = NULL;
	uint8_t* pic_buff = (uint8_t*)av_malloc(pic_size);

	int file_end = 0;

	int ret;
	char errbuf[AV_ERROR_MAX_STRING_SIZE] =  0 ;

	int y, x;

	// 8. 开始循环写入数据
	while (file_end == 0) 
		// 打开文件 获取数据
		
			memset(pic_file_name, '\\0', 50);
			sprintf_s(pic_file_name, 50, "%s/%s.%d.%s", Folder, YUV_File_Name, pic_index, YUV);
			cout << "打开文件:" << pic_file_name << endl;

			// 注意,此处一定要,使用 "rb+" 否则一次读取的数据大小远小于预期的大小
			pic_file = fopen(pic_file_name, "rb+");			// fopen(&pic_file, pic_file_name, "rb+")
			if (pic_file == NULL) 
				cout << "打开失败:" << pic_file_name << ", 开始刷新编码缓冲区\\n";
				file_end = 1;
			

			if (file_end == 0) 
				//ret = fread_s(pic_buff, pic_size, 1, pic_size, pic_file);
				ret = fread(pic_buff, 1, pic_size, pic_file);
				cout << "读取文件大小为:" << ret << "  应读大小:" << pic_size << endl;
				if (ret <= 0) 
					cout << "读取内容失败,ret=" << ret << endl;
					break;
				
				fclose(pic_file);
				pic_file = NULL;

				/* make sure the frame data is writable */
				ret = av_frame_make_writable(p_Video_Frame);
				if (ret < 0)
				
					cout << "p_Video_Frame data 不可写,强制退出\\n";
					exit(1);
				

				for (x = 0, y = 0; x < YUV_Height; x++)
				
					memcpy(p_Video_Frame->data[0] + x * p_Video_Frame->linesize[0], pic_buff + y, YUV_Width);
					y += YUV_Width;
				
				for (x = 0, y = 0; x < YUV_Height / 2; x++)
				
					memcpy(p_Video_Frame->data[1] + x * p_Video_Frame->linesize[1], pic_buff + y + YUV_Width * YUV_Height, YUV_Width / 2);
					y += YUV_Width / 2;
				
				for (x = 0, y = 0; x < YUV_Height / 2; x++)
				
					memcpy(p_Video_Frame->data[2] + x * p_Video_Frame->linesize[2], pic_buff + y + YUV_Width * YUV_Height * 5 / 4, YUV_Width / 2);
					y += YUV_Width / 2;
				

				//赋值
					///* prepare a dummy image */
					///* Y */
					//for (y = 0; y < p_Video_Enc_Ctx->height; y++) 
					//	for (x = 0; x < p_Video_Frame->linesize[0]; x++) 
					//		if (x < YUV_Width)
					//			p_Video_Frame->data[0][y * p_Video_Frame->linesize[0] + x] = pic_buff[y * YUV_Height + x];								//x + y + pic_index * 3
					//		else
					//			p_Video_Frame->data[0][y * p_Video_Frame->linesize[0] + x] = 128;
					//	
					//
					//
					///* Cb and Cr */
					//for (y = 0; y < p_Video_Enc_Ctx->height / 2; y++) 
					//	for (x = 0; x < p_Video_Frame->linesize[1]; x++) 
					//		if (x < YUV_Width / 2) 
					//			p_Video_Frame->data[1][y * p_Video_Frame->linesize[1] + x] = pic_buff[y * YUV_Height/2 + x + YUV_Height * YUV_Width];	//128 + y + pic_index * 2;
					//			p_Video_Frame->data[2][y * p_Video_Frame->linesize[2] + x] = pic_buff[y * YUV_Height/2 + x + YUV_Height * YUV_Width * 5 / 4];	// 64 + x + pic_index * 5;
					//		
					//		else 
					//			p_Video_Frame->data[1][y * p_Video_Frame->linesize[1] + x] = 128;
					//			p_Video_Frame->data[2][y * p_Video_Frame->linesize[2] + x] = 128;
					//		

					//	
					//
					//
					//cout << " linesize: " << p_Video_Frame->linesize[0] << "  " << p_Video_Frame->linesize[1] << "  " << p_Video_Frame->linesize[2] << "  " << p_Video_Frame->linesize[3] << endl;
			
		

		// 开始编码
		
			// PTS: 设置播放时间
			p_Video_Frame->pts = pic_index * (p_Video_st->time_base.den) / (p_Video_st->time_base.num * 25);
			// Encode 开始编码
			if (file_end == 0)
				ret = avcodec_send_frame(p_Video_Enc_Ctx, p_Video_Frame);
			else
				ret = avcodec_send_frame(p_Video_Enc_Ctx, NULL);
			if (ret < 0) 
				cout << "编码失败:" << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
				exit(1);
			
			// 接收编码后的数据
			while (ret >= 0) 
				AVPacket pkt =  0 ;
				ret = avcodec_receive_packet(p_Video_Enc_Ctx, &pkt);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
					break;
				else if (ret < 0) 
					cout << "编码失败:" << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
					break;
				

				av_packet_rescale_ts(&pkt, p_Video_Enc_Ctx->time_base, p_Video_st->time_base);
				pkt.stream_index = p_Video_st->index;

				AVRational* time_base = &p_FormatCtx->streams[pkt.stream_index]->time_base;
				cout << "pts:" << av_ts_make_string1(errbuf, pkt.pts) << " pts_time:" << av_ts_make_time_string1(errbuf, pkt.pts, time_base);
				cout << " dts:" << av_ts_make_string1(errbuf, pkt.dts) << " duration:" << av_ts_make_string1(errbuf, pkt.duration);
				cout << " duration_time:" << av_ts_make_time_string1(errbuf, pkt.duration, time_base) << " stream_index:" << pkt.stream_index << endl;

				// 将编码后的数据写入文件中
				ret = av_interleaved_write_frame(p_FormatCtx, &pkt);
				av_packet_unref(&pkt);

				if (ret < 0) 
					cout << "Error while writing output packet: " << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
					break;
				
			
		

		pic_index++;
	

	// 9. 写入尾信息
	av_write_trailer(p_FormatCtx);

	// 10.关闭文件
	if (!(p_FormatCtx->flags & AVFMT_NOFILE))
		avio_closep(&p_FormatCtx->pb);



	// 11. 释放资源
	if (have_video) 
		if (pic_buff)
			av_free(pic_buff);
		if (p_Video_Frame)
			av_frame_free(&p_Video_Frame);
		if (p_Video_Enc_Ctx)
			avcodec_free_context(&p_Video_Enc_Ctx);	// 释放视频解码器
	

	if (p_FormatCtx)
		avformat_free_context(p_FormatCtx);

	system("ffplay video.h264");

	return 0;



跑完后,项目根目录多了个video.h264文件

用VLC播放器能打开浏览

将YUV图片序列编码成mp4文件

参考文章:ffmpeg:将YUV原始数据编码封装为mp4格式

还是老样子,把博主代码搞下来,vs配置一下,跑

#pragma warning(disable : 4996)

// 2-muxing编码视频.cpp

#include <iostream>
#include <string>       // 字符串操作
#include <direct.h>     // 文件夹
#include <stdbool.h>

extern "C"             // ffmpeg 相关头文件
#include "libavcodec/avcodec.h"
#include <libavutil/opt.h>
#include "libavutil/imgutils.h"


using namespace std;

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "avdevice.lib")

/// 输入输出文件信息
//const ch

以上是关于ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频

ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频

FFmpeg库视频解码初探(软硬件解码)

使用FFmpeg进行音视频编解码

FFmpeg学习1:视频解码

ffmpeg 如何指定输入特殊视频格式