使用ffmpeg将opencv捕获的摄像头数据推流到本地rtsp器上
首先,为什么使用opencv?答:方便对视频进行处理,各种深度学习网络就有了用物之地。
具体流程参考的FFmpeg/opencv + C++ 实现直播拉流和直播推流(对视频帧进行处理)_c++ ffmpeg拉流_酒神无忧的博客-CSDN博客,但是细节不同。
简述一下流程:
使用opencv从摄像头中读取数据。
将cv::Mat转换为AVFrame。
打开编码器(这里用的是H264)。
设置视频的详细参数,以及编码参数。
编码并进行写入输出文件。
从cv::Mat到AVFrame的转化如下:
AVFrame *PushOpencv::CVMatToAVFrame(cv::Mat &inMat, int YUV_TYPE) { //得到Mat信息 AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P; int width = inMat.cols; int height = inMat.rows; //创建AVFrame填充参数 注:调用者释放该frame AVFrame *frame = av_frame_alloc(); frame->width = width; frame->height = height; frame->format = dstFormat; //初始化AVFrame内部空间 int ret = av_frame_get_buffer(frame, 64); if (ret < 0) { return nullptr; } ret = av_frame_make_writable(frame); if (ret < 0) { return nullptr; } //转换颜色空间为YUV420 cv::cvtColor(inMat, inMat, cv::COLOR_BGR2YUV_I420); //按YUV420格式,设置数据地址 int frame_size = width * height; unsigned char *data = inMat.data; memcpy(frame->data[0], data, frame_size); memcpy(frame->data[1], data + frame_size, frame_size/4); memcpy(frame->data[2], data + frame_size * 5/4, frame_size/4); return frame; }
这里默认用的是yuv420,你当然可以转换到yuv422或YUV444。注意422的数据分布是(frame_size, frame_size/2, frame_size/2),yuv444的数据分布是(frame_size, frame_size, frame_size)。
编码器参数设置如下:
int PushOpencv::open_codec(int width, int height, int den) { int ret = 0; avformat_network_init(); const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { throw std::logic_error("Can`t find h264 encoder!"); // 找不到264编码器 } // b 创建编码器上下文 outputVc = avcodec_alloc_context3(codec); if (!outputVc) { throw std::logic_error("avcodec_alloc_context3 failed!"); // 创建编码器失败 } // c 配置编码器参数 outputVc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 全局参数 outputVc->codec_id = codec->id; outputVc->codec_type = AVMEDIA_TYPE_VIDEO; outputVc->thread_count = 8; outputVc->bit_rate = 50 * 1024 * 8; // 压缩后每秒视频的bit位大小为50kb outputVc->width = width; outputVc->height = height; outputVc->time_base = {1, den}; outputVc->framerate = {den, 1}; outputVc->gop_size = 30; outputVc->max_b_frames = 1; outputVc->qmax = 51; outputVc->qmin = 10; outputVc->pix_fmt = AV_PIX_FMT_YUV420P; // d 打开编码器上下文 ret = avcodec_open2(outputVc, codec, 0); std::cout << "avcodec_open2 success!" << std::endl; ret = avformat_alloc_output_context2(&output, nullptr, "rtsp", url.c_str()); vs = avformat_new_stream(output, outputVc->codec); vs->codecpar->codec_tag = 0; // 从编码器复制参数 avcodec_parameters_from_context(vs->codecpar, outputVc); av_dump_format(output, 0, url.c_str(), 1); // ret = avio_open(&output->pb, url.c_str(), AVIO_FLAG_WRITE); return ret; }
解释一下控制画面清晰地参数:
outputVc->gop_size = 30;
h264的为了控制错误传递,以一个gop_size为一个编码组。在close gop模式下,同一组的只能参考同一组的帧进行帧间预测。gop总是以一个idr帧开头(注意不是I帧)且只有一个idr帧。由于I帧是全帧压缩,gop_size越小,被压缩数据越少,视频质量越高,数据量越大。
outputVc->max_b_frames = 1;顾名思义,最大连续B帧数量。B帧是双向预测帧,一般认为,相较于I帧和P帧,B帧的压缩量大。因此B帧数量越多,压缩率越大,清晰地越低,数据量越小。
outputVc->qmax = 51;
outputVc->qmin = 10;
outputVc->qmax = 51;
outputVc->qmin = 10;
量化参数的范围:h264编码过程中,经过DCT变换后的数据需要进一步进行量化。可以简单认为,量化系数越大,数据之间的细节越少,进而导致压缩率变大。
outputVc->pix_fmt = AV_PIX_FMT_YUV420P;
outputVc->pix_fmt = AV_PIX_FMT_YUV420P;
像素格式,不在赘言。
rtsp服务器的本地部署:https://github.com/bluenviron/mediamtx/releases/tag/v0.19.1
直接在控制台运行。
具体的代码放在:https://gitee.com/Lai_Wang/PushOpencvToRtsp.git
请君自取。