Compare commits

...

45 Commits
v6.50 ... v6.59

Author SHA1 Message Date
Maxim Devaev
92878e22ab Bump version: 6.58 → 6.59 2026-05-31 07:23:12 +03:00
Maxim Devaev
4dc0f73e1b janus: ssrc per client 2026-05-31 07:11:44 +03:00
Maxim Devaev
9fe42b038a ignore python build 2026-05-31 05:33:55 +03:00
Maxim Devaev
3750a22091 janus: separate audio of mic 2026-05-31 05:33:42 +03:00
Maxim Devaev
ef59c52a83 Bump version: 6.57 → 6.58 2026-05-30 12:01:35 +03:00
Maxim Devaev
f296bb57e7 added python-installer 2026-05-30 11:58:34 +03:00
Maxim Devaev
f7166b7c41 Bump version: 6.56 → 6.57 2026-05-30 11:49:10 +03:00
Maxim Devaev
b35200345e Merge pull request #334 from RSATom/master
cpu/encoder: fixed luma plane size calculation
2026-05-30 07:53:40 +03:00
sergey radionov
5bd7c9c5ef cpu/encoder: fixed luma plane size calculation
for 4:2:0 pixel formats,
and removed logic related to not used 4:1:0 pixel formats.
2026-05-28 13:46:27 +07:00
Maxim Devaev
fc35b1cd87 Merge pull request #333 from intelfx/work/fix-makefile
python: Makefile: use `python -m installer`; do not install while building
2026-05-18 08:21:39 +03:00
Maxim Devaev
92d9828894 janus: lost static 2026-05-18 08:21:09 +03:00
Maxim Devaev
e7008c6086 janus: splitted _rtpv_process_nalu() 2026-05-18 08:15:52 +03:00
Ivan Shapovalov
d0672e15b9 python: Makefile: use python -m installer; do not install while building
When building a Python wheel using `python -m build --wheel ...`,
temporary build files (including extension .so) are placed in ./build/...
from where they can be retrieved without having to install the just-built
wheel. Do both and get rid of `pip` invocations.
2026-05-18 08:13:47 +03:00
Maxim Devaev
04aa4c166c Merge pull request #332 from intelfx/work/fix-janus-rtpv
janus: fix rtpv chunking
2026-05-18 07:47:54 +03:00
Ivan Shapovalov
0fbbc27d8c janus: rtpv: fix and rework {first,last}_of_frame determination
Fixes: 2dac4db ("janus: fixed first/last frame logic for sps/pps")
Fixes: f32e256 ("janus: refactoring")
2026-05-18 07:45:09 +03:00
Ivan Shapovalov
b02bb85d8b janus: rtpv: marked -> last_nalu 2026-05-18 07:44:49 +03:00
Ivan Shapovalov
80dce276ed janus: rtp: marked -> last_header 2026-05-18 07:44:49 +03:00
Maxim Devaev
e869536a11 janus: refactoring 2026-05-16 16:14:56 +03:00
Maxim Devaev
b0acf0fa4d janus: refactoring 2026-05-15 21:43:42 +03:00
Maxim Devaev
f32e2569d2 janus: refactoring 2026-05-15 21:15:50 +03:00
Maxim Devaev
6d63f8e5d2 janus: refactoring 2026-05-15 19:32:37 +03:00
Maxim Devaev
2dac4db9fc janus: fixed first/last frame logic for sps/pps 2026-05-15 01:07:59 +03:00
Maxim Devaev
23dd2f9e66 Bump version: 6.55 → 6.56 2026-04-24 17:46:09 +03:00
Maxim Devaev
fdfd4225d0 lint fix 2026-04-24 17:43:02 +03:00
Maxim Devaev
9424b655eb janus: workaround for broken PLIs on Chromium 147 2026-04-24 17:16:19 +03:00
Maxim Devaev
f94fe25845 janus: don't advertise nack/nack-pli/goog-remb for audio 2026-04-24 17:11:24 +03:00
Maxim Devaev
bdcbaae8b3 janus: handle FIR 2026-04-24 17:10:42 +03:00
Marek Marczykowski-Górecki
52a2326426 Increase default raw memsink size to support 4k video (#327)
With this change, it's possible to use ustreamer to handle 4k HDMI input
on RK3588-based boards (tested on Orange Pi 5+ and Radxa ROCK 5B+).
2026-03-12 02:13:42 +02:00
Sergey Radionov
fa15d9693f janus: Tailscale has 1280 MTU actually (#326) 2026-03-10 15:28:13 +02:00
Maxim Devaev
88460b72e1 Bump version: 6.54 → 6.55 2026-02-13 23:26:35 +02:00
Sergey Radionov
8c69c77481 janus: fixed compatibility with Tailscale MTU (#325) 2026-02-13 17:34:52 +02:00
Maxim Devaev
5331ae14aa Bump version: 6.53 → 6.54 2026-02-12 20:38:03 +02:00
Maxim Devaev
0127dcf018 janus: hotfix: reverted sps/pps logic for first/last packages 2026-02-12 20:36:00 +02:00
Maxim Devaev
aa58b1b002 Bump version: 6.52 → 6.53 2026-02-12 19:47:33 +02:00
Maxim Devaev
a05eab71a8 janus: reserve 50 bytes for RTP extensions 2026-02-12 19:34:39 +02:00
Maxim Devaev
e013356cf0 janus: renamed US_RTP_DATAGRAM_SIZE -> US_RTP_TOTAL_SIZE 2026-02-12 19:27:00 +02:00
Maxim Devaev
c730981827 janus: set first/last_of_frame only for non-sps/pps packets 2026-02-12 18:48:22 +02:00
Maxim Devaev
3bb1ed3ef3 openwrt package 2026-02-09 17:09:09 +02:00
Paul Donald
1cda22bfd2 spell fixes (#323) 2026-02-09 16:59:05 +02:00
Maxim Devaev
29ee20e864 Bump version: 6.51 → 6.52 2026-02-07 17:06:14 +02:00
Maxim Devaev
5e13f4cd58 using CC -dumpmachine instead of uname 2026-02-07 17:02:43 +02:00
Maxim Devaev
39c1916f61 Bump version: 6.50 → 6.51 2026-02-03 19:52:09 +02:00
Ivan Shapovalov
614e83771b ustreamer: options: NULL-terminate the copy of argv (#322)
According to N2176 of ISO/IEC 9899:2017 §5.1.2.2.1 ¶2:

> - argv[argc] shall be a null pointer.

Possibly fixes openwrt/packages#28472.
2026-02-03 19:41:40 +02:00
Maxim Devaev
15a9e28ac6 spelling fix 2026-02-03 10:59:26 +02:00
Maxim Devaev
a1ae02de5d janus: refactored sdp logic 2026-01-31 16:45:26 +02:00
30 changed files with 360 additions and 208 deletions

View File

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

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@
/python/dist/
/python/ustreamer.egg-info/
/python/root/
/python/build/
/janus/build/
/ustreamer
/ustreamer-*

View File

@@ -72,7 +72,7 @@ apps:
python:
$(MAKE) -C python
$(ECHO) ln -sf python/root/usr/lib/python*/site-packages/*.so .
$(ECHO) ln -sf python/build/lib.linux-*/*.so -t .
janus:

View File

@@ -35,13 +35,14 @@ If you're going to live-stream from your backyard webcam and need to control it,
# Installation
## Building
You need to download the µStreamer onto your system and build it from the sources.
You need to download the µStreamer onto your system and build it from the sources, or use a package:
* AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
* Fedora: https://src.fedoraproject.org/rpms/ustreamer.
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer.
* Arch Linux: https://aur.archlinux.org/packages/ustreamer
* Fedora: https://src.fedoraproject.org/rpms/ustreamer
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer
* Debian: https://packages.debian.org/sid/ustreamer
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
* OpenWRT: https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer
### Preconditions
You'll need ```make```, ```gcc```, ```pkg-config```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).

View File

@@ -19,7 +19,7 @@ _BUILD = build
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _LDFLAGS += -latomic
endif

View File

@@ -55,6 +55,9 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
US_CALLOC(client, 1);
client->gw = gw;
client->session = session;
client->video_ssrc = us_get_ssrc();
client->audio_ssrc = us_get_ssrc();
atomic_init(&client->transmit, false);
atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, false);
@@ -181,6 +184,8 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_acap))
) {
us_rtp_write_ssrc(&rtp, (rtp.video ? client->video_ssrc : client->audio_ssrc));
janus_plugin_rtp packet = {
.video = rtp.video,
.buffer = (char*)rtp.datagram,

View File

@@ -37,6 +37,9 @@
typedef struct {
janus_callbacks *gw;
janus_plugin_session *session;
u32 video_ssrc;
u32 audio_ssrc;
atomic_bool transmit;
atomic_bool transmit_acap;
atomic_bool transmit_aplay;

View File

@@ -68,8 +68,8 @@ us_config_s *us_config_init(const char *config_dir_path) {
US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required");
goto error;
}
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
}
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
goto ok;

View File

@@ -22,7 +22,6 @@
#include <stdatomic.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
@@ -56,6 +55,7 @@
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
#include "sdp.h"
#include "memsinkfd.h"
#include "config.h"
@@ -279,7 +279,7 @@ static void *_acap_thread(void *arg) {
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
goto close_acap;
}
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
uz size = US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE;
u8 data[size];
u64 pts;
const int result = us_acap_get_encoded(acap, data, &size, &pts);
@@ -443,9 +443,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL);
if (_g_config->aplay_dev_name != NULL) {
US_THREAD_CREATE(_g_aplay_tid, _aplay_thread, NULL);
}
}
if (_g_config->aplay_dev_name != NULL) {
US_THREAD_CREATE(_g_aplay_tid, _aplay_thread, NULL);
}
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
@@ -641,32 +641,22 @@ static struct janus_plugin_result *_plugin_handle_message(
}
{
char *sdp;
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_strdup(""));
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep);
free(audio_sdp);
free(video_sdp);
free(sdp);
_LOCK_ALL;
US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) {
char *const sdp = us_sdp_create(
client->video_ssrc,
client->audio_ssrc,
with_acap,
with_aplay);
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
PUSH_STATUS("started", NULL, offer_jsep);
json_decref(offer_jsep);
free(sdp);
break;
}
});
_UNLOCK_ALL;
}
{
@@ -689,11 +679,10 @@ static struct janus_plugin_result *_plugin_handle_message(
} else if (!strcmp(request_str, "features")) {
const char *const ice_url = getenv("JANUS_USTREAMER_WEB_ICE_URL");
const bool acap_avail = us_au_probe(_g_config->acap_dev_name);
json_t *const features = json_pack(
"{s:b, s:b, s:{s:s?}}",
"audio", acap_avail,
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)),
"audio", us_au_probe(_g_config->acap_dev_name),
"mic", us_au_probe(_g_config->aplay_dev_name),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
);
PUSH_STATUS("features", features, NULL);
@@ -737,11 +726,15 @@ static void _plugin_incoming_rtp(janus_plugin_session *session, janus_plugin_rtp
static void _plugin_incoming_rtcp(janus_plugin_session *session, janus_plugin_rtcp *packet) {
_IF_DISABLED({ return; });
if (session == NULL || packet == NULL || !packet->video) {
if (session == NULL || packet == NULL /*|| !packet->video*/) {
// FIXME: Since Chromium 147 we can't distinguish video/audio packets
// so we just disable !package->video condition for now.
return; // Accept only valid video
}
if (janus_rtcp_has_pli(packet->buffer, packet->length)) {
// US_JLOG_INFO("main", "Got video PLI");
if (
janus_rtcp_has_pli(packet->buffer, packet->length)
|| janus_rtcp_has_fir(packet->buffer, packet->length)
) {
atomic_store(&_g_key_required, true);
}
}

View File

@@ -44,22 +44,27 @@ void us_rtp_destroy(us_rtp_s *rtp) {
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) {
rtp->payload = payload;
rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
}
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
#define WRITE_BE_U32(x_offset, x_value) \
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header) {
u32 word0 = 0x80000000;
if (marked) {
if (last_header) {
word0 |= 1 << 23;
}
word0 |= (rtp->payload & 0x7F) << 16;
word0 |= rtp->seq;
++rtp->seq;
# 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);
# undef WRITE_BE_U32
WRITE_BE_U32(8, 0); // SSRC should be written separately for each client
}
void us_rtp_write_ssrc(us_rtp_s *rtp, u32 ssrc) {
WRITE_BE_U32(8, ssrc);
}
#undef WRITE_BE_U32

View File

@@ -23,12 +23,20 @@
#pragma once
#include "uslibs/types.h"
#include "uslibs/tools.h"
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
#define US_RTP_DATAGRAM_SIZE 1200
// Max RTP size for WebRTC is 1200 bytes:
// - https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
// But(!) Tailscale has 1280 MTU. So to fit it required to subtract:
// 1. possible RTP extensions (see sdp.c)
// 2. additional SRTP fields (>= 10 bytes)
// 3. UDP header (8 bytes)
// 4. IPv6 header (40 bytes)
// Finally it looks like below value should be good enought for all cases
#define US_RTP_TOTAL_SIZE 1150
#define US_RTP_HEADER_SIZE 12
#define US_RTP_PAYLOAD_SIZE (US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE)
#define US_RTP_PAYLOAD_SIZE (US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE)
#define US_RTP_H264_PAYLOAD 96
#define US_RTP_OPUS_PAYLOAD 111
@@ -40,10 +48,9 @@
typedef struct {
uint payload;
bool video;
u32 ssrc;
u16 seq;
u8 datagram[US_RTP_DATAGRAM_SIZE];
u8 datagram[US_RTP_TOTAL_SIZE];
uz used;
bool first_of_frame;
@@ -55,8 +62,13 @@ typedef struct {
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
static inline u32 us_get_ssrc(void) {
return us_triple_u32(us_get_now_monotonic_u64());
}
us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header);
void us_rtp_write_ssrc(us_rtp_s *rtp, u32 ssrc);

View File

@@ -23,11 +23,12 @@
#include "rtpa.h"
#include <stdlib.h>
#include <inttypes.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.h"
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
us_rtpa_s *rtpa;
@@ -43,32 +44,8 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
free(rtpa);
}
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
const uint pl = rtpa->rtp->payload;
char *sdp;
US_ASPRINTF(sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/%u/%u" RN
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:a" RN
"a=msid:audio a" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=%s" RN,
pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl, pl, pl,
rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly")
);
return sdp;
}
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) {
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_SIZE) {
us_rtp_write_header(rtpa->rtp, pts, false);
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;

View File

@@ -36,5 +36,4 @@ typedef struct {
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, bool mic);
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

View File

@@ -26,8 +26,6 @@
#include "rtpv.h"
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <linux/videodev2.h>
@@ -35,10 +33,15 @@
#include "uslibs/tools.h"
#include "uslibs/frame.h"
#include "rtp.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);
static bool _is_nalu_small(uz size);
static void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
static void _rtpv_process_nalu_small(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
static void _rtpv_process_nalu_big(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu);
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback) {
@@ -55,89 +58,112 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
free(rtpv);
}
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
// 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
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F;packetization-mode=1" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:v" RN
"a=msid:video v" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1/sendonly urn:3gpp:video-orientation" RN
"a=extmap:2/sendonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN,
pl, pl, pl, pl, pl, pl,
rtpv->rtp->ssrc
);
return sdp;
}
#define _PRE 3 // Annex B prefix length
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
// There is a complicated logic here but everything works as it should:
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
US_A(frame->format == V4L2_PIX_FMT_H264);
assert(frame->format == V4L2_PIX_FMT_H264);
if (frame->used <= _PRE) {
return;
}
rtpv->rtp->first_of_frame = true;
rtpv->rtp->last_of_frame = false;
rtpv->rtp->zero_playout_delay = zero_playout_delay;
rtpv->rtp->grab_ntp_ts = us_get_now_ntp() - us_ld_to_ntp(us_get_now_monotonic() - frame->grab_begin_ts);
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 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;
// Bytestream can be like:
// [00-]00-00-01 NALU [00-]00-00-01 NALU
// ... with optional 00 prefix for _PRE.
// We need to iterate by NALUs only and throw away separators.
if (last_offset >= 0) {
const u8 *const data = frame->data + last_offset + _PRE;
uz size = offset - last_offset - _PRE;
if (data[size - 1] == 0) { // Check for extra 00
const sz offset_to_first = _find_annexb(frame->data, frame->used);
if (offset_to_first < 0) {
return;
}
uz begin = offset_to_first + _PRE;
bool first_nalu = true;
while (begin < frame->used) { // Find and iterate by NALUs
const u8 *const data = frame->data + begin;
const sz offset_to_next = _find_annexb(data, frame->used - begin);
if (offset_to_next >= 0) { // Process NALUs between prefixes
uz size = offset_to_next;
if (size > 1 && data[size - 1] == 0) { // Check for extra trailing zero
--size;
}
_rtpv_process_nalu(rtpv, data, size, pts, false);
if (size > 1) { // Skip too short NALUs
_rtpv_process_nalu(rtpv, data, size, pts, first_nalu, false);
first_nalu = false;
}
begin += offset_to_next + _PRE;
} else { // Process the tail
const uz size = frame->used - begin;
if (size > 1) {
_rtpv_process_nalu(rtpv, data, size, pts, first_nalu, true);
first_nalu = false;
}
break;
}
last_offset = offset;
}
if (last_offset >= 0) {
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 u8 *data, uz size, u32 pts, bool marked) {
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (uz i = 0; i <= size - _PRE; ++i) {
if (
data[i] == 0
&& data[i + 1] == 0
&& data[i + 2] == 1
) {
return i;
}
}
}
return -1;
}
#undef _PRE
static bool _is_nalu_small(uz size) {
return (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_SIZE);
}
static void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
if (_is_nalu_small(size)) {
_rtpv_process_nalu_small(rtpv, data, size, pts, first_nalu, last_nalu);
} else {
_rtpv_process_nalu_big(rtpv, data, size, pts, first_nalu, last_nalu);
}
}
static void _rtpv_process_nalu_small(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
US_A(_is_nalu_small(size));
us_rtp_s *rtp = rtpv->rtp;
u8 *dg = rtp->datagram;
us_rtp_write_header(rtp, pts, last_nalu);
memcpy(dg + US_RTP_HEADER_SIZE, data, size);
rtp->used = size + US_RTP_HEADER_SIZE;
rtp->first_of_frame = first_nalu;
rtp->last_of_frame = last_nalu;
rtpv->callback(rtp);
}
static void _rtpv_process_nalu_big(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool first_nalu, bool last_nalu) {
US_A(size > 1);
US_A(!_is_nalu_small(size));
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);
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
rtpv->rtp->last_of_frame = true;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
return;
}
us_rtp_s *rtp = rtpv->rtp;
u8 *dg = rtp->datagram;
const uz fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
@@ -146,13 +172,13 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
bool first = true;
while (remaining > 0) {
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
sz frag_size = US_RTP_TOTAL_SIZE - fu_overhead;
const bool last = (remaining <= frag_size);
if (last) {
frag_size = remaining;
}
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
us_rtp_write_header(rtp, pts, (last_nalu && last));
dg[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
@@ -166,27 +192,13 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
dg[US_RTP_HEADER_SIZE + 1] = fu;
memcpy(dg + fu_overhead, src, frag_size);
rtpv->rtp->used = fu_overhead + frag_size;
rtpv->rtp->last_of_frame = last;
rtpv->callback(rtpv->rtp);
rtpv->rtp->first_of_frame = false;
rtp->used = fu_overhead + frag_size;
rtp->first_of_frame = (first_nalu && first);
rtp->last_of_frame = (last_nalu && last);
rtpv->callback(rtp);
src += frag_size;
remaining -= frag_size;
first = false;
}
}
static sz _find_annexb(const u8 *data, uz size) {
// Parses buffer for 00 00 01 start codes
if (size >= _PRE) {
for (uz index = 0; index <= size - _PRE; ++index) {
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
return index;
}
}
}
return -1;
}
#undef _PRE

