Compare commits

...

25 Commits
v6.0 ... v6.5

Author SHA1 Message Date
Maxim Devaev
408157c82b Bump version: 6.4 → 6.5 2024-03-21 13:33:29 +02:00
Maxim Devaev
7356dea737 renamed options --sink* to --jpeg-sink* 2024-03-21 13:21:51 +02:00
Maxim Devaev
87a75a816a memsink: suffix-based memory limites 2024-03-20 22:55:37 +02:00
Maxim Devaev
b6a2332207 refactoring 2024-03-20 17:51:56 +02:00
Maxim Devaev
34c0dcb1ce refactoring 2024-03-19 19:33:55 +02:00
Maxim Devaev
283f31a5a6 Bump version: 6.3 → 6.4 2024-03-17 10:44:37 +02:00
Maxim Devaev
2f1264c916 janus: rtp orientation support 2024-03-17 10:42:52 +02:00
Maxim Devaev
69e7cbf746 refactoring 2024-03-16 23:20:38 +02:00
Maxim Devaev
05804e309f Bump version: 6.2 → 6.3 2024-03-14 16:36:20 +02:00
Maxim Devaev
1d8c93d3ad Issue #253: Added m2m encoder timeout 2024-03-14 16:06:32 +02:00
Maxim Devaev
f48695a04e refactoring 2024-03-13 13:09:27 +02:00
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
Maxim Devaev
b00a6ffd8d Bump version: 6.0 → 6.1 2024-03-09 04:33:44 +02:00
Maxim Devaev
ce935c431e v4p: changed logging levels 2024-03-09 04:32:12 +02:00
Maxim Devaev
ac0944ae1a v4p: added some checks and asserts 2024-03-09 04:29:50 +02:00
Maxim Devaev
66572806a2 fix 2024-03-09 03:28:13 +02:00
Maxim Devaev
a75d6487e3 font info 2024-03-09 03:28:13 +02:00
Maxim Devaev
897ad4951b v4p: dma support 2024-03-09 01:27:50 +00:00
43 changed files with 880 additions and 570 deletions

View File

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

View File

