|
|
|
|
@ -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); |
|
|
|
|
|