FFmpeg线程类封装+QT5实现直播拉流(文末含源代码)

博客围绕FFmpeg展开,介绍了其源码编译,基于MSVC编译器进行操作。还阐述了Qt5使用FFmpeg的方法,包括FFmpegThread封装,如初始化、设置参数、打开输入流、解码器操作等,以及FFmpegWidget封装和重写方法。最后说明在QT中调用FFmpeg的步骤及相关源代码。
  1. FFmpeg源码编译

参考:FFmpeg源码编译(基于MSVC编译器)

  1. Qt5使用FFmpeg

参考:Qt5 + FFmpeg

  1. FFmpegThread封装
    1. 初始化,在新的ffmpeg版本中,只需要初始化网络流格式。
//初始化(初始化一次即可)
void FFmpegThread::initlib() {
   
   
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    static bool isInit = false;
    if (!isInit) {
   
   
        //注册所有设备,主要用于本地摄像机播放支持
#ifdef ffmpegdevice
        avdevice_register_all();
#endif
        //初始化网络流格式,使用网络流时必须先执行
        avformat_network_init();

        isInit = true;
        qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;
    }
}

  1. 设置字典参数AVDictionary *options
//设置缓存大小,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);
// ,单位微秒,1000000表示1秒
av_dict_set(&options, "max_delay", "1000000", 0);
//自动开启线程数
av_dict_set(&options, "threads", "auto", 0);
//zerolatency:转码延迟,以牺牲视频质量减少时延
av_dict_set(&options, "tune", "zerolatency", 0);
  1. 打开指定的URL输入流
//打开视频流
avFormatContext = avformat_alloc_context();

int result = avformat_open_input(&avFormatContext, url.toStdString().data(), NULL, &options);
if (result < 0) {
   
   
    qDebug() << "open input error" << url;
    return false;
}
qDebug() << "open input:" << url << " " << result;
//释放设置参数
if (options != NULL) {
   
   
    av_dict_free(&options);
    qDebug() << "free option";
}
//获取流信息
result = avformat_find_stream_info(avFormatContext, NULL);
if (result < 0) {
   
   
    qDebug() << TIMEMS << "find stream info error";
    return false;
}
  1. 视频流解码器
videoStreamIndex = av_find_best_stream(avFormatContext,
                                               AVMEDIA_TYPE_VIDEO,
                                               -1,
                                               -1,
                                               const_cast<const AVCodec **>(&videoDecoder),
                                               0);
if (videoStreamIndex < 0) {
   
   
    qDebug() << "find video stream index error";
    return false;
}

//获取视频流
AVStream *videoStream = avFormatContext->streams[videoStreamIndex];

//获取视频流解码器,或者指定解码器
videoCodec = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodec, videoStream->codecpar);
videoDecoder = const_cast<AVCodec *>(avcodec_find_decoder(videoCodec->codec_id));
//videoDecoder = avcodec_find_decoder_by_name("h264_qsv");
if (videoDecoder == NULL) {
   
   
    qDebug() << "video decoder not found";
    return false;
}

//设置加速解码
videoCodec->lowres = videoDecoder->max_lowres;
videoCodec->flags2 |= AV_CODEC_FLAG2_FAST;

//打开视频解码器
result = avcodec_open2(videoCodec, videoDecoder, NULL);
if (result < 0) {
   
   
    qDebug() << "open video codec error";
    return false;
}

//获取分辨率大小
videoWidth = videoStream->codecpar->width;
videoHeight = videoStream->codecpar->height;

//如果没有获取到宽高则返回
if (videoWidth == 0 || videoHeight == 0) {
   
   
    qDebug() << "find width height error";
    return false;
}

QString videoInfo = QString(u8"视频流信息 -> 索引: %1  解码: %2  格式: %3  时长: %4 秒  分辨率: %5*%6")
        .arg(videoStreamIndex)
        .arg(videoDecoder->name, avFormatContext->iformat->name)
        .arg((avFormatContext->duration) / 1000000, videoWidth, videoHeight);

qDebug() << videoInfo;
  1. 音频流解码器
//循环查找音频流索引
audioStreamIndex = -1;
for (uint i = 0; i < avFormatContext->nb_streams; i++) {
   
   
    if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
   
   
        audioStreamIndex = i;
        break;
    }
}

//有些没有音频流,所以这里不用返回
if (audioStreamIndex == -1) {
   
   
    qDebug() << "find audio stream index error";
} else {
   
   
    //获取音频流
    AVStream *audioStream = avFormatContext->streams[audioStreamIndex];
    audioCodec = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(audioCodec, audioStream->codecpar);

    //获取音频流解码器,或者指定解码器
    audioDecoder = const_cast<AVCodec *>(avcodec_find_decoder(audioCodec->codec_id));
    //audioDecoder = avcodec_find_decoder_by_name("aac");
    if (audioDecoder == NULL) {
   
   
        qDebug() << "audio codec not found";
        return false;
    }

    //打开音频解码器
    result = avcodec_open2(audioCodec, audioDecoder, NULL);
    if (result < 0) {
   
   
        qDebug() << "open audio codec error";
        return false;
    }

    QString audioInfo = QString(u8"音频流信息 -> 索引: %1  解码: %2  比特率: %3  声道数: %4  采样: %5")
            .arg(audioStreamIndex)
            .arg(audioDecoder->name)
            .arg(avFormatContext->bit_rate, audioCodec->channels, audioCodec->sample_rate);
    qDebug() << audioInfo;
}

  1. 分配内存
//预分配好内存
avPacket = av_packet_alloc();
avFrame = av_frame_alloc();
avFrame2 = av_frame_alloc();
avFrame3 = av_frame_alloc();

//比较上一次文件的宽度高度,当改变时,需要重新分配内存
if (oldWidth != videoWidth || oldHeight != videoHeight) {
   
   
    int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    oldWidth = videoWidth;
    oldHeight = videoHeight;
}

//定义像素格式
AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;
//通过解码器获取解码格式
srcFormat = videoCodec->pix_fmt;

//默认最快速度的解码采用的SWS_FAST_BILINEAR参数,可能会丢失部分图片数据,可以自行更改成其他参数
int flags = SWS_FAST_BILINEAR;

//开辟缓存存储一帧数据
//以下两种方法都可以,avpicture_fill已经逐渐被废弃
//avpicture_fill((AVPicture *)avFrame3, buffer, dstFormat, videoWidth, videoHeight);
av_image_fill_arrays(avFrame3->data, avFrame3->linesize, buffer,
                     dstFormat, videoWidth, videoHeight, 1);

//图像转换
swsContext = sws_getContext(videoWidth, videoHeight, srcFormat,
                            videoWidth, videoHeight, dstFormat,
                            flags, NULL, NULL, NULL);
  1. 子线程循环对帧数据处理
void FFmpegThread::run() {
   
   
    // qint64 startTime = av_gettime();
    while (!stopped) {
   
   
        //根据标志位执行初始化操作
        if (isPlay) {
   
   
            bool ret = this->init();
            if (!ret) 
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

taciturn丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值