Mat 输入 AVFrame 输出, 将 AVFrame 封装进 rtsp 协议输出

708 阅读2分钟
  • 其实就实现了从AVFrame封装推流的过程(预留了rtsp in的部分, 其实不使用rtsp in输入才是比较好的方法, 利于解耦)
  • 考察要点:
    • MatAVFrame的步骤,2步,两个关键函数
      sws_getCachedContext
      sws_scale
    • 时间基和帧率的设置涉及两点: 设置AVCodecContext(初始化它), 使用av_rescale_q转时间戳
    // 初始化确定时间基和帧率
    p_out_codec_ctx->time_base = { 1,25 };
    p_out_codec_ctx->framerate = { 25,1 };
    // 转时间戳  
    p_out_pkt->pts = av_rescale_q(p_out_pkt->pts, p_out_codec_ctx->time_base, p_out_stream->time_base);
    p_out_pkt->dts = av_rescale_q(p_out_pkt->dts, p_out_codec_ctx->time_base, p_out_stream->time_base);
    p_out_pkt->duration = av_rescale_q(p_out_pkt->duration, p_out_codec_ctx->time_base, p_out_stream->time_base);  
    
    • 封装成rtsp协议,只需要告诉AVFormatContext
    • 打印AVFormatContext的信息
    av_dump_format(p_out_fmt_ctx, 0, url_dst.c_str(), 1);  
    
    • 打印错误信息
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf) - 1);
    

Rtsp_Rtsp_Pusher.h

#pragma once
#include <string>
#include <iostream>
#include <opencv2/opencv.hpp>

struct AVFormatContext;
struct AVCodecContext;
struct AVStream;
struct AVCodec;  
struct AVFrame;
struct AVPacket;
struct SwsContext;

// 输入就应该是 AVFrame 利用解耦
class Rtsp_Rtsp_Pusher
{
public:
	~Rtsp_Rtsp_Pusher() {};
	static bool initFFmpeg();  
	bool init(const std::string& url_src_in, const std::string& url_dst_in);  
	bool pushOnePkt();

private: 

	bool initOutFmtCtx();
	bool initOutCodecCtx();  
	bool initFrameAndPkt();    
	bool encodeOnceFrm();
	void freeResource();
private:

	// in 
	std::string url_src;
	// AVFormatContext*	p_in_fmt_ctx{ nullptr };
	// AVCodecContext*		p_in_codec_ctx{ nullptr };
	// AVStream*			p_in_stream{ nullptr };
	// AVCodec*			p_in_codec{ nullptr };	
	AVFrame*			p_in_frame{nullptr};
	const uint64_t rows_src = 288;
	const uint64_t cols_src = 384;

	// out
	std::string url_dst;
	AVFormatContext*	p_out_fmt_ctx{ nullptr };
	AVCodecContext*		p_out_codec_ctx{nullptr};	
	AVStream*			p_out_stream{ nullptr };
	AVCodec*			p_out_codec{nullptr};
	AVPacket*           p_out_pkt{nullptr};
	SwsContext*			p_sws_ctx{nullptr};

	const uint64_t rows_dst = 480;
	const uint64_t cols_dst = 640;
	uint64_t vpts = 0;	
};

Rtsp_Rtsp_Pusher.cpp

#include "rtsp_rtsp_pusher.h"  
// #include <opencv2/opencv.hpp>
#include <mutex>

extern "C"
{
	#include <libswscale/swscale.h>
	#include <libavcodec/avcodec.h>
	#include <libavformat/avformat.h>
	#include <libavutil/opt.h>
}
std::once_flag once_flag;


bool Rtsp_Rtsp_Pusher::initFFmpeg()
{
	//注册所有的编解码器
	avcodec_register_all();
	// 以下两个已经被弃用, 其实可以不用
	// 注册所有的封装器
	// av_register_all();
	// 注册所有网络协议
	// avformat_network_init();
	return false;
}

bool Rtsp_Rtsp_Pusher::init(const std::string& url_src_in, const std::string& url_dst_in)
{
	url_src = url_src_in;
	url_dst = url_dst_in;  
	do
	{
		if(!initFrameAndPkt())	break;
		if(!initOutCodecCtx())	break;
		if(!initOutFmtCtx())	break;  
		return true;
	} while (0);
	freeResource();
	return false;
}

