ffmpeg视频编解码 demo初探(二)(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频
创始人
2024-01-26 04:34:24
0

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

文章目录

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

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

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

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

#pragma warning(disable : 4996)
#include 
#include        // 字符串操作
#include      // 文件夹extern "C" {            // ffmpeg 相关头文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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;
}
///// 
/// 输入输出文件信息
/// 
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.cint 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 idp_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;					// 音位 1sp_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);elseret = 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 
#include        // 字符串操作
#include      // 文件夹
#include extern "C" {            // ffmpeg 相关头文件
#include "libavcodec/avcodec.h"
#include 
#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 char* Out_File_Name = "video.h264";
const char* Out_File_Name = "video_out.mp4";// 源文件名: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.cint main(int* argc, char* argv[])
{// 1. 通过名字查找Codec 编码器AVCodec* p_Video_Enc = NULL;//p_Video_Enc = avcodec_find_encoder(AV_CODEC_ID_H264);	// 可改进为通过后缀判断p_Video_Enc = avcodec_find_encoder(AV_CODEC_ID_MPEG4);	// 可改进为通过后缀判断if (!p_Video_Enc) {cout << "h264 Codec not foud\n";return -1;}// 2. 获取 Codec 上下文AVCodecContext* p_Video_Enc_Ctx = NULL;p_Video_Enc_Ctx = avcodec_alloc_context3(p_Video_Enc);// 3. 配置编码信息p_Video_Enc_Ctx->bit_rate = 2000000;	// 配置码率p_Video_Enc_Ctx->width = YUV_Width;		// 宽p_Video_Enc_Ctx->height = YUV_Height;	// 高// 配置帧率,25p_Video_Enc_Ctx->time_base.num = 1;p_Video_Enc_Ctx->time_base.den = Stream_Frame_Rate;p_Video_Enc_Ctx->framerate.num = Stream_Frame_Rate;p_Video_Enc_Ctx->framerate.den = 1;p_Video_Enc_Ctx->gop_size = 12;				// 连续画面组大小//p_Videc_Enc_Ctx->max_b_frames = 30;		// 最大连续b 帧数p_Video_Enc_Ctx->pix_fmt = AV_PIX_FMT_YUV420P;p_Video_Enc_Ctx->codec_type = AVMEDIA_TYPE_VIDEO;p_Video_Enc_Ctx->codec_id = p_Video_Enc->id;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_Video_Enc->id == AV_CODEC_ID_H264)av_opt_set(p_Video_Enc_Ctx->priv_data, "preset", "slow", 0);// 4. 打开编码器if (avcodec_open2(p_Video_Enc_Ctx, p_Video_Enc, NULL) < 0) {cout << "打开编码器失败\n";avcodec_free_context(&p_Video_Enc_Ctx);return -1;}// 5. 分配 Pkt,frame包内存AVPacket* p_Video_Pkt = NULL;p_Video_Pkt = av_packet_alloc();AVFrame* p_Video_Frame = NULL;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);// 6. 打开输出文件FILE* p_Out_File = NULL;p_Out_File = fopen(Out_File_Name, "wb");if (!p_Out_File) {cout << "打开 《" << Out_File_Name << "》 文件失败\n";return -1;}// 7. 开始编码并写入文件char pic_file_name[50] = "";int pic_index = 0, x, y, ret;uint8_t* pic_buff = (uint8_t*)av_malloc(YUV_Height * YUV_Width * 3 / 2);FILE* pic_file = NULL;bool file_end = false;while (file_end == false) {// 打开文件 获取数据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 << " ";// 注意,此处一定要,使用 "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 = true;}if (file_end == false) {ret = fread(pic_buff, 1, YUV_Height * YUV_Width * 3 / 2, pic_file);cout << "读取文件大小为:" << ret;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;}// PTS: 设置播放时间p_Video_Frame->pts = pic_index * (p_Video_Enc_Ctx->time_base.den) / (p_Video_Enc_Ctx->time_base.num * 25);pic_index++;cout << "  Frame: pts = " << p_Video_Frame->pts << " linesize = " << p_Video_Frame->linesize[0] << " " << p_Video_Frame->linesize[1] << " " << p_Video_Frame->linesize[2] << endl;}// Encode 开始编码if (file_end == false)ret = avcodec_send_frame(p_Video_Enc_Ctx, p_Video_Frame);else {cout << "发送一个空 Frame 包,用于刷新编码缓冲区\n";ret = avcodec_send_frame(p_Video_Enc_Ctx, NULL);}// 接收编码后的数据while (ret >= 0) {ret = avcodec_receive_packet(p_Video_Enc_Ctx, p_Video_Pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {cout << "编码失败:" << endl;break;}cout << "编码完毕 Video Packet " << p_Video_Pkt->pts << " (size = " << p_Video_Pkt->size << ")\n";fwrite(p_Video_Pkt->data, 1, p_Video_Pkt->size, p_Out_File);av_packet_unref(p_Video_Pkt);}}/* add sequence end code to have a real MPEG file */if (p_Video_Enc->id == AV_CODEC_ID_MPEG1VIDEO || p_Video_Enc->id == AV_CODEC_ID_MPEG2VIDEO) {uint8_t endcode[] = { 0, 0, 1, 0xb7 };fwrite(endcode, 1, sizeof(endcode), p_Out_File);}fclose(p_Out_File);if (p_Video_Frame)av_frame_free(&p_Video_Frame);if (p_Video_Pkt)av_packet_free(&p_Video_Pkt);if (p_Video_Enc_Ctx) {avcodec_close(p_Video_Enc_Ctx);avcodec_free_context(&p_Video_Enc_Ctx);}//system("ffplay video.h264");return 0;
}

