Qt通过FFmpeg打开RTSP并截图一帧作为背景

该代码基于 Qt 和 FFmpeg 实现了从 RTSP 视频流中截取一帧图像,并将其渲染到 QWidget 作为背景图。整个实现流程分为 Qt 界面构建、FFmpeg 解码 RTSP 流、视频帧转换和 QImage 显示四个主要部分。

首先,RtspImageWidget 继承自 QWidget,在其构造函数中创建了一个 QLabel,用于显示截取的图像,并将其添加到 QVBoxLayout 进行布局管理。loadRtspFrame() 方法负责使用 FFmpeg 解析 RTSP 地址,获取视频帧,并将其转换为 QImage 供 Qt 界面显示。

loadRtspFrame() 方法中,代码首先调用 avformat_network_init() 初始化 FFmpeg 网络环境,然后使用 avformat_open_input() 打开 RTSP 地址,并通过 avformat_find_stream_info() 获取流信息。随后,遍历流列表,查找视频流的索引。如果成功找到视频流,则获取 AVCodecParameters 并通过 avcodec_find_decoder() 查找解码器,再用 avcodec_alloc_context3() 创建解码器上下文,并将参数填充到该上下文中。

接着,调用 avcodec_open2() 打开解码器,并分配 AVPacketAVFrame 以存储解码数据。同时,为了将解码后的 YUV 图像转换为 RGB,代码使用 sws_getContext() 创建了 SwsContext,并分配缓冲区用于存储 RGB 图像数据。

在主解码循环中,av_read_frame() 逐帧读取数据包,检查是否属于视频流,并调用 avcodec_send_packet() 进行解码。如果解码成功,调用 sws_scale() 进行格式转换,并将 RGB 数据封装成 QImage。最后,通过 setPixmap() 方法更新 QLabel 以显示截取的图像,并使用 goto CLEANUP 语句跳出循环,确保程序在成功截取到第一帧后立即释放资源。

资源释放部分采用 FFmpeg 提供的 av_packet_free()av_frame_free()avcodec_free_context()avformat_close_input() 等方法,确保程序不发生内存泄漏。main() 函数则创建 QApplication 并实例化 RtspImageWidget,加载 RTSP 地址后显示窗口。

整体来看,该代码逻辑清晰、模块分明,结合了 Qt 的 UI 渲染能力和 FFmpeg 的视频解码能力,实现了高效的 RTSP 视频帧截取与显示。

cpp 复制代码
#include <QWidget>
#include <QLabel>
#include <QPixmap>
#include <QImage>
#include <QVBoxLayout>
#include <QDebug>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

class RtspImageWidget : public QWidget {
    Q_OBJECT

public:
    explicit RtspImageWidget(const QString& rtspUrl, QWidget* parent = nullptr)
        : QWidget(parent), rtspUrl(rtspUrl) {
        QVBoxLayout* layout = new QVBoxLayout(this);
        imageLabel = new QLabel(this);
        imageLabel->setAlignment(Qt::AlignCenter);
        layout->addWidget(imageLabel);
        this->setLayout(layout);

        // 启动截取线程
        loadRtspFrame();
    }

private:
    QLabel* imageLabel;
    QString rtspUrl;

    void loadRtspFrame() {
        // 初始化 FFmpeg
        avformat_network_init();

        AVFormatContext* fmtCtx = nullptr;
        if (avformat_open_input(&fmtCtx, rtspUrl.toStdString().c_str(), nullptr, nullptr) < 0) {
            qDebug() << "Failed to open RTSP stream";
            return;
        }

        if (avformat_find_stream_info(fmtCtx, nullptr) < 0) {
            qDebug() << "Failed to find stream info";
            avformat_close_input(&fmtCtx);
            return;
        }

        int videoStreamIndex = -1;
        for (unsigned int i = 0; i < fmtCtx->nb_streams; ++i) {
            if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoStreamIndex = i;
                break;
            }
        }
        if (videoStreamIndex == -1) {
            qDebug() << "No video stream found";
            avformat_close_input(&fmtCtx);
            return;
        }

