Carry native camera modes through Full HD capture protocol
This commit is contained in:
1 parent
9a3ef7b622
commit
eb11d2ebc5
3 files changed
+49
-12
No files matched your search
@@ -2,8 +2,15 @@
|
||||
#include <QObject>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include "stream_video_modes.h"
|
||||
|
||||
using CallCameraDevices = QList<QPair<QString, QByteArray>>;
|
||||
// Только аппаратные режимы камеры, пригодные для звонка; опрос выполняется в процессе захвата.
|
||||
struct CallCameraDevice {
|
||||
QString name;
|
||||
QByteArray id;
|
||||
QList<StreamVideoMode> modes;
|
||||
};
|
||||
using CallCameraDevices = QList<CallCameraDevice>;
|
||||
constexpr qint64 CallCameraMaxFrameAgeUs = 250000;
|
||||
|
||||
// GUI получает только собственную CPU-память. Процесс захвата завершается асинхронно.
|
||||
|
||||
@@ -6,9 +6,15 @@
|
||||
|
||||
namespace CallCameraProtocol {
|
||||
enum Type : quint8 { Configure = 1, Quit, Ack, Devices, Frame, Stage, Error, Buffer };
|
||||
constexpr quint32 FrameBytes = 1280 * 720 * 4;
|
||||
constexpr quint32 FrameBytes = 1920 * 1080 * 4;
|
||||
constexpr quint32 MaxPacket = 65536;
|
||||
|
||||
// Full HD в обеих ориентациях; чётные размеры необходимы для 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);
|
||||
}
|
||||
|
||||
// Только команды и метаданные. Пиксели — в mapping с владением Frame -> Ack, без shared mutex.
|
||||
inline QByteArray packet(Type type, quint64 generation, const std::function<void(QDataStream&)>& write = {}) {
|
||||
QByteArray result;
|
||||
|
||||
@@ -30,7 +30,20 @@ public:
|
||||
~QtCallCameraSource() override { stop(); }
|
||||
CallCameraDevices devices() override {
|
||||
CallCameraDevices result;
|
||||
for (const auto& device : QMediaDevices::videoInputs()) result.append({device.description(), device.id()});
|
||||
for (const auto& device : QMediaDevices::videoInputs()) {
|
||||
CallCameraDevice entry{device.description(), device.id(), {}};
|
||||
for (const auto& format : device.videoFormats()) {
|
||||
const QSize size = format.resolution();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "camera capability device=%s size=%dx%d pixel=%d fps=%.3f..%.3f",
|
||||
device.id().constData(), size.width(), size.height(), format.pixelFormat(), format.minFrameRate(), format.maxFrameRate());
|
||||
if (validSize(size.width(), size.height()))
|
||||
stream_video_add_camera_modes(entry.modes, size, format.minFrameRate(), format.maxFrameRate());
|
||||
}
|
||||
entry.modes.removeIf([](const StreamVideoMode& mode) { return mode.fps != 30; });
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera modes device=%s count=%lld limit=1920x1080 fps=30",
|
||||
device.id().constData(), (long long)entry.modes.size());
|
||||
result.append(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool start(const QByteArray& device, int width, int height) override {
|
||||
@@ -40,13 +53,17 @@ public:
|
||||
session = std::make_unique<QMediaCaptureSession>(); sink = std::make_unique<QVideoSink>();
|
||||
camera = std::make_unique<QCamera>(selected);
|
||||
session->setCamera(camera.get()); session->setVideoSink(sink.get());
|
||||
QCameraFormat best; int distance = INT_MAX;
|
||||
QCameraFormat best;
|
||||
for (const auto& format : selected.videoFormats()) {
|
||||
if (format.maxFrameRate() < 29.9) continue;
|
||||
int score = std::abs(format.resolution().width() - width) + std::abs(format.resolution().height() - height);
|
||||
if (score < distance) { best = format; distance = score; }
|
||||
if (!stream_video_camera_supports(format.resolution(), format.minFrameRate(), format.maxFrameRate(),
|
||||
{QSize(width, height), 30})) continue;
|
||||
if (best.isNull() || format.maxFrameRate() < best.maxFrameRate()) best = format;
|
||||
}
|
||||
if (!best.isNull()) camera->setCameraFormat(best);
|
||||
if (best.isNull()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera mode unavailable device=%s size=%dx%d fps=30", device.constData(), width, height);
|
||||
emit error(QStringLiteral("Selected camera mode is unavailable")); return false;
|
||||
}
|
||||
camera->setCameraFormat(best);
|
||||
const auto format = camera->cameraFormat();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera selected device=%s size=%dx%d pixel=%d fps=%.3f..%.3f",
|
||||
device.constData(), format.resolution().width(), format.resolution().height(), format.pixelFormat(),
|
||||
@@ -123,11 +140,14 @@ class CameraWorker : public QObject {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera frame conversion failed");
|
||||
send(packet(Error, generation, [](QDataStream& out) { out << QStringLiteral("Camera frame conversion failed"); })); return;
|
||||
}
|
||||
if (image.width() > width || image.height() > height) image = image.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation);
|
||||
if (image.size() != QSize(width, height)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera geometry changed requested=%dx%d actual=%dx%d", width, height, image.width(), image.height());
|
||||
send(packet(Error, generation, [](QDataStream& out) { out << QStringLiteral("Camera changed the selected resolution"); })); return;
|
||||
}
|
||||
image = image.convertToFormat(QImage::Format_RGB32);
|
||||
const qint64 converted = call_video_playout_now_us();
|
||||
if (converted - pts <= CallCameraMaxFrameAgeUs) {
|
||||
std::memcpy(pixels, image.constBits(), size_t(image.width()) * image.height() * 4);
|
||||
for (int row = 0; row < height; ++row) std::memcpy(pixels + size_t(row) * width * 4, image.constScanLine(row), size_t(width) * 4);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
auto bytes = packet(Frame, generation, [&](QDataStream& out) {
|
||||
out << pts << qint64(clock.correction) << qint32(image.width()) << qint32(image.height());
|
||||
@@ -155,7 +175,7 @@ class CameraWorker : public QObject {
|
||||
if (sending && revision == inFlight) { sending = false; if (pending) convert(pending, generation); }
|
||||
} else if (type == Configure) {
|
||||
bool enabled = false; QByteArray device; qint32 w = 0, h = 0; in >> enabled >> device >> w >> h;
|
||||
if (in.status() != QDataStream::Ok || w < 1 || h < 1 || w > 1280 || h > 720 || !pixels) return false;
|
||||
if (in.status() != QDataStream::Ok || !validSize(w, h) || !pixels) return false;
|
||||
stop(); generation = revision; width = w; height = h; clock = {}; cadence = {}; lastPts = -1;
|
||||
if (!enabled) { stage(QStringLiteral("idle")); return true; }
|
||||
stage(QStringLiteral("start"));
|
||||
@@ -185,7 +205,11 @@ public:
|
||||
stage(QStringLiteral("enumerate"));
|
||||
const auto devices = source->devices();
|
||||
send(packet(Devices, 0, [&](QDataStream& out) {
|
||||
out << quint32(devices.size()); for (const auto& device : devices) out << device.first << device.second;
|
||||
out << quint32(devices.size());
|
||||
for (const auto& device : devices) {
|
||||
out << device.name << device.id << quint32(device.modes.size());
|
||||
for (const auto& mode : device.modes) out << qint32(mode.size.width()) << qint32(mode.size.height());
|
||||
}
|
||||
}));
|
||||
stage(QStringLiteral("idle"));
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user