欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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(&param, "preset", "superfast", 0);  //编码形式修改
    av_dict_set(&param, "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, &param);
    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;