Files
dvr/camera.cpp
2026-02-08 08:58:29 +08:00

277 lines
9.2 KiB
C++

#include "camera.h"
std::shared_ptr<spdlog::logger> Camera::log = nullptr;
QHash<QString, Camera*> Camera::instances;
QMutex Camera::mutex;
Camera* Camera::of(CameraInfo cameraInfo) {
return of(cameraInfo.getCameraType(), cameraInfo.getIP(), cameraInfo.getPort(), cameraInfo.getUsername(), cameraInfo.getPassword());
}
Camera* Camera::of(CameraType cameraType, QString ip, unsigned int port, QString username, QString password) {
QString key = cameraKey(cameraType, ip);
QMutexLocker locker(&mutex);
if (instances.contains(key)) {
return instances[key];
}
Camera* camera = new Camera(cameraType, ip, port, username, password);
instances[key] = camera;
return camera;
}
Camera::Camera(CameraType cameraType, QString ip, unsigned int port, QString username, QString password) {
this->cameraType = cameraType;
this->ip = ip;
this->port = port;
this->username = username;
this->password = password;
}
bool Camera::login() {
if (connected) return true;
hk_net_dev_init(&ptr);
connected = hk_net_dev_login(ptr, 0, ip.toUtf8().constData(), port, nullptr, nullptr, username.toUtf8().constData(), password.toUtf8().constData());
if (!connected) {
log->error("{} |> 无法登录设备", toString());
hk_net_dev_uninit(&ptr);
ptr = nullptr;
return false;
}
log->info("{} |> 设备登录", toString());
return connected;
}
void Camera::logout() {
log->info("{} |> 设备登出", toString());
if (connected) {
hk_net_dev_logout(ptr);
hk_net_dev_uninit(&ptr);
ptr = nullptr;
connected = false;
}
QString key = cameraKey(cameraType, ip);
QMutexLocker locker(&mutex);
delete instances.take(key);
}
void Camera::startRealtimeTemperatureCallback() {
hk_net_dev_stop_realtime_temperature_v20(ptr);
hk_net_dev_set_realtime_temperature_callback_v20(ptr, Camera::temperatureCallback, this);
hk_net_dev_start_realtime_temperature_v20(ptr);
log->info("{} |> 开启温度回调", toString());
this->realtiming = true;
}
void Camera::stopRealtimeTemperatureCallback() {
hk_net_dev_stop_realtime_temperature_v20(ptr);
log->info("{} |> 停止温度回调", toString());
this->realtiming = false;
}
void Camera::attach(struct mg_connection *conn) {
this->conn = conn;
}
void Camera::unattach() {
this->conn = NULL;
}
std::vector<STRU_PRESET_CONFIG> Camera::presets() {
unsigned int count = 255;
std::vector<STRU_PRESET_CONFIG> presets(count);
hk_net_dev_ptz_get_preset_list(ptr, presets.data(), &count);
presets.erase(std::remove_if(presets.begin(), presets.end(), [](const STRU_PRESET_CONFIG& preset) { return !(preset.ui_enable == 1 || preset.ui_especial == 1);}), presets.end());
return presets;
}
bool Camera::addPreset(QJsonObject packet) {
QJsonObject preset = packet["preset"].toObject();
int presetNo = preset["no"].toInt();
float radiance = preset["radiance"].toInt() / 100.0f;
float distance = preset["distance"].toInt() / 100.0f;;
QJsonArray anas = preset["anas"].toArray();
bool ok = hk_net_dev_ptz_set_preset(ptr, presetNo);
if (ok) {
if (anas.isEmpty()) {
ok = hk_net_dev_set_preset_temperature_rules_v20(ptr, presetNo, NULL, 0);
} else {
std::vector<STRU_NDSWN_RULE> rules(anas.size());
for (int i = 0; i < anas.size(); ++i) {
QJsonValue ana = anas.at(i);
QJsonArray coords = ana["coords"].toArray();
auto& rule = rules[i];
rule.i_rule_type = ana["shape"].toInt();
rule.i_id = ana["index"].toInt();
rule.f_emiss = radiance;
rule.f_dist = distance;
rule.pt_start.fX = coords.at(0)["x"].toDouble();
rule.pt_start.fY = coords.at(0)["y"].toDouble();
if (rule.i_rule_type != 0) {
rule.pt_end.fX = coords.at(1)["x"].toDouble();
rule.pt_end.fY = coords.at(1)["y"].toDouble();
}
}
ok = hk_net_dev_set_preset_temperature_rules_v20(ptr, presetNo, rules.data(), static_cast<int>(rules.size()));
}
}
log->info("{} |> 保存预置位: {}, {}", toString(), presetNo, ok);
return ok;
}
QString Camera::getViCameraIP() {
char ip[16] = {0};
bool ok = hk_net_dev_get_association_dev_info(ptr, ip);
return ok ? QString::fromUtf8(ip) : QString();
}
CaptureData Camera::capture(bool append) {
CaptureData result;
bool ok = false;
QTemporaryFile file(QDir::tempPath() + "/camera_XXXXXXXX.jpg");
if (file.open()) {
QString filename = file.fileName();
file.close();
QByteArray f = filename.toUtf8();
if (cameraType == CameraType::IR) {
ok = hk_net_dev_capture_ir_jpg(ptr, f.data());
if (ok && append) {
QByteArray date = QDateTime::currentDateTime().toString("yyyyMMddHHmmss").toUtf8();
ok = hk_net_dev_append_temperature_to_national_grid_image(ptr, date.data(), f.data());
}
} else {
ok = hk_net_dev_capture_vi_jpg(ptr, f.data());
}
if (ok) {
QFile image(filename);
if (image.open(QIODevice::ReadOnly)) {
QByteArray buffer = image.readAll();
result.image = buffer;
image.close();
if (result.image.isEmpty()) {
ok = false;
}
if (ok) {
if (cameraType == CameraType::IR) {
InfraredType infraredType = getInfraredType(buffer);
ok = infraredType != InfraredType::UNKNOWN;
}
}
} else {
ok = false;
}
}
}
log->info("{} |> 采集{}, {}", toString(), cameraType == CameraType::IR ? "红外" : "可将光", ok);
result.ok = ok;
return result;
}
bool Camera::temperatureCallback(int, int presetNo, STRU_NDSWN_TEMPERATURE_UPLOAD* data, unsigned int len, void* user) {
if (user) {
Camera* camera = static_cast<Camera*>(user);
qint64 now = QDateTime::currentMSecsSinceEpoch();
camera->heartbeat = now; // 保活回调
// 发送心跳
if (now - camera->lastTickAt >= 30000) {
camera->broadcast(PING);
camera->lastTickAt = now;
}
// 不在预置位上
if (presetNo == 0) {
camera->stayIn = 0;
camera->broadcast(PRESET_NO_ZERO);
return true;
}
if (camera->stayIn != presetNo) {
camera->log->info("{} |> 预置点切换: {}->{}", camera->toString(), camera->stayIn, presetNo);
camera->stayIn = presetNo;
camera->stayInAt = now;
}
if (now - camera->stayInAt >= 15000 && data && len > 0) {
QVector<STRU_NDSWN_TEMPERATURE_UPLOAD> items;
items.resize(len);
memcpy(items.data(), data, len * sizeof(STRU_NDSWN_TEMPERATURE_UPLOAD));
QMetaObject::invokeMethod(camera, "onTemperatureDataReady", Qt::QueuedConnection, Q_ARG(int, presetNo),Q_ARG(QVector<STRU_NDSWN_TEMPERATURE_UPLOAD>, items));
}
}
return true;
}
void Camera::onTemperatureDataReady(int presetNo, QVector<STRU_NDSWN_TEMPERATURE_UPLOAD> items) {
QJsonArray shapes;
for (const auto& item : items) {
QJsonObject max({{"value", item.f_max}, {"x", item.st_max_pt.fX}, {"y", item.st_max_pt.fY}});
QJsonObject min({{"value", item.f_min}, {"x", item.st_min_pt.fX}, {"y", item.st_min_pt.fY}});
QJsonObject avg({{"value", item.f_avg}, {"x", QJsonValue::Null}, {"y", QJsonValue::Null}});
QJsonObject shape({{"ruleType", item.i_rule_type}, {"id", item.i_id}, {"max", max}, {"min", min}, {"avg", avg}});
shapes.append(shape);
}
broadcast(QJsonDocument(QJsonObject{{"event", "temperature"},{"payload", QJsonObject{{"presetNo", presetNo}, {"shapes", shapes}}}}).toJson(QJsonDocument::Compact));
}
QString Camera::cameraKey(CameraType cameraType, QString ip) {
return QString("%1_%2").arg(cameraType == CameraType::IR ? "IR" : "VI").arg(ip);
}
void Camera::broadcast(QByteArray byteArray) {
QtConcurrent::run([this, byteArray]()-> void {
if (conn) {
mg_ws_send(conn, byteArray.constData(), byteArray.length(), WEBSOCKET_OP_TEXT);
}
});
}
InfraredType Camera::getInfraredType(QByteArray buffer) {
static constexpr char HeiKa[] = "\x93\x5c\x80\x39\xf6\xac\x4c\x4b\xbc\xb0\xb2\x50\xf7\xe1\xcd\x55";
static constexpr char StateGrid[] = "\x37\x66\x07\x1a\x12\x3a\x4c\x9f\xa9\x5d\x21\xd2\xda\x7d\x26\xbc";
const int bufferSize = buffer.size();
if (bufferSize < 16) return InfraredType::UNKNOWN;
const char* const tail = buffer.constData() + bufferSize - 16;
if (std::memcmp(tail, HeiKa, 16) == 0) return InfraredType::HEIKA;
if (std::memcmp(tail, StateGrid, 16) == 0) return InfraredType::STATE_GRID;
if (bufferSize < 512) return InfraredType::UNKNOWN;
if (qFromLittleEndian<qint32>(reinterpret_cast<const uchar*>(buffer.constData())) == bufferSize && buffer.at(512) == 'q') return InfraredType::NEW_STATE_GRID;
return InfraredType::UNKNOWN;
}