使用ffmpeg将opencv捕获的摄像头数据推流到本地rtsp器上

发布时间 2023-10-18 12:43:38作者: 人间别久不成悲

首先,为什么使用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;
量化参数的范围:h264编码过程中,经过DCT变换后的数据需要进一步进行量化。可以简单认为,量化系数越大,数据之间的细节越少,进而导致压缩率变大。
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 
请君自取。