Compare commits

...

23 Commits
v5.44 ... v5.50

Author SHA1 Message Date
Maxim Devaev
41330940c6 Bump version: 5.49 → 5.50 2024-02-16 17:15:26 +02:00
Maxim Devaev
46e630d2f6 using (void) instead of UNUSED 2024-02-16 01:51:36 +02:00
Maxim Devaev
63d87f0526 add instance_id to stream_client cookie name 2024-02-15 15:52:03 +02:00
Maxim Devaev
b578e9897e notes about libcamerify 2024-02-03 20:14:19 +02:00
Maxim Devaev
b2ebcf99c8 Bump version: 5.48 → 5.49 2024-02-02 22:53:48 +02:00
Maxim Devaev
6a6b910869 refactoring 2024-02-02 22:41:45 +02:00
Maxim Devaev
4e8acf371f Issue #252: YVYU support 2024-02-02 22:41:45 +02:00
Thomer Gil
c4cb8288c7 Fix it's vs its typo (#255)
Co-authored-by: Thomer Gil <thomer@thomer.com>
2024-01-30 18:28:32 +02:00
Maxim Devaev
2dddb879bc Bump version: 5.47 → 5.48 2024-01-09 00:12:23 +02:00
Maxim Devaev
4d92dc662c removed some checks in gpio 2024-01-09 00:11:11 +02:00
Maxim Devaev
3cb649d97c Bump version: 5.46 → 5.47 2024-01-07 04:48:07 +02:00
Maxim Devaev
d9b5f2b03d Issue #255: libgpiod 2.0 API supported
Thanks for @jpalus for #249 with compatibility code
2024-01-07 04:36:53 +02:00
Jan Palus
2997906d98 add option to make verbose builds (#251)
if V=1 is passed to make echo invoked command verbosely
2024-01-02 02:27:12 +02:00
Jan Palus
bcd3deaa13 remove remainings of rpi vc paths (#250)
accidently left after Makefile split and subsequently switching to v4l2
based encoding
2024-01-02 02:24:37 +02:00
Maxim Devaev
f8ed7d7b3b Bump version: 5.45 → 5.46 2023-12-14 13:29:50 +02:00
Maxim Devaev
622f5cf1eb janus plugin: increased video queue 2023-12-14 12:41:29 +02:00
Maxim Devaev
81756811f3 update for bookworm 2023-11-15 14:59:34 +02:00
Maxim Devaev
bd403593a0 using libjpeg62-turbo on raspbian 2023-11-15 14:52:11 +02:00
Jason Huggins
fc3e0232e1 '-e' should be '--encoder' (#241) 2023-11-03 17:39:18 +02:00
Maxim Devaev
89fd83bfc1 added info for v2 camera 2023-10-28 11:05:55 +03:00
Maxim Devaev
83a77ea898 doc about youtube streaming 2023-10-24 20:18:07 +03:00
Maxim Devaev
33fdf9bf43 Bump version: 5.44 → 5.45 2023-10-24 14:14:00 +03:00
Ed Maste
6bd4ef59c0 avoid duplicate increment of slc (#239)
Clang reported "warning: variable 'slc' is incremented both in the loop
header and in the loop body".
2023-10-24 05:11:36 +03:00
25 changed files with 269 additions and 116 deletions

View File

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

View File

@@ -9,9 +9,6 @@ PY ?= python3
CFLAGS ?= -O3
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
export
_LINTERS_IMAGE ?= ustreamer-linters
@@ -22,6 +19,9 @@ define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifeq ($(V),)
ECHO = @
endif
# =====
all:
@@ -36,18 +36,18 @@ endif
apps:
$(MAKE) -C src
@ ln -sf src/ustreamer.bin ustreamer
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
$(ECHO) ln -sf src/ustreamer.bin ustreamer
$(ECHO) ln -sf src/ustreamer-dump.bin ustreamer-dump
python:
$(MAKE) -C python
@ ln -sf python/build/lib.*/*.so .
$(ECHO) ln -sf python/build/lib.*/*.so .
janus:
$(MAKE) -C janus
@ ln -sf janus/*.so .
$(ECHO) ln -sf janus/*.so .
install: all

View File

@@ -47,15 +47,13 @@ You need to download the µStreamer onto your system and build it from the sourc
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
* Raspbian: `sudo apt install libevent-dev libjpeg9-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Raspberry OS Bullseye: `sudo apt install libevent-dev libjpeg62-turbo libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
* Raspberry OS Bookworm: same as previous but replace `libjpeg62-turbo` to `libjpeg62-turbo-dev`.
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
> **Note**
> Raspian: In case your version of Raspian is too old for there to be a libjpeg9 package, use `libjpeg8-dev` instead: `E: Package 'libjpeg9-dev' has no installation candidate`.
### Make
The most convenient process is to clone the µStreamer Git repository onto your system. If you don't have Git installed and don't want to install it either, you can download and unzip the sources from GitHub using `wget https://github.com/pikvm/ustreamer/archive/refs/heads/master.zip`.
@@ -153,12 +151,13 @@ 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 :: -e m2m-image
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
```
For v2 camera you can use the same trick with `libcamerify` but enable legacy camera mode in `raspi-config`.
Example usage for the Raspberry Pi v1 camera:
```
@@ -181,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 {

97
docs/youtube.md Normal file
View File

@@ -0,0 +1,97 @@
## Streaming to third party services
This method provides the ability of streaming a USB Webcam and include audio to large audiences.
It uses to two machines. One is a Raspberry Pi and the other a more capable machine to performance
the encoding of the video and audio that is streamed to the third party service such as YouTube.
Another benefit of using a browser (http stream) is the video can have overlays add in the custom ustreamer webpage.
For example a cron process that retrieves weather information and updates a file to include on the page, announcements,
or other creative ideas. The audio stream can also be something other than the webcam mic (music, voice files, etc.)
and easily changed on the second machine setup. In the following example filtering is applied in ffmpeg to
improve the sound of the webcam mic making vocals clearer and more intelligible.
* Machine 1:
* USB webcam on the machine (Pi for example) running ustreamer (video) and VLC (audio). Remember to make any needed firewall changes if machine 2 is on a separate network so it can reach the ports for the video and audio.
* To stream audio from the Pi.
```
/usr/bin/vlc -I dummy -vvv alsa://hw:2,0 --sout #transcode{acodec=mp3,ab=128}:standard{access=http,mux=ts,dst=:[PickAPort}
```
* Machine 2:
* On a more capable box run the video stream in a browser using ffmpeg to combine the video (browser) and audio and stream to YouTube or other services. In this example a VM with two virtual monitors running the browser full screen one of the monitors is used.
Script to stream the combination to YouTube:
```bash
#!/bin/bash
KEY=$1
echo
echo Cleanup -------------------------------------------------
source live-yt.key
killall -9 ffmpeg
killall -9 chromium
sleep 3
echo Setup General--------------------------------------------
cd /home/[USER]
rm -f nohup.out
export DISPLAY=:0.0
export $(dbus-launch)
echo Setup Chromium-------------------------------------------
CHROMIUM_TEMP=/home/{USER]/tmp/chromium
rm -rf $CHROMIUM_TEMP.bak
mv $CHROMIUM_TEMP $CHROMIUM_TEMP.bak
mkdir -p $CHROMIUM_TEMP
echo Start Chromium ------------------------------------------
nohup /usr/lib/chromium/chromium \
--new-window "http://[ustreamerURL]" \
--start-fullscreen \
--disable \
--disable-translate \
--disable-infobars \
--disable-suggestions-service \
--disable-save-password-bubble \
--disable-new-tab-first-run \
--disable-session-crashed-bubble \
--disable-bundled-ppapi-flash \
--disable-gpu \
--enable-javascript \
--enable-user-scripts \
--disk-cache-dir=$CHROMIUM_TEMP/cache/ustreamer/ \
--user-data-dir=$CHROMIUM_TEMP/user_data/ustreamer/ \
--window-position=1440,12 \
>/dev/null 2>&1 &
sleep 5
echo Start FFMpeg---------------------------------------------
nohup /usr/bin/ffmpeg \
-loglevel level+warning \
-thread_queue_size 512 \
-framerate 30 \
-f x11grab \
-s 1920x1080 \
-probesize 42M \
-i :0.0+1024,0 \
-i http://[VLCaudioURL] \
-filter:a "volume=10, highpass=f=300, lowpass=f=2800" \
-c:v libx264 \
-pix_fmt yuv420p \
-g 60 \
-b:v 2500k \
-c:a libmp3lame \
-ar 44100 \
-b:a 32k \
-preset ultrafast \
-maxrate 5000k \
-bufsize 2500k \
-preset ultrafast \
-flvflags no_duration_filesize \
-f flv "rtmp://a.rtmp.youtube.com/live2/$KEY" \
>/home/{USER]/ff-audio.log 2>&1 &
echo Done ----------------------------------------------------
echo
```
*PS: Recipe by David Klippel*

View File

@@ -31,13 +31,13 @@ endif
# =====
$(_PLUGIN): $(_SRCS:%.c=$(_BUILD)/%.o)
$(info == SO $@)
@ $(CC) $^ -o $@ $(_LDFLAGS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)

View File

@@ -38,7 +38,7 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
atomic_init(&client->stop, false);
client->video_queue = us_queue_init(1024);
client->video_queue = us_queue_init(2048);
US_THREAD_CREATE(client->video_tid, _video_thread, client);
client->audio_queue = us_queue_init(64);

View File

@@ -99,7 +99,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,7 +118,9 @@ 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);
@@ -178,7 +182,9 @@ static void *_video_sink_thread(UNUSED void *arg) {
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);
@@ -344,7 +350,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;
@@ -503,7 +510,9 @@ static struct janus_plugin_result *_plugin_handle_message(
# 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);

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer-dump.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER-DUMP 1 "version 5.44" "January 2021"
.TH USTREAMER-DUMP 1 "version 5.50" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 5.44" "November 2020"
.TH USTREAMER 1 "version 5.50" "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.

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ PY ?= python3
# =====
all:
$(info == PY_BUILD ustreamer-*.so)
@ $(PY) setup.py build
$(ECHO) $(PY) setup.py build
install:

View File

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

View File

@@ -42,7 +42,7 @@ endef
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
@@ -86,18 +86,18 @@ install-strip: install
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
$(ECHO) mkdir -p $(dir $@) || true
$(ECHO) $(CC) $< -o $@ $(_CFLAGS)
clean:

View File

@@ -23,7 +23,7 @@
#pragma once
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 44
#define US_VERSION_MINOR 50
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

View File

@@ -73,6 +73,7 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
unsigned 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:

View File

@@ -57,7 +57,6 @@
#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)

View File

@@ -38,6 +38,7 @@ static const struct {
const unsigned format; // cppcheck-suppress unusedStructMember
} _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"YVYU", V4L2_PIX_FMT_YVYU},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
{"RGB24", V4L2_PIX_FMT_RGB24},

View File

@@ -61,7 +61,7 @@
#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"

View File

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

View File

@@ -34,24 +34,26 @@ us_gpio_s us_g_gpio = {
.line = NULL, \
.state = false \
}
.prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"),
.has_http_clients = MAKE_OUTPUT("has-http-clients"),
# undef MAKE_OUTPUT
// mutex uninitialized
.chip = NULL
# ifndef HAVE_GPIOD2
.chip = NULL,
# endif
.initialized = false,
};
static void _gpio_output_init(us_gpio_output_s *output);
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip);
static void _gpio_output_destroy(us_gpio_output_s *output);
void us_gpio_init(void) {
# ifndef HAVE_GPIOD2
assert(us_g_gpio.chip == NULL);
# endif
if (
us_g_gpio.prog_running.pin >= 0
|| us_g_gpio.stream_online.pin >= 0
@@ -59,10 +61,17 @@ void us_gpio_init(void) {
) {
US_MUTEX_INIT(us_g_gpio.mutex);
US_LOG_INFO("GPIO: Using chip device: %s", us_g_gpio.path);
if ((us_g_gpio.chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
_gpio_output_init(&us_g_gpio.prog_running);
_gpio_output_init(&us_g_gpio.stream_online);
_gpio_output_init(&us_g_gpio.has_http_clients);
struct gpiod_chip *chip;
if ((chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
_gpio_output_init(&us_g_gpio.prog_running, chip);
_gpio_output_init(&us_g_gpio.stream_online, chip);
_gpio_output_init(&us_g_gpio.has_http_clients, chip);
# ifdef HAVE_GPIOD2
gpiod_chip_close(chip);
# else
us_g_gpio.chip = chip;
# endif
us_g_gpio.initialized = true;
} else {
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_g_gpio.path);
}
@@ -73,23 +82,32 @@ void us_gpio_destroy(void) {
_gpio_output_destroy(&us_g_gpio.prog_running);
_gpio_output_destroy(&us_g_gpio.stream_online);
_gpio_output_destroy(&us_g_gpio.has_http_clients);
if (us_g_gpio.chip != NULL) {
if (us_g_gpio.initialized) {
# ifndef HAVE_GPIOD2
gpiod_chip_close(us_g_gpio.chip);
us_g_gpio.chip = NULL;
# endif
US_MUTEX_DESTROY(us_g_gpio.mutex);
us_g_gpio.initialized = false;
}
}
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
int retval = 0;
# ifndef HAVE_GPIOD2
assert(us_g_gpio.chip != NULL);
# endif
assert(output->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance
US_MUTEX_LOCK(us_g_gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
# ifdef HAVE_GPIOD2
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) {
# else
if (gpiod_line_set_value(output->line, (int)state) < 0) {
# endif
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \
_gpio_output_destroy(output);
retval = -1;
}
@@ -98,14 +116,42 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
return retval;
}
static void _gpio_output_init(us_gpio_output_s *output) {
assert(us_g_gpio.chip != NULL);
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) {
assert(output->line == NULL);
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(us_g_gpio.chip, output->pin)) != NULL) {
# ifdef HAVE_GPIOD2
struct gpiod_line_settings *line_settings;
assert(line_settings = gpiod_line_settings_new());
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
assert(!gpiod_line_settings_set_output_value(line_settings, false));
struct gpiod_line_config *line_config;
assert(line_config = gpiod_line_config_new());
const unsigned offset = output->pin;
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
struct gpiod_request_config *request_config;
assert(request_config = gpiod_request_config_new());
gpiod_request_config_set_consumer(request_config, output->consumer);
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
}
gpiod_request_config_free(request_config);
gpiod_line_config_free(line_config);
gpiod_line_settings_free(line_settings);
if (output->line == NULL) {
_gpio_output_destroy(output);
}
# else
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output);
@@ -113,12 +159,17 @@ static void _gpio_output_init(us_gpio_output_s *output) {
} else {
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
}
# endif
}
}
static void _gpio_output_destroy(us_gpio_output_s *output) {
if (output->line != NULL) {
# ifdef HAVE_GPIOD2
gpiod_line_request_release(output->line);
# else
gpiod_line_release(output->line);
# endif
output->line = NULL;
}
if (output->consumer != NULL) {

View File

@@ -36,11 +36,15 @@
typedef struct {
int pin;
const char *role;
char *consumer;
struct gpiod_line *line;
bool state;
int pin;
const char *role;
char *consumer;
# ifdef HAVE_GPIOD2
struct gpiod_line_request *line;
# else
struct gpiod_line *line;
# endif
bool state;
} us_gpio_output_s;
typedef struct {
@@ -52,7 +56,11 @@ typedef struct {
us_gpio_output_s has_http_clients;
pthread_mutex_t mutex;
# ifndef HAVE_GPIOD2
struct gpiod_chip *chip;
# endif
bool initialized;
} us_gpio_s;

View File

@@ -667,10 +667,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,7 +747,10 @@ 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;
@@ -823,7 +828,10 @@ 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) {
static void _http_request_watcher(int fd, short what, void *v_server) {
(void)fd;
(void)what;
us_server_s *server = (us_server_s *)v_server;
const long double now = us_get_now_monotonic();
@@ -837,7 +845,10 @@ static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_serv
}
}
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;

View File

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

View File

@@ -127,7 +127,6 @@ void us_stream_loop(us_stream_s *stream) {
unsigned slc = 0;
for (; slc < 10 && !atomic_load(&_RUN(stop)) && !us_stream_has_clients(stream); ++slc) {
usleep(100000);
++slc;
}
h264_force_key = (slc == 10);
}