[ffmpeg系列 04] YUV编码H264,以及推流

一  代码

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(&param, "intra-refresh", "1",0);
	av_dict_set(&param, "preset", "fast", 0);
	av_dict_set(&param, "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, &param) < 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。

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用FFmpeg C++库将RGB图像转换为H.264编码,并将其推流,可以按照以下步骤进行: 1. 初始化FFmpeg库和相关的编码器、解码器、格式器等组件。 2. 创建输入的RGB图像数据,可以使用OpenCV等库读取图像文件,或者使用自己的算法生成RGB图像数据。 3. 创建输出的H.264编码器,并初始化编码器参数。可以使用FFmpeg提供的编码器,如libx264,或者其他第三方编码器。 4. 将RGB图像数据转换为YUV420P格式,这是H.264编码器所需的格式。可以使用FFmpeg提供的sws_scale函数进行转换。 5. 将YUV420P格式的图像数据输入到编码器中进行编码,生成H.264码流。 6. 创建输出的网络流或文件,将编码后的H.264码流写入到网络流或文件中。 7. 循环执行步骤2到6,直到所有图像都被编码推流完毕。 下面是一个简单的代码示例,仅供参考: ```c++ #include <iostream> #include <string> #include <cstdlib> #include <cstdio> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> } using namespace std; int main(int argc, char* argv[]) { // 初始化FFmpeg库 av_register_all(); avcodec_register_all(); // 输入RGB图像的宽度和高度 int width = 640; int height = 480; // 创建输入的RGB图像数据,这里使用随机数据代替 uint8_t* rgb_data = new uint8_t[width * height * 3]; for (int i = 0; i < width * height * 3; i++) rgb_data[i] = rand() % 256; // 创建输出的H.264编码器 AVCodec* codec = avcodec_find_encoder_by_name("libx264"); if (!codec) { cerr << "Codec libx264 not found" << endl; exit(1); } AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { cerr << "Could not allocate video codec context" << endl; exit(1); } codec_ctx->bit_rate = 400000; codec_ctx->width = width; codec_ctx->height = height; codec_ctx->time_base = { 1, 25 }; codec_ctx->gop_size = 10; codec_ctx->max_b_frames = 1; codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; if (avcodec_open2(codec_ctx, codec, NULL) < 0) { cerr << "Could not open codec" << endl; exit(1); } // 创建输出的网络流或文件 const char* filename = "output.mp4"; AVOutputFormat* fmt = av_guess_format(NULL, filename, NULL); AVFormatContext* fmt_ctx = avformat_alloc_context(); if (!fmt_ctx) { cerr << "Could not allocate output format context" << endl; exit(1); } fmt_ctx->oformat = fmt; if (avio_open(&fmt_ctx->pb, filename, AVIO_FLAG_WRITE) < 0) { cerr << "Could not open output file '" << filename << "'" << endl; exit(1); } // 创建输出的视频流 AVStream* video_stream = avformat_new_stream(fmt_ctx, NULL); if (!video_stream) { cerr << "Could not allocate video stream" << endl; exit(1); } video_stream->id = fmt_ctx->nb_streams - 1; AVCodecParameters* codecpar = video_stream->codecpar; codecpar->codec_type = AVMEDIA_TYPE_VIDEO; codecpar->codec_id = codec->id; codecpar->bit_rate = codec_ctx->bit_rate; codecpar->width = codec_ctx->width; codecpar->height = codec_ctx->height; codecpar->format = codec_ctx->pix_fmt; avcodec_parameters_to_context(codec_ctx, codecpar); av_dump_format(fmt_ctx, 0, filename, 1); // 初始化编码器 AVFrame* frame = av_frame_alloc(); if (!frame) { cerr << "Could not allocate video frame" << endl; exit(1); } frame->format = codec_ctx->pix_fmt; frame->width = codec_ctx->width; frame->height = codec_ctx->height; if (av_frame_get_buffer(frame, 0) < 0) { cerr << "Could not allocate video frame data" << endl; exit(1); } AVPacket* pkt = av_packet_alloc(); if (!pkt) { cerr << "Could not allocate packet" << endl; exit(1); } // 将RGB图像数据转换为YUV420P格式 int y_size = codec_ctx->width * codec_ctx->height; uint8_t* yuv_data = new uint8_t[y_size * 3 / 2]; SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, 0, NULL, NULL, NULL); sws_scale(sws_ctx, &rgb_data, &width, 0, codec_ctx->height, &yuv_data, &codec_ctx->width); // 编码推流 int ret = 0; int frame_count = 0; while (frame_count < 100) { // 循环100次,测试用,可以根据实际情况修改 // 将YUV420P格式的图像数据输入到编码器中进行编码 frame->data[0] = yuv_data; frame->data[1] = yuv_data + y_size; frame->data[2] = yuv_data + y_size * 5 / 4; frame->pts = frame_count * codec_ctx->time_base.den / codec_ctx->time_base.num; ret = avcodec_send_frame(codec_ctx, frame); if (ret < 0) { cerr << "Error sending a frame for encoding" << endl; exit(1); } while (ret >= 0) { ret = avcodec_receive_packet(codec_ctx, pkt); if (ret < 0) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; cerr << "Error during encoding" << endl; exit(1); } av_packet_rescale_ts(pkt, codec_ctx->time_base, video_stream->time_base); pkt->stream_index = video_stream->index; ret = av_interleaved_write_frame(fmt_ctx, pkt); if (ret < 0) { cerr << "Error writing video frame" << endl; exit(1); } } frame_count++; } // 清理资源 av_write_trailer(fmt_ctx); av_packet_free(&pkt); av_frame_free(&frame); avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); avio_close(fmt_ctx->pb); avformat_free_context(fmt_ctx); sws_freeContext(sws_ctx); delete[] rgb_data; delete[] yuv_data; return 0; } ``` 运行该程序后,会将生成的H.264编码后的码流写入到文件"output.mp4"中。要将编码后的码流推流到网络上,可以使用FFmpeg提供的RTMP协议或者其他协议进行推流

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值