基于opencv采集推流
1.opencv采集rtsp解码 //可以基于usb 摄像机(调用系统驱动)和rtsp(调用ffmpeg 接口 转yuv加解码) 摄像机
2.ffmpeg缩放转换像素格式
3.ffmpeg编码H264
4.ffmpeg推流rtmp
sws_getCachedContext (像素格式转换 会清理之前的数据)
1.struct SwsContext *context,
2.int srcW,int srcH,enum AVPixelFormat srcFormat,
3.int dstW,int dstH
4.enum AVPixelFormat dstFormat, //AV_PIX_FMT_YUV420P
5.int flags. //SWS_BICUBIC
6.srcFilter,dstFilter,param
SWS_SCALE
1.c 上下文
2.srcSlice (原数据) srcStride(每一行数据长度)
3.srcSliceY srcSliceH(高度)
4.dst dstStride (输出每一行大小)
5.Returns 输出slice的高度
avcodec_find_encoder
1.AVCodecID
-AV_CODEC_ID_H264
2.avcodec_find_encoder_by_name
3.AVCodec
avcodec_alloc_context3
1.AVCodecContext*
2.avcodec_alloc_context3
3.(const AVCodec *codec);
int avcodec_open2
(
AVCodecContext *avctx,
const AVCodec *codec,
AVDictionary **options);
avcodec_send_frame
1.AVCodecContext *avctx;
2.const AVFrame *frame;
3.avcodec_receive_packet
XRtmp.h
#pragma once
#include
class AVCodecContext;
class AVPacket;
class XRtmp
{
public:
//工厂生产方法
static XRtmp *Get(unsigned char index = 0);
//初始化封装器的上下文
virtual bool Init(const char *url) = 0;
//添加视频或者音频
virtual bool AddStream(const AVCodecContext *c) = 0;
//打开rtmp网络IO,发送封装头
virtual bool SendHead() = 0;
//rtmp 帧推流
virtual bool SendFrame(AVPacket *pkt) = 0;
virtual ~XRtmp();
protected:
XRtmp();
private:
};
XRtmp.cpp
#include "XRtmp.h"
#include
using namespace std;
extern "C"
{
#include
}
class CXRtmp : public XRtmp
{
public:
void Close()
{
if(ic)
{
avformat_close_input(&ic);
vs = NULL;
}
vc = NULL;
url = "";
}
bool Init(const char *url)
{
///5 封装器和视频流配置
//a.创建输出封装器上下文
int ret = avformat_alloc_output_context2(&ic,0,"flv",url);
this->url = url;
if(ret != 0)
{
char buf[1024] = {0};
av_strerror(ret,buf,sizeof(buf) - 1);
cout<<buf;
return false;
}
return true;
}
bool AddStream(const AVCodecContext *c)
{
if(!c) return false;
//b.添加视频流
AVStream *st = avformat_new_stream(ic,NULL);
if(!st)
{
cout<<"avformat_new_stream failed"<<endl;
return false;
}
st->codecpar->codec_tag = 0;
//从编码器复制参数
avcodec_parameters_from_context(st->codecpar, c);
av_dump_format(ic,0,url.c_str(),1);
if(c->codec_type == AVMEDIA_TYPE_VIDEO)
{
vc = c;
vs = st;
}
return true;
}
bool SendHead()
{
//打开rtmp的网络输出IO
int ret = avio_open(&ic->pb,url.c_str(),AVIO_FLAG_WRITE);
if(ret != 0)
{
char buf[1024] = {0};
av_strerror(ret,buf,sizeof(buf) - 1);
cout<<buf<<endl;
return false;
}
//写入封装头
ret = avformat_write_header(ic,NULL);
if(ret != 0)
{
char buf[1024] = {0};
av_strerror(ret,buf,sizeof(buf) - 1);
cout<<buf<<endl;
return false;
}
return true;
}
bool SendFrame(AVPacket *pkt)
{
//推流
pkt->pts = av_rescale_q(pkt->pts,vc->time_base,vs->time_base);
pkt->dts = av_rescale_q(pkt->dts,vc->time_base,vs->time_base);
int ret = av_interleaved_write_frame(ic,pkt);
if(ret == 0)
{
cout<<"#"<<flush;
}
return true;
}
private:
//rtmp flv 封装器
AVFormatContext *ic = NULL;
//视频编码器
const AVCodecContext *vc = NULL;
AVStream *vs = NULL;
std::string url = "";
};
//工厂生产方法
XRtmp* XRtmp::Get(unsigned char index)
{
static CXRtmp cxr[255];
static bool isFirst = true;
if(isFirst)
{
//注册所有的封装器
av_register_all();
//注册所有网络协议
avformat_network_init();
isFirst = false;
}
return &cxr[index];
}
XRtmp::XRtmp()
{
}
XRtmp::~XRtmp()
{
}
XMediaEncode.h
#pragma once
class AVFrame;
class AVPacket;
class AVCodecContext;
//音视频编码接口类
class XMediaEncode
{
public:
//输入参数
int inWidth = 1280;
int inHeight = 720;
int inPixSize = 3;
//输出参数
int outWidth = 1280;
int outHeight = 720;
int bitrate = 4000000;//压缩后每秒视频的bit位大小50KB
int fps = 25;
//工厂生产方法
static XMediaEncode *Get(unsigned char index = 0);
//初始化像素格式转换的上下文初始化
virtual bool InitScale() = 0;
virtual AVFrame* RGBToYUV(char *rgb) = 0;
//编码器的初始化
virtual bool InitVideoCodec() = 0;
//视频编码
virtual AVPacket *EncodeVideo(AVFrame *frame) = 0;
virtual ~XMediaEncode();
//编码器的上下文
AVCodecContext *vc = 0;
protected:
XMediaEncode();
};
XMediaEncode.cpp
#include "XMediaEncode.h"
#include
using namespace std;
extern "C"
{
#include
#include
#include
}
class CXMediaEncode:public XMediaEncode
{
public:
void close()
{
if(vsc)
{
sws_freeContext(vsc);
vsc = NULL;
}
if(yuv)
{
av_frame_free(&yuv);
}
if(vc)
{
avcodec_free_context(&vc);
}
vpts = 0;
av_packet_unref(&packet);
}
bool InitVideoCodec()
{
//4初始化编码上下文
//a 找到编码器
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if(!codec)
{
cout<<"Can't find h264 encoder!"<<endl;
return false;
}
//b 创建编码器上下文
vc = avcodec_alloc_context3(codec);
if(!vc)
{
cout<<"avcodec_alloc_context3 failed!"<<endl;
}
//c 配置编码器参数
vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局的参数
vc->codec_id = codec->id;
vc->thread_count = 8;
vc->bit_rate = 50*1024*8; //压缩后每秒视频的bit位大小 50kb
vc->width = outWidth;
vc->height = outHeight;
vc->time_base = {1,fps}; //时间基数
vc->framerate = {fps,1};
//画面组的大小,多少帧一个关键帧
vc->gop_size = 50;
vc->max_b_frames = 0;
vc->pix_fmt = AV_PIX_FMT_YUV420P;
//d 打开编码器
int ret = avcodec_open2(vc,0,0);
if(ret!=0)
{
char buf[1024] = {0};
av_strerror(ret,buf,sizeof(buf) - 1);
cout<<buf<<endl;
return false;
}
cout<<"avcodec_open2 success!"<<endl;
return true;
}
AVPacket *EncodeVideo(AVFrame* frame)
{
av_packet_unref(&packet);
//h264编码
frame->pts = vpts;
vpts++;
int ret = avcodec_send_frame(vc,frame);
if(ret!=0)
return NULL;
//每次都会调用av_frame_unref(frame)
ret = avcodec_receive_packet(vc,&packet);
if(ret != 0 || packet.size <= 0)
return NULL;
return &packet;
}
bool InitScale()
{
//2.初始化格式转换的上下文
vsc = sws_getCachedContext(vsc,
inWidth,inHeight,AV_PIX_FMT_BGR24,//原宽度高度
outWidth,outHeight,AV_PIX_FMT_YUV420P,//输出宽,高,像素格式
SWS_BICUBIC,//尺寸变化算法
0,0,0
);
if(!vsc)
{
cout<<"sws_getCachedContext failed!";
return false;
}
//3.输出的数据结构
yuv = av_frame_alloc();
yuv->format = AV_PIX_FMT_YUV420P;
yuv->width = inWidth;
yuv->height = inHeight;
yuv->pts = 0;
//分配yuv空间
int ret = av_frame_get_buffer(yuv,32);
if(ret != 0)
{
char buf[1024] = {0};
av_strerror(ret, buf,sizeof(buf) - 1);
throw logic_error(buf);
}
return true;
}
AVFrame* RGBToYUV(char *rgb)
{
//rgb to yuv
//输入的数据格式
uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
//bgrbgrbgr
//plane inData[0]bbbb gggg rrrr
indata[0] = (uint8_t*)rgb;
int insize[AV_NUM_DATA_POINTERS] = {0};
//一行(宽)数据的字节数
insize[0] = inWidth * inPixSize;
int h = sws_scale(vsc,indata,insize,0,inHeight, //输入数据
yuv->data,yuv->linesize);
if(h<=0)
{
return NULL;
}
return yuv;
}
private:
SwsContext *vsc = NULL; //像素格式上下文
AVFrame *yuv = NULL;
AVPacket packet = {0};
int vpts = 0;
};
XMediaEncode * XMediaEncode::Get(unsigned char index)
{
static bool isFirst = true;
if(isFirst)
{
//注册所有的编码器
avcodec_register_all();
isFirst = false;
}
static CXMediaEncode cxm[255];
return &cxm[index];
}
XMediaEncode::XMediaEncode()
{
}
XMediaEncode::~XMediaEncode()
{
}
main.cpp
#include
#include
#include
#include // 1. 更换包含头文件
#include
#include
#include
#include "XMediaEncode.h"
#include "XRtmp.h"
using namespace cv;
using namespace std;
int main(int argc,char *argv)
{
VideoCapture cam;
string inUrl = "rtsp://test:[email protected]";
//nginx-rtmp 直播服务器rtmp推流URL
char *outUrl = "rtmp://0.0.0.0/live";
//编码器和像素格式转换
XMediaEncode *me = XMediaEncode::Get(0);
//封装和推流对象
XRtmp *xr = XRtmp::Get(0);
Mat frame;
namedWindow("video");
int ret = 0;
try
{
//cam.open(inUrl);
//1.使用opencv打开rtsp相机
cam.open(0);
if(!cam.isOpened())
{
throw logic_error("cam open failed");
}
cout<<inUrl<<"cam open success"<<endl;
int inWidth = cam.get(CAP_PROP_FRAME_WIDTH);
int inHeight = cam.get(CAP_PROP_FRAME_HEIGHT);
int fps = cam.get(CAP_PROP_FPS);
//2.初始化像素格式转换的上下文初始化
//3.输出的数据结构
me->inWidth = inWidth;
me->inHeight = inHeight;
me->outWidth = inWidth;
me->outHeight = inHeight;
me->InitScale();
if(!me->InitVideoCodec())
{
throw logic_error("InitVideoCodec failed!");
}
//初始化封装器的上下文
xr->Init(outUrl);
//添加视频或者音频
xr->AddStream(me->vc);
xr->SendHead();
for(;;)
{
//读取rtsp视频帧,解码视频帧
if(!cam.grab())
{
continue;
}
//yuv转为rgb
//输入的数据结构
if(!cam.retrieve(frame))
{
continue;
}
//rgb to yuv
me->inPixSize = frame.elemSize();
AVFrame *yuv = me->RGBToYUV((char*)frame.data);
if(!yuv) continue;
AVPacket *pkt = me->EncodeVideo(yuv);
if(!pkt) continue;
xr->SendFrame(pkt);
}
}
catch(exception &ex)
{
if(cam.isOpened())
cam.release();
cerr<<ex.what()<<endl;
}
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(opencv_example_project)
find_package(OpenCV REQUIRED)
message(STATUS "OpenCV library status:")
message(STATUS " config: ${OpenCV_DIR}")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
find_library(AVCODEC_LIBRARY avcodec)
find_library(AVFORMAT_LIBRARY avformat)
find_library(AVUTIL_LIBRARY avutil)
find_library(AVDEVICE_LIBRARY avdevice)
add_executable(opencv_example main.cpp XMediaEncode.cpp XRtmp.cpp)
target_link_libraries(opencv_example PRIVATE
${OpenCV_LIBS}
pthread
swresample
m
swscale
avformat
avcodec
avutil
avfilter
avdevice
postproc
z
lzma
rt)