View File

@@ -37,5 +37,4 @@ typedef struct {
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
void us_rtpv_destroy(us_rtpv_s *rtpv);
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);

118
janus/src/sdp.c Normal file
View File

@@ -0,0 +1,118 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "sdp.h"
#include <inttypes.h>
#include <janus/plugins/plugin.h>
#include "uslibs/types.h"
#include "uslibs/tools.h"
#include "rtp.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay) {
char *video_sdp;
{
// https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = US_RTP_H264_PAYLOAD;
US_ASPRINTF(
video_sdp,
"m=video 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u H264/90000" RN
"a=fmtp:%u profile-level-id=42E01F;packetization-mode=1" RN
"a=rtcp-fb:%u ccm fir" RN
"a=rtcp-fb:%u nack" RN
"a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN
"a=mid:v" RN
"a=msid:video v" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=extmap:1/sendonly urn:3gpp:video-orientation" RN
"a=extmap:2/sendonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN,
pl, pl, pl, pl, pl, pl, pl,
video_ssrc);
}
char *audio_sdp;
if (acap || aplay) {
const uint pl = US_RTP_OPUS_PAYLOAD;
char *dir = "";
if (acap && aplay) {
dir = "sendrecv";
} else if (acap && !aplay) {
dir = "sendonly";
} else if (!acap && aplay) {
dir = "recvonly";
}
US_ASPRINTF(
audio_sdp,
"m=audio 1 RTP/SAVPF %u" RN
"c=IN IP4 0.0.0.0" RN
"a=rtpmap:%u OPUS/%u/%u" RN
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
"a=mid:a" RN
"a=msid:audio a" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=%s" RN,
pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl,
audio_ssrc,
dir);
} else {
audio_sdp = us_strdup("");
}
char *sdp;
US_ASPRINTF(sdp,
"v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
"s=PiKVM uStreamer" RN
"t=0 0" RN
"%s%s",
us_get_now_id() >> 1,
# if JANUS_PLUGIN_API_VERSION >= 100
// Place video SDP before audio SDP so that the video and audio streams
// have predictable indices, even if audio is not available.
// See also client.c.
video_sdp, audio_sdp
# else
// For versions of Janus prior to 1.x, place the audio SDP first.
audio_sdp, video_sdp
# endif
);
free(audio_sdp);
free(video_sdp);
return sdp;
}

