/* Однокадровый mapping CallCameraProtocol::FrameBytes: GUI создаёт, camera worker подключается по name(). * Объект единолично владеет локальным mapping/handle, не копируется; вызовы не синхронизированы. * Доступ к общим пикселям согласуется через Frame/Ack, а не через этот класс. close не ждёт другой процесс. */ #pragma once #include "call_camera_protocol.h" #include "debug_config.h" #include #ifdef Q_OS_WIN #include #else #include #include #endif class CallCameraBuffer { QString m_name, m_error; /* Имя mapping/путь файла и последняя ошибка; close и успех не очищают эти строки. */ uchar* m_data = nullptr; /* Заимствованный адрес локального отображения, nullptr вне успешного create/attach. */ #ifdef Q_OS_WIN HANDLE m_mapping = nullptr; /* Windows file mapping, закрывается этим объектом. */ #else std::unique_ptr m_file; /* Создатель владеет QTemporaryFile с автоудалением; attach — обычным QFile. */ #endif /* Запоминает причину, освобождает локальные ресурсы и возвращает false. */ bool fail(const QString& error) { m_error = error; close(); return false; } /* Отображает FrameBytes текущего handle/файла; false освобождает ресурсы через fail. */ bool map() { #ifdef Q_OS_WIN m_data = static_cast(MapViewOfFile(m_mapping, FILE_MAP_WRITE, 0, 0, CallCameraProtocol::FrameBytes)); if (!m_data) return fail(QStringLiteral("MapViewOfFile error=%1").arg(GetLastError())); #else m_data = m_file->map(0, CallCameraProtocol::FrameBytes); if (!m_data) return fail(m_file->errorString()); #endif return true; } public: /* Пустой буфер без mapping; копирование запрещено. */ CallCameraBuffer() = default; CallCameraBuffer(const CallCameraBuffer&) = delete; CallCameraBuffer& operator=(const CallCameraBuffer&) = delete; ~CallCameraBuffer() { close(); } /* Закрывает прежний mapping и создаёт новый с уникальным именем/временным файлом. * true — data/name готовы; false — ресурсов нет, причина в errorString. Пиксели не инициализируются классом. */ bool create() { close(); #ifdef Q_OS_WIN m_name = QStringLiteral("Local\\vibechat-camera-") + QUuid::createUuid().toString(QUuid::Id128); m_mapping = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, CallCameraProtocol::FrameBytes, reinterpret_cast(m_name.utf16())); const DWORD error = GetLastError(); if (!m_mapping || error == ERROR_ALREADY_EXISTS) return fail(QStringLiteral("CreateFileMapping error=%1").arg(error)); #else auto file = std::make_unique(); if (!file->open() || !file->resize(CallCameraProtocol::FrameBytes)) return fail(file->errorString()); m_name = file->fileName(); m_file = std::move(file); #endif return map(); } /* Закрывает прежний mapping и подключается к существующему name на запись/чтение. * На Unix проверяет точный размер файла; файл peer не удаляет. Результат/ошибка — как у create. */ bool attach(const QString& name) { close(); m_name = name; #ifdef Q_OS_WIN m_mapping = OpenFileMappingW(FILE_MAP_WRITE, FALSE, reinterpret_cast(name.utf16())); if (!m_mapping) return fail(QStringLiteral("OpenFileMapping error=%1").arg(GetLastError())); #else m_file = std::make_unique(name); if (!m_file->open(QIODevice::ReadWrite | QIODevice::ExistingOnly)) return fail(m_file->errorString()); if (m_file->size() != CallCameraProtocol::FrameBytes) return fail(QStringLiteral("Invalid camera buffer size")); #endif return map(); } /* Освобождает только местное отображение/handle; повтор безопасен. У создателя Unix удаляет временный файл, * уже подключённый peer сохраняет своё отображение. Ранее выданный этим объектом data становится недействительным. */ void close() { #ifdef Q_OS_WIN if (m_data && !UnmapViewOfFile(m_data)) DEBUG_WARN(DEBUG_CATEGORY_CALL, "camera buffer unmap error=%lu", (unsigned long)GetLastError()); if (m_mapping && !CloseHandle(m_mapping)) DEBUG_WARN(DEBUG_CATEGORY_CALL, "camera buffer close error=%lu", (unsigned long)GetLastError()); m_mapping = nullptr; #else m_file.reset(); #endif m_data = nullptr; } uchar* data() const { return m_data; } /* Адрес до close/create/attach; владение пикселями задаёт Frame/Ack. */ QString name() const { return m_name; } /* Имя для attach, действительно для подключения при живом mapping. */ QString errorString() const { return m_error; } /* Последняя ошибка, может остаться после последующего успеха. */ };