@@ -22,6 +22,23 @@
#include "audio.h"
#include <stdlib.h>
#include <stdatomic.h>
#include <assert.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "logging.h"
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
@@ -31,7 +48,7 @@
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(s16))
#define _MIN_PCM_HZ 8000
#define _MAX_PCM_HZ 192000
@@ -41,13 +58,13 @@
typedef struct {
int16_t data[_MAX_BUF16];
s16 data[_MAX_BUF16];
} _pcm_buffer_s;
typedef struct {
uint8_t data[_MAX_BUF8]; // Worst case
size_t used;
uint64_t pts;
u8 data[_MAX_BUF8]; // Worst case
uz used;
u64 pts;
} _enc_buffer_s;
@@ -71,7 +88,7 @@ bool us_audio_probe(const char *name) {
return true;
}
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
us_audio_s *us_audio_init(const char *name, uint pcm_hz) {
us_audio_s *audio;
US_CALLOC(audio, 1);
audio->pcm_hz = pcm_hz;
@@ -162,7 +179,7 @@ void us_audio_destroy(us_audio_s *audio) {
free(audio);
}
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts) {
if (atomic_load(&audio->stop)) {
return -1;
}
@@ -197,8 +214,8 @@ static _enc_buffer_s *_enc_buffer_init(void) {
static void *_pcm_thread(void *v_audio) {
US_THREAD_SETTLE("us_a_pcm");
us_audio_s *const audio = (us_audio_s *)v_audio;
uint8_t in[_MAX_BUF8];
us_audio_s *const audio = v_audio;
u8 in[_MAX_BUF8];
while (!atomic_load(&audio->stop)) {
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
@@ -227,8 +244,8 @@ static void *_pcm_thread(void *v_audio) {
static void *_encoder_thread(void *v_audio) {
US_THREAD_SETTLE("us_a_enc");
us_audio_s *const audio = (us_audio_s *)v_audio;
int16_t in_res[_MAX_BUF16];
us_audio_s *const audio = v_audio;
s16 in_res[_MAX_BUF16];
while (!atomic_load(&audio->stop)) {
const int in_ri = us_ring_consumer_acquire(audio->pcm_ring, 0.1);
@@ -237,11 +254,11 @@ static void *_encoder_thread(void *v_audio) {
}
_pcm_buffer_s *const in = audio->pcm_ring->items[in_ri];
int16_t *in_ptr;
s16 *in_ptr;
if (audio->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
uint32_t in_count = audio->pcm_frames;
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
u32 in_count = audio->pcm_frames;
u32 out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res;
} else {

View File

@@ -22,50 +22,40 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <assert.h>
#include <sys/types.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include <speex/speex_resampler.h>
#include <opus/opus.h>
#include "uslibs/tools.h"
#include "uslibs/array.h"
#include "uslibs/types.h"
#include "uslibs/ring.h"
#include "uslibs/threading.h"
#include "logging.h"
typedef struct {
snd_pcm_t *pcm;
unsigned pcm_hz;
unsigned pcm_frames;
size_t pcm_size;
uint pcm_hz;
uint pcm_frames;
uz pcm_size;
snd_pcm_hw_params_t *pcm_params;
SpeexResamplerState *res;
OpusEncoder *enc;
us_ring_s *pcm_ring;
us_ring_s *enc_ring;
uint32_t pts;
us_ring_s *pcm_ring;
us_ring_s *enc_ring;
u32 pts;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
pthread_t pcm_tid;
pthread_t enc_tid;
bool tids_created;
atomic_bool stop;
} us_audio_s;
bool us_audio_probe(const char *name);
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
us_audio_s *us_audio_init(const char *name, uint pcm_hz);
void us_audio_destroy(us_audio_s *audio);
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts);

View File

@@ -51,6 +51,7 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
client->session = session;
atomic_init(&client->transmit, false);
atomic_init(&client->transmit_audio, false);
atomic_init(&client->video_orient, 0);
atomic_init(&client->stop, false);
@@ -103,7 +104,7 @@ static void *_audio_thread(void *v_client) {
}
static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = (us_janus_client_s *)v_client;
us_janus_client_s *const client = v_client;
us_ring_s *const ring = (video ? client->video_ring : client->audio_ring);
assert(ring != NULL); // Audio may be NULL
@@ -131,6 +132,7 @@ static void *_common_thread(void *v_client, bool video) {
# endif
};
janus_plugin_rtp_extensions_reset(&packet.extensions);
/*if (rtp->zero_playout_delay) {
// https://github.com/pikvm/pikvm/issues/784
packet.extensions.min_delay = 0;
@@ -142,6 +144,13 @@ static void *_common_thread(void *v_client, bool video) {
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
}*/
if (rtp.video) {
const uint video_orient = atomic_load(&client->video_orient);
if (video_orient != 0) {
packet.extensions.video_rotation = video_orient;
}
}
client->gw->relay_rtp(client->session, &packet);
}
}

View File

@@ -39,6 +39,7 @@ typedef struct us_janus_client_sx {
janus_plugin_session *session;
atomic_bool transmit;
atomic_bool transmit_audio;
atomic_uint video_orient;
pthread_t video_tid;
pthread_t audio_tid;

View File

@@ -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; \
} \
}

View File

@@ -58,7 +58,7 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id) {
}
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) {
us_frame_set_data(frame, mem->data, mem->used);
us_frame_set_data(frame, us_memsink_get_data(mem), mem->used);
US_FRAME_COPY_META(mem, frame);
*frame_id = mem->id;
mem->last_client_ts = us_get_now_monotonic();

View File

@@ -42,12 +42,12 @@
#include "uslibs/list.h"
#include "uslibs/ring.h"
#include "uslibs/memsinksh.h"
#include "uslibs/tc358743.h"
#include "const.h"
#include "logging.h"
#include "client.h"
#include "audio.h"
#include "tc358743.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
@@ -136,12 +136,18 @@ static void *_video_sink_thread(void *arg) {
int fd = -1;
us_memsink_shared_s *mem = NULL;
const uz data_size = us_memsink_calculate_size(_g_config->video_sink_name);
if (data_size == 0) {
US_ONCE({ US_JLOG_ERROR("video", "Invalid memsink object suffix"); });
goto close_memsink;
}
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
US_ONCE({ US_JLOG_PERROR("video", "Can't open memsink"); });
goto close_memsink;
}
if ((mem = us_memsink_shared_map(fd)) == NULL) {
if ((mem = us_memsink_shared_map(fd, data_size)) == NULL) {
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
goto close_memsink;
}
@@ -178,7 +184,10 @@ static void *_video_sink_thread(void *arg) {
}
close_memsink:
US_DELETE(mem, us_memsink_shared_unmap);
if (mem != NULL) {
us_memsink_shared_unmap(mem, data_size);
mem = NULL;
}
US_CLOSE_FD(fd);
US_JLOG_INFO("video", "Memsink closed");
sleep(1); // error_delay
@@ -188,6 +197,22 @@ static void *_video_sink_thread(void *arg) {
return NULL;
}
static int _check_tc358743_audio(uint *audio_hz) {
int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
return -1;
}
const int checked = us_tc358743_xioctl_get_audio_hz(fd, audio_hz);
if (checked < 0) {
US_JLOG_PERROR("audio", "Can't check TC358743 audio state (%d)", checked);
close(fd);
return -1;
}
close(fd);
return 0;
}
static void *_audio_thread(void *arg) {
(void)arg;
US_THREAD_SETTLE("us_audio");
@@ -204,32 +229,27 @@ static void *_audio_thread(void *arg) {
continue;
}
us_tc358743_info_s info = {0};
uint audio_hz = 0;
us_audio_s *audio = NULL;
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
if (_check_tc358743_audio(&audio_hz) < 0) {
goto close_audio;
}
if (!info.has_audio) {
if (audio_hz == 0) {
US_ONCE({ US_JLOG_INFO("audio", "No audio presented from the host"); });
goto close_audio;
}
US_ONCE({ US_JLOG_INFO("audio", "Detected host audio"); });
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
if ((audio = us_audio_init(_g_config->audio_dev_name, audio_hz)) == NULL) {
goto close_audio;
}
once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
if (
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|| !info.has_audio
|| audio->pcm_hz != info.audio_hz
) {
if (_check_tc358743_audio(&audio_hz) < 0 || audio->pcm_hz != audio_hz) {
goto close_audio;
}
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
u8 data[size];
u64 pts;
@@ -442,12 +462,25 @@ static struct janus_plugin_result *_plugin_handle_message(
} else if (!strcmp(request_str, "watch")) {
bool with_audio = false;
uint video_orient = 0;
{
json_t *const params = json_object_get(msg, "params");
if (params != NULL) {
json_t *const audio = json_object_get(params, "audio");
if (audio != NULL && json_is_boolean(audio)) {
with_audio = (_g_rtpa != NULL && json_boolean_value(audio));
{
json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) {
with_audio = (_g_rtpa != NULL && json_boolean_value(obj));
}
}
{
json_t *const obj = json_object_get(params, "orientation");
if (obj != NULL && json_is_integer(obj)) {
video_orient = json_integer_value(obj);
switch (video_orient) {
case 90: case 180: case 270: break;
default: video_orient = 0; break;
}
}
}
}
}
@@ -487,6 +520,7 @@ static struct janus_plugin_result *_plugin_handle_message(
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
atomic_store(&client->transmit_audio, with_audio);
atomic_store(&client->video_orient, video_orient);
}
has_listeners = (has_listeners || atomic_load(&client->transmit_audio));
});

View File

@@ -57,7 +57,7 @@ void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
++rtp->seq;
# define WRITE_BE_U32(x_offset, x_value) \
*((u32 *)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
WRITE_BE_U32(0, word0);
WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc);

View File

@@ -71,6 +71,7 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
"a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:2 urn:3gpp:video-orientation" RN
"a=sendonly" RN,
pl, pl, pl, pl,
pl, pl, pl,

View File

@@ -0,0 +1 @@
../../../src/libs/memsinksh.c

1
janus/src/uslibs/tc358743.c Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/tc358743.c

1
janus/src/uslibs/tc358743.h Symbolic link
View File

@@ -0,0 +1 @@
../../../src/libs/tc358743.h

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.0" "January 2021"
.TH USTREAMER-DUMP 1 "version 6.5" "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.0" "November 2020"
.TH USTREAMER 1 "version 6.5" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -212,25 +212,25 @@ Timeout for client connections. Default: 10.
.SS "JPEG sink options"
With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info.
.TP
.BR \-\-sink\ \fIname
Use the specified shared memory object to sink JPEG frames. Default: disabled.
.BR \-\-jpeg\-sink\ \fIname
Use the specified shared memory object to sink JPEG frames. The name should end with a suffix ".jpeg" or ":jpeg". Default: disabled.
.TP
.BR \-\-sink\-mode\ \fImode
.BR \-\-jpeg\-sink\-mode\ \fImode
Set JPEG sink permissions (like 777). Default: 660.
.TP
.BR \-\-sink\-rm
.BR \-\-jpeg\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-sink\-client\-ttl\ \fIsec
.BR \-\-jpeg\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-sink\-timeout\ \fIsec
.BR \-\-jpeg\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.SS "H264 sink options"
.TP
.BR \-\-h264\-sink\ \fIname
Use the specified shared memory object to sink H264 frames. Default: disabled.
Use the specified shared memory object to sink H264 frames. The name should end with a suffix ".h264" or ":h264". Default: disabled.
.TP
.BR \-\-h264\-sink\-mode\ \fImode
Set H264 sink permissions (like 777). Default: 660.
@@ -253,6 +253,22 @@ Interval between keyframes. Default: 30.
.BR \-\-h264\-m2m\-device\ \fI/dev/path
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
.SS "RAW sink options"
.TP
.BR \-\-raw\-sink\ \fIname
Use the specified shared memory object to sink RAW frames. The name should end with a suffix ".raw" or ":raw". Default: disabled.
.TP
.BR \-\-raw\-sink\-mode\ \fImode
Set RAW sink permissions (like 777). Default: 660.
.TP
.BR \-\-raw\-sink\-rm
Remove shared memory on stop. Default: disabled.
.TP
.BR \-\-raw\-sink\-client\-ttl\ \fIsec
Client TTL. Default: 10.
.TP
.BR \-\-raw\-sink\-timeout\ \fIsec
Timeout for lock. Default: 1.
.SS "Process options"
.TP

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=6.0
pkgver=6.5
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.0
PKG_VERSION:=6.5
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.0",
version="6.5",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

@@ -0,0 +1 @@
../../../src/libs/memsinksh.c

View File

@@ -26,6 +26,7 @@ typedef struct {
double lock_timeout;
double wait_timeout;
double drop_same_frames;
uz data_size;
int fd;
us_memsink_shared_s *mem;
@@ -37,7 +38,10 @@ typedef struct {
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
US_DELETE(self->mem, us_memsink_shared_unmap);
if (self->mem != NULL) {
us_memsink_shared_unmap(self->mem, self->data_size);
self->mem = NULL;
}
US_CLOSE_FD(self->fd);
US_DELETE(self->frame, us_frame_destroy);
}
@@ -64,13 +68,18 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
SET_DOUBLE(drop_same_frames, >= 0);
# undef SET_DOUBLE
if ((self->data_size = us_memsink_calculate_size(self->obj)) == 0) {
PyErr_SetString(PyExc_ValueError, "Invalid memsink object suffix");
return -1;
}
self->frame = us_frame_init();
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
if ((self->mem = us_memsink_shared_map(self->fd, self->data_size)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
@@ -99,11 +108,11 @@ static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
Py_INCREF(self);
return (PyObject *)self;
return (PyObject*)self;
}
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
return PyObject_CallMethod((PyObject *)self, "close", "");
return PyObject_CallMethod((PyObject*)self, "close", "");
}
static int _wait_frame(_MemsinkObject *self) {
@@ -139,7 +148,7 @@ static int _wait_frame(_MemsinkObject *self) {
if (
US_FRAME_COMPARE_GEOMETRY(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now_ts)
&& !memcmp(self->frame->data, mem->data, mem->used)
&& !memcmp(self->frame->data, us_memsink_get_data(mem), mem->used)
) {
self->frame_id = mem->id;
goto retry;
@@ -190,7 +199,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
}
us_memsink_shared_s *mem = self->mem;
us_frame_set_data(self->frame, mem->data, mem->used);
us_frame_set_data(self->frame, us_memsink_get_data(mem), mem->used);
US_FRAME_COPY_META(self->mem, self->frame);
self->frame_id = mem->id;
self->frame_ts = us_get_now_monotonic();
@@ -230,7 +239,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)self->frame->data, self->frame->used));
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used));
# undef SET_NUMBER
# undef SET_VALUE
@@ -305,7 +314,7 @@ PyMODINIT_FUNC PyInit_ustreamer(void) {
Py_INCREF(&_MemsinkType);
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
if (PyModule_AddObject(module, "Memsink", (PyObject*)&_MemsinkType) < 0) {
return NULL;
}

View File

@@ -47,7 +47,7 @@ us_output_file_s *us_output_file_init(const char *path, bool json) {
}
void us_output_file_write(void *v_output, const us_frame_s *frame) {
us_output_file_s *output = (us_output_file_s *)v_output;
us_output_file_s *output = v_output;
if (output->json) {
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
fprintf(output->fp,
@@ -66,7 +66,7 @@ void us_output_file_write(void *v_output, const us_frame_s *frame) {
}
void us_output_file_destroy(void *v_output) {
us_output_file_s *output = (us_output_file_s *)v_output;
us_output_file_s *output = v_output;
US_DELETE(output->base64_data, free);
if (output->fp && output->fp != stdout) {
if (fclose(output->fp) < 0) {

View File

@@ -182,7 +182,7 @@ int main(int argc, char *argv[]) {
_output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') {
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
if ((ctx.v_output = (void*)us_output_file_init(output_path, output_json)) == NULL) {
return 1;
}
ctx.write = us_output_file_write;

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,11 @@ us_memsink_s *us_memsink_init(
US_LOG_INFO("Using %s-sink: %s", name, obj);
if ((sink->data_size = us_memsink_calculate_size(obj)) == 0) {
US_LOG_ERROR("%s-sink: Invalid object suffix", name);
goto error;
}
const mode_t mask = umask(0);
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
umask(mask);
@@ -65,12 +70,12 @@ us_memsink_s *us_memsink_init(
goto error;
}
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s) + sink->data_size) < 0) {
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
goto error;
}
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
if ((sink->mem = us_memsink_shared_map(sink->fd, sink->data_size)) == NULL) {
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
@@ -83,7 +88,7 @@ error:
void us_memsink_destroy(us_memsink_s *sink) {
if (sink->mem != NULL) {
if (us_memsink_shared_unmap(sink->mem) < 0) {
if (us_memsink_shared_unmap(sink->mem, sink->data_size) < 0) {
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
}
}
@@ -160,9 +165,9 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
const ldf now = us_get_now_monotonic();
if (frame->used > US_MEMSINK_MAX_DATA) {
if (frame->used > sink->data_size) {
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
sink->name, frame->used, US_MEMSINK_MAX_DATA);
sink->name, frame->used, sink->data_size);
return 0; // -2
}
@@ -177,7 +182,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
*key_requested = sink->mem->key_requested;
}
memcpy(sink->mem->data, frame->data, frame->used);
memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used);
sink->mem->used = frame->used;
US_FRAME_COPY_META(frame, sink->mem);
@@ -236,7 +241,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
}
sink->last_readed_id = sink->mem->id;
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used);
US_FRAME_COPY_META(sink->mem, frame);
if (key_requested != NULL) { // We don't need it for non-H264 sinks
*key_requested = sink->mem->key_requested;

View File

@@ -34,6 +34,7 @@
typedef struct {
const char *name;
const char *obj;
uz data_size;
bool server;
bool rm;
uint client_ttl; // Only for server

72
src/libs/memsinksh.c Normal file
View File

@@ -0,0 +1,72 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#include "memsinksh.h"
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <sys/mman.h>
#include "types.h"
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
us_memsink_shared_s *mem = mmap(
NULL,
sizeof(us_memsink_shared_s) + data_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, 0);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
assert(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
}
uz us_memsink_calculate_size(const char *obj) {
const char *ptr = strrchr(obj, ':');
if (ptr == NULL) {
ptr = strrchr(obj, '.');
}
if (ptr != NULL) {
ptr += 1;
if (!strcasecmp(ptr, "jpeg")) {
return 4 * 1024 * 1024;
} else if (!strcasecmp(ptr, "h264")) {
return 2 * 1024 * 1024;
} else if (!strcasecmp(ptr, "raw")) {
return 1920 * 1200 * 3; // RGB
}
}
return 0;
}
u8 *us_memsink_get_data(us_memsink_shared_s *mem) {
return (u8*)(mem + sizeof(us_memsink_shared_s));
}

View File

@@ -22,18 +22,11 @@
#pragma once
#include <sys/mman.h>
#include "types.h"
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)4)
#ifndef US_CFG_MEMSINK_MAX_DATA
# define US_CFG_MEMSINK_MAX_DATA 33554432
#endif
#define US_MEMSINK_MAX_DATA ((uz)(US_CFG_MEMSINK_MAX_DATA))
#define US_MEMSINK_VERSION ((u32)5)
typedef struct {
@@ -57,28 +50,11 @@ typedef struct {
ldf last_client_ts;
bool key_requested;
u8 data[US_MEMSINK_MAX_DATA];
} us_memsink_shared_s;
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
us_memsink_shared_s *mem = mmap(
NULL,
sizeof(us_memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size);
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size);
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
assert(mem != NULL);
return munmap(mem, sizeof(us_memsink_shared_s));
}
uz us_memsink_calculate_size(const char *obj);
u8 *us_memsink_get_data(us_memsink_shared_s *mem);

View File

@@ -28,11 +28,9 @@
#include <linux/videodev2.h>
#include <linux/v4l2-controls.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "uslibs/xioctl.h"
#include "logging.h"
#include "types.h"
#include "tools.h"
#include "xioctl.h"
#ifndef V4L2_CID_USER_TC358743_BASE
@@ -46,28 +44,22 @@
#endif
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
US_MEMSET_ZERO(*info);
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
*audio_hz = 0;
int fd = -1;
if ((fd = open(path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
struct v4l2_control ctl = {.id = TC358743_CID_AUDIO_PRESENT};
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -1;
}
if (!ctl.value) {
return 0; // No audio
}
# define READ_CID(x_cid, x_field) { \
struct v4l2_control m_ctl = {.id = x_cid}; \
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
close(fd); \
return -1; \
} \
info->x_field = m_ctl.value; \
}
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
READ_CID(TC358743_CID_AUDIO_SAMPLING_RATE, audio_hz);
# undef READ_CID
close(fd);
US_MEMSET_ZERO(ctl);
ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
return -2;
}
*audio_hz = ctl.value;
return 0;
}

View File

@@ -22,13 +22,7 @@
#pragma once
#include "uslibs/types.h"
#include "types.h"
typedef struct {
bool has_audio;
uint audio_hz;
} us_tc358743_info_s;
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);
int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz);

View File

@@ -84,7 +84,7 @@ INLINE void us_thread_set_name(const char *name) {
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
pthread_set_name_np(pthread_self(), name);
# elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void *)name);
pthread_setname_np(pthread_self(), "%s", (void*)name);
# else
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
# endif

View File

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

View File

@@ -166,7 +166,7 @@ void us_encoder_open(us_encoder_s *enc, us_device_s *dev) {
enc->run->pool = us_workers_pool_init(
"JPEG", "jw", n_workers, desired_interval,
_worker_job_init, (void *)enc,
_worker_job_init, (void*)enc,
_worker_job_destroy,
_worker_run_job);
@@ -188,19 +188,19 @@ void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, u
static void *_worker_job_init(void *v_enc) {
us_encoder_job_s *job;
US_CALLOC(job, 1);
job->enc = (us_encoder_s *)v_enc;
job->enc = (us_encoder_s*)v_enc;
job->dest = us_frame_init();
return (void *)job;
return (void*)job;
}
static void _worker_job_destroy(void *v_job) {
us_encoder_job_s *job = (us_encoder_job_s *)v_job;
us_encoder_job_s *job = v_job;
us_frame_destroy(job->dest);
free(job);
}
static bool _worker_run_job(us_worker_s *wr) {
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
us_encoder_job_s *job = wr->job;
us_encoder_s *enc = job->enc; // Just for _ER()
const us_frame_s *src = &job->hw->raw;
us_frame_s *dest = job->dest;

View File

@@ -29,8 +29,8 @@
typedef struct {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buf; // Start of buffer
us_frame_s *frame;
} _jpeg_dest_manager_s;
@@ -63,7 +63,12 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
jpeg.image_width = src->width;
jpeg.image_height = src->height;
jpeg.input_components = 3;
jpeg.in_color_space = ((src->format == V4L2_PIX_FMT_YUYV || src->format == V4L2_PIX_FMT_UYVY) ? JCS_YCbCr : JCS_RGB);
switch (src->format) {
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY: jpeg.in_color_space = JCS_YCbCr; break;
default: jpeg.in_color_space = JCS_RGB; break;
}
jpeg_set_defaults(&jpeg);
jpeg_set_quality(&jpeg, quality, TRUE);
@@ -89,12 +94,12 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
assert((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
)) != NULL);
}
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
dest->mgr.init_destination = _jpeg_init_destination;
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
dest->mgr.term_destination = _jpeg_term_destination;
@@ -221,10 +226,10 @@ static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
static void _jpeg_init_destination(j_compress_ptr jpeg) {
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
assert((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
)) != NULL);
@@ -235,7 +240,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
// Called whenever local jpeg buffer fills up
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
@@ -249,7 +254,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
// Called by jpeg_finish_compress after all data has been written.
// Usually needs to flush buffer.
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
const size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer.

View File

@@ -487,9 +487,17 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
bool input_released = !run->p_dma;
while (true) {
struct pollfd enc_poll = {run->fd, POLLIN, 0};
// https://github.com/pikvm/ustreamer/issues/253
// За секунду точно должно закодироваться.
const ldf deadline_ts = us_get_now_monotonic() + 1;
while (true) {
if (us_get_now_monotonic() > deadline_ts) {
_E_LOG_ERROR("Waiting for the encoder is too long");
goto error;
}
struct pollfd enc_poll = {run->fd, POLLIN, 0};
_E_LOG_DEBUG("Polling encoder ...");
if (poll(&enc_poll, 1, 1000) < 0 && errno != EINTR) {
_E_LOG_PERROR("Can't poll encoder");

View File

@@ -92,7 +92,7 @@ enum _US_OPT_VALUES {
_O_##x_prefix##_RM, \
_O_##x_prefix##_CLIENT_TTL, \
_O_##x_prefix##_TIMEOUT,
ADD_SINK(SINK)
ADD_SINK(JPEG_SINK)
ADD_SINK(RAW_SINK)
ADD_SINK(H264_SINK)
_O_H264_BITRATE,
@@ -184,18 +184,25 @@ static const struct option _LONG_OPTS[] = {
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
# define ADD_SINK(x_opt, x_prefix) \
{x_opt "sink", required_argument, NULL, _O_##x_prefix}, \
{x_opt "sink-mode", required_argument, NULL, _O_##x_prefix##_MODE}, \
{x_opt "sink-rm", no_argument, NULL, _O_##x_prefix##_RM}, \
{x_opt "sink-client-ttl", required_argument, NULL, _O_##x_prefix##_CLIENT_TTL}, \
{x_opt "sink-timeout", required_argument, NULL, _O_##x_prefix##_TIMEOUT},
ADD_SINK("", SINK)
ADD_SINK("raw-", RAW_SINK)
ADD_SINK("h264-", H264_SINK)
{x_opt "-sink", required_argument, NULL, _O_##x_prefix}, \
{x_opt "-sink-mode", required_argument, NULL, _O_##x_prefix##_MODE}, \
{x_opt "-sink-rm", no_argument, NULL, _O_##x_prefix##_RM}, \
{x_opt "-sink-client-ttl", required_argument, NULL, _O_##x_prefix##_CLIENT_TTL}, \
{x_opt "-sink-timeout", required_argument, NULL, _O_##x_prefix##_TIMEOUT},
ADD_SINK("jpeg", JPEG_SINK)
ADD_SINK("raw", RAW_SINK)
ADD_SINK("h264", H264_SINK)
# undef ADD_SINK
// Extra opts for H.264
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
{"h264-gop", required_argument, NULL, _O_H264_GOP},
{"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE},
# undef ADD_SINK
// Compatibility
{"sink", required_argument, NULL, _O_JPEG_SINK},
{"sink-mode", required_argument, NULL, _O_JPEG_SINK_MODE},
{"sink-rm", no_argument, NULL, _O_JPEG_SINK_RM},
{"sink-client-ttl", required_argument, NULL, _O_JPEG_SINK_CLIENT_TTL},
{"sink-timeout", required_argument, NULL, _O_JPEG_SINK_TIMEOUT},
# ifdef WITH_GPIO
{"gpio-device", required_argument, NULL, _O_GPIO_DEVICE},
@@ -432,17 +439,17 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
# define ADD_SINK(x_opt, x_lp, x_up) \
case _O_##x_up: OPT_SET(x_lp##_name, optarg); \
case _O_##x_up##_MODE: OPT_NUMBER("--" #x_opt "sink-mode", x_lp##_mode, INT_MIN, INT_MAX, 8); \
case _O_##x_up##_MODE: OPT_NUMBER("--" #x_opt "-sink-mode", x_lp##_mode, INT_MIN, INT_MAX, 8); \
case _O_##x_up##_RM: OPT_SET(x_lp##_rm, true); \
case _O_##x_up##_CLIENT_TTL: OPT_NUMBER("--" #x_opt "sink-client-ttl", x_lp##_client_ttl, 1, 60, 0); \
case _O_##x_up##_TIMEOUT: OPT_NUMBER("--" #x_opt "sink-timeout", x_lp##_timeout, 1, 60, 0);
ADD_SINK("", jpeg_sink, SINK)
ADD_SINK("raw-", raw_sink, RAW_SINK)
ADD_SINK("h264-", h264_sink, H264_SINK)
case _O_##x_up##_CLIENT_TTL: OPT_NUMBER("--" #x_opt "-sink-client-ttl", x_lp##_client_ttl, 1, 60, 0); \
case _O_##x_up##_TIMEOUT: OPT_NUMBER("--" #x_opt "-sink-timeout", x_lp##_timeout, 1, 60, 0);
ADD_SINK("jpeg", jpeg_sink, JPEG_SINK)
ADD_SINK("raw", raw_sink, RAW_SINK)
ADD_SINK("h264", h264_sink, H264_SINK)
# undef ADD_SINK
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
# undef ADD_SINK
# ifdef WITH_GPIO
case _O_GPIO_DEVICE: OPT_SET(us_g_gpio.path, optarg);
@@ -677,18 +684,20 @@ static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, con
# define ADD_SINK(x_name, x_opt) \
SAY(x_name " sink options:"); \
SAY("══════════════════"); \
SAY(" --" x_opt "sink <name> ──────────── Use the shared memory to sink " x_name " frames. Default: disabled.\n"); \
SAY(" --" x_opt "sink-mode <mode> ─────── Set " x_name " sink permissions (like 777). Default: 660.\n"); \
SAY(" --" x_opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
SAY(" --" x_opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
SAY(" --" x_opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
ADD_SINK("JPEG", "")
ADD_SINK("RAW", "raw-")
ADD_SINK("H264", "h264-")
SAY(" --" x_opt "-sink <name> ──────────── Use the shared memory to sink " x_name " frames. Default: disabled."); \
SAY(" The name should end with a suffix \"." x_opt "\" or \"." x_opt "\"."); \
SAY(" Default: disabled.\n"); \
SAY(" --" x_opt "-sink-mode <mode> ─────── Set " x_name " sink permissions (like 777). Default: 660.\n"); \
SAY(" --" x_opt "-sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
SAY(" --" x_opt "-sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
SAY(" --" x_opt "-sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
ADD_SINK("JPEG", "jpeg")
ADD_SINK("RAW", "raw")
ADD_SINK("H264", "h264")
# undef ADD_SINK
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
SAY(" --h264-gop <N> ──────────────── Interval between keyframes. Default: %u.\n", stream->h264_gop);
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
# undef ADD_SINK
# ifdef WITH_GPIO
SAY("GPIO options:");
SAY("═════════════");

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

@@ -62,7 +62,7 @@ us_workers_pool_s *us_workers_pool_init(
WR(pool) = pool;
WR(job) = job_init(job_init_arg);
US_THREAD_CREATE(WR(tid), _worker_thread, (void *)&(pool->workers[number]));
US_THREAD_CREATE(WR(tid), _worker_thread, (void*)&(pool->workers[number]));
pool->free_workers += 1;
# undef WR
@@ -176,7 +176,7 @@ long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, const us_
}
static void *_worker_thread(void *v_worker) {
us_worker_s *wr = (us_worker_s *)v_worker;
us_worker_s *wr = v_worker;
US_THREAD_SETTLE("%s", wr->name);
US_LOG_DEBUG("Hello! I am a worker %s ^_^", wr->name);

View File

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

View File

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

View File

@@ -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);
@@ -239,7 +274,7 @@ static void *_follower_thread(void *v_unix_follow) {
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
addr.sun_family = AF_UNIX;
const bool online = !connect(fd, (struct sockaddr *)&addr, sizeof(addr));
const bool online = !connect(fd, (struct sockaddr*)&addr, sizeof(addr));
atomic_store(&_g_ustreamer_online, online);
US_CLOSE_FD(fd); // cppcheck-suppress unreadVariable