在这里插入图片描述
生成了一个.mp4文件

在这里插入图片描述
用qq影音和windows media player能正常打开

在这里插入图片描述

相关内容

热门资讯

适宜在农村创业致富的项目有哪些... 近年来,随着国家号召并鼓励发展新型农村,许多农村地区的面貌发生了很大的变化。去城里打工,也不再是农民...
大学生创新创业项目有哪些? 大... 创新创业就是让我们去打破一些常规的思维模式,然后去改变我们的日子,那么大学生创新创业有什么好的项目吗...
茶楼创业计划书范文 茶楼创业计...   1、雅士聚茶楼创业计划书一茶楼摘要:我国是一个茶叶大国。将茶楼安排在繁华的市区中心,让喧闹繁华的...
家庭致富小项目有哪些 常年赚钱... 根据目前的经济形势,对于一些创业者或者创业失败者来说,维持生存是首要任务,不要再去想什么迅猛发展,那...
好门路无成本创业加盟平台前十强... 好门路无成本创业加盟平台前十强一、投资标的数量非常多(也许在前几天刚启动一家小微企业平台,小伙伴们认...
年轻人!这几个招商加盟项目正是... 现今,汽车逐渐普及,而且在不远的将来,汽车会步入每一家,所以汽车美容也是很有市场的。创业者可以选择一...
1万以内水净化开店创业赚钱项目... 只有一万块钱的我们,要选择的创业项目最好是赚钱,而且竞争力不是很大的。以下是学习啦小编给大家带来万元...
一万元以下有什么好的创业项目吗... 一万元以下好的创业项目:早餐店、干洗店、蛋糕店、小吃摊、粥吧。1、早餐店开一家早餐店利润也是非常可观...
只有1万块钱想自己创业做点什么... 先说说我的经历吧。我是去年从建筑行业出来的,想自己做点事情啥的,只是作为一名普通人,没有资源背景、且...
创业大学生旅行项目 创业大学生... 资源描述:《大学生旅行社创业策划书》由会员分享,可在线阅读,更多相关《大学生旅行社创业策划书(28页...