mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac2fa201b | ||
|
|
2d9e51a1ca | ||
|
|
c4cf4f015b | ||
|
|
646afbffff | ||
|
|
6475eeef4c | ||
|
|
2e67a46eb8 | ||
|
|
c333e75dff | ||
|
|
72285023cb | ||
|
|
b00a6ffd8d | ||
|
|
ce935c431e | ||
|
|
ac0944ae1a | ||
|
|
66572806a2 | ||
|
|
a75d6487e3 | ||
|
|
897ad4951b |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 6.0
|
||||
current_version = 6.2
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -36,11 +36,3 @@
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
|
||||
free(m_perror_str); \
|
||||
}
|
||||
|
||||
#define US_ONCE(...) { \
|
||||
const int m_reported = __LINE__; \
|
||||
if (m_reported != once) { \
|
||||
__VA_ARGS__; \
|
||||
once = m_reported; \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 6.0" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 6.2" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 6.0" "November 2020"
|
||||
.TH USTREAMER 1 "version 6.2" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=6.0
|
||||
pkgver=6.2
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=6.0
|
||||
PKG_VERSION:=6.2
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="6.0",
|
||||
version="6.2",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 6
|
||||
#define US_VERSION_MINOR 0
|
||||
#define US_VERSION_MINOR 2
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -264,7 +264,6 @@ void us_device_close(us_device_s *dev) {
|
||||
_D_LOG_PERROR("Can't stop capturing");
|
||||
}
|
||||
run->streamon = false;
|
||||
_D_LOG_DEBUG("VIDIOC_STREAMOFF successful");
|
||||
}
|
||||
|
||||
if (run->hw_bufs != NULL) {
|
||||
@@ -291,7 +290,6 @@ void us_device_close(us_device_s *dev) {
|
||||
}
|
||||
US_DELETE(run->hw_bufs, free);
|
||||
run->n_bufs = 0;
|
||||
_D_LOG_DEBUG("All HW buffers released");
|
||||
}
|
||||
|
||||
US_CLOSE_FD(run->fd);
|
||||
|
||||
@@ -52,6 +52,34 @@ void us_frametext_destroy(us_frametext_s *ft) {
|
||||
free(ft);
|
||||
}
|
||||
|
||||
/*
|
||||
Every character in the font is encoded row-wise in 8 bytes.
|
||||
The least significant bit of each byte corresponds to the first pixel in a row.
|
||||
The character 'A' (0x41 / 65) is encoded as { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}
|
||||
|
||||
0x0C => 0000 1100 => ..XX....
|
||||
0X1E => 0001 1110 => .XXXX...
|
||||
0x33 => 0011 0011 => XX..XX..
|
||||
0x33 => 0011 0011 => XX..XX..
|
||||
0x3F => 0011 1111 => xxxxxx..
|
||||
0x33 => 0011 0011 => XX..XX..
|
||||
0x33 => 0011 0011 => XX..XX..
|
||||
0x00 => 0000 0000 => ........
|
||||
|
||||
To access the nth pixel in a row, right-shift by n.
|
||||
|
||||
. . X X . . . .
|
||||
| | | | | | | |
|
||||
(0x0C >> 0) & 1 == 0-+ | | | | | | |
|
||||
(0x0C >> 1) & 1 == 0---+ | | | | | |
|
||||
(0x0C >> 2) & 1 == 1-----+ | | | | |
|
||||
(0x0C >> 3) & 1 == 1-------+ | | | |
|
||||
(0x0C >> 4) & 1 == 0---------+ | | |
|
||||
(0x0C >> 5) & 1 == 0-----------+ | |
|
||||
(0x0C >> 6) & 1 == 0-------------+ |
|
||||
(0x0C >> 7) & 1 == 0---------------+
|
||||
*/
|
||||
|
||||
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
const u8 US_FRAMETEXT_FONT[128][8] = {
|
||||
// https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
|
||||
// Author: Daniel Hepper <daniel@hepper.net>
|
||||
// License: Public Domain
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
|
||||
|
||||
@@ -72,6 +72,14 @@
|
||||
(m_a > m_b ? m_a : m_b); \
|
||||
})
|
||||
|
||||
#define US_ONCE(...) { \
|
||||
const int m_reported = __LINE__; \
|
||||
if (m_reported != once) { \
|
||||
__VA_ARGS__; \
|
||||
once = m_reported; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
|
||||
@@ -177,12 +177,11 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
uint slowdown_count = 0;
|
||||
while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) {
|
||||
us_hw_buffer_s *hw;
|
||||
const int buf_index = us_device_grab_buffer(dev, &hw);
|
||||
switch (buf_index) {
|
||||
switch (us_device_grab_buffer(dev, &hw)) {
|
||||
case -2: continue; // Broken frame
|
||||
case -1: goto close; // Error
|
||||
default: break; // Grabbed on >= 0
|
||||
}
|
||||
assert(buf_index >= 0);
|
||||
|
||||
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic());
|
||||
if (now_sec_ts != captured_fps_ts) {
|
||||
@@ -208,7 +207,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
us_device_buffer_incref(hw); // RAW
|
||||
us_queue_put(raw_ctx.queue, hw, 0);
|
||||
}
|
||||
us_queue_put(releasers[buf_index].queue, hw, 0); // Plan to release
|
||||
us_queue_put(releasers[hw->buf.index].queue, hw, 0); // Plan to release
|
||||
|
||||
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
|
||||
_stream_check_suicide(stream);
|
||||
|
||||
735
src/v4p/drm.c
735
src/v4p/drm.c
@@ -44,15 +44,14 @@
|
||||
#include "../libs/frametext.h"
|
||||
|
||||
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run);
|
||||
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame);
|
||||
static void _drm_cleanup(us_drm_s *drm);
|
||||
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz);
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf);
|
||||
static int _drm_check_status(us_drm_s *drm);
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on);
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev);
|
||||
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
|
||||
static int _drm_init_buffers(us_drm_s *drm);
|
||||
static int _drm_start_video(us_drm_s *drm);
|
||||
|
||||
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz);
|
||||
static u32 _find_dpms(int fd, drmModeConnector *conn);
|
||||
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
|
||||
static const char *_connector_type_to_string(u32 type);
|
||||
static float _get_refresh_rate(const drmModeModeInfo *mode);
|
||||
@@ -70,33 +69,203 @@ us_drm_s *us_drm_init(void) {
|
||||
US_CALLOC(run, 1);
|
||||
run->fd = -1;
|
||||
run->status_fd = -1;
|
||||
run->dpms_state = -1;
|
||||
run->has_vsync = true;
|
||||
run->exposing_dma_fd = -1;
|
||||
run->ft = us_frametext_init();
|
||||
run->state = US_DRM_STATE_CLOSED;
|
||||
|
||||
us_drm_s *drm;
|
||||
US_CALLOC(drm, 1);
|
||||
// drm->path = "/dev/dri/card0";
|
||||
drm->path = "/dev/dri/by-path/platform-gpu-card";
|
||||
drm->port = "HDMI-A-1";
|
||||
drm->n_bufs = 4;
|
||||
drm->timeout = 5;
|
||||
drm->run = run;
|
||||
return drm;
|
||||
}
|
||||
|
||||
void us_drm_destroy(us_drm_s *drm) {
|
||||
_drm_cleanup(drm);
|
||||
us_frametext_destroy(drm->run->ft);
|
||||
US_DELETE(drm->run, free);
|
||||
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
|
||||
}
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd < 0);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
default: goto error;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Configuring DRM device for %s ...", (dev == NULL ? "STUB" : "DMA"));
|
||||
|
||||
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("DRM device fd=%d opened", run->fd);
|
||||
|
||||
int stub = 0; // Open the real device with DMA
|
||||
if (dev == NULL) {
|
||||
stub = US_DRM_STUB_USER;
|
||||
} else if (dev->run->format != V4L2_PIX_FMT_RGB24) {
|
||||
stub = US_DRM_STUB_BAD_FORMAT;
|
||||
char fourcc_str[8];
|
||||
us_fourcc_to_string(dev->run->format, fourcc_str, 8);
|
||||
_D_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
|
||||
}
|
||||
|
||||
# define CHECK_CAP(x_cap) { \
|
||||
_D_LOG_DEBUG("Checking %s ...", #x_cap); \
|
||||
u64 m_check; \
|
||||
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
|
||||
_D_LOG_PERROR("Can't check " #x_cap); \
|
||||
goto error; \
|
||||
} \
|
||||
if (!m_check) { \
|
||||
_D_LOG_ERROR(#x_cap " is not supported"); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
|
||||
if (stub == 0) {
|
||||
CHECK_CAP(DRM_CAP_PRIME);
|
||||
}
|
||||
# undef CHECK_CAP
|
||||
|
||||
const uint width = (stub > 0 ? 0 : dev->run->width);
|
||||
const uint height = (stub > 0 ? 0 : dev->run->height);
|
||||
const uint hz = (stub > 0 ? 0 : dev->run->hz);
|
||||
switch (_drm_find_sink(drm, width, height, hz)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
default: goto error;
|
||||
}
|
||||
if ((stub == 0) && (width != run->mode.hdisplay || height < run->mode.vdisplay)) {
|
||||
// We'll try to show something instead of nothing if height != vdisplay
|
||||
stub = US_DRM_STUB_BAD_RESOLUTION;
|
||||
_D_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
|
||||
}
|
||||
|
||||
if (_drm_init_buffers(drm, (stub > 0 ? NULL : dev)) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||
_D_LOG_DEBUG("Setting up CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
|
||||
_D_LOG_PERROR("Can't set CRTC");
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->opened_for_stub = (stub > 0);
|
||||
run->exposing_dma_fd = -1;
|
||||
run->unplugged_reported = false;
|
||||
_D_LOG_INFO("Opened for %s ...", (run->opened_for_stub ? "STUB" : "DMA"));
|
||||
return stub;
|
||||
|
||||
error:
|
||||
us_drm_close(drm);
|
||||
return -1;
|
||||
|
||||
unplugged:
|
||||
if (!run->unplugged_reported) {
|
||||
_D_LOG_ERROR("Display is not plugged");
|
||||
run->unplugged_reported = true;
|
||||
}
|
||||
us_drm_close(drm);
|
||||
return -2;
|
||||
}
|
||||
|
||||
void us_drm_close(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
if (run->exposing_dma_fd >= 0) {
|
||||
// Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс.
|
||||
// Просто на всякий случай.
|
||||
assert(run->fd >= 0);
|
||||
us_drm_wait_for_vsync(drm);
|
||||
run->exposing_dma_fd = -1;
|
||||
}
|
||||
|
||||
if (run->saved_crtc != NULL) {
|
||||
_D_LOG_DEBUG("Restoring CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd,
|
||||
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
|
||||
run->saved_crtc->x, run->saved_crtc->y,
|
||||
&run->conn_id, 1, &run->saved_crtc->mode
|
||||
) < 0 && errno != ENOENT) {
|
||||
_D_LOG_PERROR("Can't restore CRTC");
|
||||
}
|
||||
drmModeFreeCrtc(run->saved_crtc);
|
||||
run->saved_crtc = NULL;
|
||||
}
|
||||
|
||||
if (run->bufs != NULL) {
|
||||
_D_LOG_DEBUG("Releasing buffers ...");
|
||||
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
|
||||
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||
}
|
||||
if (buf->dumb_created) {
|
||||
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
|
||||
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
|
||||
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
US_DELETE(run->bufs, free);
|
||||
run->n_bufs = 0;
|
||||
}
|
||||
|
||||
const bool say = (run->fd >= 0);
|
||||
US_CLOSE_FD(run->status_fd);
|
||||
US_CLOSE_FD(run->fd);
|
||||
|
||||
run->crtc_id = 0;
|
||||
run->dpms_state = -1;
|
||||
run->has_vsync = true;
|
||||
run->stub_n_buf = 0;
|
||||
|
||||
if (say) {
|
||||
_D_LOG_INFO("Closed");
|
||||
}
|
||||
}
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
assert(drm->run->fd >= 0);
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return 0; // Unplugged, nice
|
||||
// Во время переключения DPMS монитор моргает один раз состоянием disconnected,
|
||||
// а потом почему-то снова оказывается connected. Так что просто считаем,
|
||||
// что отсоединенный монитор на этом этапе - это нормально.
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
if (_drm_ensure(drm, NULL, 0) < 0) {
|
||||
return -1;
|
||||
assert(run->fd >= 0);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return -2;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
|
||||
if (run->has_vsync) {
|
||||
return 0;
|
||||
}
|
||||
@@ -110,10 +279,10 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
|
||||
if (result < 0) {
|
||||
_D_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
|
||||
goto error;
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
_D_LOG_ERROR("Device timeout while waiting VSync");
|
||||
goto error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
drmEventContext ctx = {
|
||||
@@ -123,274 +292,152 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||
if (drmHandleEvent(run->fd, &ctx) < 0) {
|
||||
_D_LOG_PERROR("Can't handle DRM event");
|
||||
goto error;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_drm_cleanup(drm);
|
||||
_D_LOG_ERROR("Device destroyed due an error (vsync)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
if (_drm_ensure(drm, frame, hz) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const drmModeModeInfo *const mode = &run->mode;
|
||||
bool msg_drawn = false;
|
||||
|
||||
# define DRAW_MSG(x_msg) { \
|
||||
us_frametext_draw(run->ft, (x_msg), mode->hdisplay, mode->vdisplay); \
|
||||
frame = run->ft->frame; \
|
||||
msg_drawn = true; \
|
||||
}
|
||||
|
||||
if (frame == NULL) {
|
||||
switch (ex) {
|
||||
case US_DRM_EXPOSE_NO_SIGNAL:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
|
||||
break;
|
||||
case US_DRM_EXPOSE_BUSY:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
|
||||
break;
|
||||
default:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
|
||||
}
|
||||
} else if (mode->hdisplay != frame->width/* || mode->vdisplay != frame->height*/) {
|
||||
// XXX: At least we'll try to show something instead of nothing ^^^
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED RESOLUTION >"
|
||||
"\n \n< %ux%up%.02f >"
|
||||
"\n \nby this display",
|
||||
frame->width, frame->height, hz);
|
||||
DRAW_MSG(msg);
|
||||
} else if (frame->format != V4L2_PIX_FMT_RGB24) {
|
||||
DRAW_MSG(
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
|
||||
"\n \nIt shouldn't happen ever."
|
||||
"\n \nPlease check the logs and report a bug:"
|
||||
"\n \n- https://github.com/pikvm/pikvm -");
|
||||
}
|
||||
|
||||
# undef DRAW_MSG
|
||||
|
||||
if (_drm_expose_raw(drm, frame) < 0) {
|
||||
_drm_cleanup(drm);
|
||||
_D_LOG_ERROR("Device destroyed due an error (expose)");
|
||||
}
|
||||
return (msg_drawn ? -1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run) {
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf) {
|
||||
(void)fd;
|
||||
(void)n_frame;
|
||||
(void)sec;
|
||||
(void)usec;
|
||||
us_drm_runtime_s *const run = v_run;
|
||||
run->has_vsync = true;
|
||||
us_drm_buffer_s *const buf = v_buf;
|
||||
*buf->ctx.has_vsync = true;
|
||||
*buf->ctx.exposing_dma_fd = -1;
|
||||
_D_LOG_DEBUG("Got VSync signal");
|
||||
}
|
||||
|
||||
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[run->next_n_buf];
|
||||
|
||||
_D_LOG_DEBUG("Exposing%s framebuffer n_buf=%u, vsync=%d ...",
|
||||
(frame == NULL ? " EMPTY" : ""), run->next_n_buf, run->has_vsync);
|
||||
|
||||
if (frame == NULL) {
|
||||
memset(buf->data, 0, buf->allocated);
|
||||
} else {
|
||||
memcpy(buf->data, frame->data, US_MIN(frame->used, buf->allocated));
|
||||
}
|
||||
|
||||
run->has_vsync = false;
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
run);
|
||||
run->next_n_buf = (run->next_n_buf + 1) % run->n_bufs;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _drm_cleanup(us_drm_s *drm) {
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
_D_LOG_DEBUG("Cleaning up ...");
|
||||
|
||||
if (run->saved_crtc != NULL) {
|
||||
if (drmModeSetCrtc(run->fd,
|
||||
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
|
||||
run->saved_crtc->x, run->saved_crtc->y,
|
||||
&run->conn_id, 1, &run->saved_crtc->mode
|
||||
) < 0 && errno != ENOENT) {
|
||||
_D_LOG_PERROR("Can't restore CRTC");
|
||||
}
|
||||
drmModeFreeCrtc(run->saved_crtc);
|
||||
run->saved_crtc = NULL;
|
||||
}
|
||||
|
||||
if (run->bufs != NULL) {
|
||||
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
|
||||
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
}
|
||||
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
|
||||
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||
}
|
||||
if (buf->dumb_created) {
|
||||
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
|
||||
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
US_DELETE(run->bufs, free);
|
||||
run->n_bufs = 0;
|
||||
}
|
||||
|
||||
US_CLOSE_FD(run->status_fd);
|
||||
US_CLOSE_FD(run->fd);
|
||||
|
||||
run->crtc_id = 0;
|
||||
run->next_n_buf = 0;
|
||||
run->has_vsync = false;
|
||||
if (run->state == US_DRM_STATE_OK) {
|
||||
_D_LOG_INFO("Stopped");
|
||||
}
|
||||
run->state = US_DRM_STATE_CLOSED;
|
||||
}
|
||||
|
||||
static int _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened_for_stub);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
default: goto error;
|
||||
case -2: return -2;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
|
||||
if (frame == NULL && run->state == US_DRM_STATE_OK) {
|
||||
return 0;
|
||||
} else if (
|
||||
frame != NULL
|
||||
&& run->p_width == frame->width
|
||||
&& run->p_height == frame->height
|
||||
&& run->p_hz == hz
|
||||
&& run->state <= US_DRM_STATE_CLOSED
|
||||
) {
|
||||
return (run->state == US_DRM_STATE_OK ? 0 : -1);
|
||||
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
|
||||
switch (stub) {
|
||||
case US_DRM_STUB_BAD_RESOLUTION: {
|
||||
assert(dev != NULL);
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED RESOLUTION >"
|
||||
"\n \n< %ux%up%.02f >"
|
||||
"\n \nby this display",
|
||||
dev->run->width, dev->run->height, dev->run->hz);
|
||||
DRAW_MSG(msg);
|
||||
break;
|
||||
};
|
||||
case US_DRM_STUB_BAD_FORMAT:
|
||||
DRAW_MSG(
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
|
||||
"\n \nIt shouldn't happen ever."
|
||||
"\n \nPlease check the logs and report a bug:"
|
||||
"\n \n- https://github.com/pikvm/pikvm -");
|
||||
break;
|
||||
case US_DRM_STUB_NO_SIGNAL:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
|
||||
break;
|
||||
case US_DRM_STUB_BUSY:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
|
||||
break;
|
||||
default:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
|
||||
break;
|
||||
}
|
||||
# undef DRAW_MSG
|
||||
|
||||
const us_drm_state_e saved_state = run->state;
|
||||
_drm_cleanup(drm);
|
||||
if (saved_state > US_DRM_STATE_CLOSED) {
|
||||
run->state = saved_state;
|
||||
us_drm_buffer_s *const buf = &run->bufs[run->stub_n_buf];
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Copying STUB frame ...")
|
||||
memcpy(buf->data, run->ft->frame->data, US_MIN(run->ft->frame->used, buf->allocated));
|
||||
|
||||
_D_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
|
||||
|
||||
run->p_width = (frame != NULL ? frame->width : 0); // 0 for find the native resolution
|
||||
run->p_height = (frame != NULL ? frame->height : 0);
|
||||
run->p_hz = hz;
|
||||
run->stub_n_buf = (run->stub_n_buf + 1) % run->n_bufs;
|
||||
return retval;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Configuring DRM device ...");
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
|
||||
|
||||
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device");
|
||||
goto error;
|
||||
}
|
||||
assert(run->fd >= 0);
|
||||
assert(!run->opened_for_stub);
|
||||
|
||||
# define CHECK_CAP(x_cap) { \
|
||||
u64 m_check; \
|
||||
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
|
||||
_D_LOG_PERROR("Can't check " #x_cap); \
|
||||
goto error; \
|
||||
} \
|
||||
if (!m_check) { \
|
||||
_D_LOG_ERROR(#x_cap " is not supported"); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
|
||||
// CHECK_CAP(DRM_CAP_PRIME);
|
||||
# undef CHECK_CAP
|
||||
|
||||
switch (_drm_find_sink(drm, run->p_width, run->p_height, run->p_hz)) {
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
default: goto error;
|
||||
case -2: return -2;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
|
||||
const float mode_hz = _get_refresh_rate(&run->mode);
|
||||
if (frame == NULL) {
|
||||
run->p_width = run->mode.hdisplay;
|
||||
run->p_height = run->mode.vdisplay;
|
||||
run->p_hz = mode_hz;
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_INFO("Using %s mode: %ux%up%.02f",
|
||||
drm->port, run->mode.hdisplay, run->mode.vdisplay, mode_hz);
|
||||
|
||||
if (_drm_init_buffers(drm) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (_drm_start_video(drm) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Showing ...");
|
||||
run->state = US_DRM_STATE_OK;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_drm_cleanup(drm);
|
||||
_D_LOG_ERROR("Device destroyed due an error (ensure)");
|
||||
return -1;
|
||||
|
||||
unplugged:
|
||||
if (run->state != US_DRM_STATE_NO_DISPLAY) {
|
||||
_D_LOG_INFO("Display %s is not plugged", drm->port);
|
||||
}
|
||||
_drm_cleanup(drm);
|
||||
run->state = US_DRM_STATE_NO_DISPLAY;
|
||||
return -2;
|
||||
_D_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
|
||||
run->exposing_dma_fd = hw->dma_fd;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int _drm_check_status(us_drm_s *drm) {
|
||||
us_drm_runtime_s *run = drm->run;
|
||||
|
||||
if (run->status_fd < 0) {
|
||||
_D_LOG_DEBUG("Trying to find status file ...");
|
||||
struct stat st;
|
||||
if (stat(drm->path, &st) < 0) {
|
||||
_D_LOG_PERROR("Can't stat() DRM device");
|
||||
goto error;
|
||||
}
|
||||
const uint mi = minor(st.st_rdev);
|
||||
_D_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
|
||||
|
||||
char path[128];
|
||||
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
|
||||
_D_LOG_DEBUG("Opening status file %s ...", path);
|
||||
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device status file: %s", path);
|
||||
_D_LOG_PERROR("Can't open status file: %s", path);
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
|
||||
}
|
||||
|
||||
char status_ch;
|
||||
if (read(run->status_fd, &status_ch, 1) != 1) {
|
||||
_D_LOG_PERROR("Can't read connector status");
|
||||
_D_LOG_PERROR("Can't read status file");
|
||||
goto error;
|
||||
}
|
||||
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
|
||||
_D_LOG_PERROR("Can't rewind connector status");
|
||||
_D_LOG_PERROR("Can't rewind status file");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Current display status: %c", status_ch);
|
||||
return (status_ch == 'd' ? -2 : 0);
|
||||
|
||||
error:
|
||||
@@ -398,6 +445,94 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
if (run->dpms_id > 0 && run->dpms_state != (int)on) {
|
||||
_D_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
|
||||
if (drmModeConnectorSetProperty(
|
||||
run->fd, run->conn_id, run->dpms_id,
|
||||
(on ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)
|
||||
) < 0) {
|
||||
_D_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
|
||||
}
|
||||
}
|
||||
run->dpms_state = (int)on;
|
||||
}
|
||||
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
const uint n_bufs = (dev == NULL ? 4 : dev->run->n_bufs);
|
||||
const char *name = (dev == NULL ? "STUB" : "DMA");
|
||||
|
||||
_D_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
|
||||
|
||||
US_CALLOC(run->bufs, n_bufs);
|
||||
for (run->n_bufs = 0; run->n_bufs < n_bufs; ++run->n_bufs) {
|
||||
const uint n_buf = run->n_bufs;
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
|
||||
buf->ctx.has_vsync = &run->has_vsync;
|
||||
buf->ctx.exposing_dma_fd = &run->exposing_dma_fd;
|
||||
|
||||
u32 handles[4] = {0};
|
||||
u32 strides[4] = {0};
|
||||
u32 offsets[4] = {0};
|
||||
|
||||
if (dev == NULL) {
|
||||
struct drm_mode_create_dumb create = {
|
||||
.width = run->mode.hdisplay,
|
||||
.height = run->mode.vdisplay,
|
||||
.bpp = 24,
|
||||
};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
|
||||
_D_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->handle = create.handle;
|
||||
buf->dumb_created = true;
|
||||
|
||||
struct drm_mode_map_dumb map = {.handle = create.handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
|
||||
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||
return -1;
|
||||
}
|
||||
if ((buf->data = mmap(
|
||||
NULL, create.size,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
run->fd, map.offset
|
||||
)) == MAP_FAILED) {
|
||||
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
memset(buf->data, 0, create.size);
|
||||
buf->allocated = create.size;
|
||||
|
||||
handles[0] = create.handle;
|
||||
strides[0] = create.pitch;
|
||||
|
||||
} else {
|
||||
if (drmPrimeFDToHandle(run->fd, dev->run->hw_bufs[n_buf].dma_fd, &buf->handle) < 0) {
|
||||
_D_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
|
||||
return -1;
|
||||
}
|
||||
handles[0] = buf->handle;
|
||||
strides[0] = dev->run->stride;
|
||||
}
|
||||
|
||||
if (drmModeAddFB2(
|
||||
run->fd,
|
||||
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
|
||||
handles, strides, offsets, &buf->id, 0
|
||||
)) {
|
||||
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->fb_added = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
@@ -430,49 +565,29 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
drmModeFreeConnector(conn);
|
||||
continue;
|
||||
}
|
||||
_D_LOG_DEBUG("Found connector for port %s: conn_type=%d, conn_type_id=%d",
|
||||
_D_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
|
||||
drm->port, conn->connector_type, conn->connector_type_id);
|
||||
|
||||
if (conn->connection != DRM_MODE_CONNECTED) {
|
||||
_D_LOG_DEBUG("Display is not connected");
|
||||
_D_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
|
||||
drmModeModeInfo *best = NULL;
|
||||
drmModeModeInfo *closest = NULL;
|
||||
drmModeModeInfo *pref = NULL;
|
||||
for (int mi = 0; mi < conn->count_modes; ++mi) {
|
||||
drmModeModeInfo *const mode = &conn->modes[mi];
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
continue; // Paranoia for size and discard interlaced
|
||||
}
|
||||
const float mode_hz = _get_refresh_rate(mode);
|
||||
if (mode->hdisplay == width && mode->vdisplay == height) {
|
||||
best = mode; // Any mode with exact resolution
|
||||
if (hz > 0 && mode_hz == hz) {
|
||||
break; // Exact mode with same freq
|
||||
}
|
||||
}
|
||||
if (mode->hdisplay == width && mode->vdisplay < height) {
|
||||
if (closest == NULL || _get_refresh_rate(closest) != hz) {
|
||||
closest = mode; // Something like 1920x1080p60 for 1920x1200p60 source
|
||||
}
|
||||
}
|
||||
if (pref == NULL && (mode->type & DRM_MODE_TYPE_PREFERRED)) {
|
||||
pref = mode; // Preferred mode if nothing is found
|
||||
}
|
||||
}
|
||||
if (best == NULL) { best = closest; }
|
||||
if (best == NULL) { best = pref; }
|
||||
if (best == NULL) { best = (conn->count_modes > 0 ? &conn->modes[0] : NULL); }
|
||||
if (best == NULL) {
|
||||
_D_LOG_ERROR("Can't find any appropriate resolutions");
|
||||
drmModeModeInfo *best;
|
||||
if ((best = _find_best_mode(conn, width, height, hz)) == NULL) {
|
||||
_D_LOG_ERROR("Can't find any appropriate display modes");
|
||||
drmModeFreeConnector(conn);
|
||||
goto unplugged;
|
||||
}
|
||||
assert(best->hdisplay > 0);
|
||||
assert(best->vdisplay > 0);
|
||||
_D_LOG_INFO("Using best mode: %ux%up%.02f",
|
||||
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
|
||||
|
||||
if ((run->dpms_id = _find_dpms(run->fd, conn)) > 0) {
|
||||
_D_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
|
||||
} else {
|
||||
_D_LOG_INFO("Using DPMS: None");
|
||||
}
|
||||
|
||||
u32 taken_crtcs = 0; // Unused here
|
||||
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
|
||||
@@ -480,6 +595,8 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
_D_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
|
||||
|
||||
run->conn_id = conn->connector_id;
|
||||
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
|
||||
|
||||
@@ -496,72 +613,58 @@ unplugged:
|
||||
return -2;
|
||||
}
|
||||
|
||||
static int _drm_init_buffers(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz) {
|
||||
drmModeModeInfo *best = NULL;
|
||||
drmModeModeInfo *closest = NULL;
|
||||
drmModeModeInfo *pref = NULL;
|
||||
|
||||
_D_LOG_DEBUG("Initializing %u buffers ...", drm->n_bufs);
|
||||
|
||||
US_CALLOC(run->bufs, drm->n_bufs);
|
||||
for (run->n_bufs = 0; run->n_bufs < drm->n_bufs; ++run->n_bufs) {
|
||||
const uint n_buf = run->n_bufs;
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
|
||||
struct drm_mode_create_dumb create = {
|
||||
.width = run->mode.hdisplay,
|
||||
.height = run->mode.vdisplay,
|
||||
.bpp = 24,
|
||||
};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
|
||||
_D_LOG_PERROR("Can't create dumb buffer=%u", n_buf);
|
||||
return -1;
|
||||
for (int mi = 0; mi < conn->count_modes; ++mi) {
|
||||
drmModeModeInfo *const mode = &conn->modes[mi];
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
continue; // Discard interlaced
|
||||
}
|
||||
buf->handle = create.handle;
|
||||
buf->dumb_created = true;
|
||||
|
||||
u32 handles[4] = {create.handle};
|
||||
u32 strides[4] = {create.pitch};
|
||||
u32 offsets[4] = {0};
|
||||
if (drmModeAddFB2(
|
||||
run->fd,
|
||||
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
|
||||
handles, strides, offsets, &buf->id, 0
|
||||
)) {
|
||||
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
return -1;
|
||||
const float mode_hz = _get_refresh_rate(mode);
|
||||
if (mode->hdisplay == width && mode->vdisplay == height) {
|
||||
best = mode; // Any mode with exact resolution
|
||||
if (hz > 0 && mode_hz == hz) {
|
||||
break; // Exact mode with same freq
|
||||
}
|
||||
}
|
||||
buf->fb_added = true;
|
||||
|
||||
struct drm_mode_map_dumb map = {.handle = create.handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
|
||||
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||
return -1;
|
||||
if (mode->hdisplay == width && mode->vdisplay < height) {
|
||||
if (closest == NULL || _get_refresh_rate(closest) != hz) {
|
||||
closest = mode; // Something like 1920x1080p60 for 1920x1200p60 source
|
||||
}
|
||||
}
|
||||
|
||||
if ((buf->data = mmap(
|
||||
NULL, create.size,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
run->fd, map.offset
|
||||
)) == MAP_FAILED) {
|
||||
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
return -1;
|
||||
if (pref == NULL && (mode->type & DRM_MODE_TYPE_PREFERRED)) {
|
||||
pref = mode; // Preferred mode if nothing is found
|
||||
}
|
||||
memset(buf->data, 0, create.size);
|
||||
buf->allocated = create.size;
|
||||
}
|
||||
return 0;
|
||||
|
||||
if (best == NULL) {
|
||||
best = closest;
|
||||
}
|
||||
if (best == NULL) {
|
||||
best = pref;
|
||||
}
|
||||
if (best == NULL) {
|
||||
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
|
||||
}
|
||||
assert(best == NULL || best->hdisplay > 0);
|
||||
assert(best == NULL || best->vdisplay > 0);
|
||||
return best;
|
||||
}
|
||||
|
||||
static int _drm_start_video(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||
_D_LOG_DEBUG("Setting up CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
|
||||
_D_LOG_PERROR("Can't set CRTC");
|
||||
return -1;
|
||||
}
|
||||
if (_drm_expose_raw(drm, NULL) < 0) {
|
||||
_D_LOG_PERROR("Can't flip the first page");
|
||||
return -1;
|
||||
static u32 _find_dpms(int fd, drmModeConnector *conn) {
|
||||
for (int pi = 0; pi < conn->count_props; pi++) {
|
||||
drmModePropertyPtr prop = drmModeGetProperty(fd, conn->props[pi]);
|
||||
if (prop != NULL) {
|
||||
if (!strcmp(prop->name, "DPMS")) {
|
||||
const u32 id = prop->prop_id;
|
||||
drmModeFreeProperty(prop);
|
||||
return id;
|
||||
}
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -28,19 +28,16 @@
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/frametext.h"
|
||||
#include "../libs/device.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
US_DRM_EXPOSE_FRAME = 0,
|
||||
US_DRM_EXPOSE_NO_SIGNAL,
|
||||
US_DRM_EXPOSE_BUSY,
|
||||
} us_drm_expose_e;
|
||||
|
||||
typedef enum {
|
||||
US_DRM_STATE_OK = 0,
|
||||
US_DRM_STATE_CLOSED,
|
||||
US_DRM_STATE_NO_DISPLAY,
|
||||
} us_drm_state_e;
|
||||
US_DRM_STUB_USER = 1,
|
||||
US_DRM_STUB_BAD_RESOLUTION,
|
||||
US_DRM_STUB_BAD_FORMAT,
|
||||
US_DRM_STUB_NO_SIGNAL,
|
||||
US_DRM_STUB_BUSY,
|
||||
} us_drm_stub_e;
|
||||
|
||||
typedef struct {
|
||||
u32 id;
|
||||
@@ -49,34 +46,34 @@ typedef struct {
|
||||
uz allocated;
|
||||
bool dumb_created;
|
||||
bool fb_added;
|
||||
struct {
|
||||
bool *has_vsync;
|
||||
int *exposing_dma_fd;
|
||||
} ctx;
|
||||
} us_drm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int status_fd;
|
||||
|
||||
int fd;
|
||||
u32 crtc_id;
|
||||
u32 conn_id;
|
||||
u32 dpms_id;
|
||||
drmModeModeInfo mode;
|
||||
us_drm_buffer_s *bufs;
|
||||
uint n_bufs;
|
||||
drmModeCrtc *saved_crtc;
|
||||
uint next_n_buf;
|
||||
int dpms_state;
|
||||
bool opened_for_stub;
|
||||
bool has_vsync;
|
||||
|
||||
int exposing_dma_fd;
|
||||
uint stub_n_buf;
|
||||
bool unplugged_reported;
|
||||
us_frametext_s *ft;
|
||||
|
||||
uint p_width;
|
||||
uint p_height;
|
||||
float p_hz;
|
||||
|
||||
us_drm_state_e state;
|
||||
} us_drm_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *port;
|
||||
uint n_bufs;
|
||||
uint timeout;
|
||||
|
||||
us_drm_runtime_s *run;
|
||||
@@ -86,5 +83,10 @@ typedef struct {
|
||||
us_drm_s *us_drm_init(void);
|
||||
void us_drm_destroy(us_drm_s *drm);
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev);
|
||||
void us_drm_close(us_drm_s *drm);
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm);
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm);
|
||||
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz);
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev);
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw);
|
||||
|
||||
@@ -164,62 +164,97 @@ static void _main_loop(void) {
|
||||
|
||||
us_device_s *dev = us_device_init();
|
||||
dev->path = "/dev/kvmd-video";
|
||||
dev->n_bufs = drm->n_bufs;
|
||||
dev->n_bufs = 6;
|
||||
dev->format = V4L2_PIX_FMT_RGB24;
|
||||
dev->dv_timings = true;
|
||||
dev->persistent = true;
|
||||
dev->dma_export = true;
|
||||
dev->dma_required = true;
|
||||
|
||||
int once = 0;
|
||||
ldf blank_at_ts = 0;
|
||||
int drm_opened = -1;
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
# define CHECK(x_arg) if ((x_arg) < 0) { goto close; }
|
||||
|
||||
if (drm_opened <= 0) {
|
||||
blank_at_ts = 0;
|
||||
CHECK(drm_opened = us_drm_open(drm, NULL));
|
||||
}
|
||||
assert(drm_opened > 0);
|
||||
|
||||
if (atomic_load(&_g_ustreamer_online)) {
|
||||
if (us_drm_wait_for_vsync(drm) == 0) {
|
||||
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
|
||||
}
|
||||
if (dev->run->fd >= 0) {
|
||||
goto close;
|
||||
} else {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
blank_at_ts = 0;
|
||||
US_ONCE({ US_LOG_INFO("DRM: Online stream is active, stopping capture ..."); });
|
||||
CHECK(us_drm_wait_for_vsync(drm));
|
||||
CHECK(us_drm_expose_stub(drm, US_DRM_STUB_BUSY, NULL));
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (us_device_open(dev) < 0) {
|
||||
if (us_drm_wait_for_vsync(drm) == 0) {
|
||||
us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0);
|
||||
ldf now_ts = us_get_now_monotonic();
|
||||
if (blank_at_ts == 0) {
|
||||
blank_at_ts = now_ts + 5;
|
||||
}
|
||||
goto close;
|
||||
if (now_ts <= blank_at_ts) {
|
||||
CHECK(us_drm_wait_for_vsync(drm));
|
||||
CHECK(us_drm_expose_stub(drm, US_DRM_STUB_NO_SIGNAL, NULL));
|
||||
} else {
|
||||
US_ONCE({ US_LOG_INFO("DRM: Turning off the display by timeout ..."); });
|
||||
CHECK(us_drm_dpms_power_off(drm));
|
||||
}
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
|
||||
once = 0;
|
||||
blank_at_ts = 0;
|
||||
us_drm_close(drm);
|
||||
CHECK(drm_opened = us_drm_open(drm, dev));
|
||||
|
||||
us_hw_buffer_s *prev_hw = NULL;
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
if (atomic_load(&_g_ustreamer_online)) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (us_drm_wait_for_vsync(drm) < 0) {
|
||||
_slowdown();
|
||||
continue;
|
||||
CHECK(us_drm_wait_for_vsync(drm));
|
||||
|
||||
if (prev_hw != NULL) {
|
||||
CHECK(us_device_release_buffer(dev, prev_hw));
|
||||
prev_hw = NULL;
|
||||
}
|
||||
|
||||
us_hw_buffer_s *hw;
|
||||
const int buf_index = us_device_grab_buffer(dev, &hw);
|
||||
switch (buf_index) {
|
||||
switch (us_device_grab_buffer(dev, &hw)) {
|
||||
case -2: continue; // Broken frame
|
||||
case -1: goto close; // Any error
|
||||
default: break; // Grabbed on >= 0
|
||||
}
|
||||
assert(buf_index >= 0);
|
||||
|
||||
const int exposed = us_drm_expose(drm, US_DRM_EXPOSE_FRAME, &hw->raw, dev->run->hz);
|
||||
if (us_device_release_buffer(dev, hw) < 0) {
|
||||
goto close;
|
||||
if (drm_opened == 0) {
|
||||
CHECK(us_drm_expose_dma(drm, hw));
|
||||
prev_hw = hw;
|
||||
} else {
|
||||
CHECK(us_drm_expose_stub(drm, drm_opened, dev));
|
||||
CHECK(us_device_release_buffer(dev, hw));
|
||||
}
|
||||
if (exposed < 0) {
|
||||
|
||||
if (drm_opened > 0) {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
close:
|
||||
us_drm_close(drm);
|
||||
drm_opened = -1;
|
||||
|
||||
us_device_close(dev);
|
||||
|
||||
_slowdown();
|
||||
|
||||
# undef CHECK
|
||||
}
|
||||
|
||||
us_device_destroy(dev);
|
||||
|
||||
Reference in New Issue
Block a user