bool Rtsp_Rtsp_Pusher::pushOnePkt()
{
	auto fun_get_frm = [this]() {
		static int count = 0;
		if (count > 3)	count = 0;
		std::string file = std::to_string(count);  
		file.append(".jpg");  		
		cv::Mat img = cv::imread(file);
		++count;		
		// Mat 转 AVFrame				
		// 一行(宽)数据的字节数
		int line_size = img.cols * img.elemSize();		
		// 2 Mat 转 AVFrame 使用转换函数
		int h = sws_scale(p_sws_ctx, &img.data, &line_size, 0, img.rows,		//源数据
			p_in_frame->data, p_in_frame->linesize);	//重要函数,把Mat转成AVFrame
		if (h <= 0)
		{
			///*
			std::cout << "--------------------------------------------" << std::endl;
			std::cout << "|" << "     sws_scale return <=0      " << "|" << std::endl;
			std::cout << "--------------------------------------------" << std::endl; 
			//*/
		}				
	};

	// std::call_once(once_flag, fun_get_frm);  
	fun_get_frm();
	// h264 或 h265 编码
	encodeOnceFrm();
	return true;
}

bool Rtsp_Rtsp_Pusher::initOutFmtCtx()
{
	// 初始化转换算法上下文, 重要的与色彩空间相关的函数 
	//p_sws_ctx = sws_getCachedContext(p_sws_ctx,
	//	cols_src*3, rows_src, AV_PIX_FMT_GRAY8,	 //源宽、高、像素格式
	//	cols_dst, rows_dst, AV_PIX_FMT_YUV420P,	 //目标宽、高、像素格式
	//	SWS_BICUBIC,							 // 尺寸变化使用算法
	//	0, 0, 0
	//);  

	// Mat 转 AVFrame 两步  
	// 1 设置转换上下文
	p_sws_ctx = sws_getCachedContext(p_sws_ctx,
		cols_src, rows_src, AV_PIX_FMT_BGR24,	 //源宽、高、像素格式
		cols_dst, rows_dst, AV_PIX_FMT_YUV420P,	 //目标宽、高、像素格式
		SWS_BICUBIC,							 // 尺寸变化使用算法
		0, 0, 0
	);
	if (!p_sws_ctx)
	{
		std::cout << "sws_getCachedContext failed " << std::endl;
		return false;
	}  

	int ret = avformat_alloc_output_context2(&p_out_fmt_ctx, 0, "rtsp", url_dst.c_str());
	if (ret != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(ret, buf, sizeof(buf) - 1);
		//throw exception(buf);
		std::cout << "avformat_alloc_output_context2 failed "<< std::endl;
		return false;
	}

	// 添加视频流 
	p_out_stream = avformat_new_stream(p_out_fmt_ctx, NULL);
	if (!p_out_stream)
	{
		//throw exception("avformat_new_stream failed");
		std::cout << "avformat_new_stream" << std::endl;
		return false;
	}
	p_out_stream->codecpar->codec_tag = 0;
	// 将编码器复制到 AVStream 的编码器参数
	avcodec_parameters_from_context(p_out_stream->codecpar, p_out_codec_ctx);
	av_dump_format(p_out_fmt_ctx, 0, url_dst.c_str(), 1);

	// 写入封装头
	ret = avformat_write_header(p_out_fmt_ctx, nullptr);  
	if (ret != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(ret, buf, sizeof(buf) - 1);
		//throw exception(buf);
		std::cout << "avformat_write_header failed " << std::endl;
		return false;
	}
	av_opt_set(p_out_fmt_ctx->priv_data, "rtsp_transport", "tcp", 0);	
	return true;
}

