Files
utun2/tools/chatgui/src/call_camera_protocol.h
T

65 lines
4.7 KiB
C++

/* IPC GUI <-> camera worker: QLocalSocket передаёт QDataStream Qt_5_15, пиксели лежат в CallCameraBuffer.
* Пакет: quint32 длины тела, quint8 Type, quint64 поколения конфигурации, затем поля конкретного типа.
* Worker пишет один RGB32-кадр и посылает Frame; до Ack его пиксели не меняются, в том числе при Configure.
* Получатель копирует/отбрасывает кадр и подтверждает его поколение; shared mutex между процессами нет. */
#pragma once
#include <QDataStream>
#include <QLocalSocket>
#include <QByteArray>
#include <functional>
namespace CallCameraProtocol {
/* GUI -> worker: Configure(bool,QByteArray device,qint32 w/h/fps), Quit(), Ack(), Buffer(QString name).
* Worker -> GUI: Devices(count,[name,id,modes(w,h,fps)]), Frame(qint64 pts/correction,qint32 w/h), Stage/Error(QString).
* Buffer/Devices используют поколение 0; остальные — поколение конфигурации/подтверждаемого кадра. */
enum Type : quint8 { Configure = 1, Quit, Ack, Devices, Frame, Stage, Error, Buffer };
constexpr quint32 FrameBytes = 1920 * 1080 * 4; /* Ёмкость одного плотно упакованного RGB32-кадра, без заголовка. */
constexpr quint32 MaxPacket = 65536; /* Максимум тела IPC-пакета, без префикса длины. */
/* Чётные размеры от 16: длинная сторона <=1920, короткая <=1080; учитывает обе ориентации и YUV420. */
inline bool validSize(int width, int height) {
return width >= 16 && height >= 16 && width <= 1920 && height <= 1920
&& (width <= 1080 || height <= 1080) && !(width % 2) && !(height % 2);
}
/* Сериализует пакет, синхронно вызывая write для его дополнительных полей; размер/содержимое полей не проверяет.
* Вызывающий должен уложить тело в MaxPacket и обеспечить схему Type; пиксели сюда не включаются. */
inline QByteArray packet(Type type, quint64 generation, const std::function<void(QDataStream&)>& write = {}) {
QByteArray result;
QDataStream out(&result, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_5_15);
out << quint32(0) << quint8(type) << generation;
if (write) write(out);
out.device()->seek(0); out << quint32(result.size() - 4);
return result;
}
/* Один Reader на соединение, вызовы сериализуются в его потоке. После ошибки соединение закрыть,
* для нового соединения создать/присвоить пустой Reader; автоматического восстановления framing нет. */
class Reader {
QByteArray pending; /* Неполный префикс длины либо неполное тело текущего пакета. */
quint32 length = 0; /* 0 — читается четырёхбайтовый префикс; иначе ожидаемая длина тела (9..MaxPacket). */
public:
/* Читает доступные данные, сохраняя неполный пакет до следующего вызова. consume вызывается синхронно
* после Type/generation и должен прочесть все остальные поля и вернуть true; stream заимствуется на вызов.
* true — полные пакеты приняты либо нужны ещё байты; false — длина, consume, статус stream или лишние поля неверны. */
bool read(QLocalSocket& socket, const std::function<bool(quint8, quint64, QDataStream&)>& consume) {
while (socket.bytesAvailable()) {
const qsizetype wanted = length ? length : 4;
pending += socket.read(wanted - pending.size());
if (pending.size() != wanted) return true;
QDataStream in(pending); in.setVersion(QDataStream::Qt_5_15);
if (!length) {
in >> length; pending.clear();
if (length < 9 || length > MaxPacket) return false;
} else {
quint8 type; quint64 generation; in >> type >> generation;
bool valid = consume(type, generation, in) && in.status() == QDataStream::Ok && in.atEnd();
pending.clear(); length = 0;
if (!valid) return false;
}
}
return true;
}
};
}