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();
}
相关推荐
HL_LOVE_C1 分钟前
全面理解-c++11中的智能指针
开发语言·c++
亲爱的老吉先森7 分钟前
常见数据结构的C语言定义---《数据结构C语言版》
c语言·开发语言·数据结构
geovindu25 分钟前
java: framework from BLL、DAL、IDAL、MODEL、Factory using postgresql 17.0
java·开发语言·postgresql
007php0071 小时前
Docker、Ollama、Dify 及 DeepSeek 安装配置与搭建企业级本地私有化知识库实践
运维·服务器·开发语言·后端·docker·容器·云计算
众乐乐_20081 小时前
JVM栈帧中|局部变量表、操作数栈、动态链接各自的任务是什么?
java·开发语言·jvm
魏翾蒨1 小时前
VBA语言的数据可视化
开发语言·后端·golang
致奋斗的我们2 小时前
项目:利用rsync备份全网服务器数据
linux·运维·服务器·开发语言·github·rsync·openeuler
Bluesonli3 小时前
第 9 天:UE5 物理系统 & 碰撞检测全解析!
开发语言·学习·游戏·ue5·虚幻·unreal engine
魏翾蒨3 小时前
PHP语言的数据库交互
开发语言·后端·golang
关关钧3 小时前
【R语言】卡方检验
开发语言·r语言