diff --git a/Channel.cpp b/Channel.cpp index e53cc0a..31bd5cc 100755 --- a/Channel.cpp +++ b/Channel.cpp @@ -22,26 +22,40 @@ int Channel::maxGian = 12; int Channel::minGain = -30; int Channel::curGain = 0; -extern LinkObject* vo; -extern LinkObject* vo1; extern DatabaseManager* db; -Channel::Channel(QObject* parent) - : QObject(parent) -{ - videoInput = nullptr; - videoOutput = nullptr; - audioInput = nullptr; - audioOutput = nullptr; - videoEncoder = nullptr; - audioEncoder = nullptr; - record = nullptr; - rtsp = nullptr; - inputFile = nullptr; - videoDecoder = nullptr; - audioDecoder = nullptr; - image = Link::create("InputImage"); +void configureOutput2(QString resolutionStr); +Channel::Channel(const QString& name, const QVariantMap& params, QObject* parent) + : channelName(name) + , videoOutputParams(params) + , QObject(parent) +{ + videoOutput = Link::create("OutputVo"); + videoOutput->start(videoOutputParams); + + resolutionMap[Channel::VO_OUTPUT_1080I60] = "1080I60"; + resolutionMap[Channel::VO_OUTPUT_1600x1200_60] = "1600x1200_60"; + resolutionMap[Channel::VO_OUTPUT_1280x1024_60] = "1280x1024_60"; + resolutionMap[Channel::VO_OUTPUT_1280x720_60] = "1280x720_60"; + resolutionMap[Channel::VO_OUTPUT_1024x768_60] = "1024x768_60"; + resolutionMap[Channel::VO_OUTPUT_800x600_60] = "800x600_60"; +} + +Channel::~Channel() +{ +} + +/** + * @brief 初始化 + */ +void Channel::init() +{ + // 输出通道2的额外配置 + if (channelName == Constant::SecondaryChannel) { + QString resolution = videoOutputParams["output"].toString(); + configureOutput2(resolution); + } if (lineIn == nullptr) { lineIn = Link::create("InputAlsa"); QVariantMap dataIn; @@ -49,7 +63,7 @@ Channel::Channel(QObject* parent) dataIn["channels"] = 2; lineIn->start(dataIn); } - if (resample = nullptr) { + if (resample == nullptr) { resample = Link::create("Resample"); resample->start(); lineIn->linkA(resample); @@ -74,33 +88,9 @@ Channel::Channel(QObject* parent) rtspServer = Link::create("Rtsp"); rtspServer->start(); } -} + image = Link::create("InputImage"); -Channel::~Channel() -{ -} - -/** - * @brief 初始化 - */ -void Channel::init() -{ - if (channelName.isEmpty()) { - Log::error("channel name is empty!"); - return; - } - // 通道音频输出 - audioOutput = Link::create("OutputAo"); - QVariantMap dataVo; - if (channelName == Constant::MainChannel) { - videoOutput = vo; - dataVo["interface"] = "HDMI-OUT0"; - } else { - videoOutput = vo1; - dataVo["interface"] = "HDMI-OUT1"; - } loadOverlayConfig(); - // 水印,用于显示录制状态 overlay = Link::create("Overlay"); overlay->start(norecordOverlay); @@ -115,6 +105,9 @@ void Channel::init() videoInput->start(dataVi); videoInput->linkV(overlay)->linkV(videoOutput); + // 通道音频输出 + audioOutput = Link::create("OutputAo"); + // 通道音频输入 audioInput = Link::create("InputAi"); QVariantMap dataAi; @@ -141,7 +134,7 @@ void Channel::init() dataMp4["segmentDuration"] = duration; record->setData(dataMp4); videoInput->linkV(videoEncoder)->linkV(record); - audioInput->linkV(audioEncoder)->linkV(record); + audioInput->linkA(audioEncoder)->linkV(record); resample->linkA(audioEncoder)->linkA(record); connect(record, SIGNAL(newEvent(QString, QVariant)), this, SLOT(onNewEvent(QString, QVariant))); @@ -154,6 +147,18 @@ void Channel::init() // "onNewEvent"); // }); + // 用于计算文件时长 + calDuration = Link::create("InputFile"); + calDuration->start(); + + calAudioDecoder = Link::create("DecodeA"); + calAudioDecoder->start(); + calDuration->linkA(calAudioDecoder); + + calVideoDecoder = Link::create("DecodeV"); + calVideoDecoder->start(); + calDuration->linkV(calVideoDecoder); + // rstp流 rtsp = Link::create("Mux"); QVariantMap dataRtsp; @@ -188,6 +193,26 @@ void Channel::init() inputFile->linkV(videoDecoder)->linkV(videoOutput); } +void Channel::changeOutputResolution(Channel::Resolution resolution) +{ + QVariantMap dataVo; + dataVo["output"] = resolutionMap[resolution]; + videoOutput->setData(dataVo); + if (channelName == Constant::SecondaryChannel) { + configureOutput2(resolutionMap[resolution]); + } +} + +void Channel::changeInputResolution(int width, int height) +{ + QVariantMap dataVi; + dataVi["width"] = width; + dataVi["height"] = height; + videoInput->setData(dataVi); + + qDebug() << videoInput << "set " +} + /** * @brief 开始录制 */ @@ -220,37 +245,10 @@ void Channel::onNewEvent(QString msg, QVariant data) // 重新设置视频的录制起始时间 curTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss"); segmentId = data.toInt(); - // 修改文件名:本次录制起始时间_%d ==> 当前视频录制起始时间 - QString filename = QString("%1_%2.mp4").arg(startTime).arg(segmentId - 1); - QString newFilename = QString("%1/%2/%3.mp4").arg(Constant::VideoPath).arg(channelName).arg(datetime); - QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(channelName).arg(filename); - QFile file(path); - if (!file.rename(newFilename)) { - Log::error("rename file name failed in function onNewEvent, old filename: {}, target filename: {} , channel name: {},reason: {}", - path.toStdString(), - newFilename.toStdString(), - channelName.toStdString(), - file.errorString().toStdString()); - return; - } - // 将录制完的文件信息保存到数据库 - DatabaseManager::File fileInfo; - fileInfo.channel = channelName == Constant::MainChannel - ? DatabaseManager::MainChannel - : DatabaseManager::SecondaryChannel; - fileInfo.datetime = QDateTime::fromString(datetime, "yyyyMMddhhmmss").toString("yyyy-MM-dd hh:mm:ss"); - fileInfo.filename = QString("%1.mp4").arg(datetime); - if (db->insert(fileInfo)) { - Log::info("insert one record into database success, name: {}, channel: {}", - fileInfo.filename.toStdString(), - channelName.toStdString()); - } else { - Log::error("insert one record into database failed, name: {}, channel: {}", - fileInfo.filename.toStdString(), - channelName.toStdString()); - } - // 更新当前录制录制视频的时间 - emit appendOneVideo(channelName); + // 异步执行保存文件信息的函数 + QTimer::singleShot(3000, this, [=]() { + saveVideoInfo(datetime, segmentId - 1); + }); } } @@ -261,39 +259,12 @@ void Channel::stopRecord() { Log::info("{} stop recording...", channelName.toStdString()); record->stop(true); - // 修改文件名:本次录制起始时间_%d ==> 当前视频录制起始时间 - QString filename = QString("%1_%2.mp4").arg(startTime).arg(segmentId); - QString newFilename = QString("%1/%2/%3.mp4").arg(Constant::VideoPath).arg(channelName).arg(curTime); - QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(channelName).arg(filename); - QFile file(path); - if (!file.rename(newFilename)) { - Log::error("rename file name failed in function onNewEvent, old filename: {}, target filename: {} , channel name: {},reason: {}", - path.toStdString(), - newFilename.toStdString(), - channelName.toStdString(), - file.errorString().toStdString()); - return; - } - // 将录制文件的信息存入数据库 - DatabaseManager::File fileInfo; - fileInfo.channel = channelName == Constant::MainChannel - ? DatabaseManager::MainChannel - : DatabaseManager::SecondaryChannel; - fileInfo.datetime = QDateTime::fromString(curTime, "yyyyMMddhhmmss").toString("yyyy-MM-dd hh:mm:ss"); - fileInfo.filename = QString("%1.mp4").arg(curTime); - if (db->insert(fileInfo)) { - Log::info("insert one record into database success, name: {}, channel: {}", - fileInfo.filename.toStdString(), - channelName.toStdString()); - } else { - Log::error("insert one record into database failed, name: {}, channel: {}", - fileInfo.filename.toStdString(), - channelName.toStdString()); - } - // 更新当前录制录制视频的时间 - emit appendOneVideo(channelName); + // 异步执行保存文件信息的函数 + QTimer::singleShot(1000, this, [=]() { + saveVideoInfo(curTime, segmentId); + }); + // 重置水印和时间 overlay->setData(norecordOverlay); - // 重置时间 startTime = ""; curTime = ""; } @@ -371,25 +342,12 @@ void Channel::startPlayLive() } /** - * @brief 后退10s + * @brief 回放视频跳转 + * @param pos */ -void Channel::back() +void Channel::seek(int pos) { - Log::info("{} back 10s", channelName.toStdString()); - int curPos = inputFile->invoke("getPosition").toInt(); - curPos -= 10 * 1000; - inputFile->invoke("seek", curPos); -} - -/** - * @brief 快进10s - */ -void Channel::forward() -{ - Log::info("{} forward 10s", channelName.toStdString()); - int curPos = inputFile->invoke("getPosition").toInt(); - curPos += 10 * 1000; - inputFile->invoke("seek", curPos); + inputFile->invoke("seek", pos); } /** @@ -481,3 +439,79 @@ void Channel::loadOverlayConfig() recordOverlay = loadFromJson(Constant::RecordOverlay); norecordOverlay = loadFromJson(Constant::NoRecordOverlay); } + +/** + * @brief 修改文件名并获取视频时长,然后将信息保存到数据库中 + * @param startTime + */ +void Channel::saveVideoInfo(QString curTime, int segmentId) +{ + // 修改文件名:本次录制起始时间_%d ==> 当前视频录制起始时间 + QString filename = QString("%1_%2.mp4").arg(startTime).arg(segmentId); + QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(channelName).arg(filename); + QString newPath = QString("%1/%2/%3.mp4").arg(Constant::VideoPath).arg(channelName).arg(curTime); + QFile file(path); + if (!file.rename(newPath)) { + Log::error("rename file name failed in function onNewEvent, old filename: {}, target filename: {} , channel name: {},reason: {}", + path.toStdString(), + newPath.toStdString(), + channelName.toStdString(), + file.errorString().toStdString()); + return; + } + // 获取视频文件的时间 + int curDuration = calDuration->invoke("getDuration", newPath).toInt() / 1000; + // 将录制文件的信息存入数据库 + DatabaseManager::File fileInfo; + fileInfo.channel = channelName == Constant::MainChannel + ? DatabaseManager::MainChannel + : DatabaseManager::SecondaryChannel; + fileInfo.datetime = QDateTime::fromString(curTime, "yyyyMMddhhmmss").toString("yyyy-MM-dd hh:mm:ss"); + fileInfo.filename = QString("%1.mp4").arg(curTime); + fileInfo.duration = curDuration; + if (db->insert(fileInfo)) { + Log::info("insert one record into database success, name: {}, channel: {}, duration: {}s", + fileInfo.filename.toStdString(), + channelName.toStdString(), + curDuration); + } else { + Log::error("insert one record into database failed, name: {}, channel: {}, duration: {}s", + fileInfo.filename.toStdString(), + channelName.toStdString(), + curDuration); + } + // 更新当前录制录制视频的时间 + emit appendOneVideo(); +} + +/** + * @brief 针对输出口2的分辨率进行配置 + * @param resolutionStr + */ +void configureOutput2(QString resolutionStr) +{ + static int lastNorm = 0; + int norm = 0; + int ddr = 0; + if (resolutionStr == "1080P60") + norm = 9; + else if (resolutionStr == "1080P50") + norm = 10; + else if (resolutionStr == "1080P30") + norm = 12; + else if (resolutionStr == "720P60") + norm = 5; + else if (resolutionStr == "720P50") + norm = 6; + else if (resolutionStr == "3840x2160_30") { + norm = 14; + ddr = 1; + } + if (norm != lastNorm) { + lastNorm = norm; + QString cmd = "rmmod hi_lt8618sx_lp.ko"; + system(cmd.toLatin1().data()); + cmd = cmd.sprintf("insmod /ko/extdrv/hi_lt8618sx_lp.ko norm=%d USE_DDRCLK=%d", norm, ddr); + system(cmd.toLatin1().data()); + } +} \ No newline at end of file diff --git a/Channel.h b/Channel.h index 8e57b1b..290161f 100755 --- a/Channel.h +++ b/Channel.h @@ -16,18 +16,31 @@ public: Error, Finish }; - explicit Channel(QObject* parent = nullptr); + + enum Resolution { + VO_OUTPUT_1080I60, + VO_OUTPUT_1600x1200_60, + VO_OUTPUT_1280x1024_60, + VO_OUTPUT_1280x720_60, + VO_OUTPUT_1024x768_60, + VO_OUTPUT_800x600_60 + }; + + explicit Channel(const QString& name, const QVariantMap& params, QObject* parent = nullptr); ~Channel(); void init(); + // 输出分辨率 + void changeOutputResolution(Resolution resolution); + void changeInputResolution(int width, int height); + // 录制 void startRecord(); void stopRecord(); // 回放 bool startPlayback(QString path); - void forward(); - void back(); + void seek(int pos); void togglePause(); void startPlayLive(); void showFinishPromot(); @@ -40,11 +53,16 @@ public: public: QString channelName; - LinkObject* videoInput; - LinkObject* videoEncoder; - QVariantMap videoEncoderParams; - LinkObject* videoOutput; + // 视频输入输出 + LinkObject* videoInput = nullptr; + LinkObject* videoOutput = nullptr; + QVariantMap videoOutputParams; + // 视频编码 + LinkObject* videoEncoder = nullptr; + QVariantMap videoEncoderParams; + + // 外接音频输入输出 static LinkObject* lineIn; // 外部音频输入 static LinkObject* resample; // 重采样 static LinkObject* lineOut; // 外部音频输出 @@ -54,39 +72,51 @@ public: static int minGain; // 最小增益 static int curGain; // 当前增益 - LinkObject* audioInput; // 通道音频输入 - LinkObject* audioOutput; // 通道音频输入 - LinkObject* audioEncoder; // 音频编码器 + // 通道音频输入输出 + LinkObject* audioInput = nullptr; // 通道音频输入 + LinkObject* audioOutput = nullptr; // 通道音频输入 + LinkObject* audioEncoder = nullptr; // 音频编码器 QVariantMap audioEncoderParams; // 编码参数 - LinkObject* record; - int duration = 1 * 60 * 1000; // 单个视频时长 + // 视频录制 + LinkObject* record = nullptr; + LinkObject* calDuration = nullptr; + LinkObject* calAudioDecoder = nullptr; + LinkObject* calVideoDecoder = nullptr; + int duration = 10 * 60 * 1000; // 单个视频时长 bool isRecord = false; QString startTime; // 本次录制文件的开始时间 QString curTime; // 当前录制文件的开始时间 - int segmentId = 0; - LinkObject* overlay; // 水印,提示是否在录制视频 + int segmentId = 0; // 分片录制的id + + // 水印 + LinkObject* overlay = nullptr; // 水印,提示是否在录制视频 QVariantMap recordOverlay; // 录制状态下的水印参数 QVariantMap norecordOverlay; // 非录制状态下的水印参数 + // 视频回放 int playbackDuration = 0; // 当前播放视频的时长,单位ms - LinkObject* inputFile; - LinkObject* videoDecoder; - LinkObject* audioDecoder; - LinkObject* image; + LinkObject* inputFile = nullptr; + LinkObject* videoDecoder = nullptr; + LinkObject* audioDecoder = nullptr; + LinkObject* image = nullptr; PlaybackState state = Stop; + // 视频推流 static LinkObject* rtspServer; - LinkObject* rtsp; + LinkObject* rtsp = nullptr; QString pushCode; + QMap resolutionMap; // 分辨率列表 + private slots: void onNewEvent(QString msg, QVariant data); + void saveVideoInfo(QString curTime, int segmentId); signals: void playEnd(); void showRecordState(bool state); - void appendOneVideo(QString channelName); + void appendOneVideo(); private: void loadOverlayConfig(); diff --git a/CheckStorageThread.cpp b/CheckStorageThread.cpp index 1a0d108..ed944b2 100755 --- a/CheckStorageThread.cpp +++ b/CheckStorageThread.cpp @@ -17,7 +17,8 @@ extern QMutex mutex; extern QWaitCondition condition; extern DatabaseManager* db; -CheckStorageThread::CheckStorageThread() +CheckStorageThread::CheckStorageThread(QObject* parent) + : QThread(parent) { } diff --git a/CheckStorageThread.h b/CheckStorageThread.h index 32ba617..7e3b745 100755 --- a/CheckStorageThread.h +++ b/CheckStorageThread.h @@ -6,7 +6,7 @@ class CheckStorageThread : public QThread { Q_OBJECT public: - CheckStorageThread(); + CheckStorageThread(QObject* parent = nullptr); signals: void diskWillFull(); // 磁盘空间不足 diff --git a/DatabaseManager.cpp b/DatabaseManager.cpp index d7890d2..6c18dd3 100755 --- a/DatabaseManager.cpp +++ b/DatabaseManager.cpp @@ -66,10 +66,11 @@ void DatabaseManager::close() bool DatabaseManager::insert(File file) { QSqlQuery query; - query.prepare("insert into file (channel, datetime, filename) values (?, ?, ?)"); + query.prepare("insert into file (channel, datetime, filename, duration) values (?, ?, ?, ?)"); query.addBindValue(file.channel); query.addBindValue(file.datetime); query.addBindValue(file.filename); + query.addBindValue(file.duration); if (query.exec()) { return true; } else { @@ -88,7 +89,7 @@ QList DatabaseManager::get(QVariantMap params) { QList result; QSqlQuery query; - QString sql = "select * from file where channel=? and datetime like \'%1\'"; + QString sql = "select * from file where channel=? and datetime like ?"; QString year = params.value("year").toString(); QString month = params.value("month").toString(); QString day = params.value("day").toString(); @@ -99,7 +100,7 @@ QList DatabaseManager::get(QVariantMap params) } query.prepare(sql); query.bindValue(0, chn); - query.bindValue(1, QString("%1-%2-%3%")); + query.bindValue(1, QString("%1-%2-%3%").arg(year).arg(month).arg(day)); if (query.exec()) { while (query.next()) { DatabaseManager::File file; @@ -107,13 +108,14 @@ QList DatabaseManager::get(QVariantMap params) file.channel = static_cast(query.value("channel").toInt()); file.datetime = query.value("datetime").toString(); file.filename = query.value("filename").toString(); + file.duration = query.value("duration").toInt(); result.push_back(file); } } else { Log::error("select from database failed, reason: {}", - query.lastError().databaseText().toStdString()); + query.lastError().databaseText().toStdString() + query.lastError().driverText().toStdString()); } - Log::info("record of one day: {}", result.length()); + // Log::info("record of one day: {}", result.length()); return result; } @@ -136,6 +138,7 @@ QList DatabaseManager::get(DatabaseManager::Channel chn) file.channel = static_cast(query.value("channel").toInt()); file.datetime = query.value("datetime").toString(); file.filename = query.value("filename").toString(); + file.duration = query.value("duration").toInt(); result.push_back(file); } } else { @@ -159,6 +162,7 @@ QList DatabaseManager::getTopTwo() file.channel = static_cast(query.value("channel").toInt()); file.datetime = query.value("datetime").toString(); file.filename = query.value("filename").toString(); + file.duration = query.value("duration").toInt(); result.push_back(file); } } else { @@ -190,7 +194,7 @@ QStringList DatabaseManager::getAllYears(Channel chn) Log::error("select all years from database failed, reason: {}", query.lastError().databaseText().toStdString()); } - Log::info("number of year: {}", result.length()); + // Log::info("number of year: {}", result.length()); return result; } @@ -206,11 +210,11 @@ QStringList DatabaseManager::getAllMonths(Channel chn, QString year) query.prepare(R"( select distinct strftime('%m', datetime) as month from file - where channel = ? and strftime('%Y', datetime) = '?' + where channel = ? and strftime('%Y', datetime) = ? order by month )"); - query.bindValue(0, chn); - query.bindValue(1, year); + query.addBindValue(chn); + query.addBindValue(year); if (query.exec()) { while (query.next()) { QString month = query.value(0).toString(); @@ -218,9 +222,9 @@ QStringList DatabaseManager::getAllMonths(Channel chn, QString year) } } else { Log::error("select all months of one year from database failed, reason: {}", - query.lastError().databaseText().toStdString()); + query.lastError().databaseText().toStdString() + query.lastError().driverText().toStdString()); } - Log::info("number of month: {}", result.length()); + // Log::info("number of month: {}", result.length()); return result; } @@ -238,8 +242,8 @@ QStringList DatabaseManager::getAllDays(Channel chn, QString year, QString month select distinct strftime('%d', datetime) as day from file where channel = ? - and strftime('%Y', datetime) = '?' - and strftime('%m', datetime) = '?' + and strftime('%Y', datetime) = ? + and strftime('%m', datetime) = ? order by day )"); query.bindValue(0, chn); @@ -252,9 +256,9 @@ QStringList DatabaseManager::getAllDays(Channel chn, QString year, QString month } } else { Log::error("select all days of one month from database failed, reason: {}", - query.lastError().databaseText().toStdString()); + query.lastError().databaseText().toStdString() + query.lastError().driverText().toStdString()); } - Log::info("number of day: {}", result.length()); + // Log::info("number of day: {}", result.length()); return result; } diff --git a/DatabaseManager.h b/DatabaseManager.h index e8f097a..ab8025c 100755 --- a/DatabaseManager.h +++ b/DatabaseManager.h @@ -15,6 +15,7 @@ public: Channel channel; // 通道 QString datetime; // 时间 yyyy-MM-dd hh:mm:ss QString filename; // 文件名 + int duration; // 时间 }; static DatabaseManager* getInstace(); ~DatabaseManager(); diff --git a/Menu.cpp b/Menu.cpp index b992394..ca88a7f 100755 --- a/Menu.cpp +++ b/Menu.cpp @@ -1,16 +1,17 @@ #include "Menu.h" +#include "Channel.h" #include "Constant.h" #include "DatabaseManager.h" #include "Log.h" +#include "Tool.h" #include "ui_Menu.h" #include #include #include +#include #include -#define CONTENT_ROW 6 // 列表行 -#define CONTENT_COLUMN 6 // 列表列 -#define CONTENT_CELL_HEIGHT 56 // 单元格高度 +extern QList channelList; Menu::Menu(QWidget* parent) : QWidget(parent) @@ -25,30 +26,36 @@ Menu::Menu(QWidget* parent) ui->stackedWidget->setCurrentIndex(0); ui->btnChannelSetting->setFocus(); - QDesktopWidget* deskdop = QApplication::desktop(); - QWidget::move((deskdop->width() - this->width()) / 2, (deskdop->height() - this->height()) / 2); - ui->cmbYear->setView(new QListView()); ui->cmbMonth->setView(new QListView()); ui->cmbDay->setView(new QListView()); + ui->cmbResolutionA->setView(new QListView()); + ui->cmbResolutionB->setView(new QListView()); + ui->cmbResolutionInA->setView(new QListView()); + ui->cmbResolutionInB->setView(new QListView()); + ui->timeSlider->setOpacity(0.5); // 默认设置当前操作选择主通道 curSelectChannel = DatabaseManager::MainChannel; - curPlayChannel = DatabaseManager::MainChannel; db = DatabaseManager::getInstace(); if (!db->open()) return; // 设置ScrollArea为栅格布局 - QGridLayout* layout = new QGridLayout(ui->scrollArea); - ui->scrollAreaWidgetContents->setLayout(layout); connect(ui->cmbYear, &QComboBox::currentTextChanged, [=](QString text) { renderComboBoxMonth(); }); connect(ui->cmbMonth, &QComboBox::currentTextChanged, [=](QString text) { renderComboBoxDay(); }); + + for (const auto& chn : channelList) { + connect(chn, SIGNAL(appendOneVideo()), this, SLOT(onAppendVideo())); + } + + renderComboBoxYear(); + renderTimeSlider(); } Menu::~Menu() @@ -56,16 +63,6 @@ Menu::~Menu() delete ui; } -/** - * @brief 重写菜单show方法 - */ -void Menu::show() -{ - QWidget::show(); - renderComboBoxYear(); - getContents(); -} - /** * @brief 设置是否显示通道选择 * @param visible @@ -125,71 +122,6 @@ void Menu::renderComboBoxDay() ui->cmbDay->setCurrentIndex(0); } -/** - * @brief 获取数据 - */ -void Menu::getContents() -{ - QVariantMap params; - params["channel"] = curSelectChannel; - params["year"] = ui->cmbYear->currentText(); - params["month"] = ui->cmbMonth->currentText(); - params["day"] = ui->cmbDay->currentText(); - contentList = db->get(params); - renderContents(); -} - -/** - * @brief 将视频名称列表渲染成一堆按钮放到ScrollArea中,6*6 - */ -void Menu::renderContents() -{ - QGridLayout* layout = qobject_cast(ui->scrollAreaWidgetContents->layout()); - - // 清除原来的控件防止内存泄漏 - QList widgets = ui->scrollAreaWidgetContents->findChildren(); - for (QWidget* w : widgets) { - delete w; - } - // 重新生成若干个按钮 - for (int i = 0; i < contentList.length(); i++) { - // 文件名格式: hhmmss,只将时分秒显示到界面上 - DatabaseManager::File file = contentList.at(i); - QString text = QDateTime::fromString(file.datetime, "yyyy-MM-dd hh:mm:ss").toString("hh:mm:ss"); - QPushButton* btn = new QPushButton(text, ui->scrollArea); - btn->setProperty("name", file.filename); - btn->setMinimumHeight(CONTENT_CELL_HEIGHT); - btn->setStyleSheet("QPushButton{border-radius: 5px;}"); - btn->setCheckable(true); - btn->setAutoExclusive(true); - btn->setObjectName(QString("btn_video_%1").arg(i)); - if (file.filename == curPlayFilename) { - btn->setChecked(true); - btn->setFocus(); - } - int row = i / CONTENT_COLUMN; - int column = i % CONTENT_COLUMN; - layout->addWidget(btn, row, column); - } - - // 少于36个用空的widget占位置 - if (contentList.length() < CONTENT_ROW * CONTENT_COLUMN) { - int lastRow = (contentList.length() - 1) / CONTENT_COLUMN; - int lastColum = (contentList.length() - 1) % CONTENT_COLUMN; - - for (int i = lastRow; i < CONTENT_ROW; i++) { - for (int j = 0; j < CONTENT_COLUMN; j++) { - if (i == lastRow && j <= lastColum) { - continue; - } - QWidget* widget = new QWidget(ui->scrollArea); - widget->setMinimumHeight(CONTENT_CELL_HEIGHT); - layout->addWidget(widget, i, j); - } - } - } -} - /** * @brief 移动 * @param direction @@ -197,30 +129,51 @@ void Menu::renderContents() void Menu::move(Direction direction) { QWidget* focusWidget = QApplication::focusWidget(); + if (!focusWidget) { + return; + } // 当前焦点是否在展开后的ComboBox上 bool isCmbPopup = (strcmp(focusWidget->metaObject()->className(), "QListView") == 0); + bool isTimeSlider = focusWidget->objectName() == "timeSlider"; // 下拉框展开,则只进行上下选择 if (isCmbPopup) { // 随便赋值一个不影响功能的按键 Qt::Key key = Qt::Key_Right; if (direction == Up) { - key == Qt::Key_Up; + key = Qt::Key_Up; } else if (direction == Down) { - key == Qt::Key_Down; + key = Qt::Key_Down; } QKeyEvent pressEvent(QEvent::KeyPress, key, Qt::NoModifier); QApplication::sendEvent(focusWidget, &pressEvent); QKeyEvent releaseEvent(QEvent::KeyRelease, key, Qt::NoModifier); QApplication::sendEvent(focusWidget, &releaseEvent); + } else if (isTimeSlider) { + switch (direction) { + case Left: + if (curSegmentIndex > 0) { + curSegmentIndex--; + int cur = segments[curSegmentIndex].startTime; + ui->timeSlider->setCurrent(cur); + } + break; + case Right: + if (curSegmentIndex < segments.length() - 1) { + curSegmentIndex++; + int cur = segments[curSegmentIndex].startTime; + ui->timeSlider->setCurrent(cur); + } + break; + case Up: + case Down: + focusNext(direction); + break; + default: + break; + } } else { focusNext(direction); focusWidget = QApplication::focusWidget(); - int drawedHeight = focusWidget->visibleRegion().boundingRect().height(); - int height = focusWidget->height(); - // 绘制的高度小于实际高度,则表示控件显示不完全 - if (drawedHeight < height) { - ui->scrollArea->ensureWidgetVisible(focusWidget); - } if (focusWidget == ui->btnChannelSetting) { ui->btnChannelSetting->setChecked(true); ui->stackedWidget->setCurrentIndex(0); @@ -236,141 +189,64 @@ void Menu::move(Direction direction) */ void Menu::confirm() { - int index = ui->stackedWidget->currentIndex(); QWidget* focusWidget = QApplication::focusWidget(); - switch (index) { - // 通道设置界面 - case 0: { + // 当前操作的是按钮 + if (strcmp(focusWidget->metaObject()->className(), "QPushButton") == 0) { QPushButton* btn = qobject_cast(focusWidget); - btn->setChecked(true); - if (btn == ui->btn_hdmi_1) { - emit btnHdmi1Checked(); - } else if (btn == ui->btn_hdmi_2) { - emit btnHdmi2Checked(); - } else if (btn == ui->btn_vga_1) { - emit btnVga1Checked(); - } else if (btn == ui->btn_vga_2) { - emit btnVga2Checked(); - } - break; - } - // 回放界面 - case 1: { - // 通道选择时按下确认 - if (focusWidget == ui->btn_channel_1 || focusWidget == ui->btn_channel_2) { - QPushButton* btn = qobject_cast(focusWidget); - btn->setChecked(true); - if (btn == ui->btn_channel_1) { - curSelectChannel = DatabaseManager::MainChannel; - } else { - curSelectChannel = DatabaseManager::SecondaryChannel; + if (btn) { + // 选择通道输出类型 + if (btn == ui->btn_hdmi_1) { + btn->setChecked(true); + emit btnHdmi1Checked(); + } else if (btn == ui->btn_hdmi_2) { + btn->setChecked(true); + emit btnHdmi2Checked(); + } else if (btn == ui->btn_vga_1) { + btn->setChecked(true); + emit btnVga1Checked(); + } else if (btn == ui->btn_vga_2) { + btn->setChecked(true); + emit btnVga2Checked(); } - renderComboBoxYear(); - } - // 下拉框未展开时按下确认 - else if (strcmp(focusWidget->metaObject()->className(), "QComboBox") == 0) { - QComboBox* cmb = qobject_cast(focusWidget); - cmb->showPopup(); - } - // 下拉框展开时按下确认 - else if (strcmp(focusWidget->metaObject()->className(), "QListView") == 0) { - // QComboBox的层级 - // QComboBox - // |- QComboBoxPrivateContainer(Popup widget) - // |- QListView(or QListWidget) - // |-QViewPort(Child of QListView) - QComboBox* cmb = static_cast(focusWidget->parent()->parent()); - if (cmb) { - cmb->hidePopup(); + // 选择通道 + else if (btn == ui->btn_channel_1 || btn == ui->btn_channel_2) { + btn->setChecked(true); + curSelectChannel = btn == ui->btn_channel_1 ? DatabaseManager::MainChannel + : DatabaseManager::SecondaryChannel; + renderComboBoxYear(); } } - // 选择视频时按下确认 - else if (focusWidget->objectName().contains("btn_video")) { - QPushButton* btn = qobject_cast(focusWidget); - btn->setChecked(true); - QString filename = btn->property("name").toString(); - emit btnVideoClicked(filename); - // 记录当前正在回放的通道和文件名 - curPlayFilename = filename; - curPlayChannel = curSelectChannel; - } - // 确认按钮按下确认 - else if (focusWidget->objectName() == "btn_done") { - getContents(); - } - break; } - default: - break; - } -} - -/** - * @brief 点击查找当前列表中的上一个视频和下一个视频 - */ -void Menu::clickPreOrNext(QString type) -{ - // 重新获取当前正在回放通道的视频 - QVariantMap params; - params["channel"] = curPlayChannel; - params["year"] = ui->cmbYear->currentText(); - params["month"] = ui->cmbMonth->currentText(); - params["day"] = ui->cmbDay->currentText(); - QList list = db->get(params); - // 搜索当前播放视频的位置 - int index = -1; - for (int i = 0; i < list.length(); i++) { - DatabaseManager::File file = list[i]; - if (file.filename == curPlayFilename) { - index = i; - break; + // 当前操作下拉框 + else if (strcmp(focusWidget->metaObject()->className(), "QComboBox") == 0) { + QComboBox* cmb = qobject_cast(focusWidget); + cmb->showPopup(); + } else if (strcmp(focusWidget->metaObject()->className(), "QListView") == 0) { + // QComboBox的层级 + // QComboBox + // |- QComboBoxPrivateContainer(Popup widget) + // |- QListView(or QListWidget) + // |-QViewPort(Child of QListView) + QListView* listView = static_cast(focusWidget); + QComboBox* cmb = static_cast(listView->parent()->parent()); + if (cmb) { + cmb->setCurrentIndex(listView->currentIndex().row()); + cmb->hidePopup(); } } - Log::info("find file filename: {}, index: {}", curPlayFilename.toStdString(), index); - // 查找目标文件名 - QString filename; - if (type == "previous") { - // 已经是第一个视频了 - if (index == 0) { - emit btnVideoClicked("first"); - return; - } - index -= 1; - } else { - // 已经是最后一个视频 - if (index == list.length() - 1) { - emit btnVideoClicked("last"); - return; - } - index += 1; + // 选中进度条 + else if (focusWidget->objectName() == "timeSlider") { + QVariantMap params; + params["channel"] = curSelectChannel; + params["year"] = ui->cmbYear->currentText(); + params["month"] = ui->cmbMonth->currentText(); + params["day"] = ui->cmbDay->currentText(); + emit btnPlaybackClicked(params, curSegmentIndex); + hide(); } - filename = list[index].filename; - Log::info("type: {}, index: {}, filename", - type.toStdString(), - index, - filename.toStdString()); - emit btnVideoClicked(filename); - curPlayFilename = filename; - // 单通道回放 - if (channelSelectVisible) { - // 选中通道和回放通道不是同一个时不处理 - if (curPlayChannel != curSelectChannel) { - return; - } - } - // 双通道同时回放 - else { - if (curPlayChannel != DatabaseManager::MainChannel) { - return; - } - } - // 选中要播放的按钮 - QList btns = ui->scrollArea->findChildren(); - for (QPushButton* btn : btns) { - if (btn->property("name").toString() == filename) { - btn->setChecked(true); - btn->setFocus(); - } + // 确认按钮按下确认 + else if (focusWidget->objectName() == "btn_done") { + renderTimeSlider(); } } @@ -392,30 +268,6 @@ QList Menu::getAllFocusabelWidget() return result; } -/** - * @brief 录制完毕一个视频 - * @param channelName - */ -void Menu::onAppendOneVideo(QString channelName) -{ - // 单通道 - if (channelSelectVisible) { - // 只有生成新视频的通道和当前选择的通道一致时重新获取文件列表 - if ((channelName == Constant::MainChannel - && curSelectChannel == DatabaseManager::MainChannel) - || (channelName == Constant::SecondaryChannel - && curSelectChannel == DatabaseManager::SecondaryChannel)) { - renderContents(); - } - } - // 双通道 - else { - if (channelName == Constant::MainChannel) { - renderContents(); - } - } -} - /** * @brief 获取当前正在回放的通道 * @return @@ -423,4 +275,112 @@ void Menu::onAppendOneVideo(QString channelName) DatabaseManager::Channel Menu::getCurPlayChannel() { return curSelectChannel; -} \ No newline at end of file +} + +/** + * @brief 渲染回放时间轴 + */ +void Menu::renderTimeSlider() +{ + QVariantMap params; + params["channel"] = curSelectChannel; + params["year"] = ui->cmbYear->currentText(); + params["month"] = ui->cmbMonth->currentText(); + params["day"] = ui->cmbDay->currentText(); + QList segs = Tool::calTimeSegments(params); + if (segs.isEmpty()) { + return; + } + ui->timeSlider->setTimeSegments(segs); + ui->timeSlider->setCurrent(segs.front().startTime); + segments = segs; + curSegmentIndex = 0; +} + +/** + * @brief 新录制完一个视频槽函数 + */ +void Menu::onAppendVideo() +{ + Channel* chn = static_cast(sender()); + // 一路回放 + if (ui->widget_chnSelect->isVisible()) { + DatabaseManager::Channel c = static_cast(curSelectChannel); + QString channelName = c == DatabaseManager::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel; + if (chn->channelName != channelName) { + return; + } + } + // 两路回放 + else { + if (chn->channelName != Constant::MainChannel) { + return; + } + } + // 刷新时间轴中时间片的显示 + QVariantMap params; + params["channel"] = curSelectChannel; + params["year"] = ui->cmbYear->currentText(); + params["month"] = ui->cmbMonth->currentText(); + params["day"] = ui->cmbDay->currentText(); + segments = Tool::calTimeSegments(params); + ui->timeSlider->setTimeSegments(segments); +} + +void Menu::on_cmbResolutionB_currentIndexChanged(int index) +{ + emit resolutionOutBChanged(index); +} + +void Menu::on_cmbResolutionA_currentIndexChanged(int index) +{ + emit resolutionOutAChanged(index); +} + +void Menu::moveToCenter() +{ + QWidget* p = static_cast(parent()); + if (p) { + // 获取父窗体的位置和尺寸 + QRect parentGeometry = p->geometry(); + + // 计算子窗体的居中位置 + int x = parentGeometry.left() + (parentGeometry.width() - width()) / 2; + int y = parentGeometry.top() + (parentGeometry.height() - height()) / 2; + // 移动子窗体到父窗体的中心 + QWidget::move(x, y); + } +} + +void Menu::show() +{ + QWidget::show(); + moveToCenter(); + ui->btnChannelSetting->setFocus(); +} + +Menu* Menu::restartUI() +{ + Menu* menu = new Menu(); + close(); + menu->show(); + return menu; +} + +void Menu::on_cmbResolutionInA_currentIndexChanged(const QString& text) +{ + QStringList list = text.split("x"); + if (list.count() == 2) { + emit resolutionInAChanged(list[0].toInt(), list[1].toInt()); + } +} + +void Menu::on_cmbResolutionInB_currentIndexChanged(const QString& text) +{ + QStringList list = text.split("x"); + if (list.count() == 2) { + emit resolutionInAChanged(list[0].toInt(), list[1].toInt()); + } +} diff --git a/Menu.h b/Menu.h index d8c120a..58a17c7 100755 --- a/Menu.h +++ b/Menu.h @@ -1,9 +1,12 @@ #ifndef WIDGET_H #define WIDGET_H +#include "Channel.h" #include "DatabaseManager.h" #include "FocusWindow.h" +#include "TimeSlider.h" #include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -16,43 +19,55 @@ class Menu : public QWidget, public FW::FocusWindow { public: Menu(QWidget* parent = nullptr); ~Menu(); - void show(); void setChannelSelectVisible(bool visible); DatabaseManager::Channel getCurPlayChannel(); + void moveToCenter(); + void show(); + Menu* restartUI(); public slots: void move(Direction direction); void confirm(); - void clickPreOrNext(QString type); - void onAppendOneVideo(QString channelName); + void onAppendVideo(); signals: void btnHdmi1Checked(); void btnHdmi2Checked(); void btnVga1Checked(); void btnVga2Checked(); - void btnVideoClicked(QString name); + void btnPlaybackClicked(QVariantMap params, int segmentIndex); + + void resolutionOutAChanged(int); + void resolutionOutBChanged(int); + + void resolutionInAChanged(int width, int height); + void resolutionInBChanged(int width, int height); protected: // 重写父类的方法,以获取当前界面的所有可以获取焦点的控件 QList getAllFocusabelWidget() override; +private slots: + void on_cmbResolutionB_currentIndexChanged(int index); + + void on_cmbResolutionA_currentIndexChanged(int index); + + void on_cmbResolutionInA_currentIndexChanged(const QString& arg1); + + void on_cmbResolutionInB_currentIndexChanged(const QString& arg1); + private: Ui::Menu* ui; - // 视频名数组列表 - QList contentList; DatabaseManager* db; - QString curPlayFilename; // 当前正在回放的文件名 bool channelSelectVisible = false; // 是否显示通道选择,单通道回放显示、双通道不显示 - DatabaseManager::Channel curSelectChannel; // 当前选择的通道 - DatabaseManager::Channel curPlayChannel; // 当前回放的通道 + QList segments; // 视频时间片 + int curSegmentIndex; // 当前时间片 private: - void getContents(); - void renderContents(); void renderComboBoxYear(); void renderComboBoxMonth(); void renderComboBoxDay(); + void renderTimeSlider(); }; #endif // WIDGET_H diff --git a/Menu.ui b/Menu.ui index 5cef797..006c95a 100755 --- a/Menu.ui +++ b/Menu.ui @@ -34,6 +34,87 @@ QWidget:focus{ QPushButton:checked { background: rgba(94,92,230, 0.5); +} + + + +QComboBox{ + background:rgba(33,33,33,0.5); + font: 10pt "微软雅黑"; + padding: 1px 15px 1px 3px; + border:1px solid rgba(228,228,228,0.5); + border-radius:5px 5px 0px 0px; +} + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + border:none; + } + QComboBox QAbstractItemView{ + background:rgba(255,255,255,0.5); + border:1px solid rgba(228,228,228,0.5); + border-radius:0px 0px 5px 5px; + font-size:14px; + outline: 0px; + } +QComboBox QAbstractItemView::item{ + height:36px; + color:#666666; + padding-left:9px; + background-color:rgba(255,255,255,0.5); +} +QComboBox QAbstractItemView::item:hover{ + background-color:rgba(64,156,225,0.5); + color:#ffffff; +} +QComboBox QAbstractItemView::item:selected{ + background-color:rgba(64,156,225,0.5); + color:#ffffff; +} +QComboBox:on { + padding-top: 3px; + padding-left: 4px; + } + QComboBox::down-arrow:on { + top: 1px; + left: 1px; + } + +QScrollBar:vertical{ +width:3px; +background:transparent; +margin:0px,0px,0px,0px; +padding-top:0px; +padding-bottom:0px; +} +QScrollBar::handle:vertical +{ +width:3px; +background:rgba(69,178,255,70%); +border-radius:4px; +min-height:20 +} +QScrollBar::handle:vertical:hover{ +width:3px; +background:rgba(69,178,255,70%); +border-radius:4px; +min-height:20; +} +QScrollBar::add-line:vertical +{ +height:0px;width:4px; +subcontrol-position:bottom; +} +QScrollBar::sub-line:vertical +{ +height:0px;width:4px; +subcontrol-position:top; +} +QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical +{ +background:rgba(232,234,239,0.5); +border-radius:4px; } @@ -217,7 +298,7 @@ QPushButton:checked { - 1 + 0 @@ -238,7 +319,7 @@ QFrame#line color:rgba(51,51,51,0.5); } - + 20 @@ -255,228 +336,571 @@ QFrame#line 10 - - - - 0 - 40 - + + + 通道1: - - - 20 - - - 0 - - - 0 - - - 0 - - - 0 - + - - - - 微软雅黑 - 10 - 50 - false - false - - - - Qt::RightToLeft - - - 通道1: + + + + 0 + 40 + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 接口模式: + + + + + + + + 100 + 60 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + HDMI + + + true + + + true + + + true + + + + + + + + 100 + 60 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + VGA + + + true + + + true + + + + - - - - 100 - 60 - + + + 10 - - - 微软雅黑 - 10 - 50 - false - false - + + 0 - - HDMI - - - true - - - true - - - true - - + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 输入分辨率: + + + + + + + + 0 + 40 + + + + + 1920x1080 + + + + + 1600x1200 + + + + + 1280x1024 + + + + + 1280X720 + + + + + 1024x768 + + + + + 800x600 + + + + + - - + + + 10 + + + 0 + + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 输出分辨率: + + + + + + + + 0 + 40 + + + + + 1920x1080 + + + + + 1600x1200 + + + + + 1280x1024 + + + + + 1280X720 + + + + + 1024x768 + + + + + 800x600 + + + + + + + + + + Qt::Vertical + + - 100 - 60 + 20 + 40 - - - 微软雅黑 - 10 - 50 - false - false - - - - VGA - - - true - - - true - - + - - - - 0 - 40 - + + + 通道2: - - - 20 - - - 0 - - - 0 - - - 0 - - - 0 - + - - - - 微软雅黑 - 10 - 50 - false - false - - - - Qt::RightToLeft - - - 通道2: + + + + 0 + 40 + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 接口模式: + + + + + + + + 100 + 60 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + HDMI + + + true + + + true + + + true + + + + + + + + 100 + 60 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + VGA + + + true + + + true + + + + - - - - 100 - 60 - + + + 10 - - - 微软雅黑 - 10 - 50 - false - false - + + 0 - - HDMI - - - true - - - true - - - true - - + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 输入分辨率: + + + + + + + + 0 + 40 + + + + + 1920x1080 + + + + + 1600x1200 + + + + + 1280x1024 + + + + + 1280X720 + + + + + 1024x768 + + + + + 800x600 + + + + + - - + + + 10 + + + 0 + + + + + + 80 + 0 + + + + + 微软雅黑 + 10 + 50 + false + false + + + + Qt::RightToLeft + + + 输出分辨率: + + + + + + + + 0 + 40 + + + + + 1920x1080 + + + + + 1600x1200 + + + + + 1280x1024 + + + + + 1280X720 + + + + + 1024x768 + + + + + 800x600 + + + + + + + + + + Qt::Vertical + + - 100 - 60 + 20 + 40 - - - 微软雅黑 - 10 - 50 - false - false - - - - VGA - - - true - - - true - - + - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -495,86 +919,9 @@ QPushButton:checked { } -QComboBox{ - background:rgba(255,255,255,0.5); - font: 10pt "微软雅黑"; - padding: 1px 15px 1px 3px; - border:1px solid rgba(228,228,228,0.5); - border-radius:5px 5px 0px 0px; -} - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: top right; - width: 15px; - border:none; - } - QComboBox QAbstractItemView{ - background:rgba(255,255,255,0.5); - border:1px solid rgba(228,228,228,0.5); - border-radius:0px 0px 5px 5px; - font-size:14px; - outline: 0px; - } -QComboBox QAbstractItemView::item{ - height:36px; - color:#666666; - padding-left:9px; - background-color:rgba(255,255,255,0.5); -} -QComboBox QAbstractItemView::item:hover{ - background-color:rgba(64,156,225,0.5); - color:#ffffff; -} -QComboBox QAbstractItemView::item:selected{ - background-color:rgba(64,156,225,0.5); - color:#ffffff; -} -QComboBox:on { - padding-top: 3px; - padding-left: 4px; - } - QComboBox::down-arrow:on { - top: 1px; - left: 1px; - } - -QScrollBar:vertical{ -width:3px; -background:transparent; -margin:0px,0px,0px,0px; -padding-top:0px; -padding-bottom:0px; -} -QScrollBar::handle:vertical -{ -width:3px; -background:rgba(69,178,255,70%); -border-radius:4px; -min-height:20 -} -QScrollBar::handle:vertical:hover{ -width:3px; -background:rgba(69,178,255,70%); -border-radius:4px; -min-height:20; -} -QScrollBar::add-line:vertical -{ -height:0px;width:4px; -subcontrol-position:bottom; -} -QScrollBar::sub-line:vertical -{ -height:0px;width:4px; -subcontrol-position:top; -} -QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical -{ -background:rgba(232,234,239,0.5); -border-radius:4px; -} + - + 6 @@ -650,7 +997,7 @@ border-radius:4px; - 通道1 + 通道一 true @@ -681,7 +1028,7 @@ border-radius:4px; - 通道2 + 通道二 true @@ -828,74 +1175,37 @@ border-radius:4px; - + + + + 0 + 90 + + - Qt::NoFocus + Qt::StrongFocus - QScrollArea { - background-color:rgba(25, 25, 25, 0.5); + QWidget:focus{ + border: 3px solid rgba(15,216,82, 0.5); } - -QScrollBar:vertical{ -width:8px; -background:transparent; -margin:0px,0px,0px,0px; -padding-top:0px; -padding-bottom:0px; -} -QScrollBar::handle:vertical -{ -width:8px; -background:rgba(69,178,255,70%); -border-radius:4px; -min-height:20 -} -QScrollBar::handle:vertical:hover{ -width:8px; -background:rgba(69,178,255,70%); -border-radius:4px; -min-height:20; -} -QScrollBar::add-line:vertical -{ -height:0px;width:8px; -subcontrol-position:bottom; -} -QScrollBar::sub-line:vertical -{ -height:0px;width:8px; -subcontrol-position:top; -} -QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical -{ -background:rgba(232,234,239,0.5); -border-radius:4px; -} - - - true - - - - - 0 - 0 - 776 - 416 - - - - QWidget#scrollAreaWidgetContents { - background: rgba(25, 25, 25, 0.5); - border: none; -} - - - + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -904,6 +1214,14 @@ border-radius:4px; + + + TimeSlider + QWidget +
TimeSlider.h
+ 1 +
+
diff --git a/ProgressBar.cpp b/ProgressBar.cpp deleted file mode 100755 index 31a083c..0000000 --- a/ProgressBar.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "ProgressBar.h" -#include "ui_ProgressBar.h" -#include - -#define DURATION 10000 - -ProgressBar::ProgressBar(QWidget* parent) - : QWidget(parent) - , ui(new Ui::ProgressBar) -{ - ui->setupUi(this); - - // move to bottom of screen - this->setFixedWidth(parent->width()); - int height = parent->height(); - this->move(0, height - this->height()); - - timer = new QTimer(); - timer->setInterval(DURATION); - timer->stop(); - - connect(timer, SIGNAL(timeout()), this, SLOT(onTimeout())); - - QString styles = "QSlider::groove:horizontal { \ - border: none; \ - height: 6px; \ - border-radius: 3px; \ - background: #FF5C38; \ - } \ - QSlider::sub-page:horizontal { \ - background: #FF5C38; \ - height: 4px; \ - border-radius: 3px; \ - } \ - QSlider::add-page:horizontal { \ - background: #949494; \ - height: 4px; \ - border-radius: 3px; \ - }"; - QString max = "QSlider::handle:horizontal {\ - border: none;\ - margin: -5px 0px;\ - width: 16px;\ - height: 16px;\ - border-radius: 8px;\ - background: #FF5C38;\ - }"; - QString min = "QSlider::handle:horizontal {\ - border: none;\ - margin: -5px -8px;\ - width: 16px;\ - height: 16px;\ - border-radius: 8px;\ - background: rgba(0, 0, 0, 0);\ - }"; - sliderMaxStyles = styles + max; - sliderMinStyles = styles + min; - - this->setStyleSheet(sliderMaxStyles); -} - -ProgressBar::~ProgressBar() -{ - delete ui; - delete timer; -} - -void ProgressBar::setDuration(int dur) -{ - this->duration = dur; - ui->horizontalSlider->setMaximum(dur); - QString time = this->msecToString(dur); - this->durationStr = time; - ui->label->setText(QString("00:00:00/%1").arg(time)); -} - -void ProgressBar::setCurrent(int cur) -{ - ui->horizontalSlider->setValue(cur); - QString time = this->msecToString(cur); - ui->label->setText(QString("%1/%2").arg(time).arg(durationStr)); -} - -void ProgressBar::showMax() -{ - if (timer->isActive()) { - timer->stop(); - timer->start(); - } else { - timer->start(); - } - if (!isMax) { - this->setFixedHeight(100); - ui->horizontalSlider->setStyleSheet(sliderMaxStyles); - this->move(0, parentWidget()->height() - this->height()); - ui->widget->setStyleSheet("QWidget#widget{background-color: rgba(0, 0, 0, 0.7);}"); - isMax = true; - ui->actionContainer->show(); - } -} - -void ProgressBar::onTimeout() -{ - ui->actionContainer->hide(); - ui->horizontalSlider->setStyleSheet(sliderMinStyles); - this->setFixedHeight(ui->horizontalSlider->height() + ui->widget->layout()->contentsMargins().top()); - this->move(0, parentWidget()->height() - this->height()); - ui->widget->setStyleSheet("QWidget#widget{background-color: rgba(0, 0, 0, 0);}"); - timer->stop(); - isMax = false; -} - -void ProgressBar::setState(PlayState state) -{ - if (state == Play) { - ui->lblStatus->setPixmap(QPixmap(":/images/pause.png")); - } else { - ui->lblStatus->setPixmap(QPixmap(":/images/start.png")); - } -} diff --git a/ProgressBar.h b/ProgressBar.h deleted file mode 100755 index 992dcbf..0000000 --- a/ProgressBar.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef PROGRESSBAR_H -#define PROGRESSBAR_H - -#include -#include -#include -#include - -namespace Ui { -class ProgressBar; -} - -class ProgressBar : public QWidget { - Q_OBJECT - -public: - enum PlayState { - Play, - Pause - }; - explicit ProgressBar(QWidget* parent = 0); - ~ProgressBar(); - void showMax(); - void setDuration(int dur); - void setCurrent(int cur); - void setState(PlayState state); - inline QString msecToString(int msc); - -private slots: - void onTimeout(); - -private: - Ui::ProgressBar* ui; - int duration; - QString durationStr; - - QTimer* timer; - bool isMax = true; - - QString sliderMaxStyles; - QString sliderMinStyles; -}; - -inline QString ProgressBar::msecToString(int msc) -{ - QString formatStr = QTime(0, 0, 0).addMSecs(msc).toString(QString::fromUtf8("hh:mm:ss")); - return formatStr; -} - -#endif // PROGRESSBAR_H diff --git a/ProgressBar.ui b/ProgressBar.ui deleted file mode 100755 index abc7643..0000000 --- a/ProgressBar.ui +++ /dev/null @@ -1,144 +0,0 @@ - - - ProgressBar - - - - 0 - 0 - 776 - 100 - - - - Form - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QWidget#widget { - background-color: rgba(0, 0, 0, 0.7); -} - - - - 0 - - - 0 - - - 30 - - - 0 - - - 0 - - - - - - - - 999999999 - - - Qt::Horizontal - - - - - - - - 0 - 40 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 30 - 30 - - - - - - - :/images/begin.png - - - - - - - - 16777215 - 30 - - - - - 10 - - - - QLabel { - color: #ffffff; -} - - - 00:00:00/00:00:00 - - - - - - - - - - - - - - diff --git a/RecordControlApplication.pro b/RecordControlApplication.pro index 6024e31..d8a8898 100755 --- a/RecordControlApplication.pro +++ b/RecordControlApplication.pro @@ -34,7 +34,6 @@ SOURCES += \ TcpServer.cpp \ Channel.cpp \ Menu.cpp\ - ProgressBar.cpp \ Tool.cpp \ CheckStorageThread.cpp \ Constant.h \ @@ -45,14 +44,14 @@ SOURCES += \ TcpResponse.cpp \ SerialPortTool.cpp \ FocusWindow.cpp \ - DatabaseManager.cpp + DatabaseManager.cpp \ + TimeSlider.cpp HEADERS += \ Widget.h \ TcpServer.h \ Channel.h \ Menu.h \ - ProgressBar.h \ Tool.h \ CheckStorageThread.h \ Log.h \ @@ -62,11 +61,11 @@ HEADERS += \ TcpResponse.h \ SerialPortTool.h \ FocusWindow.h \ - DatabaseManager.h + DatabaseManager.h \ + TimeSlider.h FORMS += \ Menu.ui \ - ProgressBar.ui \ Widget.ui RESOURCES += \ diff --git a/SerialPortTool.cpp b/SerialPortTool.cpp index 87d0ddd..9d69746 100644 --- a/SerialPortTool.cpp +++ b/SerialPortTool.cpp @@ -28,7 +28,6 @@ SerialPortTool::SerialPortTool(QObject* parent) .toLatin1()); }; - cmdMap[Reset] = makeCommand(2, "00", "00"); cmdMap[PlaybackStart] = makeCommand(2, "01", "01"); cmdMap[PlaybackEnd] = makeCommand(2, "01", "00"); cmdMap[RecordStart] = makeCommand(2, "02", "01"); @@ -47,10 +46,6 @@ SerialPortTool::SerialPortTool(QObject* parent) cmdMap[MainChannelOff] = makeCommand(2, "09", "00"); cmdMap[SecondaryChannelOn] = makeCommand(2, "0A", "01"); cmdMap[SecondaryChannelOff] = makeCommand(2, "0A", "00"); - cmdMap[MainChannelHDMI] = makeCommand(3, "03", "00"); - cmdMap[MainChannelVGA] = makeCommand(3, "03", "01"); - cmdMap[SecondaryChannelHDMI] = makeCommand(3, "04", "00"); - cmdMap[SecondaryChanneVGA] = makeCommand(3, "04", "01"); } SerialPortTool::~SerialPortTool() @@ -193,12 +188,6 @@ void SerialPortTool::onReayRead() } } -void SerialPortTool::onReset() -{ - serialPort->write(cmdMap.value(Reset)); - serialPort->flush(); -} - void SerialPortTool::onPlaybackStart() { serialPort->write(cmdMap.value(PlaybackStart)); @@ -307,30 +296,3 @@ void SerialPortTool::onSecondaryChannelOff() serialPort->flush(); } -void SerialPortTool::onMainChannelHDMI() -{ - qDebug() << "main channel hdmi:" << cmdMap.value(MainChannelHDMI); - serialPort->write(cmdMap.value(MainChannelHDMI)); - serialPort->flush(); -} - -void SerialPortTool::onMainChannelVGA() -{ - qDebug() << "main channel vga:" << cmdMap.value(MainChannelVGA); - serialPort->write(cmdMap.value(MainChannelVGA)); - serialPort->flush(); -} - -void SerialPortTool::onSecondaryChannelHDMI() -{ - qDebug() << "secondary channel hdmi:" << cmdMap.value(SecondaryChannelHDMI); - serialPort->write(cmdMap.value(SecondaryChannelHDMI)); - serialPort->flush(); -} - -void SerialPortTool::onSecondaryChannelVGA() -{ - qDebug() << "secondary channel vga:" << cmdMap.value(SecondaryChanneVGA); - serialPort->write(cmdMap.value(SecondaryChanneVGA)); - serialPort->flush(); -} diff --git a/SerialPortTool.h b/SerialPortTool.h index 59db559..c7a4163 100644 --- a/SerialPortTool.h +++ b/SerialPortTool.h @@ -14,29 +14,33 @@ public: void open(); void close(); public slots: - void onReset(); void onPlaybackStart(); void onPlaybackEnd(); + void onRecordStart(); void onRecordEnd(); + void onPlayPause(); void onPlayResume(); + void onDiskWillFull(); void onDiskNotFull(); + void onPowerOn(); void onPowerOff(); + void onFoward(); void onBack(); + void onLoopOn(); void onLoopOff(); + void onMainChannelOn(); void onMainChannelOff(); + void onSecondaryChannelOn(); void onSecondaryChannelOff(); - void onMainChannelHDMI(); - void onMainChannelVGA(); - void onSecondaryChannelHDMI(); - void onSecondaryChannelVGA(); + private slots: void onSerialError(QSerialPort::SerialPortError error); diff --git a/TimeSlider.cpp b/TimeSlider.cpp new file mode 100644 index 0000000..63be4f8 --- /dev/null +++ b/TimeSlider.cpp @@ -0,0 +1,213 @@ +#include "TimeSlider.h" +#include +#include +#include + +#define MARGIN_LEFT 15 // 左边距 +#define MARGIN_RIGHT 15 // 又边距 +#define LONG_SCALE_LENGTH 10 // 长刻度的长度 +#define SHORT_SCALE_LENGTH 6 // 段刻度的长度 +#define SMALL_SCALE_COUNT 5 // 下刻度的数量 +#define TIME_BAR_HEIGHT 20 // 时间进度条高度 + +TimeSlider::TimeSlider(QWidget* parent) + : QWidget(parent) +{ + setMinimumSize(QSize(100, 90)); +} + +/** + * @brief 重写绘制函数 + * @param event + */ +void TimeSlider::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + // 有焦点绘制绿色边框 + if (hasFocus()) { + borderColor = QColor(15, 216, 82, opacity * 255); + } else { + borderColor = QColor(0, 0, 0, opacity * 255); + } + painter.setPen(QPen(borderColor, borderWidth)); + // 绘制背景颜色 + painter.setBrush(QColor(50, 50, 50, opacity * 255)); + painter.drawRect(rect().adjusted(borderWidth / 2, borderWidth / 2, -borderWidth, -borderWidth)); + // 绘制时间条 + painter.setPen(QPen(QColor { 255, 255, 255, opacity * 255 }, 1)); + painter.drawRect(MARGIN_LEFT, height() / 2, width() - MARGIN_LEFT - MARGIN_RIGHT, TIME_BAR_HEIGHT); + // 绘制时间刻度 + drawTimeScale(painter); + // 绘制当前时间 + if (curScaleVisible) { + drawCurrentTime(painter); + } + // 绘制具体的时间段信息 + drawTimeSegments(painter); + QWidget::paintEvent(event); +} + +/** + * @brief 绘制当前的时间 + * @param painter + */ +void TimeSlider::drawCurrentTime(QPainter& painter) +{ + float x = MARGIN_LEFT + (current - min) * (width() - MARGIN_LEFT - MARGIN_LEFT) / (max - min); + QString text = secToString(current, "hh:mm:ss"); + QFont font; + font.setFamily("Microsoft YaHei"); + font.setPointSize(9); + painter.setFont(font); + QFontMetrics fm(painter.font()); + QRect textRect = fm.boundingRect(text); + // 当时间接近24:00:00时,文字超出显示范围则把文字定在右上角 + if (x + textRect.width() >= rect().right()) { + textRect.moveTopRight(QPoint(rect().right(), borderWidth)); + } else { + textRect.moveTopLeft(QPoint(x, borderWidth)); + } + if (x >= width() - MARGIN_LEFT) { + x = width() - MARGIN_LEFT; + } + // 设置文字颜色位黄色以及背景为黑色 + painter.setPen(QColor(255, 248, 33, opacity * 255)); + QBrush brush(QColor(0, 0, 0, opacity * 255)); + painter.fillRect(textRect, brush); + painter.drawText(textRect, text); + painter.drawLine(x, borderWidth, x, height() - borderWidth); +} + +/** + * @brief 绘制时间刻度 + * @param paint + */ +void TimeSlider::drawTimeScale(QPainter& painter) +{ + int scale = min; + // 绘制刻度线 + while (scale % 3600 != 0) { + scale++; + } + QFont font; + font.setFamily("Microsoft YaHei"); + font.setPointSize(9); + painter.setFont(font); + // 绘制 + while (scale <= max) { + float x = MARGIN_LEFT + (scale - min) * (width() - MARGIN_LEFT - MARGIN_RIGHT) / (max - min); + // 整点绘制大刻度和文字 + if (scale % 3600 == 0) { + // 绘制大刻度 + painter.drawLine(x, height() / 2 - LONG_SCALE_LENGTH, x, height() / 2); + QString text = secToString(scale, "hh"); + QFontMetrics fm(painter.font()); + QRect textRect = fm.boundingRect(text); + textRect.moveCenter(QPoint(x, height() / 2 - LONG_SCALE_LENGTH - textRect.height() / 2)); + painter.drawText(textRect, text); + } + // 绘制小刻度 + else { + painter.drawLine(x, height() / 2 - SHORT_SCALE_LENGTH, x, height() / 2); + } + scale += 3600 / SMALL_SCALE_COUNT; + } +} + +/** + * @brief 绘制具体时间段的信息 + * @param painter + */ +void TimeSlider::drawTimeSegments(QPainter& painter) +{ + painter.setPen(Qt::NoPen); + for (const auto& segment : segments) { + int endTime = segment.startTime + segment.duration; + float startX = MARGIN_LEFT + (segment.startTime - min) * (width() - MARGIN_LEFT - MARGIN_LEFT) / (max - min); + float endX = MARGIN_LEFT + (endTime - min) * (width() - MARGIN_LEFT - MARGIN_LEFT) / (max - min); + if (endX >= width() - MARGIN_LEFT) { + endX = width() - MARGIN_LEFT; + } + painter.setBrush(QColor(0, 255, 255, opacity * 255)); + painter.drawRect(startX, height() / 2, endX - startX, TIME_BAR_HEIGHT); + } +} + +/** + * @brief 设置是否显示当前时间刻度 + * @param visible + */ +void TimeSlider::setCurScaleVisble(bool visible) +{ + curScaleVisible = visible; +} + +/** + * @brief 设置当前的时间分片 + * @param timeSegments + */ +void TimeSlider::setTimeSegments(QList& segments) +{ + this->segments = segments; +} + +/** + * @brief 设置当前时间,单位秒数 + * @param current + */ +void TimeSlider::setCurrent(int current) +{ + this->current = current; + update(); +} + +/** + * @brief 将当前设置为下一段 + */ +void TimeSlider::nextSegment() +{ + // for (auto it = segments.begin(); it != segments.end(); it++) { + // if (it->startTime > current) { + // current = it->startTime; + // repaint(); + // break; + // } + // } +} + +/** + * @brief 将当前设置为下一段 + */ +void TimeSlider::preSegment() +{ + // for(auto it = segments.rbegin(); it != segments.rend(); it++){ + // if(it->startTime < current){ + // current = it->startTime; + // repaint(); + // break; + // } + // } +} + +/** + * @brief 设置背景色 + * @param color + */ +void TimeSlider::setBackgroundColor(QColor color) +{ + backgroundColor = color; +} + +/** + * @brief 设置透明度 + * @param opa + */ +void TimeSlider::setOpacity(float opa) +{ + if (opa < 0) { + opa = 0; + } else if (opa > 1) { + opa = 1; + } + opacity = opa; +} \ No newline at end of file diff --git a/TimeSlider.h b/TimeSlider.h new file mode 100644 index 0000000..b692e60 --- /dev/null +++ b/TimeSlider.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * @file TimeSlider.h + * @brief 自定义绘制一个时间轴控件 + * @author LuoXiang + * @date 2024/08/28 + *****************************************************************************/ + +#ifndef TIMESLIDER_H +#define TIMESLIDER_H + +#include +#include +#include +#include + +class TimeSlider : public QWidget { + Q_OBJECT +public: + struct TimeSegment { + int startTime; // 开始时间,转成秒数 + int duration; // 时长,秒数 + QString filename; // 当前时间片的文件名 + }; + explicit TimeSlider(QWidget* parent = nullptr); + void setCurrent(int current); + void setTimeSegments(QList& segments); + void setCurScaleVisble(bool visible); + void nextSegment(); + void preSegment(); + void setBackgroundColor(QColor color); + void setOpacity(float opacity); + +protected: + void paintEvent(QPaintEvent* event) override; + void focusInEvent(QFocusEvent* event) override + { + repaint(); + QWidget::focusInEvent(event); + } + void focusOutEvent(QFocusEvent* event) override + { + repaint(); + QWidget::focusInEvent(event); + } + +signals: + +private: + int min = 0; // 起始时间(秒数) + int current = 0; // 当前秒数 + int max = 24 * 60 * 60; // 终止时间(秒数) + QList segments; // 时间片 + bool curScaleVisible = true; + QColor backgroundColor = QColor(50, 50, 50); + float opacity = 1.0; + int borderWidth = 3; + QColor borderColor; + +private: + void drawCurrentTime(QPainter& painter); + void drawTimeScale(QPainter& painter); + void drawTimeSegments(QPainter& painter); + inline QString secToString(int sec, QString format); +}; + +inline QString TimeSlider::secToString(int sec, QString format) +{ + return QTime(0, 0, 0).addSecs(sec).toString(format); +} + +#endif // TIMESLIDER_H diff --git a/Tool.cpp b/Tool.cpp index e1c2cf3..0c311a9 100755 --- a/Tool.cpp +++ b/Tool.cpp @@ -1,5 +1,7 @@ #include "Tool.h" +#include "DatabaseManager.h" #include "QTimer" +#include "TimeSlider.h" #include #include #include @@ -137,4 +139,39 @@ double Tool::getCostTime(std::function callback, const char* callbac double time = (double)mstimer.nsecsElapsed() / (double)1000000; qDebug() << callbackName << "cast time:" << time << "ms"; return time; +} + +/** + * @brief 从数据库中获取某天的文件信息 + * @param chn + * @param params + * @return + */ +QList Tool::calTimeSegments(QVariantMap params) +{ + QList result; + DatabaseManager* db = DatabaseManager::getInstace(); + QList fileList = db->get(params); + if (fileList.isEmpty()) { + return result; + } + // 构造时间片信息列表 + for (const auto& file : fileList) { + TimeSlider::TimeSegment seg; + QString startTime; + QStringList timeList = file.datetime.split(" "); + if (timeList.length() < 2) { + return result; + } + seg.startTime = QTime(0, 0, 0).secsTo(QTime::fromString(timeList[1], "hh:mm:ss")); + seg.duration = file.duration; + seg.filename = file.filename; + result.push_back(seg); + } + + // 对时间片信息进行排序 + std::sort(result.begin(), result.end(), [](const TimeSlider::TimeSegment& a, const TimeSlider::TimeSegment& b) { + return a.startTime < b.startTime; + }); + return result; } \ No newline at end of file diff --git a/Tool.h b/Tool.h index d427a01..a2bdf4d 100755 --- a/Tool.h +++ b/Tool.h @@ -1,5 +1,6 @@ #ifndef TOOL_H #define TOOL_H +#include "TimeSlider.h" #include #include @@ -17,6 +18,8 @@ public: static int64_t getAvailableStorage(QString mountedPath); static double getCostTime(std::function callback, const char* callbackName = ""); + + static QList calTimeSegments(QVariantMap params); }; #endif // TOOL_H diff --git a/Widget.cpp b/Widget.cpp index 2517dc6..5651f50 100755 --- a/Widget.cpp +++ b/Widget.cpp @@ -7,11 +7,16 @@ #include "TcpServer.h" #include "Tool.h" #include "ui_Widget.h" +#include +#include #include #include #include #include +// 认为两个视频是连续的时间间隔阈值 +#define TIME_GAP_THRRSHOLD 2 + // 多线程中使用 QMutex mutex; QWaitCondition condition; @@ -31,12 +36,16 @@ Widget::Widget(QWidget* parent) ui->lblTxtA->setText("未录制"); ui->lblTxtB->setText("未录制"); ui->recordWidget->hide(); + ui->timeSlider->hide(); + ui->timeSlider->setOpacity(0.5); menu = new Menu(); menu->hide(); // 设置是否显示通道选择 menu->setChannelSelectVisible(playbackMode == Constant::OneChannelPlayback); - ui->progressBar->hide(); + connect(menu, SIGNAL(btnPlaybackClicked(QVariantMap, int)), this, SLOT(onBtnPlaybackClicked(QVariantMap, int))); + connect(menu, SIGNAL(resolutionOutAChanged(int)), this, SLOT(onResolutionOutAChanged(int))); + connect(menu, SIGNAL(resolutionOutBChanged(int)), this, SLOT(onResolutionOutBChanged(int))); // 每5秒更新一次进度条 progressTimer = new QTimer(); @@ -46,14 +55,17 @@ Widget::Widget(QWidget* parent) for (Channel* chn : channelList) { connect(chn, SIGNAL(playEnd()), this, SLOT(onPlayEnd())); connect(chn, SIGNAL(showRecordState(bool)), this, SLOT(onShowRecordLabel(bool))); - connect(chn, SIGNAL(appendOneVideo(QString)), menu, SLOT(onAppendOneVideo(QString))); + connect(chn, SIGNAL(appendOneVideo()), this, SLOT(onAppendVideo())); // 监听输入信号的变化 connect(chn->videoInput, &LinkObject::newEvent, [=](QString type, QVariant msg) { if (type == "signal") { QVariantMap data = msg.toMap(); bool available = data["avalible"].toBool(); if (available) { - Log::info("video input {} is available", chn->channelName == Constant::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel); + Log::info("video input {} is available", + chn->channelName == Constant::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel); if (chn->channelName == Constant::MainChannel) { serialPortTool->onMainChannelOn(); QThread::msleep(100); @@ -62,7 +74,10 @@ Widget::Widget(QWidget* parent) QThread::msleep(100); } } else { - Log::info("video input {} is not available", chn->channelName == Constant::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel); + Log::info("video input {} is not available", + chn->channelName == Constant::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel); if (chn->channelName == Constant::MainChannel) { serialPortTool->onMainChannelOff(); QThread::msleep(100); @@ -84,28 +99,39 @@ Widget::Widget(QWidget* parent) connect(serialPortTool, SIGNAL(btnReturnClicked()), this, SLOT(onBtnReturnClicked())); connect(serialPortTool, SIGNAL(btnVolumnUpClicked()), this, SLOT(onBtnVolumnUpClicked())); connect(serialPortTool, SIGNAL(btnVolumnDownClicked()), this, SLOT(onBtnVolumnDownClicked())); - - connect(menu, SIGNAL(btnVideoClicked(QString)), this, SLOT(onBtnVideoClicked(QString))); - connect(this, SIGNAL(needPlayVideo(QString)), menu, SLOT(clickPreOrNext(QString))); } Widget::~Widget() { delete ui; delete progressTimer; - delete menu; + // delete menu; } /** - * @brief 更新进度条 + * @brief 实时获取当前回放视频的位置 */ void Widget::onProgressTimeout() { - Channel* chn = findChannelByName(Constant::MainChannel); + // 获取当前回放的通道名称 + QString channelName; + // 如果时一路回放则取当前回放的时候 + if (playbackMode == Constant::OneChannelPlayback) { + DatabaseManager::Channel curChn = static_cast(playbackParams.value("channel", 1).toInt()); + channelName = curChn == DatabaseManager::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel; + } + // 否则取主通道 + else { + channelName = Constant::MainChannel; + } + Channel* chn = findChannelByName(channelName); if (chn) { - // 获取当前播放位置,单位ms - int pos = chn->inputFile->invoke("getPosition").toInt(); - ui->progressBar->setCurrent(pos); + // 获取当前播放位置,单位s + int pos = chn->inputFile->invoke("getPosition").toInt() / 1000; + int cur = segments[curSegmentIndex].startTime + pos; + ui->timeSlider->setCurrent(cur); } } @@ -116,6 +142,7 @@ void Widget::onBtnMenuClicked() { if (!menu->isVisible()) { menu->show(); + menu->moveToCenter(); } } @@ -138,11 +165,9 @@ void Widget::onBtnReturnClicked() serialPortTool->onPlaybackEnd(); } } - // 隐藏进度条并关闭定时器 - ui->progressBar->hide(); progressTimer->stop(); - // 显示录制状态 - // ui->recordWidget->show(); + isPlayback = false; + ui->timeSlider->hide(); } } @@ -155,8 +180,9 @@ void Widget::onBtnUpClicked() menu->move(Menu::Up); return; } + // 时间轴选中移动到上一个视频的开始 if (isPlayback) { - emit needPlayVideo("previous"); + playPreSegment(); } } @@ -169,8 +195,9 @@ void Widget::onBtnDownClicked() menu->move(Menu::Down); return; } + // 时间轴移动到下一个视频 if (isPlayback) { - emit needPlayVideo("next"); + playNextSegemnt(); } } @@ -217,13 +244,10 @@ void Widget::onBtnConfirmClicked() if (chn->state == Channel::Playback || chn->state == Channel::Pause) { chn->togglePause(); if (chn->channelName == Constant::MainChannel) { - ui->progressBar->showMax(); if (chn->state == Channel::Pause) { - ui->progressBar->setState(ProgressBar::Pause); // 打开暂停灯 serialPortTool->onPlayPause(); } else { - ui->progressBar->setState(ProgressBar::Play); // 关闭暂停灯 serialPortTool->onPlayResume(); } @@ -250,28 +274,100 @@ void Widget::onBtnVolumnDownClicked() } /** - * @brief 回放视频 + * @brief 播放视频 + * @param params 参数(通道,时间) + * @param segmentIndex 时间片索引 */ -void Widget::onBtnVideoClicked(QString filename) +void Widget::onBtnPlaybackClicked(QVariantMap params, int segmentIndex) { - if (filename == "first") { - qDebug() << "click previous failed, already the first video"; - // 显示提示弹窗 - } else if (filename == "last") { - if (playbackMode == Constant::OneChannelPlayback) { - Channel* chn = findChannelByName(Constant::MainChannel); - chn->showFinishPromot(); - } else { - for (Channel* chn : channelList) - chn->showFinishPromot(); - } + if (segmentIndex < 0) { + return; + } + // 获取当前通道 + segments = Tool::calTimeSegments(params); + if (segmentIndex > segments.length() - 1) { + return; + } + playbackParams = params; + curSegmentIndex = segmentIndex; + isPlayback = true; + // 显示时间轴 + ui->timeSlider->show(); + ui->timeSlider->setTimeSegments(segments); + ui->timeSlider->setCurrent(segments[segmentIndex].startTime); + + QString filename = segments[segmentIndex].filename; + // 开始回放 + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); } else { - if (playbackMode == Constant::OneChannelPlayback) { - playOneChannel(filename); - } else { - playTwoChannels(filename); + playTwoChannels(filename); + } +} + +/** + * @brief 通道A分辨率修改 + * @param resolution 分辨率字符串 + */ +void Widget::onResolutionOutAChanged(int resolution) +{ + for (Channel* chn : channelList) { + if (chn->channelName == Constant::MainChannel) { + chn->changeOutputResolution(static_cast(resolution)); + switch (resolution) { + case 0: + setFixedSize(1920, 1080); + break; + case 1: + setFixedSize(1600, 1200); + break; + case 2: + setFixedSize(1280, 1024); + break; + case 3: + setFixedSize(1024, 768); + break; + case 4: + setFixedSize(800, 600); + break; + default: + break; + } + menu->restartUI(); + menu->update(); + update(); + } + } +} + +/** + * @brief 通道B分辨率修改 + * @param resolution + */ +void Widget::onResolutionOutBChanged(int resolution) +{ + for (Channel* chn : channelList) { + if (chn->channelName == Constant::SecondaryChannel) { + chn->changeOutputResolution(static_cast(resolution)); + } + } +} + +void Widget::onResolutionInAChanged(int width, int height) +{ + for (Channel* chn : channelList) { + if (chn->channelName == Constant::MainChannel) { + chn->changeInputResolution(width, height); + } + } +} + +void Widget::onResolutionInBChanged(int width, int height) +{ + for (Channel* chn : channelList) { + if (chn->channelName == Constant::SecondaryChannel) { + chn->changeInputResolution(width, height); } - serialPortTool->onPlaybackStart(); } } @@ -292,9 +388,7 @@ void Widget::playOneChannel(QString filename) bool ret = channel->startPlayback(path); if (!ret) { Log::info("play back error"); - ui->progressBar->hide(); } - isPlayback = true; // 全局保存当前播放的视频的文件名 mutex.lock(); @@ -302,13 +396,6 @@ void Widget::playOneChannel(QString filename) condition.wakeAll(); mutex.unlock(); - int duration = channel->playbackDuration; - ui->progressBar->setDuration(duration); - ui->progressBar->setCurrent(0); - ui->progressBar->show(); - ui->progressBar->showMax(); - ui->progressBar->setState(ProgressBar::Play); - if (progressTimer->isActive()) { progressTimer->stop(); progressTimer->start(); @@ -328,12 +415,6 @@ void Widget::playTwoChannels(QString filename) int ret = chn->startPlayback(path); if (chn->channelName == Constant::MainChannel) { if (ret) { - int duration = chn->playbackDuration; - ui->progressBar->setDuration(duration); - ui->progressBar->setCurrent(0); - ui->progressBar->show(); - ui->progressBar->showMax(); - ui->progressBar->setState(ProgressBar::Play); if (progressTimer->isActive()) { progressTimer->stop(); progressTimer->start(); @@ -342,7 +423,6 @@ void Widget::playTwoChannels(QString filename) } ui->recordWidget->hide(); } else { - ui->progressBar->hide(); ui->recordWidget->hide(); } } @@ -362,18 +442,86 @@ void Widget::seek(QString type) { if (!isPlayback) return; - for (Channel* chn : channelList) { - if (chn->state == Channel::Pause || chn->state == Channel::Playback) { - if (type == "forward") { - chn->forward(); - serialPortTool->onFoward(); - } else { - chn->back(); - serialPortTool->onBack(); + QString channelName; + if (playbackMode == Constant::OneChannelPlayback) { + DatabaseManager::Channel curChn = static_cast(playbackParams.value("channel", 1).toInt()); + channelName = curChn == DatabaseManager::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel; + } else { + channelName = Constant::MainChannel; + } + Channel* chn = findChannelByName(channelName); + int pos; + // 获取当前播放的位置 + if (chn) { + pos = chn->inputFile->invoke("getPosition").toInt() / 1000; + } + if (type == "forward") { + pos += 10; + // 目标位置超过当前视频的时长 + if (pos > segments[curSegmentIndex].duration) { + // 如果是最后一个视频,则跳转到视频最后为止 + if (curSegmentIndex == segments.length() - 1) { + pos = segments[curSegmentIndex].duration; } + // 如果不是最后一个视频 + else { + int gap = segments[curSegmentIndex + 1].startTime + - segments[curSegmentIndex].startTime + - segments[curSegmentIndex].duration; + // 视频是连续的就跳转到响应为止,认为连续的条件是间隔小于2S + if (gap <= TIME_GAP_THRRSHOLD) { + pos = pos - segments[curSegmentIndex].duration - gap; + curSegmentIndex++; + QString filename = segments[curSegmentIndex].filename; + // 开始回放 + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); + } else { + playTwoChannels(filename); + } + } + // 视频不是连续的 + else { + pos = segments[curSegmentIndex].duration; + } + } + } - if (chn->channelName == Constant::MainChannel) - ui->progressBar->showMax(); + } else { + pos -= 10; + // 目标位置小于0 + if (pos < 0) { + if (curSegmentIndex == 0) { + pos = 0; + } else { + int gap = segments[curSegmentIndex].startTime + - segments[curSegmentIndex - 1].startTime + - segments[curSegmentIndex - 1].duration; + // 视频是连续的 + if (gap <= TIME_GAP_THRRSHOLD) { + pos = segments[curSegmentIndex - 1].duration + gap + pos; + curSegmentIndex--; + QString filename = segments[curSegmentIndex].filename; + // 重新回放 + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); + } else { + playTwoChannels(filename); + } + } + // 视频不是连续的 + else { + pos = 0; + } + } + } + } + for (Channel* chn : channelList) { + if (chn->state == Channel::Playback) { + chn->inputFile->invoke("seek", pos * 1000); + Log::info("{} seek {} 10s", chn->channelName.toStdString(), type.toStdString()); } } } @@ -396,7 +544,23 @@ void Widget::onPlayEnd() } } } - emit needPlayVideo("next"); + // 如果时列表最后一个视频则显示播放结束 + if (curSegmentIndex == segments.length() - 1) { + for (Channel* chn : channelList) { + chn->showFinishPromot(); + } + } + // 不是列表最后一个就播放下一个视频 + else { + curSegmentIndex++; + ui->timeSlider->setCurrent(segments[curSegmentIndex].startTime); + QString filename = segments[curSegmentIndex].filename; + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); + } else { + playTwoChannels(filename); + } + } } /** @@ -437,3 +601,82 @@ Channel* Widget::findChannelByName(QString name) } return nullptr; } + +/** + * @brief 播放下一个视频片段 + */ +void Widget::playNextSegemnt() +{ + if (curSegmentIndex < segments.length() - 1) { + curSegmentIndex++; + // 设置时间轴指针的位置 + int cur = segments[curSegmentIndex].startTime; + ui->timeSlider->setCurrent(cur); + // 跳转到下一个视频 + QString filename = segments[curSegmentIndex].filename; + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); + } else { + playTwoChannels(filename); + } + Log::info("play next segment, filename: {}", filename.toStdString()); + } +} + +/** + * @brief 播放上一个视频片段 + */ +void Widget::playPreSegment() +{ + if (curSegmentIndex > 0) { + curSegmentIndex--; + // 设置时间轴指针的位置 + int cur = segments[curSegmentIndex].startTime; + ui->timeSlider->setCurrent(cur); + // 跳转到下一个视频 + QString filename = segments[curSegmentIndex].filename; + if (playbackMode == Constant::OneChannelPlayback) { + playOneChannel(filename); + } else { + playTwoChannels(filename); + } + Log::info("play previous segment, filename: {}", filename.toStdString()); + } +} + +/** + * @brief 一个录制片段完成的槽函数 + */ +void Widget::onAppendVideo() +{ + if (!isPlayback) { + return; + } + Channel* chn = static_cast(sender()); + // 两路回放需要是主通道 + if (playbackMode == Constant::TwoChannelPlayback) { + if (chn->channelName != Constant::MainChannel) { + return; + } + } + // 一路回放需要回放通道和录制通道一致 + else { + DatabaseManager::Channel c = static_cast(playbackParams.value("channel", 1).toInt()); + QString channelName = c == DatabaseManager::MainChannel + ? Constant::MainChannel + : Constant::SecondaryChannel; + if (chn->channelName != channelName) { + return; + } + } + // 刷新时间轴中时间片的显示 + segments = Tool::calTimeSegments(playbackParams); + ui->timeSlider->setTimeSegments(segments); +} + +void Widget::update() +{ + QRect deskRect = QApplication::desktop()->availableGeometry(); + qDebug() << "desktop rect:" << deskRect; + QWidget::update(); +} diff --git a/Widget.h b/Widget.h index 56bbd34..5c507c5 100755 --- a/Widget.h +++ b/Widget.h @@ -3,7 +3,7 @@ #include "Channel.h" #include "Menu.h" -#include "ProgressBar.h" +#include "TimeSlider.h" #include #include #include @@ -22,10 +22,13 @@ public: signals: void needPlayVideo(QString types); -private slots: +public slots: + void update(); + void onProgressTimeout(); void onPlayEnd(); void onShowRecordLabel(bool show); + void onAppendVideo(); // 按键操作。上下左右确认返回 void onBtnMenuClicked(); @@ -39,20 +42,33 @@ private slots: void onBtnVolumnDownClicked(); // 播放视频 - void onBtnVideoClicked(QString filename); + void onBtnPlaybackClicked(QVariantMap params, int segmentIndex); + + // 分辨率更改 + void onResolutionOutAChanged(int resolution); + void onResolutionOutBChanged(int resolution); + + void onResolutionInAChanged(int width, int height); + void onResolutionInBChanged(int width, int height); private: Ui::Widget* ui; - QTimer* progressTimer; - bool isPlayback = false; - Menu* menu; + Menu* menu; // 菜单指针 + + bool isPlayback = false; // 是否在回放 + QTimer* progressTimer; // 查询播放进度的定时器 + QList segments; // 当天回放的时间段 + int curSegmentIndex = 0; // 当前回放的时间段 + QVariantMap playbackParams; // 回放参数(通道和时间) private: void seek(QString type); void playOneChannel(QString filename); void playTwoChannels(QString filename); + void playNextSegemnt(); + void playPreSegment(); Channel* findChannelByName(QString name); }; -#endif // WIDG_H +#endif // WIDG_H \ No newline at end of file diff --git a/Widget.ui b/Widget.ui index 91eeabe..f77efec 100755 --- a/Widget.ui +++ b/Widget.ui @@ -217,7 +217,7 @@ - + @@ -226,9 +226,9 @@ - ProgressBar + TimeSlider QWidget -
ProgressBar.h
+
TimeSlider.h
1
diff --git a/main.cpp b/main.cpp index f5f58c6..653f728 100755 --- a/main.cpp +++ b/main.cpp @@ -15,15 +15,12 @@ #include // Tcp服务器 -TcpServer* server; +TcpServer* server; // 串口工具 SerialPortTool* serialPortTool; // 数据库管理 DatabaseManager* db; -// 视频输出通道 -LinkObject* vo; -LinkObject* vo1; -// 硬盘检测现场 +// 硬盘检测线程 CheckStorageThread* thread; // 通道列表 @@ -32,8 +29,6 @@ QList channelList; Constant::RecordMode recordMode; // 回放模式 Constant::PlaybackMode playbackMode; -QString mainChannelProtocol; -QString secondaryChannelProtocol; /** * @brief 加载配置文件 @@ -52,14 +47,13 @@ bool loadConfiguration(QString path) QVariantList list = config["interface"].toList(); // 初始化通道 for (int i = 0; i < list.count(); i++) { - Channel* chn = new Channel(); QVariantMap cfg = list.at(i).toMap(); - chn->channelName = cfg["name"].toString(); - if (chn->channelName == Constant::MainChannel) { - mainChannelProtocol = cfg["protocol"].toString(); - } else if (chn->channelName == Constant::SecondaryChannel) { - secondaryChannelProtocol = cfg["protocol"].toString(); + QString channelName = cfg["name"].toString(); + if (channelName.isEmpty()) { + return false; } + QVariantMap output = cfg["output"].toMap(); + Channel* chn = new Channel(channelName, output); QVariantMap encV = cfg["encV"].toMap(); chn->videoEncoderParams = encV; @@ -69,7 +63,6 @@ bool loadConfiguration(QString path) chn->pushCode = cfg["pushCode"].toString(); - chn->init(); channelList.push_back(chn); } return true; @@ -94,10 +87,8 @@ void startRecord() for (Channel* chn : channelList) { if (chn->channelName == Constant::MainChannel) { chn->startRecord(); - // QThread::msleep(100); serialPortTool->onRecordStart(); QThread::msleep(100); - // serialPortTool->onLoopOff(); } } } @@ -107,50 +98,19 @@ void startRecord() chn->startRecord(); serialPortTool->onRecordStart(); QThread::msleep(100); - // serialPortTool->onLoopOff(); - // QThread::msleep(100); } } - // 无录制 - else { - // 打开环路灯 - serialPortTool->onLoopOn(); - QThread::msleep(100); - } -} - -void setChannelProtocol() -{ - if (mainChannelProtocol == "HDMI") { - serialPortTool->onMainChannelHDMI(); - } else if (mainChannelProtocol == "VGA") { - serialPortTool->onMainChannelVGA(); - } - QThread::msleep(100); - if (secondaryChannelProtocol == "HDMI") { - serialPortTool->onSecondaryChannelHDMI(); - } else if (secondaryChannelProtocol == "VGA") { - serialPortTool->onSecondaryChannelVGA(); - } - QThread::msleep(100); } int main(int argc, char* argv[]) { - qDebug() << (R"( - ____ _ _____ - / ___|| |__ __ _ _ __ | ___|__ _ __ __ _ - \___ \| '_ \ / _` | '_ \| |_ / _ \ '_ \ / _` | - ___) | | | | (_| | | | | _| __/ | | | (_| | - |____/|_| |_|\__,_|_| |_|_| \___|_| |_|\__, | - |___/ )"); - qDebug() << (R"( - ____ _ _ _ __ __ _ - / ___|| |_ __ _ _ __| |_ ___ __| | \ \ / /__| | ___ ___ _ __ ___ ___ - \___ \| __/ _` | '__| __/ _ \/ _` | \ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \ - ___) | || (_| | | | || __/ (_| | \ V V / __/ | (_| (_) | | | | | | __/ - |____/ \__\__,_|_| \__\___|\__,_| \_/\_/ \___|_|\___\___/|_| |_| |_|\___| - )"); + // qDebug() << (R"( + // ____ _ _____ + // / ___|| |__ __ _ _ __ | ___|__ _ __ __ _ + // \___ \| '_ \ / _` | '_ \| |_ / _ \ '_ \ / _` | + // ___) | | | | (_| | | | | _| __/ | | | (_| | + // |____/|_| |_|\__,_|_| |_|_| \___|_| |_|\__, | + // |___/ )"); // std::atexit(resetLight); // 初始化日志 Log::init(); @@ -160,74 +120,26 @@ int main(int argc, char* argv[]) Log::error("Link init failed, exit"); return 0; } - // 在初始化QApplication之前初始化视频输出和linuxfb - // 否则不能显示qt的ui - - // HDMI-OUT0视频输出,只将ui输出在0口 - vo = Link::create("OutputVo"); - QVariantMap dataVo; - dataVo["ui"] = true; - dataVo["type"] = "hdmi"; - vo->start(dataVo); - - // HDMI-OUT1视频输出 - vo1 = Link::create("OutputVo"); - QVariantMap dataVo1; - dataVo1["ui"] = false; - dataVo1["type"] = "bt1120"; - vo1->start(dataVo1); - - /** - * 后续可能需要进行修改************************** - */ - - // HDMI-OUT1口特殊设置 - static int lastNorm = 0; - int norm = 0; - int ddr = 0; - // 获取到设置的输出分辨率 - QString str = "1080P60"; - if (str == "1080P60") - norm = 9; - else if (str == "1080P50") - norm = 10; - else if (str == "1080P30") - norm = 12; - else if (str == "720P60") - norm = 5; - else if (str == "720P50") - norm = 6; - else if (str == "3840x2160_30") { - norm = 14; - ddr = 1; - } - if (norm != lastNorm) { - lastNorm = norm; - QString cmd = "rmmod hi_lt8618sx_lp.ko"; - system(cmd.toLatin1().data()); - cmd = cmd.sprintf("insmod /ko/extdrv/hi_lt8618sx_lp.ko norm=%d USE_DDRCLK=%d", norm, ddr); - system(cmd.toLatin1().data()); - } - - QApplication a(argc, argv); - // 初始化配置文件, 需要在QApplication之后 if (!loadConfiguration(Constant::ConfigurationPath)) { Log::error("load configuration failed, exit..."); return 0; } + QApplication a(argc, argv); + + // 初始化通道配置,需要在QApplication之后进行 + for (const auto& chn : channelList) { + chn->init(); + } + // 打开串口 serialPortTool = new SerialPortTool(); serialPortTool->open(); - // setChannelProtocol(); - serialPortTool->onPowerOn(); // 打开电源灯 - QThread::msleep(100); - // 开启Tcp服务 - server = new TcpServer(); - server->listen(); + // server = new TcpServer(); + // server->listen(); // qt界面 Widget w; @@ -236,8 +148,8 @@ int main(int argc, char* argv[]) // 硬盘检测线程,检测硬盘容量 thread = new CheckStorageThread(); thread->start(); - QObject::connect(thread, SIGNAL(diskWillFull()), serialPortTool, SLOT(onDiskWillFull())); - QObject::connect(thread, SIGNAL(diskNotFull()), serialPortTool, SLOT(onDiskNotFull())); + QObject::connect(thread, SIGNAL(diskWillFull()), serialPortTool, SLOT(onDiskWillFull()), Qt::QueuedConnection); + QObject::connect(thread, SIGNAL(diskNotFull()), serialPortTool, SLOT(onDiskNotFull()), Qt::QueuedConnection); Log::info("start storage check thread..."); // 开始录制