参考文章:【FFmpeg编码实战】(1)将YUV420P图片集编码成H.264视频文件
文章目录
- 第二个项目:将YUV图片序列作为流读入,编码封装成x264 MP4视频
- 将YUV图片序列编码成.h264文件
- 将YUV图片序列编码成mp4文件
第二个项目:将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 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.c
int 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; // 高
// 配置帧率,25
p_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能正常打开