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"
}

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表示让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;
}

通过线程拉流

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();
}

释放资源

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];
    }
}

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

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zxz520zmg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值