Compare commits

...

8 Commits
v6.1 ... v6.2

Author SHA1 Message Date
Maxim Devaev
8ac2fa201b Bump version: 6.1 → 6.2 2024-03-10 18:11:26 +02:00
Maxim Devaev
2d9e51a1ca v4p: turn off the display after timeout 2024-03-10 16:05:50 +00:00
Maxim Devaev
c4cf4f015b v4p: wait for dma_fd on close 2024-03-10 16:49:49 +02:00
Maxim Devaev
646afbffff refactoring 2024-03-10 12:30:22 +02:00
Maxim Devaev
6475eeef4c refactoring 2024-03-10 12:25:08 +02:00
Maxim Devaev
2e67a46eb8 refactoring 2024-03-10 12:10:09 +02:00
Maxim Devaev
c333e75dff v4p: dpms 2024-03-09 19:55:22 +00:00
Maxim Devaev
72285023cb refactoring 2024-03-09 12:13:17 +02:00
11 changed files with 236 additions and 154 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 6.1
current_version = 6.2
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}

View File

@@ -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.1" "January 2021"
.TH USTREAMER-DUMP 1 "version 6.2" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

View 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.1" "November 2020"
.TH USTREAMER 1 "version 6.2" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=6.1
pkgver=6.2
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=6.1
PKG_VERSION:=6.2
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
if __name__ == "__main__":
setup(
name="ustreamer",
version="6.1",
version="6.2",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

@@ -26,7 +26,7 @@
#define US_VERSION_MAJOR 6
#define US_VERSION_MINOR 1
#define US_VERSION_MINOR 2
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

View File

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

View File

@@ -46,9 +46,12 @@
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 int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
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 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);
@@ -66,7 +69,9 @@ 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();
us_drm_s *drm;
@@ -157,9 +162,10 @@ int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
goto error;
}
run->opened_for_stub = (stub > 0);
run->exposing_dma_fd = -1;
run->unplugged_reported = false;
run->stub = (stub > 0);
_D_LOG_INFO("Opened for %s ...", (run->stub ? "STUB" : "DMA"));
_D_LOG_INFO("Opened for %s ...", (run->opened_for_stub ? "STUB" : "DMA"));
return stub;
error:
@@ -178,6 +184,14 @@ unplugged:
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,
@@ -217,6 +231,7 @@ void us_drm_close(us_drm_s *drm) {
US_CLOSE_FD(run->fd);
run->crtc_id = 0;
run->dpms_state = -1;
run->has_vsync = true;
run->stub_n_buf = 0;
@@ -225,6 +240,20 @@ void us_drm_close(us_drm_s *drm) {
}
}
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;
@@ -235,6 +264,7 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
case -2: return -2;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
if (run->has_vsync) {
return 0;
@@ -274,6 +304,7 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
(void)usec;
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");
}
@@ -281,13 +312,14 @@ 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;
assert(run->fd >= 0);
assert(run->stub);
assert(run->opened_for_stub);
switch (_drm_check_status(drm)) {
case 0: break;
case -2: return -2;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
switch (stub) {
@@ -349,13 +381,14 @@ int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
assert(run->fd >= 0);
assert(!run->stub);
assert(!run->opened_for_stub);
switch (_drm_check_status(drm)) {
case 0: break;
case -2: return -2;
default: return -1;
}
_drm_ensure_dpms_power(drm, true);
run->has_vsync = false;
@@ -368,6 +401,7 @@ int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
_D_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
}
_D_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
run->exposing_dma_fd = hw->dma_fd;
return retval;
}
@@ -411,105 +445,18 @@ error:
return -1;
}
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on) {
us_drm_runtime_s *const run = drm->run;
run->crtc_id = 0;
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
drmModeRes *res = drmModeGetResources(run->fd);
if (res == NULL) {
_D_LOG_PERROR("Can't get resources info");
goto done;
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);
}
}
if (res->count_connectors <= 0) {
_D_LOG_ERROR("Can't find any connectors");
goto done;
}
for (int ci = 0; ci < res->count_connectors; ++ci) {
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
if (conn == NULL) {
_D_LOG_PERROR("Can't get connector index=%d", ci);
goto done;
}
char port[32];
US_SNPRINTF(port, 31, "%s-%u",
_connector_type_to_string(conn->connector_type),
conn->connector_type_id);
if (strcmp(port, drm->port) != 0) {
drmModeFreeConnector(conn);
continue;
}
_D_LOG_DEBUG("Found connector for port %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_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 display modes");
drmModeFreeConnector(conn);
goto unplugged;
}
assert(best->hdisplay > 0);
assert(best->vdisplay > 0);
_D_LOG_INFO("The best display mode is: %ux%up%.02f",
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
u32 taken_crtcs = 0; // Unused here
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
_D_LOG_ERROR("Can't find CRTC");
drmModeFreeConnector(conn);
goto done;
}
run->conn_id = conn->connector_id;
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
drmModeFreeConnector(conn);
break;
}
done:
drmModeFreeResources(res);
return (run->crtc_id > 0 ? 0 : -1);
unplugged:
drmModeFreeResources(res);
return -2;
run->dpms_state = (int)on;
}
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
@@ -526,6 +473,7 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
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};
@@ -585,6 +533,142 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
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;
run->crtc_id = 0;
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
drmModeRes *res = drmModeGetResources(run->fd);
if (res == NULL) {
_D_LOG_PERROR("Can't get resources info");
goto done;
}
if (res->count_connectors <= 0) {
_D_LOG_ERROR("Can't find any connectors");
goto done;
}
for (int ci = 0; ci < res->count_connectors; ++ci) {
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
if (conn == NULL) {
_D_LOG_PERROR("Can't get connector index=%d", ci);
goto done;
}
char port[32];
US_SNPRINTF(port, 31, "%s-%u",
_connector_type_to_string(conn->connector_type),
conn->connector_type_id);
if (strcmp(port, drm->port) != 0) {
drmModeFreeConnector(conn);
continue;
}
_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_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
drmModeFreeConnector(conn);
goto done;
}
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;
}
_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) {
_D_LOG_ERROR("Can't find CRTC");
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));
drmModeFreeConnector(conn);
break;
}
done:
drmModeFreeResources(res);
return (run->crtc_id > 0 ? 0 : -1);
unplugged:
drmModeFreeResources(res);
return -2;
}
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz) {
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; // 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);
}
assert(best == NULL || best->hdisplay > 0);
assert(best == NULL || best->vdisplay > 0);
return best;
}
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;
}
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) {
for (int ei = 0; ei < conn->count_encoders; ++ei) {
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);

View File

@@ -47,7 +47,8 @@ typedef struct {
bool dumb_created;
bool fb_added;
struct {
bool *has_vsync;
bool *has_vsync;
int *exposing_dma_fd;
} ctx;
} us_drm_buffer_s;
@@ -56,15 +57,18 @@ typedef struct {
int fd;
u32 crtc_id;
u32 conn_id;
u32 dpms_id;
drmModeModeInfo mode;
us_drm_buffer_s *bufs;
uint n_bufs;
drmModeCrtc *saved_crtc;
int dpms_state;
bool opened_for_stub;
bool has_vsync;
bool stub;
int exposing_dma_fd;
uint stub_n_buf;
us_frametext_s *ft;
bool unplugged_reported;
us_frametext_s *ft;
} us_drm_runtime_s;
typedef struct {
@@ -82,6 +86,7 @@ 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_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);
int us_drm_wait_for_vsync(us_drm_s *drm);

View File

@@ -172,44 +172,46 @@ static void _main_loop(void) {
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) {
if ((drm_opened = us_drm_open(drm, NULL)) < 0) {
goto close;
}
blank_at_ts = 0;
CHECK(drm_opened = us_drm_open(drm, NULL));
}
assert(drm_opened > 0);
if (atomic_load(&_g_ustreamer_online)) {
US_ONCE({ US_LOG_ERROR("DRM: Online stream is active"); });
if (us_drm_wait_for_vsync(drm) < 0) {
goto close;
}
if (us_drm_expose_stub(drm, US_DRM_STUB_BUSY, NULL) < 0) {
goto close;
}
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) {
goto close;
ldf now_ts = us_get_now_monotonic();
if (blank_at_ts == 0) {
blank_at_ts = now_ts + 5;
}
if (us_drm_expose_stub(drm, US_DRM_STUB_NO_SIGNAL, NULL) < 0) {
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;
}
us_drm_close(drm);
if ((drm_opened = us_drm_open(drm, dev)) < 0) {
goto close;
}
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)) {
@@ -217,39 +219,29 @@ static void _main_loop(void) {
goto close;
}
if (us_drm_wait_for_vsync(drm) < 0) {
goto close;
}
CHECK(us_drm_wait_for_vsync(drm));
if (prev_hw != NULL) {
if (us_device_release_buffer(dev, prev_hw) < 0) {
goto close;
}
CHECK(us_device_release_buffer(dev, prev_hw));
prev_hw = NULL;
}
us_hw_buffer_s *hw;
const int n_buf = us_device_grab_buffer(dev, &hw);
switch (n_buf) {
switch (us_device_grab_buffer(dev, &hw)) {
case -2: continue; // Broken frame
case -1: goto close; // Any error
default: break; // Grabbed on >= 0
}
assert(n_buf >= 0);
int exposed;
if (drm_opened == 0) {
exposed = us_drm_expose_dma(drm, hw);
CHECK(us_drm_expose_dma(drm, hw));
prev_hw = hw;
} else {
exposed = us_drm_expose_stub(drm, drm_opened, dev);
if (us_device_release_buffer(dev, hw) < 0) {
goto close;
}
CHECK(us_drm_expose_stub(drm, drm_opened, dev));
CHECK(us_device_release_buffer(dev, hw));
}
if (exposed < 0) {
goto close;
} else if (drm_opened > 0) {
if (drm_opened > 0) {
_slowdown();
}
}
@@ -261,6 +253,8 @@ static void _main_loop(void) {
us_device_close(dev);
_slowdown();
# undef CHECK
}
us_device_destroy(dev);