#ifndef VEDIOFORM_H
#define VEDIOFORM_H
#include <QMap>
#include <QList>
#include <QWidget>
#include <QVector>
#include <QLabel>
extern "C" {
#include <vlc/vlc.h>
#include <vlc/libvlc_media_player.h>
}
QT_BEGIN_NAMESPACE
namespace Ui { class vedioForm; }
QT_END_NAMESPACE
struct LayoutItem {
int index;
int row, col;
int rowspan = 1, colspan = 1;
LayoutItem(int i, int r, int c, int rs = 1, int cs = 1)
: index(i), row(r), col(c), rowspan(rs), colspan(cs) {}
};
class VideoWidget : public QWidget {
Q_OBJECT
public:
explicit VideoWidget(int channelid,const QString &url, QWidget *parent = nullptr);
~VideoWidget();
void play();
void stop();
void startRecord();
void stopRecord();
void showEvent(QShowEvent *ev);
private:
QString m_url;
int m_channelid;
libvlc_instance_t *m_vlcInst;
libvlc_media_player_t *m_mp;
libvlc_media_t *m_media;
bool m_isRecording;
QString getfile()const;
bool isPlaying = false;
};
class vedioForm : public QWidget
{
Q_OBJECT
public:
explicit vedioForm(QWidget *parent = nullptr);
~vedioForm();
signals:
private slots:
void on_btnvideo1_clicked();
void on_btnvideo4_clicked();
void on_btnvideo6_clicked();
void on_btnvideo8_clicked();
void on_btnvideo9_clicked();
private:
Ui::vedioForm *ui;
QList<VideoWidget*>widgets;
int videoCount = 9;
QMap<QString, QList<LayoutItem>> layoutPresets; // 布局模板
void initLayoutPresets(); // 初始化所有预设
void applyLayout(const QString &presetKey); // 应用某个布局
void hide_video_all(); // 隐藏并清除当前布局// 当前最大支持 9 路显示
void loadConfig();
void saveConfig(const QString &mode);
};
#endif // VEDIOFORM_H
#include "vedioform.h"
#include "ui_vedioform.h"
#include <QStandardPaths>
#include <QDateTime>
#include <QDir>
#include <QMessageBox>
#include <QDebug>
#include <QLabel>
#include <QVBoxLayout>
// 模拟数据库数据
static const QStringList StreamUrls = {
"rtsp://admin:password@192.168.1.64:554/stream1",
"rtsp://admin:password@192.168.1.65:554/stream1",
"rtsp://admin:password@192.168.1.66:554/stream1",
"rtsp://admin:password@192.168.1.67:554/stream1",
"rtsp://admin:password@192.168.1.68:554/stream1",
"rtsp://admin:password@192.168.1.69:554/stream1",
"rtsp://admin:password@192.168.1.70:554/stream1",
"rtsp://admin:password@192.168.1.71:554/stream1",
"rtsp://admin:password@192.168.1.72:554/stream1"
};
// 全局 VLC 实例(共享,节省资源)
static libvlc_instance_t *GlobalVlcInstance = nullptr;
// 初始化全局 VLC 实例
libvlc_instance_t* getVlcInstance() {
if (!GlobalVlcInstance) {
GlobalVlcInstance = libvlc_new(0, nullptr);
if (!GlobalVlcInstance) {
qCritical() << "Failed to create libvlc instance!";
}
}
return GlobalVlcInstance;
}
// 释放全局实例
void cleanupVlcInstance() {
if (GlobalVlcInstance) {
libvlc_release(GlobalVlcInstance);
GlobalVlcInstance = nullptr;
}
}
VideoWidget::VideoWidget(int channelid,const QString &url, QWidget *parent)
: QWidget(parent),m_channelid(channelid), m_url(url),m_vlcInst(nullptr), m_mp(nullptr), m_media(nullptr)
{
setMinimumSize(100, 100);
setStyleSheet("background: black;");
setFocusPolicy(Qt::StrongFocus);
m_vlcInst = getVlcInstance();
if (!m_vlcInst) return;
m_mp = libvlc_media_player_new(m_vlcInst);
if (!m_mp) {
qCritical() << "Cannot create media player for:" << url;
return;
}
m_media = libvlc_media_new_location(m_vlcInst, m_url.toUtf8().constData());
if (!m_media) {
qCritical() << "Invalid media URL:" << m_url;
return;
}
libvlc_media_player_set_media(m_mp, m_media);
}
VideoWidget::~VideoWidget()
{
stop();
if (m_media) {
libvlc_media_release(m_media);
m_media = nullptr;
}
if (m_mp) {
libvlc_media_player_release(m_mp);
m_mp = nullptr;
}
}
void VideoWidget::play()
{
if (m_mp && m_media) {
// 将视频输出绑定到当前控件的窗口句柄(Windows)
libvlc_media_player_set_hwnd(m_mp, reinterpret_cast<void*>(winId()));
libvlc_media_player_play(m_mp);
qDebug() << "Playing:" << m_url;
isPlaying = true;
}
}
void VideoWidget::stop()
{
if (m_mp) {
libvlc_media_player_stop(m_mp);
libvlc_media_player_set_hwnd(m_mp, nullptr);
isPlaying = false;
}
}
void VideoWidget::startRecord()
{
if (m_isRecording) return;
stop();
if (m_media) {
libvlc_media_release(m_media);
m_media = nullptr;
}
// 构建 sout 参数
QString filename=getfile()+".mp4";
QString sout = QString("#duplicate{"
"dst=display,"
"dst=std{access=file,mux=mp4,dst='%1'}"
"}").arg(filename);
// 创建新的 media 并添加录制参数
m_media = libvlc_media_new_location(m_vlcInst, m_url.toUtf8().constData());
libvlc_media_add_option(m_media, QString("--sout=%1").arg(sout).toUtf8().constData());
libvlc_media_add_option(m_media, "--sout-all"); // 转发所有数据包
libvlc_media_player_set_media(m_mp, m_media);
play(); // 重新播放(带录制)
m_isRecording = true;
}
void VideoWidget::stopRecord()
{
if (!m_isRecording) return;
stop(); // 停止带录制的流
// 重建原始 media(无录制)
m_media = libvlc_media_new_location(m_vlcInst, m_url.toUtf8().constData());
libvlc_media_player_set_media(m_mp, m_media);
play(); // 恢复播放(不录制)
m_isRecording = false;
}
void VideoWidget::showEvent(QShowEvent *ev)
{
QWidget::showEvent(ev);
if (m_mp) {
libvlc_media_player_set_hwnd(m_mp, reinterpret_cast<void*>(winId()));
qDebug() << "VideoWidget::showEvent - set hwnd:" << winId();
}
}
QString VideoWidget::getfile() const
{
QString videoDir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)
+ "/SAFASYSTEM";
QDir dir(videoDir);
if (!dir.exists()) {
dir.mkpath(".");
}
QString dtStr = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss");
QString chStr = QString("ch%1").arg(m_channelid + 1, 2, 10, QChar('0')); // ch01, ch02...
return QStringLiteral("%1/%2_%3").arg(videoDir, dtStr, chStr);
}
vedioForm::vedioForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::vedioForm)
{
ui->setupUi(this);
this->setWindowTitle("视频监控播放");
for (int i = 0; i < StreamUrls.size(); ++i) {
VideoWidget *w = new VideoWidget(i,StreamUrls[i], this);
w->setObjectName(QString("video_%1").arg(i));
widgets.append(w);
}
initLayoutPresets();
loadConfig(); // 加载上次配置
}
vedioForm::~vedioForm()
{
hide_video_all(); // 停止所有播放器
qDeleteAll(widgets);
widgets.clear();
delete ui;
cleanupVlcInstance(); // 释放 VLC 全局实例
}
void vedioForm::initLayoutPresets()
{
layoutPresets["1"] = {{0,0,0,1,1}};
layoutPresets["4"] = {
{0,0,0}, {1,0,1},
{2,1,0}, {3,1,1}
};
layoutPresets["6"] = {
{0,0,0,2,2}, {1,0,2,1,1}, {2,1,2,1,1},
{3,2,2,1,1}, {4,2,1,1,1}, {5,2,0,1,1}
};
layoutPresets["8"] = {
{0,0,0,3,3}, {1,0,3,1,1}, {2,1,3,1,1},
{3,2,3,1,1}, {4,3,3,1,1}, {5,3,2,1,1},
{6,3,1,1,1}, {7,3,0,1,1}
};
layoutPresets["9"] = {
{0,0,0}, {1,0,1}, {2,0,2},
{3,1,0}, {4,1,1}, {5,1,2},
{6,2,0}, {7,2,1}, {8,2,2}
};
}
void vedioForm::applyLayout(const QString &presetKey)
{
hide_video_all();
auto items = layoutPresets.value(presetKey);
if (items.isEmpty()) {
QMessageBox::warning(this, "错误", "未找到布局: " + presetKey);
return;
}
for (const auto &item : items) {
if (item.index >= widgets.size()) continue;
VideoWidget *w = widgets[item.index];
ui->gridLayout->addWidget(w, item.row, item.col, item.rowspan, item.colspan);
w->setVisible(true);
w->play(); // 开始播放
}
}
void vedioForm::hide_video_all()
{
QLayoutItem *child;
while ((child = ui->gridLayout->takeAt(0)) != nullptr) {
if (child->widget()) {
auto *vw = static_cast<VideoWidget*>(child->widget());
vw->stop();
vw->setVisible(false);
}
delete child;
}
}
void vedioForm::loadConfig()
{
applyLayout("4");
ui->btnvideo4->setChecked(true);
}
void vedioForm::saveConfig(const QString &mode)
{
}
void vedioForm::on_btnvideo1_clicked()
{
saveConfig("1");
applyLayout("1");
ui->btnvideo1->setChecked(true);
}
void vedioForm::on_btnvideo4_clicked()
{
saveConfig("4");
applyLayout("4");
ui->btnvideo4->setChecked(true);
}
void vedioForm::on_btnvideo6_clicked()
{
saveConfig("6");
applyLayout("6");
ui->btnvideo6->setChecked(true);
}
void vedioForm::on_btnvideo8_clicked()
{
saveConfig("8");
applyLayout("8");
ui->btnvideo8->setChecked(true);
}
void vedioForm::on_btnvideo9_clicked()
{
saveConfig("9");
applyLayout("9");
ui->btnvideo9->setChecked(true);
}
14:45:35: Starting D:\qtproject\safeopencv\build\Desktop_Qt_6_2_4_MinGW_64_bit-Debug\debug\safeopencv.exe...
main libvlc debug: VLC media player - 3.0.20 Vetinari
main libvlc debug: Copyright ? 1996-2023 the VideoLAN team
main libvlc debug: revision 3.0.20-0-g6f0d0ab126
main libvlc debug: configured with /builds/videolan/vlc/extras/package/win32/../../../configure '--enable-update-check' '--enable-lua' '--enable-faad' '--enable-flac' '--enable-theora' '--enable-avcodec' '--enable-merge-ffmpeg' '--enable-dca' '--enable-mpc' '--enable-libass' '--enable-schroedinger' '--enable-realrtsp' '--enable-live555' '--enable-shout' '--enable-goom' '--enable-sse' '--enable-mmx' '--enable-libcddb' '--enable-zvbi' '--disable-telx' '--enable-nls' '--host=x86_64-w64-mingw32' '--with-contrib=../contrib/x86_64-w64-mingw32' '--with-breakpad=https://win.crashes.videolan.org' '--enable-qt' '--enable-skins2' '--enable-dvdread' '--enable-caca' 'host_alias=x86_64-w64-mingw32' 'CFLAGS= -D_WIN32_WINNT=0x0502 -DWINVER=0x502 -D__MSVCRT_VERSION__=0x700 ' 'CXXFLAGS= -D_WIN32_WINNT=0x0502 -DWINVER=0x502 -D__MSVCRT_VERSION__=0x700 ' 'PKG_CONFIG=pkg-config' 'PKG_CONFIG_LIBDIR=/usr/x86_64-w64-mingw32/lib/pkgconfig:/usr/lib/x86_64-w64-mingw32/pkgconfig'
main libvlc debug: using multimedia timers as clock source
main libvlc debug: min period: 1 ms, max period: 1000000 ms
main libvlc debug: searching plug-in modules
main libvlc debug: loading plugins cache file D:\qtproject\safeopencv\build\Desktop_Qt_6_2_4_MinGW_64_bit-Debug\debug\plugins\plugins.dat
main libvlc warning: cannot read D:\qtproject\safeopencv\build\Desktop_Qt_6_2_4_MinGW_64_bit-Debug\debug\plugins\plugins.dat: No such file or directory
main libvlc debug: recursively browsing `D:\qtproject\safeopencv\build\Desktop_Qt_6_2_4_MinGW_64_bit-Debug\debug\plugins'
main libvlc debug: plug-ins loaded: 497 modules
main logger debug: looking for logger module matching "any": 2 candidates
main logger debug: using logger module "console"
main libvlc debug: translation test: code is "C"
main keystore debug: looking for keystore module matching "memory": 3 candidates
main keystore debug: using keystore module "memory"
main libvlc debug: CPU has capabilities MMX MMXEXT SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 FPU
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
main generic debug: creating audio output
main audio output debug: looking for audio output module matching "any": 6 candidates
mmdevice audio output debug: using default device
mmdevice audio output debug: version 2 session control unavailable
mmdevice audio output debug: volume from -63.500000 dB to +0.000000 dB with 0.500000 dB increments
main audio output debug: using audio output module "mmdevice"
main generic debug: keeping audio output
14:45:44: The command "D:\qtproject\safeopencv\build\Desktop_Qt_6_2_4_MinGW_64_bit-Debug\debug\safeopencv.exe" terminated abnormally.