Preserve packed RGB formats and fix PipeWire test setup
This commit is contained in:
1 parent
5725cfb686
commit
ea971b0856
2 files changed
+32
-11
No files matched your search
@@ -38,6 +38,7 @@ struct StreamPortalCapture::Impl {
|
||||
spa_hook coreHook{}, streamHook{};
|
||||
std::unique_ptr<QSocketNotifier> notifier;
|
||||
QVideoFrameFormat format;
|
||||
int bytesPerPixel = 4;
|
||||
uint64_t frames = 0;
|
||||
};
|
||||
|
||||
@@ -173,7 +174,10 @@ void StreamPortalCapture::openPipeWire(int fd, uint node, const QString& serial)
|
||||
auto* self = static_cast<StreamPortalCapture*>(data); spa_video_info_raw info{};
|
||||
if (spa_format_video_raw_parse(param, &info) < 0) { self->fail("Invalid PipeWire video format"); return; }
|
||||
QVideoFrameFormat::PixelFormat pixel = QVideoFrameFormat::Format_Invalid;
|
||||
self->m->bytesPerPixel = 4;
|
||||
switch (info.format) {
|
||||
case SPA_VIDEO_FORMAT_BGR: pixel = QVideoFrameFormat::Format_BGRX8888; self->m->bytesPerPixel = 3; break;
|
||||
case SPA_VIDEO_FORMAT_RGB: pixel = QVideoFrameFormat::Format_RGBX8888; self->m->bytesPerPixel = 3; break;
|
||||
case SPA_VIDEO_FORMAT_BGRx: pixel = QVideoFrameFormat::Format_BGRX8888; break;
|
||||
case SPA_VIDEO_FORMAT_BGRA: pixel = QVideoFrameFormat::Format_BGRA8888; break;
|
||||
case SPA_VIDEO_FORMAT_RGBx: pixel = QVideoFrameFormat::Format_RGBX8888; break;
|
||||
@@ -203,7 +207,8 @@ void StreamPortalCapture::openPipeWire(int fd, uint node, const QString& serial)
|
||||
spa_rectangle size{1920, 1080}, minimum{1, 1}, maximum{16384, 16384};
|
||||
const spa_pod* format = static_cast<spa_pod*>(spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(4, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA),
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRx,
|
||||
SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR),
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&size, &minimum, &maximum)));
|
||||
int result = pw_stream_connect(m->stream, PW_DIRECTION_INPUT, serial.isEmpty() ? node : PW_ID_ANY,
|
||||
pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), &format, 1);
|
||||
@@ -234,17 +239,22 @@ void StreamPortalCapture::processFrame() {
|
||||
if (!chunk.size) return; // Статичный экран может прислать пустой буфер.
|
||||
if (chunk.flags & SPA_CHUNK_FLAG_CORRUPTED) { DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "Wayland capture discarded corrupted frame"); return; }
|
||||
int width = m->format.frameWidth(), height = m->format.frameHeight();
|
||||
uint64_t stride = chunk.stride > 0 ? chunk.stride : width * 4;
|
||||
uint64_t required = height > 0 ? (height - 1) * stride + width * 4 : 0;
|
||||
if (width <= 0 || height <= 0 || chunk.stride < 0 || stride < uint64_t(width * 4) || chunk.offset > data.maxsize
|
||||
int rowBytes = width * m->bytesPerPixel;
|
||||
uint64_t stride = chunk.stride > 0 ? chunk.stride : rowBytes;
|
||||
uint64_t required = height > 0 ? (height - 1) * stride + rowBytes : 0;
|
||||
if (width <= 0 || height <= 0 || chunk.stride < 0 || stride < uint64_t(rowBytes) || chunk.offset > data.maxsize
|
||||
|| required > data.maxsize - chunk.offset || required > chunk.size) {
|
||||
fail(QString("Invalid PipeWire buffer size=%1x%2 stride=%3 offset=%4 bytes=%5 capacity=%6")
|
||||
.arg(width).arg(height).arg(chunk.stride).arg(chunk.offset).arg(chunk.size).arg(data.maxsize)); return;
|
||||
}
|
||||
QVideoFrame frame(m->format);
|
||||
if (!frame.map(QVideoFrame::WriteOnly)) { fail("Cannot allocate captured video frame"); return; }
|
||||
for (int y = 0; y < height; y++) std::memcpy(frame.bits(0) + y * frame.bytesPerLine(0),
|
||||
static_cast<const uint8_t*>(data.data) + chunk.offset + y * stride, width * 4);
|
||||
for (int y = 0; y < height; y++) {
|
||||
auto* target = frame.bits(0) + y * frame.bytesPerLine(0);
|
||||
const auto* source = static_cast<const uint8_t*>(data.data) + chunk.offset + y * stride;
|
||||
if (m->bytesPerPixel == 4) std::memcpy(target, source, rowBytes);
|
||||
else for (int x = 0; x < width; x++) { std::memcpy(target + x * 4, source + x * 3, 3); target[x * 4 + 3] = 255; }
|
||||
}
|
||||
frame.unmap();
|
||||
auto* header = static_cast<spa_meta_header*>(spa_buffer_find_meta_data(raw, SPA_META_Header, sizeof(spa_meta_header)));
|
||||
if (header && header->pts >= 0) frame.setStartTime(header->pts / 1000);
|
||||
|
||||
@@ -105,7 +105,7 @@ class StreamPortalTest : public QObject {
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_DEBUG);
|
||||
qDBusRegisterMetaType<PortalStreams>();
|
||||
qDBusRegisterMetaType<PortalStream>(); qDBusRegisterMetaType<PortalStreams>();
|
||||
QVERIFY(QDBusConnection::sessionBus().registerService(service));
|
||||
}
|
||||
void init() {
|
||||
@@ -152,10 +152,11 @@ context.properties = { core.daemon = true core.name = pipewire-0 }
|
||||
context.spa-libs = { support.* = support/libspa-support videotestsrc = videotestsrc/libspa-videotestsrc }
|
||||
context.modules = [
|
||||
{ name = libpipewire-module-protocol-native }
|
||||
{ name = libpipewire-module-metadata }
|
||||
{ name = libpipewire-module-spa-node-factory }
|
||||
{ name = libpipewire-module-client-node }
|
||||
{ name = libpipewire-module-link-factory }
|
||||
{ name = libpipewire-module-access args = { access.force = unrestricted } }
|
||||
{ name = libpipewire-module-access args = { access.legacy = true } }
|
||||
]
|
||||
context.objects = [
|
||||
{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = cursor-test node.description = cursor-test
|
||||
@@ -164,13 +165,19 @@ context.objects = [
|
||||
)"); config.close();
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
environment.insert("PIPEWIRE_RUNTIME_DIR", runtime.path());
|
||||
QProcess server; server.setProcessEnvironment(environment); server.setProcessChannelMode(QProcess::MergedChannels);
|
||||
QProcess server; server.setProcessChannelMode(QProcess::MergedChannels);
|
||||
auto serverEnvironment = environment; serverEnvironment.insert("PIPEWIRE_DEBUG", "3"); server.setProcessEnvironment(serverEnvironment);
|
||||
server.start("pipewire", {"-c", config.fileName()}); QVERIFY(server.waitForStarted());
|
||||
auto stop = qScopeGuard([&] { server.terminate(); if (!server.waitForFinished(3000)) { server.kill(); server.waitForFinished(); } });
|
||||
portal->socket = runtime.filePath("pipewire-0");
|
||||
QTRY_VERIFY(QFile::exists(portal->socket));
|
||||
QProcess dump; dump.setProcessEnvironment(environment); dump.start("pw-dump");
|
||||
QVERIFY(dump.waitForFinished(5000)); QCOMPARE(dump.exitCode(), 0);
|
||||
bool dumped = QTest::qWaitFor([&] { return dump.state() == QProcess::NotRunning; }, 5000);
|
||||
if (!dumped || dump.exitCode()) {
|
||||
qWarning().noquote() << "PipeWire server:" << server.readAll() << "dump:" << dump.readAllStandardError();
|
||||
if (!dumped) { dump.kill(); dump.waitForFinished(); }
|
||||
}
|
||||
QVERIFY(dumped); QCOMPARE(dump.exitCode(), 0);
|
||||
uint node = 0; QVariantMap props;
|
||||
for (const auto& value : QJsonDocument::fromJson(dump.readAllStandardOutput()).array()) {
|
||||
auto object = value.toObject(), properties = object.value("info").toObject().value("props").toObject();
|
||||
@@ -184,11 +191,15 @@ context.objects = [
|
||||
// Без менеджера сессий соединяем только тестовые video ports, сохраняя настоящий транспорт PipeWire.
|
||||
QElapsedTimer elapsed; elapsed.start();
|
||||
while (frames.isEmpty() && errors.isEmpty() && elapsed.elapsed() < 6000) {
|
||||
link.start("pw-link", {"cursor-test:out_0", "vibechat-screen:in_0"}); link.waitForFinished(1000);
|
||||
link.start("pw-link", {"cursor-test:capture_1", "vibechat-screen:input_1"});
|
||||
if (!QTest::qWaitFor([&] { return link.state() == QProcess::NotRunning; }, 1000)) { link.kill(); link.waitForFinished(); }
|
||||
QTest::qWait(50);
|
||||
}
|
||||
if (!errors.isEmpty() || frames.isEmpty()) {
|
||||
qWarning().noquote() << "PipeWire server:" << server.readAll() << "link:" << link.readAllStandardError();
|
||||
link.start("pw-link", {"-io"});
|
||||
if (!QTest::qWaitFor([&] { return link.state() == QProcess::NotRunning; }, 1000)) { link.kill(); link.waitForFinished(); }
|
||||
qWarning().noquote() << "PipeWire ports:" << link.readAllStandardOutput();
|
||||
}
|
||||
QVERIFY(errors.isEmpty()); QVERIFY(!frames.isEmpty()); QCOMPARE(portal->cursor, 2u);
|
||||
auto frame = frames.last().front().value<QVideoFrame>(); QVERIFY(frame.isValid()); QVERIFY(!frame.toImage().isNull());
|
||||
|
||||
Reference in new issue
Block a user