OpenCv + ffmpeg + rtmp 实现摄像头采集数据直播功能
程序员文章站
2022-07-02 09:26:31
...
采用OpenCv获取图像数据,通过ffmpeg推流给rtmp服务器
OpenCV获取的图像数据为BGR格式,需要转换成YUV格式,再将其编码为h264格式,通过ffmpeg推流
ps:ffmpeg版本是2.8
头文件
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/opt.h>
#include <libavutil/time.h>
#include <libavutil/timestamp.h>
#include <libswscale/swscale.h>
}
主要实现
//nginx-rtmp 直播服务器rtmp推流URL
char *outUrl = "rtmp://localhost:6666/live/Cam";
//注册所有的编解码器
avcodec_register_all();
//注册所有的封装器
av_register_all();
//注册所有网络协议
avformat_network_init();
Mat orgFrame;
//像素格式转换上下文
SwsContext *vsc = NULL;
//输出的数据结构
AVFrame *yuv = NULL;
//编码器上下文
AVCodecContext *avctx = NULL;
//rtmp flv 封装器
AVFormatContext *ofmt_ctx = NULL;
if(m_cap) //VideoCapture* m_cap;
{
if(m_cap->isOpened() == false)
{
bool ret = m_cap->open(0);
if(ret == false && QFile::exists(DEFAULT_CAMERA_DEV))
{
ret = m_cap->open(DEFAULT_CAMERA_DEV);
}
// if(ret)
// {
// m_cap->set(CV_CAP_PROP_FRAME_WIDTH, 640);
// m_cap->set(CV_CAP_PROP_FRAME_HEIGHT, 480);
// m_cap->set(CV_CAP_PROP_FPS, 30);
// }
}
}
else
return -1;
int framecnt = 0;
int inWidth = m_cap->get(CAP_PROP_FRAME_WIDTH);
int inHeight = m_cap->get(CAP_PROP_FRAME_HEIGHT);
int fps = m_cap->get(CAP_PROP_FPS);
qDebug() << "m_cap info" << inWidth << inHeight << fps;
fps = 25;
///2 初始化格式转换上下文
vsc = sws_getCachedContext(vsc,
inWidth, inHeight, AV_PIX_FMT_BGR24, //、高、像素格式
inWidth, inHeight, AV_PIX_FMT_YUV420P,//目标宽、高、像素格式
SWS_BICUBIC, // 尺寸变化使用算法
0, 0, 0
);
if (!vsc)
{
return -2;
}
///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)
{
ERROR_INFO "av_frame_get_buffer fail";
return -3;
}
///4 初始化编码上下文
//a 找到编码器
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
ERROR_INFO ("Can`t find h264 encoder!");
return -13;
}
//b 创建编码器上下文
avctx = avcodec_alloc_context3(codec);
if (!avctx)
{
ERROR_INFO ("avcodec_alloc_context3 failed!");
return -8;
}
qDebug() << "init avctx--------------------";
//c 配置编码器参数
avctx->codec_id = codec->id;
avctx->thread_count = 8;
// avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
AVDictionary *param = 0;
av_dict_set(¶m, "preset", "superfast", 0); //编码形式修改
av_dict_set(¶m, "tune", "zerolatency", 0); //实时编码
avctx->width = inWidth;
avctx->height = inHeight;
// avctx->bit_rate = 400000;
avctx->bit_rate = 50 * 1024 * 8;
avctx->time_base.num = 1;
avctx->time_base.den = fps;
avctx->framerate.num = fps;
avctx->framerate.den = 1;
avctx->qmin = 10; //调节清晰度和编码速度 //这个值调节编码后输出数据量越大输出数据量越小,越大编码速度越快,清晰度越差
avctx->qmax = 51;
//画面组的大小,多少帧一个关键帧
avctx->gop_size = 50; //编码一旦有gopsize很大的时候或者用了opencodec,有些播放器会等待I帧,无形中增加延迟。
avctx->max_b_frames = 0; //编码时如果有B帧会再解码时缓存很多帧数据才能解B帧,因此只留下I帧和P帧。
avctx->pix_fmt = AV_PIX_FMT_YUV420P;
//d 打开编码器上下文
ret = avcodec_open2(avctx, codec, ¶m);
if (ret != 0)
{
qDebug() << "avcodec_open2 fail";
return -5;
}
qDebug() << "avcodec_open2 success! --------------------";
///5 输出封装器和视频流配置
//a 创建输出封装器上下文
// ret = avformat_alloc_output_context2(&ofmt_ctx, 0, "flv", outUrl);
ret = avformat_alloc_output_context2(&ofmt_ctx, 0, "flv", NULL); //++Huey
if (ret != 0)
{
ERROR_INFO "avformat_alloc_output_context2 fail";
return -6;
}
qDebug() << "avformat_new_stream --------------------";
//b 添加视频流
AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream)
{
qDebug() << ("avformat_new_stream failed");
return -7;
}
// vs->codecpar->codec_tag = 0;
out_stream->codec->codec_tag = 0; //++Huey
//从编码器复制参数
qDebug() << "avcodec_copy_context --------------------";
// avcodec_parameters_from_context(vs->codecpar, vc);
//++Huey
avcodec_copy_context(out_stream->codec,avctx);
out_stream->time_base.num = 1;
out_stream->time_base.den = fps;
//End++
av_dump_format(ofmt_ctx, 0, outUrl, 1);
qDebug() << "avio_open --------------------";
///打开rtmp 的网络输出IO
ret = avio_open(&ofmt_ctx->pb, outUrl, AVIO_FLAG_WRITE);
if (ret != 0)
{
qDebug() << "avio_open fail";
return -9;
}
qDebug() << "avformat_write_header --------------------";
//写入封装头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret != 0)
{
qDebug() << "avformat_write_header fail";
return -10;
}
qDebug() << "run CamPush --------------------";
AVPacket pkt;
av_init_packet(&pkt);
int vpts = 0;
while(1)
{
ERROR_INFO "m_cap->grab --------------------";
///读取rtsp视频帧,解码视频帧
if (!m_cap->grab())
{
continue;
}
ERROR_INFO "m_cap->retrieve --------------------";
///yuv转换为rgb
if (!m_cap->retrieve(orgFrame))
{
continue;
}
imshow("video", orgFrame);
waitKey(20);
ERROR_INFO "rgb to yuv --------------------";
///rgb to yuv
//输入的数据结构
uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
//indata[0] bgrbgrbgr
//plane indata[0] bbbbb indata[1]ggggg indata[2]rrrrr
indata[0] = orgFrame.data;
int insize[AV_NUM_DATA_POINTERS] = { 0 };
//一行(宽)数据的字节数
insize[0] = orgFrame.cols * orgFrame.elemSize();
int h = sws_scale(vsc, indata, insize, 0, orgFrame.rows, //源数据
yuv->data, yuv->linesize);
if (h <= 0)
{
continue;
}
ERROR_INFO "AVFrame to AVPacket --------------------";
///h264编码
yuv->pts = vpts;
vpts++;
int got_packet = 0;
av_init_packet(&pkt);
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO)
ret = avcodec_encode_video2(avctx, &pkt,yuv, &got_packet);
qDebug() << "AVPacket Bef --"
<< pkt.size << pkt.pts << pkt.dts << pkt.duration
<< (pkt.data == NULL) << (pkt.buf == NULL);
if(got_packet == 0 || ret != 0)
{
qDebug() << "avcodec_encode_video2 fail -------------";
continue;
}
//推流
pkt.pts = av_rescale_q(pkt.pts, avctx->time_base, out_stream->time_base);
// pkt.dts = av_rescale_q(pkt.dts, avctx->time_base, out_stream->time_base);
pkt.dts = pkt.pts; //++Huey
pkt.duration = av_rescale_q(pkt.duration, avctx->time_base, out_stream->time_base);
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
//ret = av_write_frame(ofmt_ctx, &pkt); //++Huey
qDebug() << "end --------------------" << ret;
}
return 0;