        AVCodecParameters* codecPar = fmtCtx->streams[videoStreamIndex]->codecpar;
        AVCodec* codec = avcodec_find_decoder(codecPar->codec_id);
        if (!codec) {
            qDebug() << "Failed to find codec";
            avformat_close_input(&fmtCtx);
            return;
        }

        AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
        if (!codecCtx) {
            qDebug() << "Failed to allocate codec context";
            avformat_close_input(&fmtCtx);
            return;
        }

        if (avcodec_parameters_to_context(codecCtx, codecPar) < 0) {
            qDebug() << "Failed to copy codec parameters to codec context";
            avcodec_free_context(&codecCtx);
            avformat_close_input(&fmtCtx);
            return;
        }

        if (avcodec_open2(codecCtx, codec, nullptr) < 0) {
            qDebug() << "Failed to open codec";
            avcodec_free_context(&codecCtx);
            avformat_close_input(&fmtCtx);
            return;
        }

        AVPacket* packet = av_packet_alloc();
        AVFrame* frame = av_frame_alloc();
        AVFrame* rgbFrame = av_frame_alloc();
        if (!packet || !frame || !rgbFrame) {
            qDebug() << "Failed to allocate frames or packet";
            av_packet_free(&packet);
            av_frame_free(&frame);
            av_frame_free(&rgbFrame);
            avcodec_free_context(&codecCtx);
            avformat_close_input(&fmtCtx);
            return;
        }

        SwsContext* swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
                                            codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB24,
                                            SWS_BILINEAR, nullptr, nullptr, nullptr);

        int rgbBufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecCtx->width, codecCtx->height, 1);
        uint8_t* rgbBuffer = (uint8_t*)av_malloc(rgbBufferSize);

        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbBuffer, AV_PIX_FMT_RGB24,
                             codecCtx->width, codecCtx->height, 1);

        while (av_read_frame(fmtCtx, packet) >= 0) {
            if (packet->stream_index == videoStreamIndex) {
                if (avcodec_send_packet(codecCtx, packet) == 0) {
                    while (avcodec_receive_frame(codecCtx, frame) == 0) {
                        // 转换为RGB格式
                        sws_scale(swsCtx, frame->data, frame->linesize, 0, codecCtx->height,
                                  rgbFrame->data, rgbFrame->linesize);

                        // 创建QImage并设置为背景
                        QImage image(rgbFrame->data[0], codecCtx->width, codecCtx->height,
                                     rgbFrame->linesize[0], QImage::Format_RGB888);

                        imageLabel->setPixmap(QPixmap::fromImage(image).scaled(this->size(), Qt::KeepAspectRatio));
                        goto CLEANUP;  // 截取一帧后退出循环
                    }
                }
            }
            av_packet_unref(packet);
        }

    CLEANUP:
        av_packet_free(&packet);
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        av_free(rgbBuffer);
        sws_freeContext(swsCtx);
        avcodec_free_context(&codecCtx);
        avformat_close_input(&fmtCtx);
    }
};

#include <QApplication>

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);

    RtspImageWidget widget("rtsp://your_rtsp_address");
    widget.resize(800, 600);
    widget.show();

    return app.exec();
}
相关推荐
你怎么知道我是队长12 小时前
C语言---枚举变量
c语言·开发语言
李慕婉学姐12 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
吃茄子的猫12 小时前
quecpython中&的具体含义和使用场景
开发语言·python
云栖梦泽12 小时前
易语言中小微企业Windows桌面端IoT监控与控制
开发语言
数据大魔方12 小时前
【期货量化实战】日内动量策略:顺势而为的短线交易法(Python源码)
开发语言·数据库·python·mysql·算法·github·程序员创富
Edward.W14 小时前
Python uv:新一代Python包管理工具,彻底改变开发体验
开发语言·python·uv
小熊officer14 小时前
Python字符串
开发语言·数据库·python
月疯14 小时前
各种信号的模拟(ECG信号、质谱图、EEG信号),方便U-net训练
开发语言·python
荒诞硬汉14 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国14 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos