Qt+FFmpeg网络视频流播放

发布于:2025-07-12 ⋅ 阅读:(16) ⋅ 点赞:(0)

  • init 函数用于初始化 FFmpeg,包括设置参数、打开输入、初始化视频和音频等。
  • initOption 函数用于设置 FFmpeg 的参数选项。
bool FFmpegThread::init()
{
    if (url.isEmpty()) {
        return false;
    }

    //判断该摄像机是否能联通
    if (checkConn && isRtsp) {
        if (!checkUrl(url, checkTime)) {
            return false;
        }
    }

    //启动计时
    QElapsedTimer time;
    time.start();

    //初始化参数
    this->initOption();
    //初始化输入
    if (!initInput()) {
        return false;
    }
    //初始化视频
    if (!initVideo()) {
        return false;
    }
    //初始化音频
    if (!initAudio()) {
        return false;
    }
    //初始化其他
    this->initOther();

    QString useTime = QString::number((float)time.elapsed() / 1000, 'f', 3);
    qDebug() << TIMEMS << fileFlag << QString("初始化完 -> 用时: %1 秒  地址: %2").arg(useTime).arg(url);
    return true;
}

bool FFmpegThread::initInput()
{
    //实例化格式处理上下文
    formatCtx = avformat_alloc_context();
    //设置超时回调,有些不存在的地址或者网络不好的情况下要卡很久
    formatCtx->interrupt_callback.callback = AVInterruptCallBackFun;
    formatCtx->interrupt_callback.opaque = this;

    //必须要有tryOpen标志位来控制超时回调,由他来控制是否继续阻塞
    tryOpen = false;
    tryRead = true;

    //先判断是否是本地设备(video=设备名字符串),打开的方式不一样
    QByteArray urlData = url.toUtf8();
    AVInputFormat *ifmt = nullptr;
    if (isUsbCamera) {
#if defined(Q_OS_WIN)
        ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)
        //ifmt = av_find_input_format("v4l2");
        ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)
        ifmt = av_find_input_format("avfoundation");
#endif
    }

    //设置 avformat_open_input 非阻塞默认阻塞 不推荐这样设置推荐采用回调
    //formatCtx->flags |= AVFMT_FLAG_NONBLOCK;
    int result = avformat_open_input(&formatCtx, urlData.data(), ifmt, &options);
    tryOpen = true;
    if (result < 0) {
        qDebug() << TIMEMS << fileFlag << "open input error" << getError(result) << url;
        emit ffmpegDecodeSignal(fileFlag + " open input error " + getError(result));
        return false;
    }

    //释放设置参数
    if (options != nullptr) {
        av_dict_free(&options);
    }

    //根据自己项目需要开启下面部分代码加快视频流打开速度
#if 0
    //接口内部读取的最大数据量,从源文件中读取的最大字节数
    //默认值5000000导致这里卡很久最耗时,可以调小来加快打开速度
    formatCtx->probesize = 50000;
    //从文件中读取的最大时长,单位为 AV_TIME_BASE units
    formatCtx->max_analyze_duration = 5 * AV_TIME_BASE;
    //内部读取的数据包不放入缓冲区
    //formatCtx->flags |= AVFMT_FLAG_NOBUFFER;
#endif

    //获取流信息
    result = avformat_find_stream_info(formatCtx, nullptr);
    if (result < 0) {
        qDebug() << TIMEMS << fileFlag << "find stream info error" << getError(result);
        emit ffmpegDecodeSignal(fileFlag + " find stream info error " + getError(result));
        return false;
    }

    return true;
}
  • run 函数是线程的运行函数,用于循环读取音视频数据包,并进行解码和播放。
void FFmpegThread::run()
{
    //记住开始解码的时间用于用视频同步
    startTime = av_gettime();
    while (!stopped) {
        //根据标志位执行初始化操作
        if (isPlay) {
            if (init()) {
                //这里也需要更新下最后的时间
                lastTime = QDateTime::currentDateTime();
                initSave();
                //初始化完成变量放在这里,绘制那边判断这个变量是否完成才需要开始绘制
                if (videoIndex >= 0) {
                    isInit = true;
                }
                emit receivePlayStart();
            } else {
                emit receivePlayError();
                break;
            }

            isPlay = false;
            continue;
        }

        //处理暂停 本地文件才会执行到这里 视频流的暂停在其他地方处理
        if (isPause) {
            //这里需要假设正常,暂停期间继续更新时间
            lastTime = QDateTime::currentDateTime();
            msleep(1);
            continue;
        }

        //QMutexLocker locker(&mutex);
        //解码队列中帧数过多暂停读取 下面这两个值可以自行调整 表示缓存的大小
        if (videoSync->getPacketCount() >= 100 || audioSync->getPacketCount() >= 100) {
            msleep(1);
            continue;
        }

        //必须要有tryRead标志位来控制超时回调,由他来控制是否继续阻塞
        tryRead = false;

        //下面还有个可以改进的地方就是如果是视频流暂停情况下只要保证 av_read_frame 一直读取就行无需解码处理
        frameFinish = av_read_frame(formatCtx, packet);
        //qDebug() << TIMEMS << fileFlag << "av_read_frame" << frameFinish;
        if (frameFinish >= 0) {
            tryRead = true;
            //更新最后的解码时间 错误计数清零
            errorCount = 0;
            lastTime = QDateTime::currentDateTime();
            //判断当前包是视频还是音频
            int index = packet->stream_index;
            if (index == videoIndex) {
                //qDebug() << TIMEMS << fileFlag << "videoPts" << qint64(getPtsTime(formatCtx, packet) / 1000) << packet->pts << packet->dts;
                decodeVideo(packet);
            } else if (index == audioIndex) {
                //qDebug() << TIMEMS << fileFlag << "audioPts" << qint64(getPtsTime(formatCtx, packet) / 1000) << packet->pts << packet->dts;
                decodeAudio(packet);
            }
        } else if (!isRtsp) {
            //如果不是视频流则说明是视频文件播放完毕
            if (frameFinish == AVERROR_EOF) {
                //当同步队列中的数量为0才需要跳出 表示解码处理完成
                if (videoSync->getPacketCount() == 0 && audioSync->getPacketCount() == 0) {
                    //循环播放则重新设置播放位置,在这里执行的代码可以做到无缝切换循环播放
                    if (playRepeat) {
                        this->position = 0;
                        videoSync->reset();
                        audioSync->reset();
                        videoSync->start();
                        audioSync->start();
                        QMetaObject::invokeMethod(this, "setPosition", Q_ARG(qint64, position));
                        qDebug() << TIMEMS << fileFlag << "repeat" << url;
                    } else {
                        break;
                    }
                }
            }
        } else {
            //下面这种情况在摄像机掉线后出现,如果想要快速识别这里直接break即可
            //一般3秒钟才会执行一次错误累加
            errorCount++;
            //qDebug() << TIMEMS << fileFlag << "errorCount" << errorCount << url;
            if (errorCount >= 3) {
                errorCount = 0;
                break;
            }
        }

        free(packet);
        msleep(2);
    }

    QMetaObject::invokeMethod(this, "stopSave");

    //线程结束后释放资源
    msleep(100);
    free();
    freeAudioDevice();
    emit receivePlayFinsh();
    //qDebug() << TIMEMS << fileFlag << "stop ffmpeg thread" << url;
}

以上是部分代码,这个类的主要目的是使用 FFmpeg 库来处理多媒体数据,包括视频和音频的解码、播放、保存等操作。


网站公告

今日签到

点亮在社区的每一天
去签到