一 代码
ffmpeg版本5.1.2,dll是:ffmpeg-5.1.2-full_build-shared。x64的。
代码是windows端,用VS编译。
/*
author: ashleycoder
CSDN blog: https://blog.csdn.net/chenquangobeijing
*/
#pragma once
#include <string>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavutil/pixdesc.h"
#include "libavutil/display.h"
#include "libavutil/imgutils.h"
};
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
class CEncodeH264
{
public:
CEncodeH264();
~CEncodeH264();
public:
int EncodeH264();
int Start();
int EncodeH264_Init(AVPixelFormat input_pix_fmt, int yuvwidth, int yuvheight);
int OutPut_Init(const char* pOutFileName, const char* pFormat);
int OutPut_WriteHeader();
int EncodeH264_Fun();
int Release();
public:
AVFormatContext* m_pOutputFormatCtx = nullptr;
AVCodecContext* m_pVideoEncodeCodecCtx = nullptr;
const AVCodec* m_pCodec = nullptr;
AVStream* m_pVideoStream = nullptr;
AVFrame* m_pInputFrameYUV = nullptr;
uint8_t* m_pInputYUVBuff = nullptr;
enum AVCodecID m_VideoCodecID = AV_CODEC_ID_H264;
int m_nYSize = 0;
int m_nFrameHeight = 0;
int m_nFrameWidth = 0;
int m_nYUVFrameSize = 0;
AVPixelFormat m_nInput_pix_fmt;
FILE* m_pH264File = nullptr; //test use
public:
char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum)
};
/*
author: ashleycoder
CSDN blog: https://blog.csdn.net/chenquangobeijing
*/
#include "EncodeH264.h"
#include <thread>
#include <functional>
#include <codecvt>
#include <locale>
CEncodeH264::CEncodeH264()
{
fopen_s(&m_pH264File, "output.264", "wb");
}
CEncodeH264::~CEncodeH264()
{
Release();
}
int CEncodeH264::EncodeH264_Init(AVPixelFormat input_pix_fmt, int yuv_width, int yuv_height)
{
m_nInput_pix_fmt = input_pix_fmt;
m_nFrameWidth = yuv_width;
m_nFrameHeight = yuv_height;
m_pCodec = avcodec_find_encoder(m_VideoCodecID);
if (!m_pCodec)
{
printf("find h264 encoder fail! \n");
return -1;
}
//参数设置得不正确,打不开编码器,或者编出来的视频不正确
//码率的参数值:1920×1080:500 kbps~4000 kbps。
m_pVideoEncodeCodecCtx = avcodec_alloc_context3(m_pCodec);
m_pVideoEncodeCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
m_pVideoEncodeCodecCtx->pix_fmt = input_pix_fmt;
m_pVideoEncodeCodecCtx->width = yuv_width;
m_pVideoEncodeCodecCtx->height = yuv_height;
m_pVideoEncodeCodecCtx->time_base.num = 1;
m_pVideoEncodeCodecCtx->time_base.den = 25;
m_pVideoEncodeCodecCtx->bit_rate = 4000000;
m_pVideoEncodeCodecCtx->gop_size = 25; //一秒25帧
m_pVideoEncodeCodecCtx->qmin = 10;
m_pVideoEncodeCodecCtx->qmax = 51;
m_pVideoEncodeCodecCtx->max_b_frames = 0; //不编B帧
//如果是编码h264, 设置这个flag, 编出的h264不带sps,pps, 导致文件播不了
//流地址rtmp, 没有设置这个, av_interleaved_write_frame返回-10053
//必须加判断
if (m_pOutputFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
{
m_pVideoEncodeCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVDictionary* param = NULL;
//int n=av_dict_set(¶m, "intra-refresh", "1",0);
av_dict_set(¶m, "preset", "fast", 0);
av_dict_set(¶m, "tune", "zerolatency", 0); //zerolatency零延迟
//方法二
//av_opt_set(m_pVideoEncodeCodecCtx->priv_data, "preset", "fast",0);
//av_opt_set(m_pVideoEncodeCodecCtx->priv_data, "tune", "zerolatency",0);
//if (avcodec_open2(m_pVideoEncodeCodecCtx, pCodec, nulltr) < 0)
if (avcodec_open2(m_pVideoEncodeCodecCtx, m_pCodec, ¶m) < 0)
{
printf("avcodec_open2 fail! \n");
return -1;
}
m_pInputFrameYUV = av_frame_alloc();
m_pInputFrameYUV->format = m_pVideoEncodeCodecCtx->pix_fmt;
m_pInputFrameYUV->width = m_pVideoEncodeCodecCtx->width;
m_pInputFrameYUV->height = m_pVideoEncodeCodecCtx->height;
av_frame_get_buffer(m_pInputFrameYUV, 32);
m_nYUVFrameSize = av_image_get_buffer_size(m_nInput_pix_fmt, m_nFrameWidth,
m_nFrameHeight, 1);
m_pInputYUVBuff = (uint8_t*)av_malloc(m_nYUVFrameSize);
av_image_fill_arrays(m_pInputFrameYUV->data, m_pInputFrameYUV->linesize, m_pInputYUVBuff,
m_nInput_pix_fmt, m_nFrameWidth, m_nFrameHeight, 1);
return 0;
}
//自己用write存h264,OutPut_Init就不需要
//存成flv和mp4, 或者推流。需要,因为带头还有协议
int CEncodeH264::OutPut_Init(const char* pOutFileName, const char* pFormat)
{
int nRet = -1;
avformat_alloc_output_context2(&m_pOutputFormatCtx, nullptr, pFormat, pOutFileName);
if (!(m_pOutputFormatCtx->oformat->flags & AVFMT_NOFILE))
{
nRet = avio_open(&m_pOutputFormatCtx->pb, pOutFileName, AVIO_FLAG_WRITE);
if (nRet < 0)
{
char* err_str = av_err2str(nRet);
printf("avio_open fail\n");
return -1;
}
}
return 0;
}
int CEncodeH264::OutPut_WriteHeader()
{
int nRet = -1;
m_pVideoStream = avformat_new_stream(m_pOutputFormatCtx, m_pCodec);
if (m_pVideoStream == nullptr)
{
return -1;
}
m_pVideoStream->id = 0;
m_pVideoStream->time_base = m_pVideoEncodeCodecCtx->time_base;
//m_pVideoStream->codecpar->format = AV_PIX_FMT_YUV420P;
nRet = avcodec_parameters_from_context(m_pVideoStream->codecpar, m_pVideoEncodeCodecCtx);
//write_header必须在:avformat_new_stream, avcodec_open2后, 因为需要编码的格式
nRet = avformat_write_header(m_pOutputFormatCtx, nullptr);
if (nRet < 0)
{
char* err_str = av_err2str(nRet);
printf("avformat_write_header fail: %s\n", err_str);
return -1;
}
return 0;
}
int CEncodeH264::EncodeH264_Fun()
{
int success_num = 0;
int nFrameNum = 600;
FILE* pInFile = nullptr;
fopen_s(&pInFile,"1920x1080_yuv420p.yuv", "rb");
int YSize = m_nFrameHeight* m_nFrameWidth;
AVPacket* video_pkt = av_packet_alloc();
for (int k = 0; k < nFrameNum; k++)
{
printf("encode k=%d\n", k);
if (fread(m_pInputYUVBuff, 1, m_nYUVFrameSize, pInFile) <= 0) {
printf("Failed to read raw data! \n");
return -1;
}
else if (feof(pInFile))
{
break;
}
m_pInputFrameYUV->data[0] = m_pInputYUVBuff;
m_pInputFrameYUV->data[1] = m_pInputYUVBuff + YSize;
m_pInputFrameYUV->data[2] = m_pInputYUVBuff + YSize*5/4;
m_pInputFrameYUV->pts = k;//pts一定要赋值, 一般是递增
int video_send_frame_ret = avcodec_send_frame(m_pVideoEncodeCodecCtx, m_pInputFrameYUV);
//printf("encode video send_frame %d\n", video_send_frame_ret);
if (video_send_frame_ret >= 0) {
int video_receive_packet_ret = avcodec_receive_packet(m_pVideoEncodeCodecCtx, video_pkt);
//char* err_str = av_err2str2(video_receive_packet_ret);
if (video_receive_packet_ret == AVERROR(EAGAIN) || video_receive_packet_ret == AVERROR_EOF) {
//break;
}
else if (video_send_frame_ret < 0) {
printf("Error encoding audio frame\n");
//break;
}
if (video_pkt->size > 0)
{
video_pkt->stream_index = m_pVideoStream->index; //不能掉的
//如果是h264,m_pVideoStream->time_base=m_pVideoEncodeCodecCtx->time_base
//如果是flv, m_pVideoStream->time_base= flv
av_packet_rescale_ts(video_pkt, m_pVideoEncodeCodecCtx->time_base, m_pVideoStream->time_base);
//printf("video_pkt->stream_index:%d, pts=%d\r\n", video_pkt->stream_index, video_pkt->pts);
//fwrite(video_pkt->data, 1, video_pkt->size, m_pH264File);
int video_write_ret = av_interleaved_write_frame(m_pOutputFormatCtx, video_pkt);
char* err_str = av_err2str(video_write_ret);
printf("video write_ret:%d, size=%d, success_num=%d\r\n", video_write_ret, video_pkt->size, ++success_num);
av_packet_unref(video_pkt);
}
}
}//while
//不能掉,否则帧数不够
avcodec_send_frame(m_pVideoEncodeCodecCtx, nullptr);
while (true) {
int ret = avcodec_receive_packet(m_pVideoEncodeCodecCtx, video_pkt);
if (ret == 0) {
ret = av_interleaved_write_frame(m_pOutputFormatCtx, video_pkt);
printf("video write_ret:%d, success_num=%d\r\n", ret, ++success_num);
av_packet_unref(video_pkt);
}
else if (ret == AVERROR_EOF) {
break;
}
else {
break;
}
}
//这个掉了,如果是mp4文件,播不了
//moov atom not found
av_write_trailer(m_pOutputFormatCtx);
return 0;
}
int CEncodeH264::EncodeH264()
{
//const char* pFormat = "flv";
//const char* pOutFileName = "rtmp://127.0.0.1/live/now";
const char* pFormat = nullptr;
const char* pOutFileName = "encode.h264";
//const char* pOutFileName = "encode.flv";
//const char* pOutFileName = "encode.mp4";
if (OutPut_Init(pOutFileName, pFormat) != 0)
{
return -1;
}
if (EncodeH264_Init(AV_PIX_FMT_YUV420P, 1920, 1080) != 0)
{
return -1;
}
if (OutPut_WriteHeader() != 0)
{
return -1;
}
auto video_func = std::bind(&CEncodeH264::EncodeH264_Fun, this);
std::thread video_thread(video_func);
video_thread.join();
return 0;
}
int CEncodeH264::Start()
{
EncodeH264();
return 1;
}
int CEncodeH264::Release()
{
if (m_pInputFrameYUV)
{
av_frame_free(&m_pInputFrameYUV);
}
av_free(m_pInputYUVBuff);
avcodec_close(m_pVideoEncodeCodecCtx);
avformat_close_input(&m_pOutputFormatCtx);
return 0;
}
#include <iostream>
#include <Windows.h>
#include "2__EncodeH264/EncodeH264.h"
int main()
{
CEncodeH264* m_pEncodeVideo = new CEncodeH264();
m_pEncodeVideo->Start();
return 0;
}
二 demo需要注意的点
1 yuv文件如何获取?
电脑有电影的话,用系列1讲的ffmpeg命令行生成。
2 对比YUV文件和H264文件,计算一下大小之比,对H264压缩率有一个直观的认识。H264压缩率起码100: 1。
3 码率,调整码率,直观对比不同码率对视频质量的影响。
如果不知道码率设置什么值,可以参考阿里云等音视频SDK厂商建议的值。
4 m_pVideoEncodeCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
5 函数调用顺序:注意avformat_write_header调用时候。
avformat_new_stream的参数从m_pVideoEncodeCodecCtx获取的,参数必须赋值,否则avformat_write_header返回负值。
(图是雷神博客的)
6 YUV帧数==H264帧数
如果没有进行缩放,yuv的宽高和h264宽高是一样的。
达到缩放效果:1 yuv进行sws_scale后再编码。 2 使用filter。
三 H264基本知识
H264是一帧一帧存放的。
1 如何查找一帧数据?
答:起始码0x00 0x00 0x00 0x01或者0x00 0x00 0x01。
不同:0x00 0x00 0x01是1帧包括多个slice。标准文档中没找到出处。
2 怎么判断视频文件是否有sps、pps?
起始码后1字节,后7位是nalu type。sps的nalu type =7, pps的nalu type = 8,I帧的nalu type=5。
16进制打开H264文件,搜索起始码。
3 H264有两种封装:Annex-B,AVCC
Annex-B:带起始码。
AVCC:不带起始码。有4字节长度。长度是大端。
MP4文件,没有起始码,sps、pps等信息在container中。
从mp4中提取h264:h264_mp4toannexb_filter做转换。
4 mp4写尾的原因?
Moov atom not found。