28
janus/src/sdp.h Normal file
View File

@@ -0,0 +1,28 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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 "uslibs/types.h"
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay);

View File

@@ -37,7 +37,7 @@ deps =
[testenv:mypy]
allowlist_externals = bash
commands = bash -c 'mypy --config-file=linters/mypy.ini tools/*.py python/*.py'
commands = bash -c 'mypy --config-file=linters/mypy.ini --cache-dir=/dev/null tools/*.py python/*.py'
deps =
mypy

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.50" "November 2020"
.TH USTREAMER 1 "version 6.59" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -23,7 +23,7 @@ For example, the recommended way of running µStreamer with TC358743-based captu
.RS
\fB\-\-format=uyvy \e\fR # Device input format
.nf
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M interface
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
@@ -66,7 +66,7 @@ Available: MMAP, USERPTR; default: MMAP.
Desired FPS. Default: maximum possible.
.TP
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
Drop frames smaller than this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP
.BR \-T ", " \-\-allow\-truncated\-frames
Allows to handle truncated frames. Useful if the device produces incorrect but still acceptable frames. Default: disabled.
@@ -78,7 +78,7 @@ Suppress repetitive signal source errors. Default: disabled.
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
.TP
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
The number of buffers to receive data from the device. Each buffer may be processed using an independent thread.
Default: 2 (the number of CPU cores (but not more than 4) + 1).
.TP
.BR \-w\ \fIN ", " \-\-workers\ \fIN

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=6.50
pkgver=6.59
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
@@ -19,7 +19,7 @@ _options="WITH_GPIO=1 WITH_SYSTEMD=1"
if [ -e /usr/bin/python3 ]; then
_options="$_options WITH_PYTHON=1"
depends+=("python>=3.14" "python<3.15")
makedepends+=(python-setuptools python-pip python-build python-wheel)
makedepends+=(python-setuptools python-pip python-build python-wheel python-installer)
fi
if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway alsa-lib opus)

