first commit

This commit is contained in:
zc 2024-01-17 23:41:43 -08:00
commit ab606d358a
20 changed files with 1708 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
bin/
build/
*.pro.user

277
BoxController.cpp Normal file
View File

@ -0,0 +1,277 @@
#include "BoxController.h"
#include <QProcess>
#include <QThread>
#include <QTime>
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);
}

72
BoxController.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef BOXCONTROLLER_H
#define BOXCONTROLLER_H
#include "Link.h"
#include <QObject>
#include <QTimer>
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

304
Channel.cpp Normal file
View File

@ -0,0 +1,304 @@
#include "Channel.h"
#include <QProcess>
#include <QTime>
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;
}

89
Channel.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef CHANNEL_H
#define CHANNEL_H
#include "Link.h"
#include <QObject>
#include <QTimer>
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

59
Config.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "Config.h"
#include "Channel.h"
#include "Json.h"
#include <QDebug>
#include <QFileInfo>
#include <QList>
QList<Channel*> 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;
}

17
Config.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "Channel.h"
#include <QString>
class Config {
public:
Config();
static void loadConfig(QString path);
static Channel* findChannelByName(QString name);
private:
void initConfig();
};
#endif // CONFIG_H

42
Menu.cpp Normal file
View File

@ -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);
}

23
Menu.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef MENU_H
#define MENU_H
#include <QWidget>
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

100
Menu.ui Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Menu</class>
<widget class="QWidget" name="Menu">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="btnChannelA">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<pointsize>17</pointsize>
</font>
</property>
<property name="text">
<string>视频源1</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnChannelB">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<pointsize>17</pointsize>
</font>
</property>
<property name="text">
<string>视频源2</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

0
README.md Normal file
View File

View File

@ -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

97
TcpController.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "TcpController.h"
#include <QDebug>
#include <QDir>
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];
}

29
TcpController.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef TCPCONTROLLER_H
#define TCPCONTROLLER_H
#include <QMap>
#include <QObject>
#include <QTcpSocket>
#include <functional>
using Routes = QMap<QString, std::function<void(QTcpSocket*)>>;
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

42
TcpServer.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "TcpServer.h"
#include <QTcpSocket>
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<QTcpSocket*>(sender());
qDebug() << "clients " << sk << "disconnected";
clients.removeOne(sk);
});
}
}
/**
* @brief receive data from clients
*/
void TcpServer::onReadyRead()
{
QTcpSocket* socket = static_cast<QTcpSocket*>(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");
}
}

22
TcpServer.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include "TcpController.h"
#include <QTcpServer>
class TcpServer : public QTcpServer {
Q_OBJECT
public:
TcpServer(QObject* parent = nullptr);
private slots:
void onReadyRead();
void onNewConnect();
private:
TcpController* controller;
QList<QTcpSocket*> clients;
};
#endif // TCPSERVER_H

249
Widget.cpp Normal file
View File

@ -0,0 +1,249 @@
#include "Widget.h"
#include "BoxController.h"
#include "Channel.h"
#include "Config.h"
#include "TcpServer.h"
#include "ui_Widget.h"
#include <QDebug>
#include <QDir>
#include <QScrollBar>
extern char* VideoPath;
extern QList<Channel*> 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;
}
}

59
Widget.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef WIDG_H
#define WIDG_H
#include "Menu.h"
#include <QMap>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QWidget>
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<QString, Command> commandMap;
bool isPlayback = false;
QString curChannelName;
QString curFileName;
Menu* menu;
private:
void initSerialPort();
void initCommandMap();
};
#endif // WIDG_H

98
Widget.ui Normal file
View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1920</width>
<height>1080</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1511</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QListWidget" name="listWidget">
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">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;
}</string>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

79
main.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "BoxController.h"
#include "Config.h"
#include "Link.h"
#include "TcpServer.h"
#include "Widget.h"
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <unistd.h>
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();
}