RecordControlApplication/Channel.cpp
2024-08-11 20:26:42 -07:00

441 lines
12 KiB
C++
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Channel.h"
#include "Constant.h"
#include "DatabaseManager.h"
#include "Json.h"
#include "Log.h"
#include "Tool.h"
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QTime>
#include <chrono>
#include <QElapsedTimer>
LinkObject* Channel::lineIn = nullptr;
LinkObject* Channel::lineOut = nullptr;
LinkObject* Channel::rtspServer = nullptr;
LinkObject* Channel::resample = nullptr;
extern LinkObject* vo;
extern LinkObject* vo1;
extern DatabaseManager* db;
Channel::Channel(QObject* parent)
: QObject(parent)
{
videoInput = nullptr;
videoOutput = nullptr;
audioInput = nullptr;
audioOutput = nullptr;
videoEncoder = nullptr;
audioEncoder = nullptr;
record = nullptr;
rtsp = nullptr;
inputFile = nullptr;
videoDecoder = nullptr;
audioDecoder = nullptr;
image = Link::create("InputImage");
if (lineIn == nullptr) {
lineIn = Link::create("InputAlsa");
QVariantMap dataIn;
dataIn["path"] = "hw:0,0";
dataIn["channels"] = 2;
lineIn->start(dataIn);
}
if (resample = nullptr) {
resample = Link::create("Resample");
resample->start();
lineIn->linkA(resample);
}
if (lineOut == nullptr) {
lineOut = Link::create("OutputAlsa");
QVariantMap dataOut;
dataOut["path"] = "hw:0,0";
lineOut->start(dataOut);
}
if (rtspServer == nullptr) {
rtspServer = Link::create("Rtsp");
rtspServer->start();
}
}
/**
* @brief 初始化
*/
void Channel::init()
{
if (channelName.isEmpty()) {
Log::error("channel name is empty!");
return;
}
// 通道音频输出
audioOutput = Link::create("OutputAo");
QVariantMap dataVo;
if (channelName == Constant::MainChannel) {
videoOutput = vo;
dataVo["interface"] = "HDMI-OUT0";
} else {
videoOutput = vo1;
dataVo["interface"] = "HDMI-OUT1";
}
loadOverlayConfig();
// 水印,用于显示录制状态
overlay = Link::create("Overlay");
overlay->start(norecordOverlay);
overlay->linkV(videoOutput);
// 视频输入
videoInput = Link::create("InputVi");
QVariantMap dataVi;
dataVi["interface"] = channelName;
dataVi["width"] = 1920;
dataVi["height"] = 1080;
videoInput->start(dataVi);
videoInput->linkV(overlay)->linkV(videoOutput);
// 通道音频输入
audioInput = Link::create("InputAi");
QVariantMap dataAi;
dataAi["interface"] = channelName;
dataAi["channels"] = 2;
audioInput->start(dataAi);
audioInput->linkA(audioOutput);
// 音量
gain = Link::create("Gain");
gain->start();
volume = Link::create("Volume");
volume->start();
gain->linkA(volume);
lineOut->linkA(gain);
// 音频编码
audioEncoder = Link::create("EncodeA");
audioEncoder->start(audioEncoderParams);
// 视频编码
videoEncoder = Link::create("EncodeV");
videoEncoder->start(videoEncoderParams);
// 录制
record = Link::create("Mux");
QVariantMap dataMp4;
dataMp4["format"] = "mp4";
dataMp4["filecache"] = 20480000;
dataMp4["lowLatency"] = true;
dataMp4["thread"] = true;
dataMp4["segmentDuration"] = duration;
record->setData(dataMp4);
videoInput->linkV(videoEncoder)->linkV(record);
audioInput->linkV(audioEncoder)->linkV(record);
resample->linkA(audioEncoder)->linkA(record);
connect(record, SIGNAL(newEvent(QString, QVariant)), this, SLOT(onNewEvent(QString, QVariant)));
// 测试执行时间用时18-20ms
// connect(record, &LinkObject::newEvent, [=](QString msg, QVariant data) {
// Tool::getCostTime(
// [=] {
// onNewEvent(msg, data);
// },
// "onNewEvent");
// });
// rstp流
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)->linkV(rtsp)->linkV(rtspServer);
resample->linkA(audioEncoder)->linkA(rtsp)->linkA(rtspServer);
// 播放文件
inputFile = Link::create("InputFile");
connect(inputFile, &LinkObject::newEvent, [=](QString type, QVariant msg) {
if (type == "EOF") {
Log::info("{} one video playback end", channelName.toStdString());
emit playEnd();
}
});
// 音频解码只在HDMI-OUT0输出音频
audioDecoder = Link::create("DecodeA");
audioDecoder->start();
if (channelName == Constant::MainChannel) {
inputFile->linkA(audioDecoder)->linkA(lineOut);
} else {
inputFile->linkA(audioDecoder);
}
// 视频解码
videoDecoder = Link::create("DecodeV");
videoDecoder->start();
inputFile->linkV(videoDecoder)->linkV(videoOutput);
}
/**
* @brief 开始录制
*/
void Channel::startRecord()
{
QElapsedTimer mstimer;
mstimer.start();
// 记录本次录制开始时间以及当前视频录制的开始时间
QString curTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
startTime = curTime;
currentTime = curTime;
QVariantMap dataRecord;
QString path = QString("%1/%2/%3_%d.mp4").arg(Constant::VideoPath).arg(channelName).arg(curTime);
dataRecord["path"] = path;
record->start(dataRecord);
isRecord = true;
Log::info("{} start recording...", channelName.toStdString());
Log::info("open done {}", path.toStdString());
// 显示录制状态水印
overlay->setData(recordOverlay);
float time = (double)mstimer.nsecsElapsed() / (double)1000000;
qDebug() << channelName << "startRecord cast time:" << time << "ms";
}
/**
* @brief 新事件槽函数
* @param msg 时间类型
* @param data 数据
*/
void Channel::onNewEvent(QString msg, QVariant data)
{
if (msg == "newSegment") {
int id = data.toInt();
// 将上一次的文件信息存入数据库中
DatabaseManager::File file;
file.channel = channelName == Constant::MainChannel
? DatabaseManager::MainChannel
: DatabaseManager::SecondaryChannel;
// 设置当前视频的录制时间信息
file.year = currentTime.mid(0, 4);
file.month = currentTime.mid(4, 2);
file.day = currentTime.mid(6, 2);
file.time = currentTime.mid(8, 6);
// 设置当前视频的文件名格式本次录制开始时间_第几次分片
file.filename = QString("%1_%2.mp4").arg(startTime).arg(id - 1);
if (db->insert(file)) {
Log::info("insert one record into database success, name: {}, channel: {}",
file.filename.toStdString(),
channelName.toStdString());
} else {
Log::error("insert one record into database failed, name: {}, channel: {}",
file.filename.toStdString(),
channelName.toStdString());
}
// 更新当前录制录制视频的时间
currentTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
}
}
/**
* @brief 停止录制
*/
void Channel::stopRecord()
{
Log::info("{} stop recording...", channelName.toStdString());
record->stop(true);
// 将录制文件的信息存入数据库
DatabaseManager::File file;
file.channel = channelName == Constant::MainChannel
? DatabaseManager::MainChannel
: DatabaseManager::SecondaryChannel;
file.year = startTime.mid(0, 4);
file.month = startTime.mid(4, 2);
file.day = startTime.mid(6, 2);
file.time = startTime.mid(8, 6);
overlay->setData(norecordOverlay);
}
/**
* @brief 开始回放
* @param path 路径
* @return 成功/失败
*/
bool Channel::startPlayback(QString path)
{
QFileInfo info(path);
if (!info.exists()) {
Log::error("cannot open video {} , video file does not exist", path.toStdString());
videoInput->unLinkV(videoOutput);
QVariantMap dataImage;
dataImage["path"] = Constant::EmptyImagePath;
image->start(dataImage);
image->linkV(videoOutput);
state = Error;
return false;
}
// 开始回放
QVariantMap dataFile;
dataFile["path"] = path;
dataFile["sync"] = true;
inputFile->start(dataFile);
// 判断视频是否损坏,如果损坏则输出提示图片
int duration = inputFile->invoke("getDuration", path).toInt();
if (duration == 0) {
Log::error("cannot open video {}, video file was corrupted", path.toStdString());
inputFile->stop();
videoInput->unLinkV(videoOutput);
QVariantMap dataImage;
dataImage["path"] = Constant::ErrorImagePath;
image->start(dataImage);
image->linkV(videoOutput);
state = Error;
return false;
}
// 断开视频信号输出,启动回放输出
overlay->unLinkV(videoOutput);
videoDecoder->linkV(videoOutput);
// 断开音频信号输出,启动回放输出
audioInput->unLinkA(audioOutput);
audioDecoder->linkA(lineOut);
playbackDuration = duration;
state = Playback;
return true;
}
/**
* @brief 播放直播
*/
void Channel::startPlayLive()
{
if (state == Playback) {
videoDecoder->unLinkV(videoOutput);
audioDecoder->unLinkA(audioOutput);
inputFile->stop(true);
} else if (state == Error) {
image->unLinkV(videoOutput);
image->stop(true);
}
// 打开视频和音频输出
overlay->linkV(videoOutput);
audioInput->linkA(audioOutput);
// 关闭外部音频输出
audioDecoder->unLinkA(lineOut);
state = Stop;
}
/**
* @brief 后退10s
*/
void Channel::back()
{
Log::info("{} back 10s", channelName.toStdString());
int curPos = inputFile->invoke("getPosition").toInt();
curPos -= 10 * 1000;
inputFile->invoke("seek", curPos);
}
/**
* @brief 快进10s
*/
void Channel::forward()
{
Log::info("{} forward 10s", channelName.toStdString());
int curPos = inputFile->invoke("getPosition").toInt();
curPos += 10 * 1000;
inputFile->invoke("seek", curPos);
}
/**
* @brief 暂停
*/
void Channel::togglePause()
{
if (state == Stop || state == Error)
return;
if (state == Playback)
state = Pause;
else
state = Playback;
inputFile->invoke("pause", state == Pause);
}
/**
* @brief 显示播放结束提示
*/
void Channel::showFinishPromot()
{
videoInput->unLinkV(videoDecoder);
QVariantMap dataImage;
dataImage["path"] = Constant::FinishImagePath;
image->start(dataImage);
image->linkV(videoOutput);
state = Finish;
}
/**
* @brief 获取音量
* @return L左声道音量R右声道音量
*/
QVariantMap Channel::getVolume()
{
QVariantMap result;
QVariantMap data = volume->invoke("getVolume").toMap();
result["L"] = data["max"].toInt();
if (data["avg"].toInt() < 15)
result["L"] = 0;
result["R"] = data["max2"].toInt();
if (data["avg2"].toInt() < 15)
result["R"] = 0;
return result;
}
/**
* @brief 增大音量
*/
void Channel::volumeUp()
{
if (curGain < maxGian)
curGain += 6;
QVariantMap data;
data["gain"] = curGain;
gain->setData(data);
}
/**
* @brief 减小音量
*/
void Channel::volumeDown()
{
if (curGain > minGain)
curGain -= 6;
QVariantMap data;
data["gain"] = curGain;
gain->setData(data);
}
/**
* @brief 将水印配置属性加载到内存中
*/
void Channel::loadOverlayConfig()
{
auto loadFromJson = [](const QString& path) {
QVariantMap dataOver;
QVariantList list = Json::loadFile(path).toList();
QVariantList list2;
for (int i = 0; i < list.count(); i++) {
QVariantMap map = list[i].toMap();
list2 << map;
}
dataOver["lays"] = list2;
return dataOver;
};
recordOverlay = loadFromJson(Constant::RecordOverlay);
norecordOverlay = loadFromJson(Constant::NoRecordOverlay);
}