From ab606d358acbf94e3bcbd1ea202758a7c713f78d Mon Sep 17 00:00:00 2001 From: zc Date: Wed, 17 Jan 2024 23:41:43 -0800 Subject: [PATCH] first commit --- .gitignore | 3 + BoxController.cpp | 277 +++++++++++++++++++++++++++++++ BoxController.h | 72 +++++++++ Channel.cpp | 304 +++++++++++++++++++++++++++++++++++ Channel.h | 89 ++++++++++ Config.cpp | 59 +++++++ Config.h | 17 ++ Menu.cpp | 42 +++++ Menu.h | 23 +++ Menu.ui | 100 ++++++++++++ README.md | 0 RecordControlApplication.pro | 47 ++++++ TcpController.cpp | 97 +++++++++++ TcpController.h | 29 ++++ TcpServer.cpp | 42 +++++ TcpServer.h | 22 +++ Widget.cpp | 249 ++++++++++++++++++++++++++++ Widget.h | 59 +++++++ Widget.ui | 98 +++++++++++ main.cpp | 79 +++++++++ 20 files changed, 1708 insertions(+) create mode 100644 .gitignore create mode 100644 BoxController.cpp create mode 100644 BoxController.h create mode 100644 Channel.cpp create mode 100644 Channel.h create mode 100644 Config.cpp create mode 100644 Config.h create mode 100644 Menu.cpp create mode 100644 Menu.h create mode 100644 Menu.ui create mode 100644 README.md create mode 100644 RecordControlApplication.pro create mode 100644 TcpController.cpp create mode 100644 TcpController.h create mode 100644 TcpServer.cpp create mode 100644 TcpServer.h create mode 100644 Widget.cpp create mode 100644 Widget.h create mode 100644 Widget.ui create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29d72c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +build/ +*.pro.user diff --git a/BoxController.cpp b/BoxController.cpp new file mode 100644 index 0000000..748d1aa --- /dev/null +++ b/BoxController.cpp @@ -0,0 +1,277 @@ +#include "BoxController.h" +#include +#include +#include + +const char* path = "/root/usb"; + +BoxController::BoxController(QObject* parent) + : QObject(parent) +{ + // init output in constructor; + videoOutput = Link::create("OutputVo"); + QVariantMap dataVo; + dataVo["type"] = "hdmi"; + dataVo["interface"] = "HDMI-OUT0"; + videoOutput->setData(dataVo); + videoOutput->start(); + + // init video duration + videoDuration = 1 * 60 * 1000; + recordTimer = new QTimer(); + recordTimer->setInterval(videoDuration); + recordTimer->stop(); + connect(recordTimer, SIGNAL(timeout()), this, SLOT(onTimeout())); + + // init record params + videoEncodeParams["codec"] = "h264"; + videoEncodeParams["width"] = 1920; + videoEncodeParams["height"] = 1080; + videoEncodeParams["bitrate"] = 4000; + videoEncodeParams["framerate"] = 30; + + audioEncodeParams["codec"] = "aac"; + audioEncodeParams["samplerate"] = 48000; + audioEncodeParams["bitrate"] = 128000; + audioEncodeParams["channels"] = 1; +} + +/** + * @brief initialize "Link" components + */ +void BoxController::init() +{ + // audio input by external microphone + audioInput = Link::create("InputAlsa"); + QVariantMap dataAi; + dataAi["path"] = "hw:0,0"; + dataAi["channels"] = 2; + audioInput->start(dataAi); + + // audio output by external speaker + audioOutput = Link::create("OutputAlsa"); + QVariantMap dataAo; + dataAo["path"] = "hw:0,0"; + audioOutput->start(dataAo); + + videoInputA = Link::create("InputVi"); + QVariantMap dataVi; + dataVi["interface"] = "HDMI-A"; + videoInputA->start(dataVi); + + // capture picture from video + snap = Link::create("EncodeV"); + QVariantMap dataSnap; + dataSnap["codec"] = "jpeg"; + dataSnap["snap"] = true; + dataSnap["width"] = videoEncodeParams.value("width"); + dataSnap["height"] = videoEncodeParams.value("height"); + snap->start(dataSnap); + videoInputA->linkV(snap); + // sleep 1s, if didnt sleep , function snap would return flase and capture failed + QThread::sleep(1); + + videoInputA->linkV(videoOutput); + + // video encode + videoEncoder = Link::create("EncodeV"); + videoEncoder->start(videoEncodeParams); + + // audio encode + audioEncoder = Link::create("EncodeA"); + audioEncoder->start(audioEncodeParams); + + // record + record = Link::create("Mux"); + QVariantMap dataMp4; + dataMp4["format"] = "mp4"; + dataMp4["mute"] = true; + record->setData(dataMp4); + videoInputA->linkV(videoEncoder)->linkV(record); + audioInput->linkA(audioEncoder)->linkA(record); + + // rtmp server + rtmpServer = Link::create("Mux"); + QVariantMap dataRtmp; + dataRtmp["path"] = "rtmp://127.0.0.1/live/stream"; + dataRtmp["mute"] = true; + rtmpServer->setData(dataRtmp); + videoInputA->linkV(videoEncoder)->linkV(rtmpServer); + audioInput->linkA(audioEncoder)->linkA(rtmpServer); + + // init file and decoder + file = Link::create("InputFile"); + file->start(); + videoDecoder = Link::create("DecodeV"); + videoDecoder->start(); + audioDecoder = Link::create("DecodeA"); + audioDecoder->start(); + file->linkV(videoDecoder)->linkV(videoOutput); + file->linkA(audioDecoder)->linkA(audioOutput); + + connect(file, &LinkObject::newEvent, [=](QString type, QVariant) { + if (type == "EOF") { + qDebug() << "one video playback end"; + } + }); +} + +/** + * @brief start record + */ +void BoxController::startRecord() +{ + if (!isMountDisk()) { + return; + } + QString curTime = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"); + QVariantMap dataRecord; + dataRecord["path"] = QString("%1/videos/%2.mp4").arg(path).arg(curTime); + record->setData(dataRecord); + record->start(); + isRecord = true; + + recordTimer->start(); + snap->invoke("snapSync", QString("%1/snap/%2.jpg").arg(path).arg(curTime)); +} + +/** + * @brief stop record + */ +void BoxController::stopRecord() +{ + record->stop(true); + isRecord = false; + recordTimer->stop(); +} + +/** + * @brief BoxController::onTimeout + */ +void BoxController::onTimeout() +{ + if (!isMountDisk()) { + return; + } + record->stop(true); + recordTimer->stop(); + + QString curTime = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"); + QVariantMap dataRecord; + dataRecord["path"] = QString("%1/videos/%2.mp4").arg(path).arg(curTime); + record->setData(dataRecord); + record->start(); + + recordTimer->start(); + snap->invoke("snapSync", QString("%1/snap/%2.jpg").arg(path).arg(curTime)); +} + +void BoxController::startRtmpServer() +{ + if (rtmpServer->getState() != "started") { + rtmpServer->start(); + qDebug() << "start rtmp server..."; + } +} + +/** + * @brief open console process, and use it by command + * @param com + * @return + */ +QString BoxController::writeCom(const QString& com) +{ + QProcess proc; + QStringList argList; + argList << "-c" << com; + proc.start("/bin/sh", argList); + // wait process start + proc.waitForFinished(); + proc.waitForReadyRead(); + // read data from console + QByteArray procOutput = proc.readAll(); + proc.close(); + return QString(procOutput); +} + +/** + * @brief judge the disk whether mounted + * @return + */ +bool BoxController::isMountDisk() +{ + QString mount = writeCom(QString("df %1").arg(path)); + if (!mount.contains(path)) + return false; + return true; +} + +/** + * @brief start playback and output hdmi signals + * @param fileName + */ +void BoxController::startPlayback(QString fileName) +{ + QString path = "/root/usb/videos/" + fileName; + videoInputA->unLinkV(videoOutput); + QVariantMap dataFile; + dataFile["path"] = path; + file->start(dataFile); + emit playListNeedHide(); +} + +/** + * @brief play live + */ +void BoxController::startPlayLive() +{ + videoInputA->linkV(videoOutput); + file->stop(true); + emit playListNeedHide(); +} + +/** + * @brief playback -10s + */ +void BoxController::back() +{ + qDebug() << "back"; + int curPos = file->invoke("getPosition").toInt(); + curPos -= 10 * 100; + file->invoke("seek", curPos); +} + +/** + * @brief playback +10s + */ +void BoxController::forward() +{ + qDebug() << "forward"; + int curPos = file->invoke("getPosition").toInt(); + curPos += 10 * 100; + file->invoke("seek", curPos); +} + +/** + * @brief playback pause + */ +void BoxController::pause() +{ + QString state = file->getState(); + if (state != "started") { + return; + } + file->invoke("pause", true); +} + +/** + * @brief playback resume + */ +void BoxController::resume() +{ + QString state = file->getState(); + if (state != "started") { + return; + } + file->invoke("pause", false); +} diff --git a/BoxController.h b/BoxController.h new file mode 100644 index 0000000..bfe7e89 --- /dev/null +++ b/BoxController.h @@ -0,0 +1,72 @@ +#ifndef BOXCONTROLLER_H +#define BOXCONTROLLER_H + +#include "Link.h" +#include +#include + +class BoxController : public QObject { + Q_OBJECT +public: + BoxController(QObject* parent = nullptr); + + // io + LinkObject* audioInput; + LinkObject* videoInputA; + LinkObject* videoInputB; + LinkObject* audioOutput; + LinkObject* videoOutput; + + // record + LinkObject* audioEncoder; + LinkObject* videoEncoder; + LinkObject* overlay; + LinkObject* record; + LinkObject* snap; + + // stream server + LinkObject* rtmpServer; + + // play + LinkObject* file; + LinkObject* videoDecoder; + LinkObject* audioDecoder; + + QVariantMap videoEncodeParams; + QVariantMap audioEncodeParams; + + // record flag + bool isRecord = false; + // video file format + QString format; + + void startRecord(); + void stopRecord(); + void startRtmpServer(); + void init(); + + void startPlayback(QString fileName); + void startPlayLive(); + void back(); + void forward(); + void pause(); + void resume(); + +signals: + void playListNeedHide(); + +private slots: + void onTimeout(); + +private: + // timer used to limit record time each time + QTimer* recordTimer; + // duration each video file + int videoDuration; + +private: + QString writeCom(const QString& com); + bool isMountDisk(); +}; + +#endif // BOXCONTROLLER_H diff --git a/Channel.cpp b/Channel.cpp new file mode 100644 index 0000000..4256958 --- /dev/null +++ b/Channel.cpp @@ -0,0 +1,304 @@ +#include "Channel.h" +#include +#include + +LinkObject* Channel::audioInput = nullptr; +LinkObject* Channel::audioOutput = nullptr; +LinkObject* Channel::rtspServer = nullptr; +LinkObject* Channel::file = nullptr; +LinkObject* Channel::audioDecoder = nullptr; +LinkObject* Channel::videoDecoder = nullptr; + +extern LinkObject* vo; +extern LinkObject* vo1; + +const char* VideoPath = "/root/usb/videos"; +const char* SnapPath = "/root/usb/snap"; + +Channel::Channel(QObject* parent) + : QObject(parent) +{ + timer = new QTimer(); + timer->setInterval(duration); + timer->stop(); + + videoInput = nullptr; + videoEncoder = nullptr; + videoOutput = nullptr; + audioEncoder = nullptr; + record = nullptr; + // file = nullptr; + // videoDecoder = nullptr; + // audioDecoder = nullptr; + rtsp = nullptr; + + snap = Link::create("EncodeV"); + overLay = Link::create("Overlay"); + + if (audioInput == nullptr) { + audioInput = Link::create("InputAlsa"); + QVariantMap dataIn; + dataIn["path"] = "hw:0,0"; + dataIn["channels"] = 2; + audioInput->start(dataIn); + } + if (audioOutput == nullptr) { + audioOutput = Link::create("OutputAlsa"); + QVariantMap dataOut; + dataOut["path"] = "hw:0,0"; + audioOutput->start(dataOut); + } + if (rtspServer == nullptr) { + rtspServer = Link::create("Rtsp"); + rtspServer->start(); + } + if (file == nullptr) { + file = Link::create("InputFile"); + file->start(); + + // one video playback end + connect(file, &LinkObject::newEvent, [=](QString type, QVariant) { + if (type == "EOF") { + qDebug() << "one video playback end"; + } + }); + } + if (audioDecoder == nullptr) { + audioDecoder = Link::create("DecodeA"); + audioDecoder->start(); + file->linkA(audioDecoder)->linkA(audioOutput); + } + if (videoDecoder == nullptr) { + videoDecoder = Link::create("DecodeV"); + videoDecoder->start(); + file->linkV(videoDecoder); + } + + connect(timer, SIGNAL(timeout()), this, SLOT(onTimeout())); +} + +/** + * @brief load configuration and init components + */ +void Channel::init() +{ + if (channelName.isEmpty()) { + qDebug() << "channel name is empty!"; + return; + } + if (channelName == "HDMI-A") { + videoOutput = vo; + } else { + videoOutput = vo1; + } + + // init video input by channel name + videoInput = Link::create("InputVi"); + QVariantMap dataVi; + dataVi["interface"] = channelName; + videoInput->start(dataVi); + + // start water mask + overLay->start(); + + // link video input and output, and add water mask + videoInput->linkV(overLay)->linkV(videoOutput); + + // capture picture from video + QVariantMap dataSnap; + dataSnap["codec"] = "jpeg"; + dataSnap["snap"] = true; + dataSnap["width"] = 1920; + dataSnap["height"] = 1080; + snap->start(dataSnap); + videoInput->linkV(snap); + // sleep 1s, if didnt sleep , function snap would return flase and capture failed + QThread::sleep(1); + + // video encode + videoEncoder = Link::create("EncodeV"); + videoEncoder->start(videoEncoderParams); + + // audio encode + audioEncoder = Link::create("EncodeA"); + audioEncoder->start(audioEncoderParams); + + // record + record = Link::create("Mux"); + QVariantMap dataMp4; + dataMp4["format"] = "mp4"; + record->setData(dataMp4); + videoInput->linkV(videoEncoder)->linkV(record); + audioInput->linkA(audioEncoder)->linkA(record); + + // rtsp + rtsp = Link::create("Mux"); + QVariantMap dataRtsp; + dataRtsp["path"] = QString("mem://%1").arg(pushCode); + dataRtsp["format"] = "rtsp"; + rtsp->start(dataRtsp); + videoInput->linkV(videoEncoder)->linkV(rtsp)->linkV(rtspServer); + audioInput->linkA(audioEncoder)->linkA(rtsp)->linkA(rtspServer); + + // init file and decoder + // file = Link::create("InputFile"); + // file->start(); + + // videoDecoder = Link::create("DecodeV"); + // videoDecoder->start(); + // audioDecoder = Link::create("DecodeA"); + // audioDecoder->start(); + + // file->linkV(videoDecoder)->linkV(videoOutput); + // file->linkA(audioDecoder)->linkA(audioOutput); +} + +/** + * @brief end previous record and start new record + */ +void Channel::onTimeout() +{ + record->stop(true); + timer->stop(); + + if (!isMountDisk()) { + return; + } + QString curTime = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"); + QVariantMap dataRecord; + dataRecord["path"] = QString("%1/%2/%3.mp4").arg(VideoPath).arg(channelName).arg(curTime); + record->setData(dataRecord); + record->start(); + + timer->start(); + snap->invoke("snapSync", QString("%1/%2/%3.jpg").arg(SnapPath).arg(channelName).arg(curTime)); +} + +/** + * @brief start record + */ +void Channel::startRecord() +{ + if (!isMountDisk()) { + return; + } + QString curTime = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"); + QVariantMap dataRecord; + // /usb/root/videos/HDMI-A/2024-1-1_10:00:00 + dataRecord["path"] = QString("%1/%2/%3.mp4").arg(VideoPath).arg(channelName).arg(curTime); + record->setData(dataRecord); + record->start(); + setOverlay("Record"); + + timer->start(); + snap->invoke("snapSync", QString("%1/%2/%3.jpg").arg(SnapPath).arg(channelName).arg(curTime)); +} + +/** + * @brief stop record + */ +void Channel::stopRecord() +{ + record->stop(true); + setOverlay("No Record"); + timer->stop(); +} + +/** + * @brief start playback and output hdmi signals + * @param fileName + */ +void Channel::startPlayback(QString fileName) +{ + qDebug() << channelName << "start play back, file name:" << fileName; + QString path = QString("%1/%2/%3").arg(VideoPath).arg(channelName).arg(fileName); + + // break video input and output + videoInput->unLinkV(videoOutput); + + file->linkV(videoDecoder)->linkV(videoOutput); + + QVariantMap dataFile; + dataFile["path"] = path; + file->setData(dataFile); + // file->start(); + + isPlayback = true; +} + +/** + * @brief playback -10s + */ +void Channel::back() +{ + qDebug() << "back 10s"; + int curPos = file->invoke("getPosition").toInt(); + curPos -= 10 * 100; + file->invoke("seek", curPos); +} + +/** + * @brief playback +10s + */ +void Channel::forward() +{ + qDebug() << "forward 10s"; + int curPos = file->invoke("getPosition").toInt(); + curPos += 10 * 100; + file->invoke("seek", curPos); +} + +/** + * @brief Channel::togglePause + */ +void Channel::togglePause() +{ + isPause = !isPause; + file->invoke("pause", isPause); +} + +/** + * @brief play live + */ +void Channel::startPlayLive() +{ + // stop playback + videoDecoder->unLinkV(videoOutput); + + file->stop(true); + isPlayback = false; + + videoInput->linkV(videoOutput); +} + +/** + * @brief open console process, and use it by command + * @param com + * @return + */ +QString Channel::writeCom(const QString& com) +{ + QProcess proc; + QStringList argList; + argList << "-c" << com; + proc.start("/bin/sh", argList); + // wait process start + proc.waitForFinished(); + proc.waitForReadyRead(); + // read data from console + QByteArray procOutput = proc.readAll(); + proc.close(); + return QString(procOutput); +} + +/** + * @brief judge the disk whether mounted + * @return + */ +bool Channel::isMountDisk() +{ + QString mount = writeCom("df /root/usb"); + if (!mount.contains("/root/usb")) + return false; + return true; +} diff --git a/Channel.h b/Channel.h new file mode 100644 index 0000000..fd0618c --- /dev/null +++ b/Channel.h @@ -0,0 +1,89 @@ +#ifndef CHANNEL_H +#define CHANNEL_H + +#include "Link.h" +#include +#include + +class Channel : public QObject { + Q_OBJECT +public: + explicit Channel(QObject* parent = nullptr); + void init(); + + QString channelName; + + LinkObject* videoInput; + LinkObject* videoEncoder; + QVariantMap videoEncoderParams; + LinkObject* videoOutput; + + static LinkObject* audioInput; + static LinkObject* audioOutput; + LinkObject* audioEncoder; + QVariantMap audioEncoderParams; + + LinkObject* record; + // 1 hour each video + int duration = 1 * 60 * 1000; + bool isRecord = false; + + LinkObject* snap; + LinkObject* overLay; + + static LinkObject* file; + static LinkObject* videoDecoder; + static LinkObject* audioDecoder; + + bool isPlayback = false; + bool isPause = false; + + static LinkObject* rtspServer; + LinkObject* rtsp; + QString pushCode; + + void startRecord(); + void stopRecord(); + void recordPause(); + + void startPlayback(QString fileName); + void back(); + void forward(); + void togglePause(); + + void startPlayLive(); +signals: + +private slots: + void onTimeout(); + +private: + // timer used to record segement + QTimer* timer; + +private: + QString writeCom(const QString& com); + bool isMountDisk(); + inline void setOverlay(QString str); +}; + +inline void Channel::setOverlay(QString str) +{ + QVariantMap dataOver; + QVariantList lays; + QVariantMap lay; + lay["type"] = "text"; + lay["enable"] = true; + lay["font"] = "/link/res/font.ttf"; + lay["content"] = str; + lay["x"] = 0.1; + lay["y"] = 0.1; + lay["scale"] = 2; + lay["color"] = "#669900"; + lay["alpha"] = 1; + lays << lay; + dataOver["lays"] = lays; + overLay->setData(dataOver); +} + +#endif // CHANNEL_H diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 0000000..5a6b167 --- /dev/null +++ b/Config.cpp @@ -0,0 +1,59 @@ +#include "Config.h" +#include "Channel.h" +#include "Json.h" +#include +#include +#include + +QList channelList; + +Config::Config() +{ +} + +void Config::loadConfig(QString path) +{ + QFileInfo fileInfo(path); + if (!fileInfo.exists()) { + qDebug() << "config.json does not exist, exit"; + exit(0); + } + // read the configuration + QVariantMap config = Json::loadFile(path).toMap(); + 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(); + + QVariantMap encV = cfg["encV"].toMap(); + chn->videoEncoderParams = encV; + + QVariantMap encA = cfg["encA"].toMap(); + chn->audioEncoderParams = encA; + + chn->pushCode = cfg["pushCode"].toString(); + chn->init(); + + bool autoRecord = cfg["autoRecord"].toBool(); + if (autoRecord) { + // chn->startRecord(); + } + channelList.push_back(chn); + } +} + +/** + * @brief find the channel from channel list by channel name + * @param name + * @return + */ +Channel* Config::findChannelByName(QString name) +{ + for (Channel* chn : channelList) { + if (chn->channelName == name) { + return chn; + } + } + return nullptr; +} diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..7734e49 --- /dev/null +++ b/Config.h @@ -0,0 +1,17 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "Channel.h" +#include + +class Config { +public: + Config(); + static void loadConfig(QString path); + static Channel* findChannelByName(QString name); + +private: + void initConfig(); +}; + +#endif // CONFIG_H diff --git a/Menu.cpp b/Menu.cpp new file mode 100644 index 0000000..b86a5e3 --- /dev/null +++ b/Menu.cpp @@ -0,0 +1,42 @@ +#include "Menu.h" +#include "ui_Menu.h" + +Menu::Menu(QWidget* parent) + : QWidget(parent) + , ui(new Ui::Menu) +{ + ui->setupUi(this); + ui->btnChannelA->setChecked(true); + + // set window background transparent + // QPalette pal = palette(); + // pal.setBrush(QPalette::Base, Qt::transparent); + // setPalette(pal); + // setAttribute(Qt::WA_TranslucentBackground, true); + + QPoint globalPos = parent->mapToGlobal(QPoint(0, 0)); //父窗口绝对坐标 + int x = globalPos.x() + (parent->width() - this->width()) / 2; //x坐标 + int y = globalPos.y() + (parent->height() - this->height()) / 2; //y坐标 + this->move(x, y); //窗口移动 +} + +Menu::~Menu() +{ + delete ui; +} + +QString Menu::getCurChannel() +{ + if (ui->btnChannelA->isChecked()) + return "HDMI-A"; + else + return "HDMI-B"; +} + +void Menu::setCurChannel(QString channel) +{ + if (channel == "HDMI-A") + ui->btnChannelA->setChecked(true); + else + ui->btnChannelB->setChecked(true); +} diff --git a/Menu.h b/Menu.h new file mode 100644 index 0000000..3e13ce2 --- /dev/null +++ b/Menu.h @@ -0,0 +1,23 @@ +#ifndef MENU_H +#define MENU_H + +#include + +namespace Ui { +class Menu; +} + +class Menu : public QWidget { + Q_OBJECT + +public: + explicit Menu(QWidget* parent = 0); + ~Menu(); + QString getCurChannel(); + void setCurChannel(QString channel); + +private: + Ui::Menu* ui; +}; + +#endif // MENU_H diff --git a/Menu.ui b/Menu.ui new file mode 100644 index 0000000..a6f8ff6 --- /dev/null +++ b/Menu.ui @@ -0,0 +1,100 @@ + + + Menu + + + + 0 + 0 + 300 + 200 + + + + Form + + + QWidget#Menu{ + +} + +QPushButton { + border: none; + background: rbga(0, 0, 0, 0.5); + color: #ffffff; +} + +QPushButton::checked{ + color: #0082E5; + background: rgba(0, 130, 229, 0.5); +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 100 + + + + + 17 + + + + 视频源1 + + + true + + + true + + + + + + + + 0 + 100 + + + + + 17 + + + + 视频源2 + + + true + + + true + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/RecordControlApplication.pro b/RecordControlApplication.pro new file mode 100644 index 0000000..8558950 --- /dev/null +++ b/RecordControlApplication.pro @@ -0,0 +1,47 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2024-01-05T00:12:20 +# +#------------------------------------------------- + +QT += core gui network serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = RecordControlApplication +TEMPLATE = app + +include(../../work/Example/build.pri) + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + Widget.cpp \ + TcpServer.cpp \ + TcpController.cpp \ + Config.cpp \ + Channel.cpp \ + Menu.cpp\ + +HEADERS += \ + Widget.h \ + TcpServer.h \ + TcpController.h \ + Config.h \ + Channel.h \ + Menu.h \ + +FORMS += \ + Widget.ui \ + Menu.ui diff --git a/TcpController.cpp b/TcpController.cpp new file mode 100644 index 0000000..0567879 --- /dev/null +++ b/TcpController.cpp @@ -0,0 +1,97 @@ +#include "TcpController.h" +#include +#include + +TcpController::TcpController(QObject* parent) + : QObject(parent) +{ + // map the requesr url and the handler + routes.insert("set_ipaddr", std::bind(&TcpController::setIpaddr, this, std::placeholders::_1)); + routes.insert("set_video_enc", std::bind(&TcpController::setVideoEnc, this, std::placeholders::_1)); + routes.insert("get_file_list", std::bind(&TcpController::getFileList, this, std::placeholders::_1)); + routes.insert("delete_file", std::bind(&TcpController::deleteFile, this, std::placeholders::_1)); +} + +Routes TcpController::getRoutes() +{ + return this->routes; +} + +/** + * @brief set box's ip address + * @param socket + */ +void TcpController::setIpaddr(QTcpSocket* socket) +{ +} + +/** + * @brief set the video encode params + * @param socket + */ +void TcpController::setVideoEnc(QTcpSocket* socket) +{ +} + +/** + * @brief get the video file list + * @param socket + */ +void TcpController::getFileList(QTcpSocket* socket) +{ + QDir dir("/root/usb/videos"); + if (!dir.exists()) { + qDebug() << "dictionary /root/usb/videos dont exists"; + return; + } + dir.setFilter(QDir::Files); + dir.setSorting(QDir::Name); + QStringList nameFilters; + nameFilters << "*.mp4"; + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + QString data = fileList.join(","); + socket->write(data.toStdString().data()); +} + +/** + * @brief download video file + * @param socket + */ +void TcpController::downloadFile(QTcpSocket* socket) +{ + QString url = socket->readLine().trimmed(); + QString fileName = url.split("=")[1]; + fileName = "/root/usb/videos/" + fileName; + qDebug() << fileName; + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "read file failed"; + return; + } + int64_t sendSize = 0; + int64_t fileSize = file.size(); + + socket->write(QString("fileSize=%1\r\n").arg(fileSize).toStdString().data()); + + // loop reading and sending file, 15k bytes once + while (sendSize < fileSize) { + char buf[1024 * 15] = { 0 }; + int len = file.read(buf, sizeof(buf)); + len = socket->write(buf, len); + sendSize += len; + } + qDebug() << "send file to " << socket << "end!"; + file.close(); +} + +/** + * @brief delete video file by file name + * @param socket + */ +void TcpController::deleteFile(QTcpSocket* socket) +{ + QString data = socket->readLine().trimmed(); + QString fileName = data.split("=")[1]; +} diff --git a/TcpController.h b/TcpController.h new file mode 100644 index 0000000..65df6f3 --- /dev/null +++ b/TcpController.h @@ -0,0 +1,29 @@ +#ifndef TCPCONTROLLER_H +#define TCPCONTROLLER_H + +#include +#include +#include +#include + +using Routes = QMap>; + +class TcpController : public QObject { + Q_OBJECT +public: + TcpController(QObject* parent = nullptr); + Routes getRoutes(); + +private: + Routes routes; + + void setIpaddr(QTcpSocket* socket); + void setVideoEnc(QTcpSocket* socket); + void getFileList(QTcpSocket* socket); + void downloadFile(QTcpSocket* socket); + void deleteFile(QTcpSocket* socket); + void playLive(QTcpSocket* socket); + void playFile(QTcpSocket* socket); +}; + +#endif // TCPCONTROLLER_H diff --git a/TcpServer.cpp b/TcpServer.cpp new file mode 100644 index 0000000..6e81711 --- /dev/null +++ b/TcpServer.cpp @@ -0,0 +1,42 @@ +#include "TcpServer.h" +#include + +TcpServer::TcpServer(QObject* parent) + : QTcpServer(parent) +{ + controller = new TcpController(); + connect(this, &TcpServer::newConnection, this, &TcpServer::onNewConnect); +} + +/** + * @brief new connection slots + */ +void TcpServer::onNewConnect() +{ + if (this->hasPendingConnections()) { + QTcpSocket* socket = this->nextPendingConnection(); + clients.push_back(socket); + connect(socket, &QTcpSocket::readyRead, this, &TcpServer::onReadyRead); + connect(socket, &QTcpSocket::disconnected, [=] { + QTcpSocket* sk = static_cast(sender()); + qDebug() << "clients " << sk << "disconnected"; + clients.removeOne(sk); + }); + } +} + +/** + * @brief receive data from clients + */ +void TcpServer::onReadyRead() +{ + QTcpSocket* socket = static_cast(sender()); + QString url = socket->readLine().trimmed(); + bool contains = controller->getRoutes().contains(url); + if (contains) { + auto handler = controller->getRoutes().value(url); + handler(socket); + } else { + socket->write("error: request url not found"); + } +} diff --git a/TcpServer.h b/TcpServer.h new file mode 100644 index 0000000..96e5706 --- /dev/null +++ b/TcpServer.h @@ -0,0 +1,22 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include "TcpController.h" +#include + +class TcpServer : public QTcpServer { + Q_OBJECT + +public: + TcpServer(QObject* parent = nullptr); + +private slots: + void onReadyRead(); + void onNewConnect(); + +private: + TcpController* controller; + QList clients; +}; + +#endif // TCPSERVER_H diff --git a/Widget.cpp b/Widget.cpp new file mode 100644 index 0000000..7079552 --- /dev/null +++ b/Widget.cpp @@ -0,0 +1,249 @@ +#include "Widget.h" +#include "BoxController.h" +#include "Channel.h" +#include "Config.h" +#include "TcpServer.h" +#include "ui_Widget.h" +#include +#include +#include + +extern char* VideoPath; +extern QList channelList; + +extern Widget::Widget(QWidget* parent) + : QWidget(parent) + , ui(new Ui::Widget) +{ + ui->setupUi(this); + + menu = new Menu(this); + menu->hide(); + + // set window background transparent + QPalette pal = palette(); + pal.setBrush(QPalette::Base, Qt::transparent); + setPalette(pal); + setAttribute(Qt::WA_TranslucentBackground, true); + + // set list no scroll bar + ui->listWidget->horizontalScrollBar()->hide(); + ui->listWidget->verticalScrollBar()->hide(); + ui->listWidget->hide(); + + // serial port + initSerialPort(); + + // init command map + initCommandMap(); +} + +Widget::~Widget() +{ + serialPort->close(); + delete ui; +} + +/** + * @brief open serial port and link the signals and slots + */ +void Widget::initSerialPort() +{ + // timer used to reopen serial port + timer = new QTimer(); + timer->setInterval(1000); + timer->stop(); + + // serialport + serialPort = new QSerialPort(); + serialPort->setPortName("ttyAMA2"); + serialPort->setBaudRate(QSerialPort::Baud115200); + serialPort->setDataBits(QSerialPort::Data8); + serialPort->setParity(QSerialPort::NoParity); + serialPort->setStopBits(QSerialPort::OneStop); + serialPort->setFlowControl(QSerialPort::NoFlowControl); + if (serialPort->open(QIODevice::ReadWrite)) { + qDebug() << "open serial port: /dev/ttyAMA2 success"; + } else { + qDebug() << "open serial port: /dev/ttyAMA2 failed, ready to retry..."; + timer->start(); + } + connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialError(QSerialPort::SerialPortError))); + connect(serialPort, SIGNAL(readyRead()), this, SLOT(onReadyRead())); +} + +/** + * @brief get the video list of current channel, and show + */ +void Widget::showPlayList() +{ + // find the videos in dictionary "/root/usb/video" + QString dirPath = QString("%1/%2").arg(VideoPath).arg(curChannelName); + QDir dir(dirPath); + if (!dir.exists()) { + qDebug() << "dictionary /root/usb/videos dont exists"; + return; + } + dir.setFilter(QDir::Files); + dir.setSorting(QDir::Name); + QStringList nameFilters; + nameFilters << "*.mp4"; + dir.setNameFilters(nameFilters); + fileList = dir.entryList(); + + ui->listWidget->clear(); + // append fileList to qlistwidget and show + for (QString& file : fileList) { + QListWidgetItem* item = new QListWidgetItem(file); + ui->listWidget->addItem(item); + } + ui->listWidget->show(); + ui->listWidget->setCurrentRow(0); +} + +/** + * @brief try to reopen serial port + */ +void Widget::onTimeout() +{ + timer->stop(); + bool ret = serialPort->open(QIODevice::ReadWrite); + // if open failed, then start timer and reopen + if (!ret) { + qDebug() << "reopen serial port: /dev/ttyAMA2 success"; + timer->start(); + } else { + qDebug() << "reopen serial port: /dev/ttyAMA2 failed, ready to retry..."; + } +} + +/** + * @brief handling error + * @param error + */ +void Widget::onSerialError(QSerialPort::SerialPortError error) +{ + if (error == QSerialPort::ResourceError) { + qDebug() << "serial port break, try to reopen"; + serialPort->close(); + timer->start(); + } +} + +/** + * @brief map command from string to int + */ +void Widget::initCommandMap() +{ + commandMap.insert("A1", Playback); + commandMap.insert("A2", Next); + commandMap.insert("A3", Previous); + commandMap.insert("A4", Forward); + commandMap.insert("A5", Back); + commandMap.insert("A6", Pause); + commandMap.insert("A7", Return); + commandMap.insert("A8", Enter); +} + +/** + * @brief receive data from serial port, and handle it + */ +void Widget::onReadyRead() +{ + serialPort->waitForReadyRead(100); + QString data = serialPort->readLine(); + if (!commandMap.contains(data)) { + qDebug() << QString("receive unkown command %1 from serial port: /dev/ttyAMA2").arg(data); + return; + } + Command cmd = commandMap.value(data); + switch (cmd) { + case Playback: { + if (menu->isVisible()) + return; + menu->show(); + } + case Previous: { + // if show menu, then switch menu select + if (menu->isVisible()) { + menu->setCurChannel("HDMI-A"); + } + // if menu hide, then select previous of listwidget + else { + int curRow = ui->listWidget->currentRow(); + if (curRow == 0) + curRow = ui->listWidget->count(); + ui->listWidget->setCurrentRow(curRow - 1); + } + break; + } + case Next: { + if (menu->isVisible()) { + menu->setCurChannel("HDMI-B"); + } else { + int curRow = ui->listWidget->currentRow(); + if (curRow == ui->listWidget->count() - 1) { + curRow = -1; + } + ui->listWidget->setCurrentRow(curRow + 1); + } + break; + } + case Forward: + if (isPlayback) { + Channel* chn = Config::findChannelByName(curChannelName); + chn->forward(); + } + break; + case Back: + if (isPlayback) { + Channel* chn = Config::findChannelByName(curChannelName); + chn->back(); + } + break; + case Pause: + if (isPlayback) { + Channel* chn = Config::findChannelByName(curChannelName); + chn->togglePause(); + } + break; + case Enter: + // if menu show, then hide menu and show play list + if (menu->isVisible()) { + curChannelName = menu->getCurChannel(); + menu->hide(); + showPlayList(); + } + // if menu hide, then current channel playback and other channel play live + else { + QString fileName = ui->listWidget->currentItem()->text(); + Channel* channel = nullptr; + for (int i = 0; i < channelList.count(); i++) { + if (channelList.at(i)->channelName == curChannelName) { + channel = channelList.at(i); + } + if (channelList.at(i)->isPlayback) { + channelList.at(i)->startPlayLive(); + } + } + if (channel) + channel->startPlayback(fileName); + ui->listWidget->hide(); + } + break; + case Return: + if (menu->isVisible()) { + menu->hide(); + } + if (ui->listWidget->isVisible()) { + ui->listWidget->hide(); + } + if (isPlayback) { + Channel* chn = Config::findChannelByName(curChannelName); + chn->startPlayLive(); + } + break; + default: + break; + } +} diff --git a/Widget.h b/Widget.h new file mode 100644 index 0000000..136c50d --- /dev/null +++ b/Widget.h @@ -0,0 +1,59 @@ +#ifndef WIDG_H +#define WIDG_H + +#include "Menu.h" +#include +#include +#include +#include +#include + +namespace Ui { +class Widget; +} + +class Widget : public QWidget { + Q_OBJECT + enum Command { + Playback, + Next, + Previous, + Forward, + Back, + Pause, + Return, + Enter + }; + +public: + explicit Widget(QWidget* parent = 0); + ~Widget(); + +public slots: + void showPlayList(); + +private slots: + void onSerialError(QSerialPort::SerialPortError error); + void onTimeout(); + void onReadyRead(); + +private: + Ui::Widget* ui; + QStringList fileList; + + QTimer* timer; + QSerialPort* serialPort; + QMap commandMap; + + bool isPlayback = false; + QString curChannelName; + QString curFileName; + + Menu* menu; + +private: + void initSerialPort(); + void initCommandMap(); +}; + +#endif // WIDG_H diff --git a/Widget.ui b/Widget.ui new file mode 100644 index 0000000..0860f80 --- /dev/null +++ b/Widget.ui @@ -0,0 +1,98 @@ + + + Widget + + + + 0 + 0 + 1920 + 1080 + + + + + 20 + + + + Widget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 1511 + 20 + + + + + + + + + 400 + 0 + + + + + 16777215 + 16777215 + + + + + 20 + + + + QListWidget { + color: rgba(255, 255, 255); + outline: none; + background-color: rgba(0, 0, 0, 0.5); + border: none; +} + +#listWidget::item { + + border: transparent; + padding: 6px; +} + +#listWidget::item:selected { + background-color: rgba(0, 0, 0, 0.7); + border-left: 10px solid 00AEEC; + color: #00AEEC; +} + + +QScrollBar { + width: 0; +} + + + + + + + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..b8cdc17 --- /dev/null +++ b/main.cpp @@ -0,0 +1,79 @@ +#include "BoxController.h" +#include "Config.h" +#include "Link.h" +#include "TcpServer.h" +#include "Widget.h" +#include +#include +#include +#include + +TcpServer* server; +LinkObject* vo; +LinkObject* vo1; +const char* ConfigurationPath = "/opt/RecordControlApplication/bin/config.json"; + +int main(int argc, char* argv[]) +{ + // init Link + if (!Link::init()) { + qDebug() << "Link init failed!"; + return 0; + } + + // init videooOutput and linuxfb before init QApplication, + // otherwise dump because cant init screen and we have ui components + + // video output in channel HDMI-OUT0 + vo = Link::create("OutputVo"); + QVariantMap dataVo; + dataVo["vid"] = 0; + dataVo["type"] = "hdmi"; + vo->start(dataVo); + + // video output in channel HDMI-OUT1 + vo1 = Link::create("OutputVo"); + QVariantMap dataVo1; + dataVo1["vid"] = 1; + dataVo1["ui"] = false; + dataVo1["type"] = "vga|bt1120"; + vo1->start(dataVo1); + + // special settings used channel HDMI-OUT1 + static int lastNorm = 0; + int norm = 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; + if (norm != lastNorm) { + lastNorm = norm; + QString cmd = "rmmod hi_lt8618sx_lp.ko"; + system(cmd.toLatin1().data()); + cmd = "insmod /ko/extdrv/hi_lt8618sx_lp.ko norm=" + QString::number(norm); + system(cmd.toLatin1().data()); + } + + QApplication a(argc, argv); + + // initialize configuration + Config::loadConfig(ConfigurationPath); + + // create server and listen port 8080 + // server = new TcpServer(); + // server->listen(QHostAddress::Any, 8080); + + Widget w; + w.show(); + + return a.exec(); +}