View File

@@ -2,11 +2,14 @@
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# This package is just an example. For OpenWRT it is recommended to use upstream package:
# - https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
#
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=6.50
PKG_VERSION:=6.59
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

View File

@@ -9,13 +9,12 @@ all: root
root: $(shell find src -type f,l) setup.py
$(info == PY_BUILD ustreamer-*.so)
rm -rf root
$(ECHO) $(PY) -m build --skip-dependency-check --no-isolation
$(ECHO) $(PY) -m pip install dist/*.whl --ignore-installed --root=./root
$(ECHO) $(PY) -m build --wheel --skip-dependency-check --no-isolation
install:
$(PY) -m pip install dist/*.whl --ignore-installed --prefix=$(PREFIX) --root=$(if $(R_DESTDIR),$(R_DESTDIR),/)
$(PY) -m installer --prefix=$(PREFIX) $(if $(R_DESTDIR),--destdir=$(R_DESTDIR),) dist/*.whl
clean:
rm -rf root dist ustreamer.egg-info
rm -rf root dist build ustreamer.egg-info

View File

@@ -34,7 +34,7 @@ def main() -> None:
flags = _find_flags()
setup(
name="ustreamer",
version="6.50",
version="6.59",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

@@ -46,7 +46,7 @@ _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
# =====
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -latomic
override _DUMP_LDFLAGS += -latomic
override _V4P_LDFLAGS += -latomic
@@ -78,7 +78,7 @@ endif
ifneq ($(MK_WITH_SETPROCTITLE),)
override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE
ifeq ($(shell uname -s | tr A-Z a-z),linux)
ifneq ($(findstring linux,$(shell $(CC) -dumpmachine)),)
override _USTR_LDFLAGS += -lbsd
endif
endif

View File

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

View File

@@ -61,7 +61,7 @@ uz us_memsink_calculate_size(const char *obj) {
} else if (!strcasecmp(ptr, "h264")) {
return 2 * 1024 * 1024;
} else if (!strcasecmp(ptr, "raw")) {
return 1920 * 1200 * 3; // RGB
return 3840 * 2160 * 3; // RGB
}
}
return 0;

View File

@@ -27,7 +27,7 @@
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
#define US_MEMSINK_VERSION ((u32)7)
#define US_MEMSINK_VERSION ((u32)8)
typedef struct {

View File

@@ -216,15 +216,11 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
US_CALLOC(line_buf, frame->width * 3);
const uint padding = us_frame_get_padding(frame);
const uint image_size = frame->width * frame->height;
const uint chroma_array_size = (frame->used - image_size) / 2;
const uint chroma_matrix_order = (image_size / chroma_array_size) == 16 ? 4 : 2;
const uint luma_array_size = (frame->width + padding) * frame->height;
const uint chroma_array_size = (frame->used - luma_array_size) >> 1;
const u8 *data = frame->data;
const u8 *chroma1_data = frame->data + image_size;
const u8 *chroma2_data = frame->data + image_size + chroma_array_size;
//US_LOG_DEBUG("Planar data: Image Size %u, Chroma Array Size %u, Chroma Matrix Order %u",
// image_size, chroma_array_size, chroma_matrix_order);
const u8 *chroma1_data = frame->data + luma_array_size;
const u8 *chroma2_data = frame->data + luma_array_size + chroma_array_size;
while (jpeg->next_scanline < frame->height) {
u8 *ptr = line_buf;
@@ -234,7 +230,7 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
u8 y = data[x];
u8 u;
u8 v;
uint chroma_position = x / chroma_matrix_order;
uint chroma_position = x >> 1;
switch (frame->format) {
case V4L2_PIX_FMT_YUV420:
@@ -257,9 +253,9 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
data += frame->width + padding;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % chroma_matrix_order == 0) {
chroma1_data += (frame->width + padding) / chroma_matrix_order;
chroma2_data += (frame->width + padding) / chroma_matrix_order;
if (jpeg->next_scanline > 0 && jpeg->next_scanline % 2 == 0) {
chroma1_data += (frame->width + padding) >> 1;
chroma2_data += (frame->width + padding) >> 1;
}
JSAMPROW scanlines[1] = {line_buf};

View File

@@ -300,10 +300,11 @@ us_options_s *us_options_init(uint argc, char *argv[]) {
opts->argc = argc;
opts->argv = argv;
US_CALLOC(opts->argv_copy, argc);
US_CALLOC(opts->argv_copy, argc + 1);
for (uint i = 0; i < argc; ++i) {
opts->argv_copy[i] = us_strdup(argv[i]);
}
opts->argv_copy[argc] = NULL;
return opts;
}
@@ -704,7 +705,7 @@ static void _help(
SAY(" Changing of this parameter may increase the performance. Or not.");
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller than this limit. Useful if the device");
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", cap->min_frame_size);
SAY(" -T|--allow-truncated-frames ───────── Allows to handle truncated frames. Useful if the device");
SAY(" produces incorrect but still acceptable frames. Default: disabled.\n");
@@ -712,7 +713,7 @@ static void _help(
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
SAY(" to automatic resolution change. Default: disabled.\n");
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
SAY(" Each buffer may processed using an independent thread.");
SAY(" Each buffer may be processed using an independent thread.");
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", cap->n_bufs);
SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.");
SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers);