• FFmpeg拉流教程


    FFmpeg拉流教程
    做了一个项目学习下FFmpeg拉流的过程。在此记录下。
    开发环境:
    QT:MSVC 2017 64bit和Qt for android (跨平台)
    FFmpeg:4.4.2
    一、.pro的配置(此处贴出我的示例,请根据自己的环境配置):

    win32{
    	INCLUDEPATH += $$PWD/ffmpeg/include
    	LIBS += $$PWD/ffmpeg/bin/avdevice.lib\
    	        $$PWD/ffmpeg/bin/avfilter.lib\
    	        $$PWD/ffmpeg/bin/avformat.lib\
    	        $$PWD/ffmpeg/bin/avutil.lib\
    	        $$PWD/ffmpeg/bin/postproc.lib\
    	        $$PWD/ffmpeg/bin/swresample.lib\
    	        $$PWD/ffmpeg/bin/swscale.lib\
    	        $$PWD/ffmpeg/bin/avcodec.lib
    }
    android{
    INCLUDEPATH += $$PWD/AndroidFFmpeg/include
    LIBS += $$PWD/AndroidFFmpeg/lib/libavformat.a\
            $$PWD/AndroidFFmpeg/lib/libavcodec.a\
            $$PWD/AndroidFFmpeg/lib/libavdevice.a\
            $$PWD/AndroidFFmpeg/lib/libavfilter.a\
            $$PWD/AndroidFFmpeg/lib/libavutil.a\
            $$PWD/AndroidFFmpeg/lib/libswresample.a\
            $$PWD/AndroidFFmpeg/lib/libswscale.a
    }
    note:据说FFmpeg库的加载顺序还有要求。
    二、引入FFmpeg的头文件
    extern "C" {
    #include "libavutil/opt.h"
    #include "libavutil/time.h"
    #include "libavutil/frame.h"
    #include "libavutil/pixdesc.h"
    #include "libavutil/avassert.h"
    #include "libavutil/imgutils.h"
    #include "libavutil/ffversion.h"
    #include "libavcodec/avcodec.h"
    #include "libswscale/swscale.h"
    #include "libavformat/avformat.h"
    #include "libavfilter/avfilter.h"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    note:因为FFmpeg是用C语言的规则,所以此处引用头文件要加上extern “C”
    三、FFmpeg的拉流

    1. FFmepg库的初始化(该语句必须且只能执行一次)
      avformat_network_init();

    2. 相关变量的定义
      int videoStreamIndex; //视频流索引
      AVPacket *avPacket; //包对象
      AVFrame *avFrame; //帧对象
      AVFormatContext *avFormatContext;//格式对象
      AVCodecContext *videoCodec; //视频解码器
      SwsContext *swsContext; //处理图片数据对象
      AVCodec *videoDecoder; //视频解码
      uint8_t *VideoData[AV_NUM_DATA_POINTERS];
      int lines[AV_NUM_DATA_POINTERS];
      QTime m_CurTime; //计算av_read_frame超时时间时使用
      QString m_strUrl;

    3. 打开URL
      . /*

    • 功能:判断av_read_frame是否超时返回
    • 入参:
    •  context 到达该时间后av_read_frame 超时返回
      
      • 1
    • 返回值:1表示让av_read_frame返回,否则表示不返回
      */
    static int interruptCallback(void *context){
        QTime *pStartTime = (QTime *)(context);
        if (pStartTime == NULL) {
            return 0;
        }
        if( QTime::currentTime() > *pStartTime)
        {
            //超时
            qDebug()<<"Timeout;QTime::currentTime() =  "<<QTime::currentTime() <<"Start = "<<*pStartTime;
            return 1;
        }
        return 0;
    
    }
     int FFmpegThread::init(QString strUrl)
    {
        m_strUrl = strUrl;
        qDebug()<<"URL = " << strUrl;
        AVDictionary *options = NULL;          //参数对象
        //在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等
        //设置缓存大小,1080p可将值调大
        av_dict_set(&options, "buffer_size", "8192000", 0);
        //以tcp方式打开,如果以udp方式打开将tcp替换为udp
        av_dict_set(&options, "rtsp_transport", "tcp", 0);
        //设置超时断开连接时间,单位微秒,3000000表示3秒
        av_dict_set(&options, "stimeout", "3000000", 0);
        av_dict_set(&options, "rw_timeout", "3000", 0);//单位:ms
        //打开视频流
        avFormatContext = avformat_alloc_context();
        //这里是设置超时的防止调用FFmpeg库时卡死。必须设置在这里不然功能可能失效,interruptCallback是回调函数,m_CurTime是传入回调函数的参数,详情请百度。
        avFormatContext->interrupt_callback.callback = interruptCallback;
        avFormatContext->interrupt_callback.opaque =(void *)(&m_CurTime);
        avFormatContext->avio_flags |= AVIO_FLAG_NONBLOCK;
        const AVCodec *pCodec = NULL;
        m_CurTime = QTime::currentTime().addSecs(3);
        int result = avformat_open_input(&avFormatContext, m_strUrl.toStdString().c_str(), NULL, &options);
        if (result < 0) {
            return RET_ERROR_OPEN_INPUT;
        }
        //释放设置参数
        if (options != NULL) {
            av_dict_free(&options);
        }
         //查找流信息
         m_CurTime = QTime::currentTime().addSecs(3);
         if (avformat_find_stream_info(avFormatContext, NULL) < 0)
         {
            // emit ErrofInfo(QString("获取流信息错误"));
            return RET_ERROR_FIND_STREAM;
         }
         //查找输入流
         videoStreamIndex = -1;
         for (uint32_t i = 0; i < avFormatContext->nb_streams; i++)
         {
           AVStream *pStream = avFormatContext->streams[i];
           if (pStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
           {
             videoCodec = avcodec_alloc_context3(NULL);
             avcodec_parameters_to_context(videoCodec, pStream->codecpar);
            pCodec = avcodec_find_decoder(videoCodec->codec_id);
             videoStreamIndex = i;
             break;
           }
         }
         if (!videoCodec)
         {
             return RET_ERROR_FIND_CODEC;
         }
         //打开视频流解码器失败
         int iRet = avcodec_open2(videoCodec, pCodec, NULL);
         if (iRet != 0)
         {
             emit ErrofInfo(QString("打开视频流解码器失败"));
             return RET_ERROR_OPEN_CODEC;
         }
         //打印关于输入或输出格式的详细信息
          av_dump_format(avFormatContext,videoStreamIndex, m_strUrl.toStdString().data(), 0);
          avPacket = av_packet_alloc();
          avFrame = av_frame_alloc();
          //获取缩放上下文失败
          swsContext = sws_getContext(videoCodec->width,videoCodec->height,videoCodec->pix_fmt\
                                      ,videoCodec->width, videoCodec->height,AV_PIX_FMT_RGB24\
                                      ,SWS_BICUBIC, NULL, NULL, NULL);
          if (swsContext == NULL)
         {
             return RET_ERROR_SWS_GET_CONTEXT;
         }
    
        char *rgb = new char[videoCodec->width*videoCodec->height*3];
        memset(rgb, 0 , videoCodec->width*videoCodec->height*3);
        VideoData[0] = (uint8_t *)rgb;
        lines[0] = videoCodec->width * 3;
        m_bInitFlag = true;
        return RET_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    通过线程拉流

    void FFmpegThread::run()
    {
        int index;
        int temp_ret = 0;
        char errors[1024] = "";
        while(!isInterruptionRequested()){
            m_CurTime = QTime::currentTime().addSecs(5);
            av_packet_unref(avPacket); //如果没有该语句av_read_frame会导致内存泄漏
           temp_ret = av_read_frame(avFormatContext, avPacket);
           if ( temp_ret < 0) {
               av_strerror(temp_ret,errors,sizeof(errors));
               qDebug()<<"av_read_frame return error" << (uint32_t)temp_ret << "info" << errors;
               emit ErrofInfo("视频连接断开,请检查网络连接!");
               break;
           }
           //判断当前包是视频还是音频
           index = avPacket->stream_index;
           if (index != videoStreamIndex) {
               //非视频流
                continue;
           }
           //解码视频流
           temp_ret = avcodec_send_packet(videoCodec,avPacket);
           if (0 != temp_ret) {
               //解码错误
               continue;
           }
            if(avcodec_receive_frame(videoCodec, avFrame) != temp_ret)
            {
                //从解码器中读取数据错误
               continue;
            }
           //将数据转成一张图片
            sws_scale(swsContext, (const uint8_t *const *)avFrame->data, avFrame->linesize, 0\
                      , videoCodec->height, VideoData, lines);
           QImage image(VideoData[0], videoCodec->width,  videoCodec->height, QImage::Format_RGB888);
           if (!image.isNull()) {
               emit receiveImage(image);
           }
           av_packet_unref(avPacket);
        }
        //线程结束后释放资源
        free();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    释放资源

    void FFmpegThread::free()
    {
        m_bInitFlag = false;
        if (swsContext != NULL) {
            sws_freeContext(swsContext);
            m_bInitFlag = NULL;
        }
    
        if (avPacket != NULL) {
            av_packet_unref(avPacket);
            avPacket = NULL;
        }
    
        if (avFrame != NULL) {
            av_frame_free(&avFrame);
            avFrame = NULL;
        }
        if (videoCodec != NULL) {
            avcodec_close(videoCodec);
            videoCodec = NULL;
        }
        if (avFormatContext != NULL) {
            avformat_close_input(&avFormatContext);
            avFormatContext = NULL;
        }
        if(VideoData[0]!= NULL)
        {
            delete []VideoData[0];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    音视频流程图(这两张图从公众号八小时码字员中摘取。链接: 八小时码字员
    音视频解码流程:
    在这里插入图片描述
    音视频播放流程:
    在这里插入图片描述
    android版FFmpeg库资源:链接: Android版FFmpeg
    本人新手,有什么问题欢迎指教

  • 相关阅读:
    【软件测试】如何用python连接Linux服务器
    【uniapp小程序】视图容器cover-view
    第七章 查找
    【《雨夜》 RocketMQ源码系列(一) NameServer 核心源码解析】
    酷快讯:Eminem和Snoop Dogg无聊猿新MV加持后ApeCoin上涨22%
    「秋招系列」MySQL 面试核心 25 问(附答案)
    【接口协议】基于 FPGA 的 HMDI 彩条显示实验
    el-select,el-option下拉选择框
    202203-3 CCF 计算资源调度器 (运用stl容器模拟 满分题解)
    C++异常处理
  • 原文地址:https://blog.csdn.net/m0_37759974/article/details/126128296