- 其实就实现了从
AVFrame封装推流的过程(预留了rtsp in的部分, 其实不使用rtsp in输入才是比较好的方法, 利于解耦) - 考察要点:
Mat转AVFrame的步骤,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;
}
}