该代码基于 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()
打开解码器,并分配 AVPacket
和 AVFrame
以存储解码数据。同时,为了将解码后的 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 视频帧截取与显示。
#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();
}