Compare commits

...

4 Commits
v6.58 ... 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
17 changed files with 75 additions and 42 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@@ -55,6 +55,9 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->gw = gw; client->gw = gw;
client->session = session; client->session = session;
client->video_ssrc = us_get_ssrc();
client->audio_ssrc = us_get_ssrc();
atomic_init(&client->transmit, false); atomic_init(&client->transmit, false);
atomic_init(&client->transmit_acap, false); atomic_init(&client->transmit_acap, false);
atomic_init(&client->transmit_aplay, 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) atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_acap)) && (video || atomic_load(&client->transmit_acap))
) { ) {
us_rtp_write_ssrc(&rtp, (rtp.video ? client->video_ssrc : client->audio_ssrc));
janus_plugin_rtp packet = { janus_plugin_rtp packet = {
.video = rtp.video, .video = rtp.video,
.buffer = (char*)rtp.datagram, .buffer = (char*)rtp.datagram,

View File

@@ -37,6 +37,9 @@
typedef struct { typedef struct {
janus_callbacks *gw; janus_callbacks *gw;
janus_plugin_session *session; janus_plugin_session *session;
u32 video_ssrc;
u32 audio_ssrc;
atomic_bool transmit; atomic_bool transmit;
atomic_bool transmit_acap; atomic_bool transmit_acap;
atomic_bool transmit_aplay; 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"); US_JLOG_ERROR("config", "Either acap.sampling_rate or acap.tc358743 required");
goto error; goto error;
} }
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
} }
config->aplay_dev_name = _get_value(jcfg, "aplay", "device");
goto ok; goto ok;

View File

@@ -443,9 +443,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
if (_g_config->acap_dev_name != NULL) { if (_g_config->acap_dev_name != NULL) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients); _g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_acap_tid, _acap_thread, NULL); 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_rtp_tid, _video_rtp_thread, NULL);
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL); US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
@@ -641,14 +641,22 @@ static struct janus_plugin_result *_plugin_handle_message(
} }
{ {
char *const sdp = us_sdp_create( _LOCK_ALL;
_g_rtpv, US_LIST_ITERATE(_g_clients, client, {
(with_acap ? _g_rtpa : NULL), if (client->session == session) {
(with_acap && with_aplay)); char *const sdp = us_sdp_create(
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp); client->video_ssrc,
PUSH_STATUS("started", NULL, offer_jsep); client->audio_ssrc,
json_decref(offer_jsep); with_acap,
free(sdp); 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;
} }
{ {
@@ -671,11 +679,10 @@ static struct janus_plugin_result *_plugin_handle_message(
} else if (!strcmp(request_str, "features")) { } else if (!strcmp(request_str, "features")) {
const char *const ice_url = getenv("JANUS_USTREAMER_WEB_ICE_URL"); 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( json_t *const features = json_pack(
"{s:b, s:b, s:{s:s?}}", "{s:b, s:b, s:{s:s?}}",
"audio", acap_avail, "audio", us_au_probe(_g_config->acap_dev_name),
"mic", (acap_avail && us_au_probe(_g_config->aplay_dev_name)), "mic", us_au_probe(_g_config->aplay_dev_name),
"ice", "url", (ice_url != NULL ? ice_url : default_ice_url) "ice", "url", (ice_url != NULL ? ice_url : default_ice_url)
); );
PUSH_STATUS("features", features, NULL); PUSH_STATUS("features", features, NULL);

View File

@@ -44,9 +44,11 @@ void us_rtp_destroy(us_rtp_s *rtp) {
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) { void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video) {
rtp->payload = payload; rtp->payload = payload;
rtp->video = video; rtp->video = video;
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
} }
#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) { void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header) {
u32 word0 = 0x80000000; u32 word0 = 0x80000000;
if (last_header) { if (last_header) {
@@ -56,10 +58,13 @@ void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header) {
word0 |= rtp->seq; word0 |= rtp->seq;
++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(0, word0);
WRITE_BE_U32(4, pts); WRITE_BE_U32(4, pts);
WRITE_BE_U32(8, rtp->ssrc); WRITE_BE_U32(8, 0); // SSRC should be written separately for each client
# undef WRITE_BE_U32
} }
void us_rtp_write_ssrc(us_rtp_s *rtp, u32 ssrc) {
WRITE_BE_U32(8, ssrc);
}
#undef WRITE_BE_U32

View File

@@ -23,6 +23,7 @@
#pragma once #pragma once
#include "uslibs/types.h" #include "uslibs/types.h"
#include "uslibs/tools.h"
// Max RTP size for WebRTC is 1200 bytes: // Max RTP size for WebRTC is 1200 bytes:
@@ -47,7 +48,6 @@
typedef struct { typedef struct {
uint payload; uint payload;
bool video; bool video;
u32 ssrc;
u16 seq; u16 seq;
u8 datagram[US_RTP_TOTAL_SIZE]; u8 datagram[US_RTP_TOTAL_SIZE];
@@ -62,8 +62,13 @@ typedef struct {
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp); 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); us_rtp_s *us_rtp_init(void);
void us_rtp_destroy(us_rtp_s *rtp); void us_rtp_destroy(us_rtp_s *rtp);
void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video); void us_rtp_assign(us_rtp_s *rtp, uint payload, bool video);
void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool last_header); 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

@@ -34,12 +34,12 @@
#include "rtpa.h" #include "rtpa.h"
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) { char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay) {
char *video_sdp; char *video_sdp;
{ {
// https://tools.ietf.org/html/rfc6184 // https://tools.ietf.org/html/rfc6184
// https://github.com/meetecho/janus-gateway/issues/2443 // https://github.com/meetecho/janus-gateway/issues/2443
const uint pl = rtpv->rtp->payload; const uint pl = US_RTP_H264_PAYLOAD;
US_ASPRINTF( US_ASPRINTF(
video_sdp, video_sdp,
"m=video 1 RTP/SAVPF %u" RN "m=video 1 RTP/SAVPF %u" RN
@@ -58,14 +58,22 @@ char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) {
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN "a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
"a=sendonly" RN, "a=sendonly" RN,
pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl,
rtpv->rtp->ssrc); video_ssrc);
} }
char *audio_sdp; char *audio_sdp;
if (rtpa == NULL) { if (acap || aplay) {
audio_sdp = us_strdup(""); const uint pl = US_RTP_OPUS_PAYLOAD;
} else {
const uint pl = rtpa->rtp->payload; char *dir = "";
if (acap && aplay) {
dir = "sendrecv";
} else if (acap && !aplay) {
dir = "sendonly";
} else if (!acap && aplay) {
dir = "recvonly";
}
US_ASPRINTF( US_ASPRINTF(
audio_sdp, audio_sdp,
"m=audio 1 RTP/SAVPF %u" RN "m=audio 1 RTP/SAVPF %u" RN
@@ -79,8 +87,10 @@ char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) {
pl, pl, pl, pl,
US_RTP_OPUS_HZ, US_RTP_OPUS_CH, US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
pl, pl,
rtpa->rtp->ssrc, audio_ssrc,
(mic ? "sendrecv" : "sendonly")); dir);
} else {
audio_sdp = us_strdup("");
} }
char *sdp; char *sdp;

View File

@@ -24,8 +24,5 @@
#include "uslibs/types.h" #include "uslibs/types.h"
#include "rtpv.h"
#include "rtpa.h"
char *us_sdp_create(u32 video_ssrc, u32 audio_ssrc, bool acap, bool aplay);
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic);

View File

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

View File

@@ -1,6 +1,6 @@
.\" Manpage for ustreamer. .\" Manpage for ustreamer.
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos .\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
.TH USTREAMER 1 "version 6.58" "November 2020" .TH USTREAMER 1 "version 6.59" "November 2020"
.SH NAME .SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network ustreamer \- stream MJPEG video from any V4L2 device to the network

View File

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

View File

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

View File

@@ -17,4 +17,4 @@ install:
clean: 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() flags = _find_flags()
setup( setup(
name="ustreamer", name="ustreamer",
version="6.58", version="6.59",
description="uStreamer tools", description="uStreamer tools",
author="Maxim Devaev", author="Maxim Devaev",
author_email="mdevaev@gmail.com", author_email="mdevaev@gmail.com",

View File

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