bool Rtsp_Rtsp_Pusher::initOutCodecCtx()
{
	p_out_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!p_out_codec)
	{
		std::cout << "avcodec_find_encoder failed" << std::endl;
		return false;
	}
	p_out_codec_ctx = avcodec_alloc_context3(p_out_codec);
	if (!p_out_codec_ctx)
	{
		std::cout << "avcodec_alloc_context3 failed" << std::endl;
		return false;
	}
	// 编码器上下文设置
	p_out_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
	p_out_codec_ctx->codec_id = p_out_codec->id;
	p_out_codec_ctx->thread_count = 8;

	p_out_codec_ctx->bit_rate = 500 * 1024 * 8;				//压缩后每秒视频的bit位大小 500kB
	p_out_codec_ctx->width = cols_dst;
	p_out_codec_ctx->height = rows_dst;
	p_out_codec_ctx->time_base = { 1,25 };
	p_out_codec_ctx->framerate = { 25,1 };
	//画面组的大小,								//I帧间隔
	p_out_codec_ctx->gop_size = 10;
	// p_out_codec_ctx->gop_size = 2;
	p_out_codec_ctx->max_b_frames = 0;
	p_out_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	//avcodec_open2()函数会打印信息
	int ret = avcodec_open2(p_out_codec_ctx, 0, 0);
	if (ret != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(ret, buf, sizeof(buf) - 1);
		//throw exception(buf);
		std::cout << "avcodec_open2 failed " << std::endl;
		return false;
	}
	return true;
}

bool Rtsp_Rtsp_Pusher::initFrameAndPkt()
{
	p_in_frame = av_frame_alloc();
	p_in_frame->format = AV_PIX_FMT_YUV420P;
	p_in_frame->width = cols_dst;
	p_in_frame->height = rows_dst;
	p_in_frame->pts = 0;
	int ret = av_frame_get_buffer(p_in_frame, 32);
	if (!p_in_frame)
	{
		std::cout << "av_frame_alloc failed" << std::endl;
		return false;
	}  
	p_out_pkt = av_packet_alloc();	
	if (!p_out_pkt)
	{
		std::cout << "av_packet_alloc failed" << std::endl;
		return false;
	}
	return true;
}

bool Rtsp_Rtsp_Pusher::encodeOnceFrm()
{
	p_in_frame->pts = vpts;
	int ret = avcodec_send_frame(p_out_codec_ctx, p_in_frame);
	if (ret != 0)
	{
		std::cout << "--------------------------------------------" << std::endl;
		std::cout << "|" << "  avcodec_send_frame failed    " << "|" << std::endl;
		std::cout << "--------------------------------------------" << std::endl;
	}

	ret = avcodec_receive_packet(p_out_codec_ctx, p_out_pkt);  
	if (ret != 0)
	{
		///*
		std::cout << "--------------------------------------------" << std::endl;
		std::cout << "|" << "avcodec_receive_packet failed  " << "|" << std::endl;
		std::cout << "--------------------------------------------" << std::endl;
		return false;  
		//*/
	}

	// 转时间戳  
	p_out_pkt->pts = av_rescale_q(p_out_pkt->pts, p_out_codec_ctx->time_base, p_out_stream->time_base);
	p_out_pkt->dts = av_rescale_q(p_out_pkt->dts, p_out_codec_ctx->time_base, p_out_stream->time_base);
	p_out_pkt->duration = av_rescale_q(p_out_pkt->duration, p_out_codec_ctx->time_base, p_out_stream->time_base);  

	// 直播来讲,dts和pts一致的没有b帧
	ret = av_interleaved_write_frame(p_out_fmt_ctx, p_out_pkt);
	if (ret != 0)
	{
		std::cout << "av_interleaved_write_frame ret: " << ret << std::endl;  
		return false;
	}
	std::cout << "#" ;
	vpts++;
	return true;
}

void Rtsp_Rtsp_Pusher::freeResource()
{  

	if (p_out_codec_ctx != nullptr)
	{
		avcodec_free_context(&p_out_codec_ctx);
		p_out_codec_ctx = nullptr;
	}
	if (p_out_fmt_ctx != nullptr)
	{
		avformat_free_context(p_out_fmt_ctx);
		p_out_fmt_ctx = nullptr;
	}
	
	if (p_sws_ctx != nullptr)
	{
		sws_freeContext(p_sws_ctx);
		p_sws_ctx = nullptr;
	}

	if (p_out_pkt != nullptr)
	{
		av_packet_free(&p_out_pkt);
		p_out_pkt = nullptr;
	}

	if (p_in_frame != nullptr)
	{
		av_frame_free(&p_in_frame);
		p_in_frame = nullptr;
	}
}