commit ab606d358acbf94e3bcbd1ea202758a7c713f78d Author: zc Date: Wed Jan 17 23:41:43 2024 -0800 first commit 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(); +}