AVPacket结构体和接口
AVPacket 使用方法和 AVFrame 类似,里面也有一个缓冲区,采用引用计数的方式。
pts:时间戳
data:压缩后的数据
size:压缩后的数据大小
stream_index:用于表示数据流是音频的还是视频的
flags:压缩后帧的标识,比如:关键帧
FFmpeg编码接口
查找编码器
编码上下文
参数设定
打开编码器
发送原始帧
接收压缩帧
最少参数配置的视频编码
【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~
代码
#include <iostream>
#include <fstream>
using namespace std;
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
}
// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
int main(int argc, char* argv[])
{
// 1 找到编码器 AV_CODEC_ID_HEVC(H265)
AVCodec* codec = nullptr;
AVFrame* frame = nullptr;
AVPacket* packet = nullptr;
AVCodecID codec_id = AV_CODEC_ID_H264;
ofstream ofs;
string file_name = "400_300_25";
int r = 0;
if (argc > 1)
{
string suffix = argv[1];
cout << "suffix = " << suffix << endl;
if ((suffix == "h265") || (suffix == "hevc"))
{
codec_id = AV_CODEC_ID_HEVC;
}
}
if (codec_id == AV_CODEC_ID_H264)
{
file_name += ".h264";
}
else if (codec_id == AV_CODEC_ID_HEVC)
{
file_name += ".h265";
}
ofs.open(file_name, ios::out | ios::binary);
if (!ofs)
{
cout << "open " << file_name << " failed!" << endl;
return -1;
}
codec = avcodec_find_encoder(codec_id);
if (codec == nullptr)
{
cout << "avcodec_find_encoder failed!" << endl;
return -1;
}
// 2 编码上下文
AVCodecContext* context = avcodec_alloc_context3(codec);
if (context == nullptr)
{
cout << "avcodec_alloc_context3 failed!" << endl;
return -1;
}
// 3 设定上下文参数
context->width = 400; //视频宽高
context->height = 300;
context->pix_fmt = AV_PIX_FMT_YUV420P; // 源数据像素格式,与编码算法相关
context->time_base = { 1, 25 }; // 帧时间戳的时间单位 pts*time_base = 播放时间(秒) 分数 1/25
context->thread_count = 16; // 编码线程数,可以通过调用系统接口获取cpu核心数量
// 4 打开编码上下文
r = avcodec_open2(context, codec, nullptr);
if (r != 0)
{
char buf[1024] = { 0 };
av_strerror(r, buf, sizeof(buf) - 1);
cout << "avcodec_open2 failed! " << buf << endl;
avcodec_free_context(&context);
return -1;
}
// 创建好AVFrame空间 未压缩数据
frame = av_frame_alloc();
frame->width = context->width;
frame->height = context->height;
frame->format = context->pix_fmt;
r = av_frame_get_buffer(frame, 0);
if (r != 0)
{
char buf[1024] = { 0 };
av_strerror(r, buf, sizeof(buf) - 1);
cout << "av_frame_get_buffer failed! " << buf << endl;
avcodec_free_context(&context);
return -1;
}
packet = av_packet_alloc();
/* 十秒视频 250帧 */
for (int i = 0; i < 250; i++)
{
// 生成AVFrame 数据 每帧数据不同
// Y
for (int h = 0; h < frame->height; h++)
{
for (int w = 0; w < frame->width; w++)
{
frame->data[0][frame->linesize[0] * h + w] = h + w + i * 3;
}
}
// UV
for (int h = 0; h < frame->height / 2; h++)
{
for (int w = 0; w < frame->width / 2; w++)
{
frame->data[1][frame->linesize[1] * h + w] = 128 + h + i * 2;
frame->data[2][frame->linesize[2] * h + w] = 64 + w + i * 5;
}
}
frame->pts = i; // 显示的时间
r = avcodec_send_frame(context, frame);
if (r != 0)
{
break;
}
while (r >= 0) // 返回多帧
{
// 接收压缩帧,一般前几次调用返回空(缓冲,立刻返回,编码未完成)
// 编码是在独立的线程中
// 每次调用会重新分配packet中的空间
r = avcodec_receive_packet(context, packet);
if ((r == AVERROR(EAGAIN)) || (r == AVERROR_EOF))
{
break;
}
else if (r < 0)
{
char buf[1024] = { 0 };
av_strerror(r, buf, sizeof(buf) - 1);
cout << "avcodec_receive_packet failed! " << buf << endl;
avcodec_free_context(&context);
break;
}
cout << packet->size << " " << flush;
ofs.write((char*)packet->data, packet->size);
av_packet_unref(packet);
}
}
ofs.close();
av_packet_free(&packet);
av_frame_free(&frame);
avcodec_free_context(&context); // 释放编码器上下文
return 0;
}
发送了250帧的原始未压缩的帧,由于是非阻塞的方式去接收,导致发送了250帧的未压缩帧后,我们只能接收到一部分压缩好的帧。
使用 H264 编码后的结果
使用 H265 编码后的结果
使用 H264 和 H265 压缩后的视频文件大小
可以看出使用 H265 编码后比使用 H264 编码的压缩后的数据更小了, 但 H265 编码的速度比 H264 慢。