mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
284b17d1db | ||
|
|
506a4496d1 | ||
|
|
b5c201d1c1 | ||
|
|
032faa9882 | ||
|
|
414baadc40 | ||
|
|
2d6716aa47 | ||
|
|
260619923a | ||
|
|
3715c89ec8 | ||
|
|
5026108079 | ||
|
|
5f7c556697 | ||
|
|
3c7564da19 | ||
|
|
f0e070be5b | ||
|
|
aa05c470b3 | ||
|
|
13af11a3a6 | ||
|
|
41330940c6 | ||
|
|
46e630d2f6 | ||
|
|
63d87f0526 | ||
|
|
b578e9897e | ||
|
|
b2ebcf99c8 | ||
|
|
6a6b910869 | ||
|
|
4e8acf371f | ||
|
|
c4cb8288c7 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.48
|
||||
current_version = 5.53
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
5
Makefile
5
Makefile
@@ -36,8 +36,7 @@ endif
|
||||
|
||||
apps:
|
||||
$(MAKE) -C src
|
||||
$(ECHO) ln -sf src/ustreamer.bin ustreamer
|
||||
$(ECHO) ln -sf src/ustreamer-dump.bin ustreamer-dump
|
||||
$(ECHO) ln -sf src/*.bin .
|
||||
|
||||
|
||||
python:
|
||||
@@ -119,7 +118,7 @@ clean-all: linters clean
|
||||
|
||||
clean:
|
||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||
rm -f ustreamer ustreamer-dump *.so
|
||||
rm -f *.bin *.so
|
||||
$(MAKE) -C src clean
|
||||
$(MAKE) -C python clean
|
||||
$(MAKE) -C janus clean
|
||||
|
||||
@@ -151,7 +151,7 @@ Add `-e EDID=1` to set HDMI EDID before starting ustreamer. Use together with `-
|
||||
-----
|
||||
# Raspberry Pi Camera Example
|
||||
|
||||
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` on Raspbian):
|
||||
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` and `libcamera-v4l2` (install both) on Raspbian):
|
||||
```
|
||||
$ sudo modprobe bcm2835-v4l2
|
||||
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
|
||||
@@ -180,7 +180,7 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||
|
||||
## Nginx
|
||||
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||
When uStreamer is behind an Nginx proxy, its buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||
|
||||
```nginx
|
||||
location /stream {
|
||||
|
||||
@@ -22,6 +22,22 @@
|
||||
|
||||
#include "client.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "queue.h"
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
static void *_video_thread(void *v_client);
|
||||
static void *_audio_thread(void *v_client);
|
||||
@@ -99,16 +115,16 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
atomic_load(&client->transmit)
|
||||
&& (video || atomic_load(&client->transmit_audio))
|
||||
) {
|
||||
janus_plugin_rtp packet = {0};
|
||||
packet.video = rtp->video;
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||
// The uStreamer Janus plugin places video in stream index 0 and audio
|
||||
// (if available) in stream index 1.
|
||||
packet.mindex = (rtp->video ? 0 : 1);
|
||||
# endif
|
||||
|
||||
janus_plugin_rtp packet = {
|
||||
.video = rtp->video,
|
||||
.buffer = (char *)rtp->datagram,
|
||||
.length = rtp->used,
|
||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||
// The uStreamer Janus plugin places video in stream index 0 and audio
|
||||
// (if available) in stream index 1.
|
||||
.mindex = (rtp->video ? 0 : 1),
|
||||
# endif
|
||||
};
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
/*if (rtp->zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
|
||||
@@ -22,19 +22,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/list.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "queue.h"
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||
// static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
@@ -59,13 +70,14 @@ us_config_s *us_config_init(const char *config_dir_path) {
|
||||
}
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
us_config_destroy(config);
|
||||
config = NULL;
|
||||
ok:
|
||||
US_DELETE(jcfg, janus_config_destroy);
|
||||
free(config_file_path);
|
||||
return config;
|
||||
|
||||
error:
|
||||
US_DELETE(config, us_config_destroy);
|
||||
|
||||
ok:
|
||||
US_DELETE(jcfg, janus_config_destroy);
|
||||
free(config_file_path);
|
||||
return config;
|
||||
}
|
||||
|
||||
void us_config_destroy(us_config_s *config) {
|
||||
|
||||
@@ -22,17 +22,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <janus/config.h>
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "const.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *video_sink_name;
|
||||
|
||||
@@ -22,13 +22,24 @@
|
||||
|
||||
#include "memsinkfd.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||
long double now;
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, u64 last_id) {
|
||||
const ldf deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||
ldf now_ts;
|
||||
do {
|
||||
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = us_get_now_monotonic();
|
||||
now_ts = us_get_now_monotonic();
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
US_JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
@@ -42,11 +53,11 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id)
|
||||
}
|
||||
}
|
||||
usleep(1000); // lock_polling
|
||||
} while (now < deadline_ts);
|
||||
} while (now_ts < deadline_ts);
|
||||
return -2;
|
||||
}
|
||||
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required) {
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, u64 *frame_id, bool key_required) {
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_frame_set_data(frame, mem->data, mem->used);
|
||||
US_FRAME_COPY_META(mem, frame);
|
||||
@@ -66,8 +77,7 @@ us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
us_frame_destroy(frame);
|
||||
frame = NULL;
|
||||
US_DELETE(frame, us_frame_destroy);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -22,18 +22,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id, bool key_required);
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, u64 last_id);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, u64 *frame_id, bool key_required);
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
@@ -37,6 +35,7 @@
|
||||
#include <janus/plugins/plugin.h>
|
||||
#include <janus/rtcp.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/const.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
@@ -99,7 +98,9 @@ static atomic_bool _g_key_required = false;
|
||||
janus_plugin *create(void);
|
||||
|
||||
|
||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
static void *_video_rtp_thread(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
US_THREAD_RENAME("us_video_rtp");
|
||||
atomic_store(&_g_video_rtp_tid_created, true);
|
||||
|
||||
@@ -116,11 +117,13 @@ static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_video_sink_thread(UNUSED void *arg) {
|
||||
static void *_video_sink_thread(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
US_THREAD_RENAME("us_video_sink");
|
||||
atomic_store(&_g_video_sink_tid_created, true);
|
||||
|
||||
uint64_t frame_id = 0;
|
||||
u64 frame_id = 0;
|
||||
int once = 0;
|
||||
|
||||
while (!_STOP) {
|
||||
@@ -165,20 +168,18 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
close_memsink:
|
||||
if (mem != NULL) {
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
us_memsink_shared_unmap(mem);
|
||||
}
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
close_memsink:
|
||||
US_DELETE(mem, us_memsink_shared_unmap);
|
||||
US_CLOSE_FD(fd, close);
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
static void *_audio_thread(void *arg) {
|
||||
(void)arg;
|
||||
|
||||
US_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_config->audio_dev_name != NULL);
|
||||
@@ -218,9 +219,9 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
u8 data[size];
|
||||
u64 pts;
|
||||
const int result = us_audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
_LOCK_AUDIO;
|
||||
@@ -231,9 +232,9 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
close_audio:
|
||||
US_DELETE(audio, us_audio_destroy);
|
||||
sleep(1); // error_delay
|
||||
close_audio:
|
||||
US_DELETE(audio, us_audio_destroy);
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -344,7 +345,8 @@ static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
return info;
|
||||
}
|
||||
|
||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
||||
static void _set_transmit(janus_plugin_session *session, const char *msg, bool transmit) {
|
||||
(void)msg;
|
||||
_IF_DISABLED({ return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
@@ -494,16 +496,18 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
PUSH_ERROR(405, "Not implemented");
|
||||
}
|
||||
|
||||
ok_wait:
|
||||
FREE_MSG_JSEP;
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
||||
ok_wait:
|
||||
FREE_MSG_JSEP;
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
|
||||
|
||||
# undef PUSH_STATUS
|
||||
# undef PUSH_ERROR
|
||||
# undef FREE_MSG_JSEP
|
||||
}
|
||||
|
||||
static void _plugin_incoming_rtcp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtcp *packet) {
|
||||
static void _plugin_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
|
||||
(void)handle;
|
||||
(void)packet;
|
||||
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
|
||||
// US_JLOG_INFO("main", "Got video PLI");
|
||||
atomic_store(&_g_key_required, true);
|
||||
|
||||
@@ -22,8 +22,18 @@
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
us_queue_s *us_queue_init(unsigned capacity) {
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(uint capacity) {
|
||||
us_queue_s *queue;
|
||||
US_CALLOC(queue, 1);
|
||||
US_CALLOC(queue->items, capacity);
|
||||
@@ -61,7 +71,7 @@ void us_queue_destroy(us_queue_s *queue) {
|
||||
} \
|
||||
}
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
||||
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
@@ -80,7 +90,7 @@ int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
||||
int us_queue_get(us_queue_s *queue, void **item, ldf timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
@@ -96,7 +106,7 @@ int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
||||
|
||||
int us_queue_get_free(us_queue_s *queue) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
const unsigned size = queue->size;
|
||||
const uint size = queue->size;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return queue->capacity - size;
|
||||
}
|
||||
|
||||
@@ -22,24 +22,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
|
||||
// Based on https://github.com/seifzadeh/c-pthread-queue/blob/master/queue.h
|
||||
|
||||
typedef struct {
|
||||
void **items;
|
||||
unsigned size;
|
||||
unsigned capacity;
|
||||
unsigned in;
|
||||
unsigned out;
|
||||
uint size;
|
||||
uint capacity;
|
||||
uint in;
|
||||
uint out;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t full_cond;
|
||||
@@ -60,9 +56,9 @@ typedef struct {
|
||||
}
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(unsigned capacity);
|
||||
us_queue_s *us_queue_init(uint capacity);
|
||||
void us_queue_destroy(us_queue_s *queue);
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
|
||||
int us_queue_put(us_queue_s *queue, void *item, ldf timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, ldf timeout);
|
||||
int us_queue_get_free(us_queue_s *queue);
|
||||
|
||||
@@ -25,8 +25,13 @@
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video) {
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
us_rtp_s *us_rtp_init(uint payload, bool video) {
|
||||
us_rtp_s *rtp;
|
||||
US_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
@@ -46,8 +51,8 @@ void us_rtp_destroy(us_rtp_s *rtp) {
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
uint32_t word0 = 0x80000000;
|
||||
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
|
||||
u32 word0 = 0x80000000;
|
||||
if (marked) {
|
||||
word0 |= 1 << 23;
|
||||
}
|
||||
@@ -55,7 +60,8 @@ void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
word0 |= rtp->seq;
|
||||
++rtp->seq;
|
||||
|
||||
# define WRITE_BE_U32(_offset, _value) *((uint32_t *)(rtp->datagram + _offset)) = __builtin_bswap32(_value)
|
||||
# define WRITE_BE_U32(x_offset, 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);
|
||||
|
||||
@@ -22,13 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/types.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
@@ -37,21 +31,21 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned payload;
|
||||
bool video;
|
||||
uint32_t ssrc;
|
||||
uint payload;
|
||||
bool video;
|
||||
u32 ssrc;
|
||||
|
||||
uint16_t seq;
|
||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
||||
size_t used;
|
||||
bool zero_playout_delay;
|
||||
u16 seq;
|
||||
u8 datagram[US_RTP_DATAGRAM_SIZE];
|
||||
uz used;
|
||||
bool zero_playout_delay;
|
||||
} us_rtp_s;
|
||||
|
||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||
|
||||
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video);
|
||||
us_rtp_s *us_rtp_init(uint payload, bool video);
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||
void us_rtp_destroy(us_rtp_s *rtp);
|
||||
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
|
||||
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked);
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
|
||||
#include "rtpa.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||
us_rtpa_s *rtpa;
|
||||
@@ -37,7 +43,7 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
||||
}
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||
# define PAYLOAD rtpa->rtp->payload
|
||||
const uint pl = rtpa->rtp->payload;
|
||||
char *sdp;
|
||||
US_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
@@ -49,14 +55,13 @@ char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD, // PAYLOAD,
|
||||
pl, pl, pl, pl, pl, // pl,
|
||||
rtpa->rtp->ssrc
|
||||
);
|
||||
# undef PAYLOAD
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
||||
|
||||
@@ -22,15 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/types.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
@@ -45,4 +37,4 @@ us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);
|
||||
|
||||
@@ -25,10 +25,20 @@
|
||||
|
||||
#include "rtpv.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
|
||||
|
||||
static sz _find_annexb(const u8 *data, uz size);
|
||||
|
||||
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
|
||||
@@ -45,9 +55,9 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
}
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
const uint pl = rtpv->rtp->payload;
|
||||
char *sdp;
|
||||
US_ASPRINTF(sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
@@ -61,12 +71,11 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
pl, pl, pl, pl,
|
||||
pl, pl, pl,
|
||||
rtpv->rtp->ssrc
|
||||
);
|
||||
return sdp;
|
||||
# undef PAYLOAD
|
||||
}
|
||||
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
@@ -79,20 +88,20 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
|
||||
|
||||
rtpv->rtp->zero_playout_delay = zero_playout_delay;
|
||||
|
||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -_PRE;
|
||||
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
sz last_offset = -_PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + _PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
const uz next_start = last_offset + _PRE;
|
||||
sz offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
}
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = offset - last_offset - _PRE;
|
||||
const u8 *const data = frame->data + last_offset + _PRE;
|
||||
uz size = offset - last_offset - _PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
@@ -103,34 +112,33 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = frame->used - last_offset - _PRE;
|
||||
const u8 *const data = frame->data + last_offset + _PRE;
|
||||
uz size = frame->used - last_offset - _PRE;
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked) {
|
||||
const uint ref_idc = (data[0] >> 5) & 3;
|
||||
const uint type = data[0] & 0x1F;
|
||||
u8 *dg = rtpv->rtp->datagram;
|
||||
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
||||
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
const u8 *src = data + 1;
|
||||
sz remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
@@ -138,18 +146,18 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
|
||||
|
||||
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
|
||||
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
u8 fu = type;
|
||||
if (first) {
|
||||
fu |= 0x80;
|
||||
}
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
DG[US_RTP_HEADER_SIZE + 1] = fu;
|
||||
dg[US_RTP_HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(DG + fu_overhead, src, frag_size);
|
||||
memcpy(dg + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
|
||||
@@ -157,14 +165,12 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint3
|
||||
remaining -= frag_size;
|
||||
first = false;
|
||||
}
|
||||
|
||||
# undef DG
|
||||
}
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
static sz _find_annexb(const u8 *data, uz size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= _PRE) {
|
||||
for (size_t index = 0; index <= size - _PRE; ++index) {
|
||||
for (uz index = 0; index <= size - _PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -22,16 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/frame.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
@@ -22,6 +22,18 @@
|
||||
|
||||
#include "tc358743.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/xioctl.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
#ifndef V4L2_CID_USER_TC358743_BASE
|
||||
# define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
|
||||
@@ -44,8 +56,7 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
||||
}
|
||||
|
||||
# define READ_CID(x_cid, x_field) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
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); \
|
||||
@@ -53,10 +64,8 @@ int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
||||
} \
|
||||
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);
|
||||
|
||||
@@ -22,24 +22,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/xioctl.h"
|
||||
|
||||
#include "logging.h"
|
||||
#include "uslibs/types.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool has_audio;
|
||||
unsigned audio_hz;
|
||||
bool has_audio;
|
||||
uint audio_hz;
|
||||
} us_tc358743_info_s;
|
||||
|
||||
|
||||
|
||||
44
janus/src/uslibs/types.h
Normal file
44
janus/src/uslibs/types.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long long ull;
|
||||
typedef size_t uz;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef long double ldf;
|
||||
@@ -19,7 +19,7 @@ commands = cppcheck \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
--include=linters/cppcheck.h \
|
||||
src python/*.? janus/*.?
|
||||
src python/src/*.? janus/src/*.?
|
||||
|
||||
[testenv:flake8]
|
||||
allowlist_externals = bash
|
||||
|
||||
@@ -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 5.48" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.53" "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 5.48" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.53" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
|
||||
.TP
|
||||
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
||||
Image format.
|
||||
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
||||
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
||||
.TP
|
||||
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
||||
Force TV standard.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.48
|
||||
pkgver=5.53
|
||||
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:=5.48
|
||||
PKG_VERSION:=5.53
|
||||
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="5.48",
|
||||
version="5.53",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
44
python/src/uslibs/types.h
Normal file
44
python/src/uslibs/types.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long long ull;
|
||||
typedef size_t uz;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef long double ldf;
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
@@ -29,29 +30,16 @@ typedef struct {
|
||||
int fd;
|
||||
us_memsink_shared_s *mem;
|
||||
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
u64 frame_id;
|
||||
ldf frame_ts;
|
||||
us_frame_s *frame;
|
||||
} _MemsinkObject;
|
||||
|
||||
|
||||
#define _MEM(x_next) self->mem->x_next
|
||||
#define _FRAME(x_next) self->frame->x_next
|
||||
|
||||
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
us_memsink_shared_unmap(self->mem);
|
||||
self->mem = NULL;
|
||||
}
|
||||
if (self->fd >= 0) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->frame != NULL) {
|
||||
us_frame_destroy(self->frame);
|
||||
self->frame = NULL;
|
||||
}
|
||||
US_DELETE(self->mem, us_memsink_shared_unmap);
|
||||
US_CLOSE_FD(self->fd, close);
|
||||
US_DELETE(self->frame, us_frame_destroy);
|
||||
}
|
||||
|
||||
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
@@ -65,17 +53,15 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define SET_DOUBLE(_field, _cond) { \
|
||||
if (!(self->_field _cond)) { \
|
||||
PyErr_SetString(PyExc_ValueError, #_field " must be " #_cond); \
|
||||
# define SET_DOUBLE(x_field, x_cond) { \
|
||||
if (!(self->x_field x_cond)) { \
|
||||
PyErr_SetString(PyExc_ValueError, #x_field " must be " #x_cond); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
SET_DOUBLE(lock_timeout, > 0);
|
||||
SET_DOUBLE(wait_timeout, > 0);
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
self->frame = us_frame_init();
|
||||
@@ -84,22 +70,20 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
error:
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||
char repr[1024];
|
||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
US_SNPRINTF(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
return Py_BuildValue("s", repr);
|
||||
}
|
||||
|
||||
@@ -123,7 +107,7 @@ static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(i
|
||||
}
|
||||
|
||||
static int _wait_frame(_MemsinkObject *self) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||
const ldf deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||
|
||||
# define RETURN_OS_ERROR { \
|
||||
Py_BLOCK_THREADS \
|
||||
@@ -131,25 +115,26 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
return -1; \
|
||||
}
|
||||
|
||||
long double now;
|
||||
ldf now_ts;
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
||||
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = us_get_now_monotonic();
|
||||
now_ts = us_get_now_monotonic();
|
||||
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
|
||||
us_memsink_shared_s *mem = self->mem;
|
||||
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != self->frame_id) {
|
||||
if (self->drop_same_frames > 0) {
|
||||
if (
|
||||
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now)
|
||||
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
|
||||
&& (self->frame_ts + self->drop_same_frames > now_ts)
|
||||
&& !memcmp(self->frame->data, mem->data, mem->used)
|
||||
) {
|
||||
self->frame_id = _MEM(id);
|
||||
self->frame_id = mem->id;
|
||||
goto drop;
|
||||
}
|
||||
}
|
||||
@@ -163,22 +148,18 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
}
|
||||
}
|
||||
|
||||
drop:
|
||||
|
||||
drop:
|
||||
if (usleep(1000) < 0) {
|
||||
RETURN_OS_ERROR;
|
||||
}
|
||||
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (PyErr_CheckSignals() < 0) {
|
||||
return -1;
|
||||
}
|
||||
} while (now < deadline_ts);
|
||||
|
||||
# undef RETURN_OS_ERROR
|
||||
} while (now_ts < deadline_ts);
|
||||
|
||||
return -2;
|
||||
# undef RETURN_OS_ERROR
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
@@ -199,13 +180,14 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
|
||||
us_memsink_shared_s *mem = self->mem;
|
||||
us_frame_set_data(self->frame, mem->data, mem->used);
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = _MEM(id);
|
||||
self->frame_id = mem->id;
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
_MEM(last_client_ts) = self->frame_ts;
|
||||
mem->last_client_ts = self->frame_ts;
|
||||
if (key_required) {
|
||||
_MEM(key_requested) = true;
|
||||
mem->key_requested = true;
|
||||
}
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
@@ -217,18 +199,18 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
# define SET_VALUE(_key, _maker) { \
|
||||
PyObject *_tmp = _maker; \
|
||||
if (_tmp == NULL) { \
|
||||
# define SET_VALUE(x_key, x_maker) { \
|
||||
PyObject *m_tmp = x_maker; \
|
||||
if (m_tmp == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
||||
Py_DECREF(_tmp); \
|
||||
if (PyDict_SetItemString(dict_frame, x_key, m_tmp) < 0) { \
|
||||
Py_DECREF(m_tmp); \
|
||||
return NULL; \
|
||||
} \
|
||||
Py_DECREF(_tmp); \
|
||||
Py_DECREF(m_tmp); \
|
||||
}
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
|
||||
# define SET_NUMBER(x_key, x_from, x_to) SET_VALUE(#x_key, Py##x_to##_From##x_from(self->frame->x_key))
|
||||
|
||||
SET_NUMBER(width, Long, Long);
|
||||
SET_NUMBER(height, Long, Long);
|
||||
@@ -240,7 +222,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 *)_FRAME(data), _FRAME(used)));
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)self->frame->data, self->frame->used));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
@@ -252,21 +234,19 @@ static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNU
|
||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||
}
|
||||
|
||||
#define FIELD_GETTER(_field, _from, _to) \
|
||||
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##_to##_From##_from(self->_field); \
|
||||
#define FIELD_GETTER(x_field, x_from, x_to) \
|
||||
static PyObject *_MemsinkObject_getter_##x_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##x_to##_From##x_from(self->x_field); \
|
||||
}
|
||||
|
||||
FIELD_GETTER(obj, String, Unicode)
|
||||
FIELD_GETTER(lock_timeout, Double, Float)
|
||||
FIELD_GETTER(wait_timeout, Double, Float)
|
||||
FIELD_GETTER(drop_same_frames, Double, Float)
|
||||
|
||||
#undef FIELD_GETTER
|
||||
|
||||
static PyMethodDef _MemsinkObject_methods[] = {
|
||||
# define ADD_METHOD(_name, _method, _flags) \
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
# define ADD_METHOD(x_name, x_method, x_flags) \
|
||||
{.ml_name = x_name, .ml_meth = (PyCFunction)_MemsinkObject_##x_method, .ml_flags = (x_flags)}
|
||||
ADD_METHOD("close", close, METH_NOARGS),
|
||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||
@@ -277,7 +257,7 @@ static PyMethodDef _MemsinkObject_methods[] = {
|
||||
};
|
||||
|
||||
static PyGetSetDef _MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
|
||||
# define ADD_GETTER(x_field) {.name = #x_field, .get = (getter)_MemsinkObject_getter_##x_field}
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
|
||||
35
src/Makefile
35
src/Makefile
@@ -9,6 +9,7 @@ LDFLAGS ?=
|
||||
# =====
|
||||
_USTR = ustreamer.bin
|
||||
_DUMP = ustreamer-dump.bin
|
||||
_V4P = ustreamer-v4p.bin
|
||||
|
||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
@@ -32,9 +33,19 @@ _DUMP_SRCS = $(shell ls \
|
||||
dump/*.c \
|
||||
)
|
||||
|
||||
_V4P_LIBS = $(_COMMON_LIBS)
|
||||
_V4P_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
v4p/*.c \
|
||||
)
|
||||
|
||||
_BUILD = build
|
||||
|
||||
|
||||
_TARGETS = $(_USTR) $(_DUMP)
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
|
||||
|
||||
define optbool
|
||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||
endef
|
||||
@@ -69,19 +80,25 @@ override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
endif
|
||||
|
||||
|
||||
WITH_V4P ?= 0
|
||||
ifneq ($(call optbool,$(WITH_V4P)),)
|
||||
override _TARGETS += $(_V4P)
|
||||
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||
override _CFLAGS += $(shell pkg-config --cflags libdrm)
|
||||
_V4P_LDFLAGS = $(shell pkg-config --libs libdrm)
|
||||
endif
|
||||
|
||||
# =====
|
||||
all: $(_USTR) $(_DUMP)
|
||||
all: $(_TARGETS)
|
||||
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
for i in $(_TARGETS); do install -m755 $$i $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$$i); done
|
||||
|
||||
|
||||
install-strip: install
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
||||
for i in $(_TARGETS); do strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$$i); done
|
||||
|
||||
|
||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||
@@ -94,6 +111,11 @@ $(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
|
||||
|
||||
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
$(info -- CC $<)
|
||||
$(ECHO) mkdir -p $(dir $@) || true
|
||||
@@ -101,8 +123,7 @@ $(_BUILD)/%.o: %.c
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
|
||||
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
|
||||
|
||||
|
||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
-include $(_OBJS:%.o=%.d)
|
||||
|
||||
@@ -231,6 +231,8 @@ static int _dump_sink(
|
||||
bool key_required,
|
||||
_output_context_s *ctx) {
|
||||
|
||||
int retval = -1;
|
||||
|
||||
if (count == 0) {
|
||||
count = -1;
|
||||
}
|
||||
@@ -300,18 +302,13 @@ static int _dump_sink(
|
||||
}
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
goto ok;
|
||||
retval = 0;
|
||||
|
||||
error:
|
||||
retval = -1;
|
||||
|
||||
ok:
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
us_frame_destroy(frame);
|
||||
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
error:
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
us_frame_destroy(frame);
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _help(FILE *fp) {
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
static const char _ENCODING_TABLE[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
@@ -34,11 +41,11 @@ static const char _ENCODING_TABLE[] = {
|
||||
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
||||
static const uint _MOD_TABLE[] = {0, 2, 1};
|
||||
|
||||
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
|
||||
const uz encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||
|
||||
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
||||
US_REALLOC(*encoded, encoded_size);
|
||||
@@ -47,14 +54,14 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned data_index = 0, encoded_index = 0; data_index < size;) {
|
||||
# define OCTET(_name) unsigned _name = (data_index < size ? (uint8_t)data[data_index++] : 0)
|
||||
for (uint data_index = 0, encoded_index = 0; data_index < size;) {
|
||||
# define OCTET(_name) uint _name = (data_index < size ? (u8)data[data_index++] : 0)
|
||||
OCTET(octet_a);
|
||||
OCTET(octet_b);
|
||||
OCTET(octet_c);
|
||||
# undef OCTET
|
||||
|
||||
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
|
||||
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||
ENCODE(3);
|
||||
@@ -64,7 +71,7 @@ void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *
|
||||
# undef ENCODE
|
||||
}
|
||||
|
||||
for (unsigned index = 0; index < _MOD_TABLE[size % 3]; index++) {
|
||||
for (uint index = 0; index < _MOD_TABLE[size % 3]; index++) {
|
||||
(*encoded)[encoded_size - 2 - index] = '=';
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "types.h"
|
||||
|
||||
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated);
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 48
|
||||
#define US_VERSION_MINOR 53
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
||||
|
||||
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||
#define US_VERSION_U ((uint)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,46 +22,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/array.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/xioctl.h"
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define US_VIDEO_MAX_WIDTH ((unsigned)15360)
|
||||
#define US_VIDEO_MIN_WIDTH ((uint)160)
|
||||
#define US_VIDEO_MAX_WIDTH ((uint)15360)
|
||||
|
||||
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)8640)
|
||||
#define US_VIDEO_MIN_HEIGHT ((uint)120)
|
||||
#define US_VIDEO_MAX_HEIGHT ((uint)8640)
|
||||
|
||||
#define US_VIDEO_MAX_FPS ((unsigned)120)
|
||||
#define US_VIDEO_MAX_FPS ((uint)120)
|
||||
|
||||
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
|
||||
#define US_FORMAT_UNKNOWN -1
|
||||
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
|
||||
#define US_FORMATS_STR "YUYV, YVYU, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
|
||||
|
||||
#define US_IO_METHOD_UNKNOWN -1
|
||||
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
||||
@@ -76,16 +55,18 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned hw_fps;
|
||||
unsigned jpeg_quality;
|
||||
size_t raw_size;
|
||||
unsigned n_bufs;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint stride;
|
||||
float hz;
|
||||
uint hw_fps;
|
||||
uint jpeg_quality;
|
||||
uz raw_size;
|
||||
uint n_bufs;
|
||||
us_hw_buffer_s *hw_bufs;
|
||||
enum v4l2_buf_type capture_type;
|
||||
bool capture_mplane;
|
||||
bool capturing;
|
||||
bool persistent_timeout_reported;
|
||||
} us_device_runtime_s;
|
||||
@@ -120,19 +101,19 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
unsigned input;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned jpeg_quality;
|
||||
uint input;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint jpeg_quality;
|
||||
v4l2_std_id standard;
|
||||
enum v4l2_memory io_method;
|
||||
bool dv_timings;
|
||||
unsigned n_bufs;
|
||||
unsigned desired_fps;
|
||||
size_t min_frame_size;
|
||||
uint n_bufs;
|
||||
uint desired_fps;
|
||||
uz min_frame_size;
|
||||
bool persistent;
|
||||
unsigned timeout;
|
||||
uint timeout;
|
||||
us_controls_s ctl;
|
||||
us_device_runtime_s *run;
|
||||
} us_device_s;
|
||||
@@ -22,6 +22,16 @@
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
us_frame_s *us_frame_init(void) {
|
||||
us_frame_s *frame;
|
||||
@@ -36,21 +46,21 @@ void us_frame_destroy(us_frame_s *frame) {
|
||||
free(frame);
|
||||
}
|
||||
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
|
||||
void us_frame_realloc_data(us_frame_s *frame, uz size) {
|
||||
if (frame->allocated < size) {
|
||||
US_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size) {
|
||||
us_frame_realloc_data(frame, size);
|
||||
memcpy(frame->data, data, size);
|
||||
frame->used = size;
|
||||
}
|
||||
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
const size_t new_used = frame->used + size;
|
||||
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size) {
|
||||
const uz new_used = frame->used + size;
|
||||
us_frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
@@ -69,10 +79,11 @@ bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
|
||||
);
|
||||
}
|
||||
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame) {
|
||||
unsigned bytes_per_pixel = 0;
|
||||
uint us_frame_get_padding(const us_frame_s *frame) {
|
||||
uint bytes_per_pixel = 0;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_YVYU:
|
||||
case V4L2_PIX_FMT_UYVY:
|
||||
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
||||
case V4L2_PIX_FMT_BGR24:
|
||||
@@ -88,13 +99,13 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
buf[3] = (format >> 24) & 0x7F;
|
||||
if (format & ((unsigned)1 << 31)) {
|
||||
if (format & ((uint)1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
buf[6] = 'E';
|
||||
|
||||
@@ -22,39 +22,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
u8 *data;
|
||||
uz used;
|
||||
uz allocated;
|
||||
int dma_fd;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint stride;
|
||||
// Stride is a bytesperline in V4L2
|
||||
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
unsigned gop;
|
||||
bool online;
|
||||
bool key;
|
||||
uint gop;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
ldf grab_ts;
|
||||
ldf encode_begin_ts;
|
||||
ldf encode_end_ts;
|
||||
} us_frame_s;
|
||||
|
||||
|
||||
@@ -87,7 +81,7 @@ static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
||||
)
|
||||
|
||||
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
|
||||
assert(src->used > 0);
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
@@ -105,17 +99,17 @@ static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
us_frame_s *us_frame_init(void);
|
||||
void us_frame_destroy(us_frame_s *frame);
|
||||
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
void us_frame_realloc_data(us_frame_s *frame, uz size);
|
||||
void us_frame_set_data(us_frame_s *frame, const u8 *data, uz size);
|
||||
void us_frame_append_data(us_frame_s *frame, const u8 *data, uz size);
|
||||
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
||||
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame);
|
||||
uint us_frame_get_padding(const us_frame_s *frame);
|
||||
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
const char *us_fourcc_to_string(uint format, char *buf, uz size);
|
||||
|
||||
static inline bool us_is_jpeg(unsigned format) {
|
||||
static inline bool us_is_jpeg(uint format) {
|
||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
enum us_log_level_t us_g_log_level;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@@ -33,6 +32,7 @@
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
|
||||
@@ -91,7 +91,7 @@ extern pthread_mutex_t us_g_log_mutex;
|
||||
|
||||
|
||||
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||
us_thread_get_name(m_tname_buf); \
|
||||
if (us_g_log_colored) { \
|
||||
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
||||
|
||||
@@ -22,10 +22,26 @@
|
||||
|
||||
#include "memsink.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
||||
mode_t mode, bool rm, uint client_ttl, uint timeout) {
|
||||
|
||||
us_memsink_s *sink;
|
||||
US_CALLOC(sink, 1);
|
||||
@@ -58,12 +74,11 @@ us_memsink_s *us_memsink_init(
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return sink;
|
||||
|
||||
error:
|
||||
us_memsink_destroy(sink);
|
||||
return NULL;
|
||||
error:
|
||||
us_memsink_destroy(sink);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
@@ -117,7 +132,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
}
|
||||
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested) {
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested) {
|
||||
assert(sink->server);
|
||||
|
||||
const long double now = us_get_now_monotonic();
|
||||
@@ -164,7 +179,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *con
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required) { // cppcheck-suppress unusedFunction
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) { // cppcheck-suppress unusedFunction
|
||||
assert(!sink->server); // Client only
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
@@ -176,6 +191,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
|
||||
}
|
||||
|
||||
int retval = -2; // Not updated
|
||||
|
||||
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
@@ -196,10 +212,10 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
retval = -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -22,47 +22,37 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
uint client_ttl; // Only for server
|
||||
uint timeout;
|
||||
|
||||
int fd;
|
||||
us_memsink_shared_s *mem;
|
||||
uint64_t last_id;
|
||||
u64 last_id;
|
||||
atomic_bool has_clients; // Only for server
|
||||
} us_memsink_s;
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
||||
mode_t mode, bool rm, uint client_ttl, uint timeout);
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink);
|
||||
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *const key_requested);
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key_requested);
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *const key_requested, bool key_required);
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required);
|
||||
|
||||
@@ -22,45 +22,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((uint32_t)4)
|
||||
|
||||
#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 ((size_t)(US_CFG_MEMSINK_MAX_DATA))
|
||||
#define US_MEMSINK_MAX_DATA ((uz)(US_CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic;
|
||||
uint32_t version;
|
||||
u64 magic;
|
||||
u32 version;
|
||||
|
||||
uint64_t id;
|
||||
u64 id;
|
||||
|
||||
size_t used;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
bool online;
|
||||
bool key;
|
||||
unsigned gop;
|
||||
uz used;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint stride;
|
||||
bool online;
|
||||
bool key;
|
||||
uint gop;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
ldf grab_ts;
|
||||
ldf encode_begin_ts;
|
||||
ldf encode_end_ts;
|
||||
|
||||
long double last_client_ts;
|
||||
bool key_requested;
|
||||
ldf last_client_ts;
|
||||
bool key_requested;
|
||||
|
||||
uint8_t data[US_MEMSINK_MAX_DATA];
|
||||
u8 data[US_MEMSINK_MAX_DATA];
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
|
||||
@@ -22,10 +22,17 @@
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
#include "types.h"
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
for (uint short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
if (isalpha(opts[opt_index].val)) {
|
||||
short_opts[short_index] = opts[opt_index].val;
|
||||
|
||||
@@ -22,12 +22,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "types.h"
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, uz size);
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#if defined(__linux__)
|
||||
# define HAS_PDEATHSIG
|
||||
@@ -58,6 +56,7 @@
|
||||
# include <sys/procctl.h>
|
||||
# endif
|
||||
#endif
|
||||
#include "types.h"
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# include "tools.h"
|
||||
#endif
|
||||
@@ -102,14 +101,14 @@ INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefi
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
char *cmdline = NULL;
|
||||
size_t allocated = 2048;
|
||||
size_t used = 0;
|
||||
uz allocated = 2048;
|
||||
uz used = 0;
|
||||
|
||||
US_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
size_t arg_len = strlen(argv[index]);
|
||||
uz arg_len = strlen(argv[index]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <pthread.h>
|
||||
@@ -37,13 +36,14 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
# define US_THREAD_NAME_SIZE ((uz)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define US_MAX_THREAD_NAME ((size_t)16)
|
||||
# define US_THREAD_NAME_SIZE ((uz)16)
|
||||
#endif
|
||||
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
@@ -51,8 +51,8 @@
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
||||
char m_new_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||
US_SNPRINTF(m_new_tname_buf, (US_THREAD_NAME_SIZE - 1), (x_fmt), ##__VA_ARGS__); \
|
||||
us_thread_set_name(m_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
@@ -89,12 +89,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
int retval = -1;
|
||||
# if defined(__linux__) || defined (__NetBSD__)
|
||||
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||
# elif \
|
||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||
|| defined(__DragonFly__)
|
||||
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||
if (name[0] != '\0') {
|
||||
retval = 0;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
const pid_t tid = 0; // Makes cppcheck happy
|
||||
# warning gettid() not implemented
|
||||
#endif
|
||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
US_SNPRINTF(name, (US_THREAD_NAME_SIZE - 1), "tid=%d", tid);
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
|
||||
@@ -23,19 +23,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||
@@ -44,6 +40,8 @@
|
||||
# include <signal.h>
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
#ifdef NDEBUG
|
||||
# error WTF dude? Asserts are good things!
|
||||
@@ -57,14 +55,27 @@
|
||||
#define RN "\r\n"
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
|
||||
#define US_CLOSE_FD(x_dest, x_close) { if (x_dest >= 0) { x_close(x_dest); x_dest = -1; } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
||||
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
|
||||
#define US_MIN(x_a, x_b) ({ \
|
||||
__typeof__(x_a) m_a = (x_a); \
|
||||
__typeof__(x_b) m_b = (x_b); \
|
||||
(m_a < m_b ? m_a : m_b); \
|
||||
})
|
||||
|
||||
#define US_MAX(x_a, x_b) ({ \
|
||||
__typeof__(x_a) m_a = (x_a); \
|
||||
__typeof__(x_b) m_b = (x_b); \
|
||||
(m_a > m_b ? m_a : m_b); \
|
||||
})
|
||||
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
@@ -77,23 +88,15 @@ INLINE const char *us_bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t us_align_size(size_t size, size_t to) {
|
||||
INLINE uz us_align_size(uz size, uz to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned us_min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
INLINE sll us_floor_ms(ldf now) {
|
||||
return (sll)now - (now < (sll)now); // floor()
|
||||
}
|
||||
|
||||
INLINE unsigned us_max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long us_floor_ms(long double now) {
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE uint32_t us_triple_u32(uint32_t x) {
|
||||
INLINE u32 us_triple_u32(u32 x) {
|
||||
// https://nullprogram.com/blog/2018/07/31/
|
||||
x ^= x >> 17;
|
||||
x *= UINT32_C(0xED5AD4BB);
|
||||
@@ -117,38 +120,38 @@ INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double us_get_now_monotonic(void) {
|
||||
INLINE ldf us_get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
us_get_now(CLOCK_MONOTONIC, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
return (ldf)sec + ((ldf)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
||||
INLINE u64 us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
INLINE uint64_t us_get_now_id(void) {
|
||||
const uint64_t now = us_get_now_monotonic_u64();
|
||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
||||
INLINE u64 us_get_now_id(void) {
|
||||
const u64 now = us_get_now_monotonic_u64();
|
||||
return (u64)us_triple_u32(now) | ((u64)us_triple_u32(now + 12345) << 32);
|
||||
}
|
||||
|
||||
INLINE long double us_get_now_real(void) {
|
||||
INLINE ldf us_get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
return (ldf)sec + ((ldf)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE unsigned us_get_cores_available(void) {
|
||||
INLINE uint us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return us_max_u(us_min_u(cores_sysconf, 4), 1);
|
||||
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
if (ts->tv_nsec > 999999999L) {
|
||||
@@ -157,12 +160,12 @@ INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
||||
INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + timeout;
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
|
||||
const ldf deadline_ts = us_get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
@@ -178,15 +181,16 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
}
|
||||
|
||||
INLINE char *us_errno_to_string(int error) {
|
||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
||||
char *buf;
|
||||
if (locale) {
|
||||
buf = us_strdup(strerror_l(error, locale));
|
||||
freelocale(locale);
|
||||
} else {
|
||||
buf = us_strdup("!!! newlocale() error !!!");
|
||||
char buf[2048];
|
||||
const uz max_len = sizeof(buf) - 1;
|
||||
# if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE
|
||||
if (strerror_r(error, buf, max_len) != 0) {
|
||||
US_SNPRINTF(buf, max_len, "Errno = %d", error);
|
||||
}
|
||||
return buf;
|
||||
return us_strdup(buf);
|
||||
# else
|
||||
return us_strdup(strerror_r(error, buf, max_len));
|
||||
# endif
|
||||
}
|
||||
|
||||
INLINE char *us_signum_to_string(int signum) {
|
||||
|
||||
44
src/libs/types.h
Normal file
44
src/libs/types.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long long ull;
|
||||
typedef size_t uz;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef long double ldf;
|
||||
@@ -22,6 +22,17 @@
|
||||
|
||||
#include "unjpeg.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_error_mgr mgr; // Default manager
|
||||
@@ -77,9 +88,9 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
jpeg_finish_decompress(&jpeg);
|
||||
}
|
||||
|
||||
done:
|
||||
jpeg_destroy_decompress(&jpeg);
|
||||
return retval;
|
||||
done:
|
||||
jpeg_destroy_decompress(&jpeg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||
|
||||
@@ -22,18 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
#ifndef US_CFG_XIOCTL_RETRIES
|
||||
# define US_CFG_XIOCTL_RETRIES 4
|
||||
#endif
|
||||
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
|
||||
#define _XIOCTL_RETRIES ((uint)(US_CFG_XIOCTL_RETRIES))
|
||||
|
||||
|
||||
INLINE int us_xioctl(int fd, int request, void *arg) {
|
||||
|
||||
@@ -63,16 +63,16 @@ static us_frame_s *_init_external(const char *path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
# define CHUNK_SIZE ((size_t)(100 * 1024))
|
||||
const size_t chunk_size = 100 * 1024;
|
||||
while (true) {
|
||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
||||
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
if (blank->used + chunk_size >= blank->allocated) {
|
||||
us_frame_realloc_data(blank, blank->used + chunk_size * 2);
|
||||
}
|
||||
|
||||
const size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
||||
const size_t readed = fread(blank->data + blank->used, 1, chunk_size, fp);
|
||||
blank->used += readed;
|
||||
|
||||
if (readed < CHUNK_SIZE) {
|
||||
if (readed < chunk_size) {
|
||||
if (feof(fp)) {
|
||||
break;
|
||||
} else {
|
||||
@@ -81,7 +81,6 @@ static us_frame_s *_init_external(const char *path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
# undef CHUNK_SIZE
|
||||
|
||||
us_frame_s *const decoded = us_frame_init();
|
||||
if (us_unjpeg(blank, decoded, false) < 0) {
|
||||
@@ -94,12 +93,10 @@ static us_frame_s *_init_external(const char *path) {
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
us_frame_destroy(blank);
|
||||
blank = NULL;
|
||||
|
||||
ok:
|
||||
US_DELETE(fp, fclose);
|
||||
error:
|
||||
US_DELETE(blank, us_frame_destroy);
|
||||
|
||||
ok:
|
||||
US_DELETE(fp, fclose);
|
||||
return blank;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
|
||||
|
||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||
unsigned quality = dev->jpeg_quality;
|
||||
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
|
||||
unsigned n_workers = US_MIN(enc->n_workers, DR(n_bufs));
|
||||
bool cpu_forced = false;
|
||||
|
||||
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
||||
@@ -120,7 +120,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
|
||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
|
||||
US_SNPRINTF(name, 31, "JPEG-%u", _ER(n_m2ms));
|
||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
} else {
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/device.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "workers.h"
|
||||
#include "m2m.h"
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ typedef struct {
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
@@ -71,21 +70,17 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(x_format, x_func) \
|
||||
case x_format: { x_func(&jpeg, src); break; }
|
||||
|
||||
switch (src->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
|
||||
WRITE_SCANLINES(V4L2_PIX_FMT_BGR24, _jpeg_write_scanlines_bgr24);
|
||||
default: assert(0 && "Unsupported input format for CPU encoder");
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_YVYU:
|
||||
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_BGR24: _jpeg_write_scanlines_bgr24(&jpeg, src); break;
|
||||
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||
}
|
||||
|
||||
# undef WRITE_SCANLINES
|
||||
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
|
||||
@@ -108,40 +103,7 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
||||
frame->used = 0;
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
|
||||
while (jpeg->next_scanline < frame->height) {
|
||||
uint8_t *ptr = line_buf;
|
||||
|
||||
for (unsigned x = 0; x < frame->width; ++x) {
|
||||
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-yuyv.html
|
||||
const bool is_odd_pixel = x & 1;
|
||||
const uint8_t y = data[is_odd_pixel ? 2 : 0];
|
||||
const uint8_t u = data[1];
|
||||
const uint8_t v = data[3];
|
||||
|
||||
ptr[0] = y;
|
||||
ptr[1] = u;
|
||||
ptr[2] = v;
|
||||
ptr += 3;
|
||||
|
||||
data += (is_odd_pixel ? 4: 0);
|
||||
}
|
||||
data += padding;
|
||||
|
||||
JSAMPROW scanlines[1] = {line_buf};
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
|
||||
free(line_buf);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
@@ -154,9 +116,23 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
|
||||
for (unsigned x = 0; x < frame->width; ++x) {
|
||||
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
|
||||
const bool is_odd_pixel = x & 1;
|
||||
const uint8_t y = data[is_odd_pixel ? 3 : 1];
|
||||
const uint8_t u = data[0];
|
||||
const uint8_t v = data[2];
|
||||
uint8_t y, u, v;
|
||||
if (frame->format == V4L2_PIX_FMT_YUYV) {
|
||||
y = data[is_odd_pixel ? 2 : 0];
|
||||
u = data[1];
|
||||
v = data[3];
|
||||
} else if (frame->format == V4L2_PIX_FMT_YVYU) {
|
||||
y = data[is_odd_pixel ? 2 : 0];
|
||||
u = data[3];
|
||||
v = data[1];
|
||||
} else if (frame->format == V4L2_PIX_FMT_UYVY) {
|
||||
y = data[is_odd_pixel ? 3 : 1];
|
||||
u = data[0];
|
||||
v = data[2];
|
||||
} else {
|
||||
assert(0 && "Unsupported pixel format");
|
||||
return; // Makes linter happy
|
||||
}
|
||||
|
||||
ptr[0] = y;
|
||||
ptr[1] = u;
|
||||
|
||||
@@ -31,10 +31,9 @@ char *us_bufferevent_format_reason(short what) {
|
||||
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
|
||||
bool first = true;
|
||||
|
||||
strcat(reason, perror_str);
|
||||
strncat(reason, perror_str, 1023);
|
||||
free(perror_str);
|
||||
strcat(reason, " (");
|
||||
|
||||
# define FILL_REASON(x_bev, x_name) { \
|
||||
if (what & x_bev) { \
|
||||
if (first) { \
|
||||
@@ -51,7 +50,6 @@ char *us_bufferevent_format_reason(short what) {
|
||||
FILL_REASON(BEV_EVENT_ERROR, "error");
|
||||
FILL_REASON(BEV_EVENT_TIMEOUT, "timeout");
|
||||
FILL_REASON(BEV_EVENT_EOF, "eof"); // cppcheck-suppress unreadVariable
|
||||
|
||||
# undef FILL_REASON
|
||||
|
||||
strcat(reason, ")");
|
||||
|
||||
@@ -51,10 +51,8 @@ static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define _RUN(x_next) server->run->x_next
|
||||
#define _STREAM(x_next) _RUN(stream->x_next)
|
||||
#define _VID(x_next) _STREAM(run->video->x_next)
|
||||
#define _EX(x_next) _RUN(exposed->x_next)
|
||||
#define _VID(x_next) server->run->stream->run->video->x_next
|
||||
#define _EX(x_next) server->run->exposed->x_next
|
||||
|
||||
|
||||
us_server_s *us_server_init(us_stream_s *stream) {
|
||||
@@ -64,6 +62,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
||||
|
||||
us_server_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->ext_fd = -1;
|
||||
run->stream = stream;
|
||||
run->exposed = exposed;
|
||||
|
||||
@@ -88,78 +87,81 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
||||
}
|
||||
|
||||
void us_server_destroy(us_server_s *server) {
|
||||
if (_RUN(refresher) != NULL) {
|
||||
event_del(_RUN(refresher));
|
||||
event_free(_RUN(refresher));
|
||||
us_server_runtime_s *const run = server->run;
|
||||
|
||||
if (run->refresher != NULL) {
|
||||
event_del(run->refresher);
|
||||
event_free(run->refresher);
|
||||
}
|
||||
|
||||
if (_RUN(request_watcher) != NULL) {
|
||||
event_del(_RUN(request_watcher));
|
||||
event_free(_RUN(request_watcher));
|
||||
if (run->request_watcher != NULL) {
|
||||
event_del(run->request_watcher);
|
||||
event_free(run->request_watcher);
|
||||
}
|
||||
|
||||
evhttp_free(_RUN(http));
|
||||
if (_RUN(ext_fd) >= 0) {
|
||||
close(_RUN(ext_fd));
|
||||
}
|
||||
event_base_free(_RUN(base));
|
||||
evhttp_free(run->http);
|
||||
US_CLOSE_FD(run->ext_fd, close);
|
||||
event_base_free(run->base);
|
||||
|
||||
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||
libevent_global_shutdown();
|
||||
# endif
|
||||
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
});
|
||||
|
||||
US_DELETE(_RUN(auth_token), free);
|
||||
US_DELETE(run->auth_token, free);
|
||||
|
||||
us_frame_destroy(_EX(frame));
|
||||
free(_RUN(exposed));
|
||||
us_frame_destroy(run->exposed->frame);
|
||||
free(run->exposed);
|
||||
free(server->run);
|
||||
free(server);
|
||||
}
|
||||
|
||||
int us_server_listen(us_server_s *server) {
|
||||
us_server_runtime_s *const run = server->run;
|
||||
us_stream_s *const stream = run->stream;
|
||||
|
||||
{
|
||||
if (server->static_path[0] != '\0') {
|
||||
US_LOG_INFO("Enabling HTTP file server: %s", server->static_path);
|
||||
evhttp_set_gencb(_RUN(http), _http_callback_static, (void *)server);
|
||||
evhttp_set_gencb(run->http, _http_callback_static, (void *)server);
|
||||
} else {
|
||||
assert(!evhttp_set_cb(_RUN(http), "/", _http_callback_root, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/favicon.ico", _http_callback_favicon, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (void *)server));
|
||||
}
|
||||
assert(!evhttp_set_cb(_RUN(http), "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/stream", _http_callback_stream, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
|
||||
}
|
||||
|
||||
us_frame_copy(_STREAM(blank), _EX(frame));
|
||||
us_frame_copy(stream->blank, _EX(frame));
|
||||
_EX(notify_last_width) = _EX(frame->width);
|
||||
_EX(notify_last_height) = _EX(frame->height);
|
||||
|
||||
if (server->exit_on_no_clients > 0) {
|
||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
||||
run->last_request_ts = us_get_now_monotonic();
|
||||
struct timeval interval = {0};
|
||||
interval.tv_usec = 100000;
|
||||
assert((_RUN(request_watcher) = event_new(_RUN(base), -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
|
||||
assert(!event_add(_RUN(request_watcher), &interval));
|
||||
assert((run->request_watcher = event_new(run->base, -1, EV_PERSIST, _http_request_watcher, server)) != NULL);
|
||||
assert(!event_add(run->request_watcher, &interval));
|
||||
}
|
||||
|
||||
{
|
||||
struct timeval interval = {0};
|
||||
if (_STREAM(dev->desired_fps) > 0) {
|
||||
interval.tv_usec = 1000000 / (_STREAM(dev->desired_fps) * 2);
|
||||
if (stream->dev->desired_fps > 0) {
|
||||
interval.tv_usec = 1000000 / (stream->dev->desired_fps * 2);
|
||||
} else {
|
||||
interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
assert((_RUN(refresher) = event_new(_RUN(base), -1, EV_PERSIST, _http_refresher, server)) != NULL);
|
||||
assert(!event_add(_RUN(refresher), &interval));
|
||||
assert((run->refresher = event_new(run->base, -1, EV_PERSIST, _http_refresher, server)) != NULL);
|
||||
assert(!event_add(run->refresher, &interval));
|
||||
}
|
||||
|
||||
evhttp_set_timeout(_RUN(http), server->timeout);
|
||||
evhttp_set_timeout(run->http, server->timeout);
|
||||
|
||||
if (server->user[0] != '\0') {
|
||||
char *encoded_token = NULL;
|
||||
@@ -169,7 +171,7 @@ int us_server_listen(us_server_s *server) {
|
||||
us_base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
free(raw_token);
|
||||
|
||||
US_ASPRINTF(_RUN(auth_token), "Basic %s", encoded_token);
|
||||
US_ASPRINTF(run->auth_token, "Basic %s", encoded_token);
|
||||
free(encoded_token);
|
||||
|
||||
US_LOG_INFO("Using HTTP basic auth");
|
||||
@@ -177,8 +179,8 @@ int us_server_listen(us_server_s *server) {
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
US_LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
if ((_RUN(ext_fd) = us_evhttp_bind_unix(
|
||||
_RUN(http),
|
||||
if ((run->ext_fd = us_evhttp_bind_unix(
|
||||
run->http,
|
||||
server->unix_path,
|
||||
server->unix_rm,
|
||||
server->unix_mode)) < 0
|
||||
@@ -190,7 +192,7 @@ int us_server_listen(us_server_s *server) {
|
||||
# ifdef WITH_SYSTEMD
|
||||
} else if (server->systemd) {
|
||||
US_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
if ((_RUN(ext_fd) = us_evhttp_bind_systemd(_RUN(http))) < 0) {
|
||||
if ((run->ext_fd = us_evhttp_bind_systemd(run->http)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
US_LOG_INFO("Listening systemd socket ...");
|
||||
@@ -198,7 +200,7 @@ int us_server_listen(us_server_s *server) {
|
||||
|
||||
} else {
|
||||
US_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(_RUN(http), server->host, server->port) < 0) {
|
||||
if (evhttp_bind_socket(run->http, server->host, server->port) < 0) {
|
||||
US_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
@@ -210,18 +212,20 @@ int us_server_listen(us_server_s *server) {
|
||||
|
||||
void us_server_loop(us_server_s *server) {
|
||||
US_LOG_INFO("Starting HTTP eventloop ...");
|
||||
event_base_dispatch(_RUN(base));
|
||||
event_base_dispatch(server->run->base);
|
||||
US_LOG_INFO("HTTP eventloop stopped");
|
||||
}
|
||||
|
||||
void us_server_loop_break(us_server_s *server) {
|
||||
event_base_loopbreak(_RUN(base));
|
||||
event_base_loopbreak(server->run->base);
|
||||
}
|
||||
|
||||
#define ADD_HEADER(x_key, x_value) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), x_key, x_value))
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
|
||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
||||
us_server_runtime_s *const run = server->run;
|
||||
|
||||
run->last_request_ts = us_get_now_monotonic();
|
||||
|
||||
if (server->allow_origin[0] != '\0') {
|
||||
const char *const cors_headers = _http_get_header(request, "Access-Control-Request-Headers");
|
||||
@@ -242,10 +246,10 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_RUN(auth_token) != NULL) {
|
||||
if (run->auth_token != NULL) {
|
||||
const char *const token = _http_get_header(request, "Authorization");
|
||||
|
||||
if (token == NULL || strcmp(token, _RUN(auth_token)) != 0) {
|
||||
if (token == NULL || strcmp(token, run->auth_token) != 0) {
|
||||
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
||||
return -1;
|
||||
@@ -269,24 +273,22 @@ static int _http_preprocess_request(struct evhttp_request *request, us_server_s
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
||||
// MJPG-Streamer compatibility layer
|
||||
|
||||
struct evkeyvalq params;
|
||||
int error = 0;
|
||||
int retval = -1;
|
||||
|
||||
struct evkeyvalq params;
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
const char *const action = evhttp_find_header(¶ms, "action");
|
||||
|
||||
if (action && !strcmp(action, "snapshot")) {
|
||||
_http_callback_snapshot(request, v_server);
|
||||
goto ok;
|
||||
retval = 0;
|
||||
} else if (action && !strcmp(action, "stream")) {
|
||||
_http_callback_stream(request, v_server);
|
||||
goto ok;
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
error = -1;
|
||||
ok:
|
||||
evhttp_clear_headers(¶ms);
|
||||
return error;
|
||||
evhttp_clear_headers(¶ms);
|
||||
return retval;
|
||||
}
|
||||
|
||||
#define COMPAT_REQUEST { \
|
||||
@@ -338,14 +340,12 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
|
||||
{
|
||||
const char *uri_path;
|
||||
|
||||
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
|
||||
goto bad_request;
|
||||
}
|
||||
if ((uri_path = (char *)evhttp_uri_get_path(uri)) == NULL) {
|
||||
uri_path = "/";
|
||||
}
|
||||
|
||||
if ((decoded_path = evhttp_uridecode(uri_path, 0, NULL)) == NULL) {
|
||||
goto bad_request;
|
||||
}
|
||||
@@ -383,34 +383,34 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bad_request:
|
||||
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
||||
goto cleanup;
|
||||
bad_request:
|
||||
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
||||
goto cleanup;
|
||||
|
||||
not_found:
|
||||
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
||||
goto cleanup;
|
||||
not_found:
|
||||
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
US_DELETE(static_path, free);
|
||||
US_DELETE(buf, evbuffer_free);
|
||||
US_DELETE(decoded_path, free);
|
||||
US_DELETE(uri, evhttp_uri_free);
|
||||
cleanup:
|
||||
US_CLOSE_FD(fd, close); // cppcheck-suppress unreadVariable
|
||||
US_DELETE(static_path, free);
|
||||
US_DELETE(buf, evbuffer_free);
|
||||
US_DELETE(decoded_path, free);
|
||||
US_DELETE(uri, evhttp_uri_free);
|
||||
}
|
||||
|
||||
#undef COMPAT_REQUEST
|
||||
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
us_server_s *const server = (us_server_s *)v_server;
|
||||
us_server_runtime_s *const run = server->run;
|
||||
us_stream_s *const stream = run->stream;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
us_encoder_type_e enc_type;
|
||||
unsigned enc_quality;
|
||||
us_encoder_get_runtime_params(_STREAM(enc), &enc_type, &enc_quality);
|
||||
us_encoder_get_runtime_params(stream->enc, &enc_type, &enc_quality);
|
||||
|
||||
struct evbuffer *buf;
|
||||
_A_EVBUFFER_NEW(buf);
|
||||
@@ -424,28 +424,28 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
enc_quality
|
||||
);
|
||||
|
||||
if (_STREAM(run->h264) != NULL) {
|
||||
if (stream->run->h264 != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||
_STREAM(h264_bitrate),
|
||||
_STREAM(h264_gop),
|
||||
us_bool_to_string(atomic_load(&_STREAM(run->h264->online)))
|
||||
stream->h264_bitrate,
|
||||
stream->h264_gop,
|
||||
us_bool_to_string(atomic_load(&stream->run->h264->online))
|
||||
);
|
||||
}
|
||||
|
||||
if (_STREAM(sink) != NULL || _STREAM(h264_sink) != NULL) {
|
||||
if (stream->sink != NULL || stream->h264_sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
|
||||
if (_STREAM(sink) != NULL) {
|
||||
if (stream->sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
"\"jpeg\": {\"has_clients\": %s}",
|
||||
us_bool_to_string(atomic_load(&_STREAM(sink->has_clients)))
|
||||
us_bool_to_string(atomic_load(&stream->sink->has_clients))
|
||||
);
|
||||
}
|
||||
if (_STREAM(h264_sink) != NULL) {
|
||||
if (stream->h264_sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
(_STREAM(sink) ? ", " : ""),
|
||||
us_bool_to_string(atomic_load(&_STREAM(h264_sink->has_clients)))
|
||||
(stream->sink ? ", " : ""),
|
||||
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients))
|
||||
);
|
||||
}
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "},");
|
||||
@@ -458,13 +458,13 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
(server->fake_width ? server->fake_width : _EX(frame->width)),
|
||||
(server->fake_height ? server->fake_height : _EX(frame->height)),
|
||||
us_bool_to_string(_EX(frame->online)),
|
||||
_STREAM(dev->desired_fps),
|
||||
stream->dev->desired_fps,
|
||||
_EX(captured_fps),
|
||||
_EX(queued_fps),
|
||||
_RUN(stream_clients_count)
|
||||
run->stream_clients_count
|
||||
);
|
||||
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
|
||||
@@ -502,12 +502,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
char header_buf[256];
|
||||
|
||||
# define ADD_TIME_HEADER(x_key, x_value) { \
|
||||
snprintf(header_buf, 255, "%.06Lf", x_value); \
|
||||
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
|
||||
ADD_HEADER(x_key, header_buf); \
|
||||
}
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
||||
snprintf(header_buf, 255, "%u", x_value); \
|
||||
US_SNPRINTF(header_buf, 255, "%u", x_value); \
|
||||
ADD_HEADER(x_key, header_buf); \
|
||||
}
|
||||
|
||||
@@ -544,6 +544,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
|
||||
|
||||
us_server_s *const server = (us_server_s *)v_server;
|
||||
us_server_runtime_s *const run = server->run;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
@@ -570,9 +571,9 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
client->hostport = _http_get_client_hostport(request);
|
||||
client->id = us_get_now_id();
|
||||
|
||||
US_LIST_APPEND_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
||||
US_LIST_APPEND_C(run->stream_clients, client, run->stream_clients_count);
|
||||
|
||||
if (_RUN(stream_clients_count) == 1) {
|
||||
if (run->stream_clients_count == 1) {
|
||||
atomic_store(&_VID(has_clients), true);
|
||||
# ifdef WITH_GPIO
|
||||
us_gpio_set_has_http_clients(true);
|
||||
@@ -580,10 +581,10 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
}
|
||||
|
||||
US_LOG_INFO("HTTP: NEW client (now=%u): %s, id=%" PRIx64,
|
||||
_RUN(stream_clients_count), client->hostport, client->id);
|
||||
run->stream_clients_count, client->hostport, client->id);
|
||||
|
||||
struct bufferevent *const buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && !_RUN(ext_fd)) {
|
||||
if (server->tcp_nodelay && run->ext_fd >= 0) {
|
||||
US_LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
||||
assert(fd >= 0);
|
||||
@@ -602,8 +603,6 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
#undef PREPROCESS_REQUEST
|
||||
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
|
||||
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
||||
us_server_s *const server = client->server;
|
||||
|
||||
@@ -639,6 +638,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
// Кроме того, advance_headers форсит отключение заголовков X-UStreamer-*
|
||||
// по тем же причинам, по которым у нас нет Content-Length.
|
||||
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
|
||||
# define ADD_ADVANCE_HEADERS \
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real())
|
||||
@@ -667,10 +668,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||
"Set-Cookie: stream_client%s%s=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||
RN
|
||||
"--" BOUNDARY RN,
|
||||
(server->instance_id[0] == '\0' ? "" : "_"),
|
||||
server->instance_id,
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
client->id
|
||||
);
|
||||
@@ -745,13 +748,17 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
# undef BOUNDARY
|
||||
}
|
||||
|
||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_client) {
|
||||
(void)buf_event;
|
||||
(void)what;
|
||||
|
||||
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
||||
us_server_s *const server = client->server;
|
||||
us_server_runtime_s *const run = server->run;
|
||||
|
||||
US_LIST_REMOVE_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
||||
US_LIST_REMOVE_C(run->stream_clients, client, run->stream_clients_count);
|
||||
|
||||
if (_RUN(stream_clients_count) == 0) {
|
||||
if (run->stream_clients_count == 0) {
|
||||
atomic_store(&_VID(has_clients), false);
|
||||
# ifdef WITH_GPIO
|
||||
us_gpio_set_has_http_clients(false);
|
||||
@@ -760,10 +767,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
|
||||
char *const reason = us_bufferevent_format_reason(what);
|
||||
US_LOG_INFO("HTTP: DEL client (now=%u): %s, id=%" PRIx64 ", %s",
|
||||
_RUN(stream_clients_count), client->hostport, client->id, reason);
|
||||
run->stream_clients_count, client->hostport, client->id, reason);
|
||||
free(reason);
|
||||
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
US_DELETE(conn, evhttp_connection_free);
|
||||
|
||||
free(client->key);
|
||||
@@ -772,10 +779,12 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated) {
|
||||
us_server_runtime_s *const run = server->run;
|
||||
|
||||
bool has_clients = false;
|
||||
bool queued = false;
|
||||
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
||||
if (conn != NULL) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
@@ -823,21 +832,28 @@ static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bo
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
static void _http_request_watcher(int fd, short what, void *v_server) {
|
||||
(void)fd;
|
||||
(void)what;
|
||||
|
||||
us_server_s *const server = (us_server_s *)v_server;
|
||||
us_server_runtime_s *const run = server->run;
|
||||
const long double now = us_get_now_monotonic();
|
||||
|
||||
if (us_stream_has_clients(_RUN(stream))) {
|
||||
_RUN(last_request_ts) = now;
|
||||
} else if (_RUN(last_request_ts) + server->exit_on_no_clients < now) {
|
||||
if (us_stream_has_clients(run->stream)) {
|
||||
run->last_request_ts = now;
|
||||
} else if (run->last_request_ts + server->exit_on_no_clients < now) {
|
||||
US_LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
server->exit_on_no_clients);
|
||||
us_process_suicide();
|
||||
_RUN(last_request_ts) = now;
|
||||
run->last_request_ts = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
static void _http_refresher(int fd, short what, void *v_server) {
|
||||
(void)fd;
|
||||
(void)what;
|
||||
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
bool stream_updated = false;
|
||||
bool frame_updated = false;
|
||||
@@ -912,10 +928,11 @@ static bool _expose_new_frame(us_server_s *server) {
|
||||
_EX(frame->online), _EX(expose_end_ts) - _EX(expose_begin_ts));
|
||||
|
||||
updated = true;
|
||||
not_updated:
|
||||
atomic_store(&_VID(updated), false);
|
||||
US_MUTEX_UNLOCK(_VID(mutex));
|
||||
return updated;
|
||||
|
||||
not_updated:
|
||||
atomic_store(&_VID(updated), false);
|
||||
US_MUTEX_UNLOCK(_VID(mutex));
|
||||
return updated;
|
||||
}
|
||||
|
||||
static const char *_http_get_header(struct evhttp_request *request, const char *key) {
|
||||
|
||||
@@ -125,7 +125,7 @@ typedef struct {
|
||||
|
||||
struct event *refresher;
|
||||
us_stream_s *stream;
|
||||
us_exposed_s *exposed;
|
||||
us_exposed_s *exposed;
|
||||
|
||||
us_stream_client_s *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
|
||||
@@ -33,7 +33,7 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
||||
}
|
||||
|
||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||
|
||||
struct stat st;
|
||||
# define LOAD_STAT { \
|
||||
@@ -42,14 +42,12 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
LOAD_STAT;
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||
strcat(path, "/index.html");
|
||||
LOAD_STAT;
|
||||
}
|
||||
|
||||
# undef LOAD_STAT
|
||||
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
@@ -64,12 +62,10 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
US_DELETE(path, free);
|
||||
path = NULL;
|
||||
|
||||
ok:
|
||||
free(simplified_path);
|
||||
error:
|
||||
US_DELETE(path, free);
|
||||
|
||||
ok:
|
||||
free(simplified_path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
@@ -25,19 +25,16 @@
|
||||
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
struct sockaddr_un addr = {0};
|
||||
const size_t max_sun_path = sizeof(addr.sun_path) - 1;
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
|
||||
|
||||
if (strlen(path) > MAX_SUN_PATH) {
|
||||
US_LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
if (strlen(path) > max_sun_path) {
|
||||
US_LOG_ERROR("UNIX socket path is too long; max=%zu", max_sun_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncpy(addr.sun_path, path, MAX_SUN_PATH);
|
||||
strncpy(addr.sun_path, path, max_sun_path);
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
# undef MAX_SUN_PATH
|
||||
|
||||
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/device.h"
|
||||
|
||||
#include "options.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http/server.h"
|
||||
@@ -52,14 +52,16 @@ static void _block_thread_signals(void) {
|
||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(UNUSED void *arg) {
|
||||
static void *_stream_loop_thread(void *arg) {
|
||||
(void)arg;
|
||||
US_THREAD_RENAME("stream");
|
||||
_block_thread_signals();
|
||||
us_stream_loop(_g_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_server_loop_thread(UNUSED void *arg) {
|
||||
static void *_server_loop_thread(void *arg) {
|
||||
(void)arg;
|
||||
US_THREAD_RENAME("http");
|
||||
_block_thread_signals();
|
||||
us_server_loop(_g_server);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
@@ -38,8 +39,8 @@
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
#include "../libs/device.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "blank.h"
|
||||
#include "stream.h"
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/device.h"
|
||||
|
||||
#include "blank.h"
|
||||
#include "device.h"
|
||||
#include "encoder.h"
|
||||
#include "workers.h"
|
||||
#include "h264.h"
|
||||
|
||||
611
src/v4p/drm.c
Normal file
611
src/v4p/drm.c
Normal file
@@ -0,0 +1,611 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "drm.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include <libdrm/drm.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "ftext.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 void _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz);
|
||||
static int _drm_init_video(us_drm_s *drm);
|
||||
static int _drm_init_buffers(us_drm_s *drm);
|
||||
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
#define _D_LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_drm_s *us_drm_init(void) {
|
||||
us_drm_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->fd = -1;
|
||||
run->conn_type = -1;
|
||||
run->conn_type_id = -1;
|
||||
run->ft = us_ftext_init();
|
||||
run->state = US_DRM_STATE_CLOSED;
|
||||
|
||||
us_drm_s *drm;
|
||||
US_CALLOC(drm, 1);
|
||||
drm->path = "/dev/dri/card0";
|
||||
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_ftext_destroy(drm->run->ft);
|
||||
US_DELETE(drm->run, free);
|
||||
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
|
||||
}
|
||||
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
_drm_ensure(drm, NULL, 0);
|
||||
if (run->state != US_DRM_STATE_OK) {
|
||||
return -1;
|
||||
}
|
||||
if (run->has_vsync) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct timeval timeout = {.tv_sec = drm->timeout};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(run->fd, &fds);
|
||||
|
||||
_D_LOG_DEBUG("Calling select() for VSync ...");
|
||||
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;
|
||||
} else if (result == 0) {
|
||||
_D_LOG_ERROR("Device timeout while waiting VSync");
|
||||
goto error;
|
||||
}
|
||||
|
||||
drmEventContext ctx = {
|
||||
.version = DRM_EVENT_CONTEXT_VERSION,
|
||||
.page_flip_handler = _drm_vsync_callback,
|
||||
};
|
||||
_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;
|
||||
|
||||
_drm_ensure(drm, frame, hz);
|
||||
if (run->state != US_DRM_STATE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const drmModeModeInfo *const mode = &run->mode;
|
||||
bool msg_drawn = false;
|
||||
|
||||
# define DRAW_MSG(x_msg) { \
|
||||
us_ftext_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);
|
||||
}
|
||||
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run) {
|
||||
(void)fd;
|
||||
(void)n_frame;
|
||||
(void)sec;
|
||||
(void)usec;
|
||||
us_drm_runtime_s *const run = v_run;
|
||||
run->has_vsync = true;
|
||||
_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);
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
if (frame == NULL) {
|
||||
memset(buf->data, 0, buf->allocated);
|
||||
} else {
|
||||
memcpy(buf->data, frame->data, US_MIN(frame->used, buf->allocated));
|
||||
}
|
||||
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
run);
|
||||
if (retval == 0) {
|
||||
run->next_n_buf = (run->next_n_buf + 1) % run->n_bufs;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _drm_cleanup(us_drm_s *drm) {
|
||||
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) {
|
||||
_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;
|
||||
}
|
||||
|
||||
if (run->fd >= 0) {
|
||||
if (close(run->fd) < 0) {
|
||||
_D_LOG_PERROR("Can't close device");
|
||||
}
|
||||
run->fd = -1;
|
||||
}
|
||||
|
||||
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 void _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
if (frame == NULL) {
|
||||
if (run->state == US_DRM_STATE_OK) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
run->p_width == frame->width
|
||||
&& run->p_height == frame->height
|
||||
&& run->p_hz == hz
|
||||
&& run->state <= US_DRM_STATE_RETRY
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_drm_cleanup(drm);
|
||||
|
||||
if (run->state <= US_DRM_STATE_RETRY) {
|
||||
_D_LOG_INFO("Configuring DRM device ...");
|
||||
}
|
||||
|
||||
if (frame == NULL) {
|
||||
run->p_width = 0; // Find the native resolution
|
||||
run->p_height = 0;
|
||||
} else {
|
||||
run->p_width = frame->width;
|
||||
run->p_height = frame->height;
|
||||
}
|
||||
run->p_hz = hz;
|
||||
|
||||
if ((run->fd = open(drm->path, O_RDWR|O_CLOEXEC|O_NONBLOCK)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device");
|
||||
goto error;
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
if (_drm_find_sink(drm, run->p_width, run->p_height, run->p_hz) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (run->crtc_id == 0) {
|
||||
if (run->state != US_DRM_STATE_NO_DISPLAY) {
|
||||
_D_LOG_INFO("Using %s mode: Display unplugged", drm->port);
|
||||
run->state = US_DRM_STATE_NO_DISPLAY;
|
||||
}
|
||||
goto error;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
_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_init_video(drm) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Showing ...");
|
||||
run->state = US_DRM_STATE_OK;
|
||||
return;
|
||||
|
||||
error:
|
||||
if (run->state == US_DRM_STATE_CLOSED) {
|
||||
_D_LOG_ERROR("Device destroyed due an error (prepare)");
|
||||
}
|
||||
_drm_cleanup(drm);
|
||||
}
|
||||
|
||||
static int _drm_init_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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _drm_init_buffers(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
_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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 ((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;
|
||||
}
|
||||
buf->allocated = create.size;
|
||||
}
|
||||
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;
|
||||
|
||||
int retval = -1;
|
||||
|
||||
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 error;
|
||||
}
|
||||
_D_LOG_DEBUG("Found %u connectors", res->count_connectors);
|
||||
|
||||
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 error;
|
||||
}
|
||||
|
||||
if (run->conn_type < 0) {
|
||||
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)) {
|
||||
run->conn_type = conn->connector_type;
|
||||
run->conn_type_id = conn->connector_type_id;
|
||||
}
|
||||
}
|
||||
if (
|
||||
(int)conn->connector_type != run->conn_type
|
||||
|| (int)conn->connector_type_id != run->conn_type_id
|
||||
) {
|
||||
drmModeFreeConnector(conn);
|
||||
continue;
|
||||
}
|
||||
_D_LOG_DEBUG("Found requested connector for port %s: conn_type=%d, conn_type_id=%d",
|
||||
drm->port, run->conn_type, run->conn_type_id);
|
||||
|
||||
if (conn->connection != DRM_MODE_CONNECTED) {
|
||||
_D_LOG_DEBUG("Display is not connected");
|
||||
drmModeFreeConnector(conn);
|
||||
break;
|
||||
}
|
||||
|
||||
drmModeModeInfo *best = NULL;// (conn->count_modes > 0 ? &conn->modes[0] : 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 resolutions");
|
||||
drmModeFreeConnector(conn);
|
||||
break;
|
||||
}
|
||||
assert(best->hdisplay > 0);
|
||||
assert(best->vdisplay > 0);
|
||||
|
||||
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 error;
|
||||
}
|
||||
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
|
||||
run->conn_id = conn->connector_id;
|
||||
|
||||
drmModeFreeConnector(conn);
|
||||
break;
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
|
||||
error:
|
||||
drmModeFreeResources(res);
|
||||
return retval;
|
||||
}
|
||||
|
||||
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]);
|
||||
if (enc == NULL) {
|
||||
continue;
|
||||
}
|
||||
for (int ci = 0; ci < res->count_crtcs; ++ci) {
|
||||
u32 bit = (1 << ci);
|
||||
if (!(enc->possible_crtcs & bit)) {
|
||||
continue; // Not compatible
|
||||
}
|
||||
if (*taken_crtcs & bit) {
|
||||
continue; // Already taken
|
||||
}
|
||||
drmModeFreeEncoder(enc);
|
||||
*taken_crtcs |= bit;
|
||||
return res->crtcs[ci];
|
||||
}
|
||||
drmModeFreeEncoder(enc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *_connector_type_to_string(u32 type) {
|
||||
switch (type) {
|
||||
# define CASE_NAME(x_suffix, x_name) \
|
||||
case DRM_MODE_CONNECTOR_##x_suffix: return x_name;
|
||||
CASE_NAME(VGA, "VGA");
|
||||
CASE_NAME(DVII, "DVI-I");
|
||||
CASE_NAME(DVID, "DVI-D");
|
||||
CASE_NAME(DVIA, "DVI-A");
|
||||
CASE_NAME(Composite, "Composite");
|
||||
CASE_NAME(SVIDEO, "SVIDEO");
|
||||
CASE_NAME(LVDS, "LVDS");
|
||||
CASE_NAME(Component, "Component");
|
||||
CASE_NAME(9PinDIN, "DIN");
|
||||
CASE_NAME(DisplayPort, "DP");
|
||||
CASE_NAME(HDMIA, "HDMI-A");
|
||||
CASE_NAME(HDMIB, "HDMI-B");
|
||||
CASE_NAME(TV, "TV");
|
||||
CASE_NAME(eDP, "eDP");
|
||||
CASE_NAME(VIRTUAL, "Virtual");
|
||||
CASE_NAME(DSI, "DSI");
|
||||
case DRM_MODE_CONNECTOR_Unknown: break;
|
||||
# undef CASE_NAME
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static float _get_refresh_rate(const drmModeModeInfo *mode) {
|
||||
int mhz = (mode->clock * 1000000LL / mode->htotal + mode->vtotal / 2) / mode->vtotal;
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
mhz *= 2;
|
||||
}
|
||||
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
|
||||
mhz /= 2;
|
||||
}
|
||||
if (mode->vscan > 1) {
|
||||
mhz /= mode->vscan;
|
||||
}
|
||||
return (float)mhz / 1000;
|
||||
}
|
||||
92
src/v4p/drm.h
Normal file
92
src/v4p/drm.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "ftext.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_RETRY, // Not used directly
|
||||
US_DRM_STATE_NO_DISPLAY,
|
||||
} us_drm_state_e;
|
||||
|
||||
typedef struct {
|
||||
u32 id;
|
||||
u32 handle;
|
||||
u8 *data;
|
||||
uz allocated;
|
||||
bool dumb_created;
|
||||
bool fb_added;
|
||||
} us_drm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
int conn_type;
|
||||
int conn_type_id;
|
||||
u32 crtc_id;
|
||||
drmModeModeInfo mode;
|
||||
u32 conn_id;
|
||||
us_drm_buffer_s *bufs;
|
||||
uint n_bufs;
|
||||
drmModeCrtc *saved_crtc;
|
||||
uint next_n_buf;
|
||||
bool has_vsync;
|
||||
|
||||
us_ftext_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;
|
||||
} us_drm_s;
|
||||
|
||||
|
||||
us_drm_s *us_drm_init(void);
|
||||
void us_drm_destroy(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);
|
||||
156
src/v4p/ftext.c
Normal file
156
src/v4p/ftext.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "ftext.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
#include "ftext_font.h"
|
||||
|
||||
|
||||
static void _ftext_draw_line(
|
||||
us_ftext_s *ft, const char *line,
|
||||
uint scale_x, uint scale_y,
|
||||
uint start_x, uint start_y);
|
||||
|
||||
|
||||
us_ftext_s *us_ftext_init(void) {
|
||||
us_ftext_s *ft;
|
||||
US_CALLOC(ft, 1);
|
||||
ft->frame = us_frame_init();
|
||||
return ft;
|
||||
}
|
||||
|
||||
void us_ftext_destroy(us_ftext_s *ft) {
|
||||
us_frame_destroy(ft->frame);
|
||||
US_DELETE(ft->text, free);
|
||||
free(ft);
|
||||
}
|
||||
|
||||
void us_ftext_draw(us_ftext_s *ft, const char *text, uint width, uint height) {
|
||||
us_frame_s *const frame = ft->frame;
|
||||
|
||||
if (
|
||||
frame->width == width && frame->height == height
|
||||
&& ft->text != NULL && !strcmp(ft->text, text)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
US_DELETE(ft->text, free);
|
||||
ft->text = us_strdup(text);
|
||||
strcpy(ft->text, text);
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->format = V4L2_PIX_FMT_RGB24;
|
||||
frame->stride = width * 3;
|
||||
frame->used = width * height * 3;
|
||||
us_frame_realloc_data(frame, frame->used);
|
||||
memset(frame->data, 0, frame->used);
|
||||
|
||||
if (frame->width == 0 || frame->height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *str = us_strdup(text);
|
||||
char *line;
|
||||
char *rest;
|
||||
|
||||
uint block_width = 0;
|
||||
uint block_height = 0;
|
||||
while ((line = strtok_r((block_height == 0 ? str : NULL), "\n", &rest)) != NULL) {
|
||||
block_width = US_MAX(strlen(line) * 8, block_width);
|
||||
block_height += 8;
|
||||
}
|
||||
if (block_width == 0 || block_height == 0) {
|
||||
goto empty;
|
||||
}
|
||||
uint scale_x = frame->width / block_width / 2;
|
||||
uint scale_y = frame->height / block_height / 3;
|
||||
if (scale_x < scale_y / 1.5) {
|
||||
scale_y = scale_x * 1.5;
|
||||
} else if (scale_y < scale_x * 1.5) {
|
||||
scale_x = scale_y / 1.5;
|
||||
}
|
||||
|
||||
strcpy(str, text);
|
||||
|
||||
const uint start_y = (frame->height >= (block_height * scale_y)
|
||||
? ((frame->height - (block_height * scale_y)) / 2)
|
||||
: 0);
|
||||
uint n_line = 0;
|
||||
while ((line = strtok_r((n_line == 0 ? str : NULL), "\n", &rest)) != NULL) {
|
||||
const uint line_width = strlen(line) * 8 * scale_x;
|
||||
const uint start_x = (frame->width >= line_width
|
||||
? ((frame->width - line_width) / 2)
|
||||
: 0);
|
||||
_ftext_draw_line(ft, line, scale_x, scale_y, start_x, start_y + n_line * 8 * scale_y);
|
||||
++n_line;
|
||||
}
|
||||
|
||||
empty:
|
||||
free(str);
|
||||
}
|
||||
|
||||
void _ftext_draw_line(
|
||||
us_ftext_s *ft, const char *line,
|
||||
uint scale_x, uint scale_y,
|
||||
uint start_x, uint start_y) {
|
||||
|
||||
us_frame_s *const frame = ft->frame;
|
||||
|
||||
const size_t len = strlen(line);
|
||||
|
||||
for (uint ch_y = 0; ch_y < 8 * scale_y; ++ch_y) {
|
||||
const uint canvas_y = start_y + ch_y;
|
||||
for (uint ch_x = 0; ch_x < 8 * len * scale_x; ++ch_x) {
|
||||
if ((start_x + ch_x) >= frame->width) {
|
||||
break;
|
||||
}
|
||||
const uint canvas_x = (start_x + ch_x) * 3;
|
||||
const uint offset = canvas_y * frame->stride + canvas_x;
|
||||
if (offset >= frame->used) {
|
||||
break;
|
||||
}
|
||||
|
||||
const u8 ch = US_MIN((u8)line[ch_x / 8 / scale_x], sizeof(US_FTEXT_FONT) / 8 - 1);
|
||||
const uint ch_byte = (ch_y / scale_y) % 8;
|
||||
const uint ch_bit = (ch_x / scale_x) % 8;
|
||||
const bool pix_on = !!(US_FTEXT_FONT[ch][ch_byte] & (1 << ch_bit));
|
||||
|
||||
u8 *const b = &frame->data[offset]; // XXX: Big endian for Raspberry
|
||||
u8 *const g = b + 1;
|
||||
u8 *const r = g + 1;
|
||||
|
||||
*r = pix_on * 0x51;
|
||||
*g = pix_on * 0x65;
|
||||
*b = pix_on * 0x65;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/v4p/ftext.h
Normal file
39
src/v4p/ftext.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *text;
|
||||
us_frame_s *frame;
|
||||
} us_ftext_s;
|
||||
|
||||
|
||||
us_ftext_s *us_ftext_init(void);
|
||||
void us_ftext_destroy(us_ftext_s *ft);
|
||||
|
||||
void us_ftext_draw(us_ftext_s *ft, const char *text, uint width, uint height);
|
||||
156
src/v4p/ftext_font.c
Normal file
156
src/v4p/ftext_font.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 "ftext_font.h"
|
||||
|
||||
|
||||
const u8 US_FTEXT_FONT[128][8] = {
|
||||
// https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
|
||||
{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
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
|
||||
{0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
|
||||
{0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
|
||||
{0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
|
||||
{0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
|
||||
{0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
|
||||
{0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
|
||||
{0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
|
||||
{0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
|
||||
{0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
|
||||
{0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
|
||||
{0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
|
||||
{0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
|
||||
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
|
||||
{0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
|
||||
{0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
|
||||
{0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
|
||||
{0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
|
||||
{0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
|
||||
{0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
|
||||
{0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
|
||||
{0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
|
||||
{0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
|
||||
{0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
|
||||
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
|
||||
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
|
||||
{0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
|
||||
{0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
|
||||
{0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
|
||||
{0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
|
||||
{0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
|
||||
{0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
|
||||
{0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
|
||||
{0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
|
||||
{0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
|
||||
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
|
||||
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
|
||||
{0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
|
||||
{0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
|
||||
{0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
|
||||
{0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
|
||||
{0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
|
||||
{0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
|
||||
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
|
||||
{0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
|
||||
{0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
|
||||
{0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
|
||||
{0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
|
||||
{0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
|
||||
{0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
|
||||
{0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
|
||||
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
|
||||
{0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
|
||||
{0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
|
||||
{0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
|
||||
{0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
|
||||
{0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
|
||||
{0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
|
||||
{0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
|
||||
{0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
|
||||
{0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
|
||||
{0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
|
||||
{0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
|
||||
{0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
|
||||
{0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
|
||||
{0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
|
||||
{0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
|
||||
{0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
|
||||
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
|
||||
{0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
|
||||
{0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
|
||||
{0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
|
||||
{0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
|
||||
{0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
|
||||
{0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
|
||||
{0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
|
||||
{0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
|
||||
{0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
|
||||
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
|
||||
{0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
|
||||
{0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
|
||||
{0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
|
||||
{0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
|
||||
{0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
|
||||
{0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
|
||||
{0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
|
||||
{0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
|
||||
{0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
|
||||
{0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
|
||||
{0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
|
||||
{0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
|
||||
{0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F
|
||||
};
|
||||
28
src/v4p/ftext_font.h
Normal file
28
src/v4p/ftext_font.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../libs/types.h"
|
||||
|
||||
|
||||
extern const u8 US_FTEXT_FONT[128][8];
|
||||
333
src/v4p/main.c
Normal file
333
src/v4p/main.c
Normal file
@@ -0,0 +1,333 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
#include "drm.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
_O_UNIX_FOLLOW = 'f',
|
||||
|
||||
_O_HELP = 'h',
|
||||
_O_VERSION = 'v',
|
||||
|
||||
_O_LOG_LEVEL = 10000,
|
||||
_O_PERF,
|
||||
_O_VERBOSE,
|
||||
_O_DEBUG,
|
||||
_O_FORCE_LOG_COLORS,
|
||||
_O_NO_LOG_COLORS,
|
||||
};
|
||||
|
||||
static const struct option _LONG_OPTS[] = {
|
||||
{"unix-follow", required_argument, NULL, _O_UNIX_FOLLOW},
|
||||
|
||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||
{"perf", no_argument, NULL, _O_PERF},
|
||||
{"verbose", no_argument, NULL, _O_VERBOSE},
|
||||
{"debug", no_argument, NULL, _O_DEBUG},
|
||||
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
|
||||
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
|
||||
|
||||
{"help", no_argument, NULL, _O_HELP},
|
||||
{"version", no_argument, NULL, _O_VERSION},
|
||||
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
|
||||
volatile atomic_bool _g_stop = false;
|
||||
atomic_bool _g_ustreamer_online = false;
|
||||
|
||||
|
||||
static void _signal_handler(int signum);
|
||||
static void _install_signal_handlers(void);
|
||||
|
||||
static void _main_loop();
|
||||
static void *_follower_thread(void *v_unix_follow);
|
||||
static void _slowdown(void);
|
||||
|
||||
static void _help(FILE *fp);
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
char *unix_follow = NULL;
|
||||
|
||||
# define OPT_SET(_dest, _value) { \
|
||||
_dest = _value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||
return 1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
char short_opts[128];
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_UNIX_FOLLOW: OPT_SET(unix_follow, optarg);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout); return 0;
|
||||
case _O_VERSION: puts(US_VERSION); return 0;
|
||||
|
||||
case 0: break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_NUMBER
|
||||
# undef OPT_SET
|
||||
|
||||
_install_signal_handlers();
|
||||
|
||||
pthread_t follower_tid;
|
||||
if (unix_follow != NULL) {
|
||||
US_THREAD_CREATE(follower_tid, _follower_thread, unix_follow);
|
||||
}
|
||||
_main_loop(unix_follow);
|
||||
if (unix_follow != NULL) {
|
||||
US_THREAD_JOIN(follower_tid);
|
||||
}
|
||||
|
||||
US_LOGGING_DESTROY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
char *const name = us_signum_to_string(signum);
|
||||
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
|
||||
free(name);
|
||||
atomic_store(&_g_stop, true);
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = _signal_handler;
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
static void _main_loop(void) {
|
||||
us_drm_s *drm = us_drm_init();
|
||||
drm->port = "HDMI-A-2";
|
||||
|
||||
us_device_s *dev = us_device_init();
|
||||
dev->path = "/dev/kvmd-video";
|
||||
dev->n_bufs = drm->n_bufs;
|
||||
dev->format = V4L2_PIX_FMT_RGB24;
|
||||
dev->dv_timings = true;
|
||||
dev->persistent = true;
|
||||
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
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->capturing) {
|
||||
goto close;
|
||||
} else {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (us_device_open(dev) < 0) {
|
||||
goto close;
|
||||
}
|
||||
if (us_device_switch_capturing(dev, true) < 0) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
if (atomic_load(&_g_ustreamer_online)) {
|
||||
if (us_drm_wait_for_vsync(drm) == 0) {
|
||||
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
|
||||
}
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (us_drm_wait_for_vsync(drm) < 0) {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool has_read;
|
||||
bool has_write;
|
||||
bool has_error;
|
||||
const int selected = us_device_select(dev, &has_read, &has_write, &has_error);
|
||||
|
||||
if (selected < 0) {
|
||||
if (errno != EINTR) {
|
||||
US_LOG_PERROR("Mainloop select() error");
|
||||
goto close;
|
||||
}
|
||||
} else if (selected == 0) { // Persistent timeout
|
||||
if (us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0) < 0) {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (has_read) {
|
||||
US_LOG_DEBUG("Frame is ready");
|
||||
us_hw_buffer_s *hw;
|
||||
const int buf_index = us_device_grab_buffer(dev, &hw);
|
||||
if (buf_index >= 0) {
|
||||
if (us_drm_expose(drm, US_DRM_EXPOSE_FRAME, &hw->raw, dev->run->hz) < 0) {
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
if (us_device_release_buffer(dev, hw) < 0) {
|
||||
goto close;
|
||||
}
|
||||
} else if (buf_index == -2) {
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
if (has_error) {
|
||||
US_LOG_INFO("Got V4L2 event");
|
||||
if (us_device_consume_event(dev) < 0) {
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close:
|
||||
us_device_switch_capturing(dev, false);
|
||||
us_device_close(dev);
|
||||
_slowdown();
|
||||
}
|
||||
|
||||
us_device_destroy(dev);
|
||||
us_drm_destroy(drm);
|
||||
}
|
||||
|
||||
static void *_follower_thread(void *v_unix_follow) {
|
||||
const char *path = v_unix_follow;
|
||||
assert(path != NULL);
|
||||
|
||||
US_THREAD_RENAME("follower");
|
||||
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
|
||||
struct sockaddr_un addr = {0};
|
||||
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));
|
||||
atomic_store(&_g_ustreamer_online, online);
|
||||
US_CLOSE_FD(fd, close); // cppcheck-suppress unreadVariable
|
||||
|
||||
usleep(200 * 1000);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _slowdown(void) {
|
||||
if (!atomic_load(&_g_stop)) {
|
||||
usleep(500 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void _help(FILE *fp) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer-V4P - Video passthrough for PiKVM V4 Plus");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
SAY(" ustreamer-v4p\n");
|
||||
SAY("Passthrough options:");
|
||||
SAY("════════════════════");
|
||||
SAY(" -f|--unix-follow <path> ──────── Pause the process if the specified socked exists.\n");
|
||||
SAY("Logging options:");
|
||||
SAY("════════════════");
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
SAY(" Enabling debugging messages can slow down the program.");
|
||||
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
||||
SAY(" Default: %d.\n", us_g_log_level);
|
||||
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
|
||||
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
|
||||
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
|
||||
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
|
||||
SAY("Help options:");
|
||||
SAY("═════════════");
|
||||
SAY(" -h|--help ─────── Print this text and exit.\n");
|
||||
SAY(" -v|--version ──── Print version and exit.\n");
|
||||
# undef SAY
|
||||
}
|
||||
Reference in New Issue
Block a user