Compare commits

...

9 Commits
v6.19 ... v6.23

Author SHA1 Message Date
Maxim Devaev
80ffc8b2bd Bump version: 6.22 → 6.23 2025-01-17 20:53:21 +02:00
Maxim Devaev
ba246d90c0 refactoring 2025-01-17 20:40:18 +02:00
Maxim Devaev
29c98e3908 Bump version: 6.21 → 6.22 2025-01-13 17:17:27 +02:00
Maxim Devaev
acc8cecbe4 lint fix 2025-01-13 17:15:55 +02:00
Maxim Devaev
8c31af2f03 janus: sendonly/sendrecv audio flag 2025-01-13 17:10:42 +02:00
Maxim Devaev
a727c9b7c5 Bump version: 6.20 → 6.21 2024-12-27 05:22:35 +02:00
Maxim Devaev
eabc8d8343 fixed bug with reversed logic of parent notification 2024-12-27 05:20:22 +02:00
Maxim Devaev
4e4ae21a83 Bump version: 6.19 → 6.20 2024-12-26 04:31:23 +02:00
Maxim Devaev
412a1775a6 hotfixed online flag 2024-12-26 04:29:15 +02:00
18 changed files with 186 additions and 178 deletions

View File

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

View File

@@ -20,7 +20,7 @@
*****************************************************************************/ *****************************************************************************/
#include "audio.h" #include "acap.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdatomic.h> #include <stdatomic.h>
@@ -73,44 +73,44 @@ typedef struct {
static _pcm_buffer_s *_pcm_buffer_init(void); static _pcm_buffer_s *_pcm_buffer_init(void);
static _enc_buffer_s *_enc_buffer_init(void); static _enc_buffer_s *_enc_buffer_init(void);
static void *_pcm_thread(void *v_audio); static void *_pcm_thread(void *v_acap);
static void *_encoder_thread(void *v_audio); static void *_encoder_thread(void *v_acap);
bool us_audio_probe(const char *name) { bool us_acap_probe(const char *name) {
snd_pcm_t *pcm; snd_pcm_t *pcm;
int err; int err;
US_JLOG_INFO("audio", "Probing PCM capture ..."); US_JLOG_INFO("acap", "Probing PCM capture ...");
if ((err = snd_pcm_open(&pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) { if ((err = snd_pcm_open(&pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
_JLOG_PERROR_ALSA(err, "audio", "Can't probe PCM capture"); _JLOG_PERROR_ALSA(err, "acap", "Can't probe PCM capture");
return false; return false;
} }
snd_pcm_close(pcm); snd_pcm_close(pcm);
US_JLOG_INFO("audio", "PCM capture is available"); US_JLOG_INFO("acap", "PCM capture is available");
return true; return true;
} }
us_audio_s *us_audio_init(const char *name, uint pcm_hz) { us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
us_audio_s *audio; us_acap_s *acap;
US_CALLOC(audio, 1); US_CALLOC(acap, 1);
audio->pcm_hz = pcm_hz; acap->pcm_hz = pcm_hz;
US_RING_INIT_WITH_ITEMS(audio->pcm_ring, 8, _pcm_buffer_init); US_RING_INIT_WITH_ITEMS(acap->pcm_ring, 8, _pcm_buffer_init);
US_RING_INIT_WITH_ITEMS(audio->enc_ring, 8, _enc_buffer_init); US_RING_INIT_WITH_ITEMS(acap->enc_ring, 8, _enc_buffer_init);
atomic_init(&audio->stop, false); atomic_init(&acap->stop, false);
int err; int err;
{ {
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) { if ((err = snd_pcm_open(&acap->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
audio->pcm = NULL; acap->pcm = NULL;
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture"); _JLOG_PERROR_ALSA(err, "acap", "Can't open PCM capture");
goto error; goto error;
} }
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params)); assert(!snd_pcm_hw_params_malloc(&acap->pcm_params));
# define SET_PARAM(_msg, _func, ...) { \ # define SET_PARAM(_msg, _func, ...) { \
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \ if ((err = _func(acap->pcm, acap->pcm_params, ##__VA_ARGS__)) < 0) { \
_JLOG_PERROR_ALSA(err, "audio", _msg); \ _JLOG_PERROR_ALSA(err, "acap", _msg); \
goto error; \ goto error; \
} \ } \
} }
@@ -119,85 +119,85 @@ us_audio_s *us_audio_init(const char *name, uint pcm_hz) {
SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED); SET_PARAM("Can't set PCM access type", snd_pcm_hw_params_set_access, SND_PCM_ACCESS_RW_INTERLEAVED);
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2); SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE); SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0); SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &acap->pcm_hz, 0);
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) { if (acap->pcm_hz < _MIN_PCM_HZ || acap->pcm_hz > _MAX_PCM_HZ) {
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u", US_JLOG_ERROR("acap", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ); acap->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
goto error; goto error;
} }
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz); acap->pcm_frames = _HZ_TO_FRAMES(acap->pcm_hz);
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz); acap->pcm_size = _HZ_TO_BUF8(acap->pcm_hz);
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params); SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
# undef SET_PARAM # undef SET_PARAM
} }
if (audio->pcm_hz != _ENCODER_INPUT_HZ) { if (acap->pcm_hz != _ENCODER_INPUT_HZ) {
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err); acap->res = speex_resampler_init(2, acap->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err < 0) { if (err < 0) {
audio->res = NULL; acap->res = NULL;
_JLOG_PERROR_RES(err, "audio", "Can't create resampler"); _JLOG_PERROR_RES(err, "acap", "Can't create resampler");
goto error; goto error;
} }
} }
{ {
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY // OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err); acap->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
assert(err == 0); assert(err == 0);
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000))); assert(!opus_encoder_ctl(acap->enc, OPUS_SET_BITRATE(48000)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND))); assert(!opus_encoder_ctl(acap->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC))); assert(!opus_encoder_ctl(acap->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)));
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c // OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
} }
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz); US_JLOG_INFO("acap", "Pipeline configured on %uHz; capturing ...", acap->pcm_hz);
audio->tids_created = true; acap->tids_created = true;
US_THREAD_CREATE(audio->enc_tid, _encoder_thread, audio); US_THREAD_CREATE(acap->enc_tid, _encoder_thread, acap);
US_THREAD_CREATE(audio->pcm_tid, _pcm_thread, audio); US_THREAD_CREATE(acap->pcm_tid, _pcm_thread, acap);
return audio; return acap;
error: error:
us_audio_destroy(audio); us_acap_destroy(acap);
return NULL; return NULL;
} }
void us_audio_destroy(us_audio_s *audio) { void us_acap_destroy(us_acap_s *acap) {
if (audio->tids_created) { if (acap->tids_created) {
atomic_store(&audio->stop, true); atomic_store(&acap->stop, true);
US_THREAD_JOIN(audio->pcm_tid); US_THREAD_JOIN(acap->pcm_tid);
US_THREAD_JOIN(audio->enc_tid); US_THREAD_JOIN(acap->enc_tid);
} }
US_DELETE(audio->enc, opus_encoder_destroy); US_DELETE(acap->enc, opus_encoder_destroy);
US_DELETE(audio->res, speex_resampler_destroy); US_DELETE(acap->res, speex_resampler_destroy);
US_DELETE(audio->pcm, snd_pcm_close); US_DELETE(acap->pcm, snd_pcm_close);
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free); US_DELETE(acap->pcm_params, snd_pcm_hw_params_free);
US_RING_DELETE_WITH_ITEMS(audio->enc_ring, free); US_RING_DELETE_WITH_ITEMS(acap->enc_ring, free);
US_RING_DELETE_WITH_ITEMS(audio->pcm_ring, free); US_RING_DELETE_WITH_ITEMS(acap->pcm_ring, free);
if (audio->tids_created) { if (acap->tids_created) {
US_JLOG_INFO("audio", "Pipeline closed"); US_JLOG_INFO("acap", "Pipeline closed");
} }
free(audio); free(acap);
} }
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts) { int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts) {
if (atomic_load(&audio->stop)) { if (atomic_load(&acap->stop)) {
return -1; return -1;
} }
const int ri = us_ring_consumer_acquire(audio->enc_ring, 0.1); const int ri = us_ring_consumer_acquire(acap->enc_ring, 0.1);
if (ri < 0) { if (ri < 0) {
return US_ERROR_NO_DATA; return US_ERROR_NO_DATA;
} }
const _enc_buffer_s *const buf = audio->enc_ring->items[ri]; const _enc_buffer_s *const buf = acap->enc_ring->items[ri];
if (*size < buf->used) { if (*size < buf->used) {
us_ring_consumer_release(audio->enc_ring, ri); us_ring_consumer_release(acap->enc_ring, ri);
return US_ERROR_NO_DATA; return US_ERROR_NO_DATA;
} }
memcpy(data, buf->data, buf->used); memcpy(data, buf->data, buf->used);
*size = buf->used; *size = buf->used;
*pts = buf->pts; *pts = buf->pts;
us_ring_consumer_release(audio->enc_ring, ri); us_ring_consumer_release(acap->enc_ring, ri);
return 0; return 0;
} }
@@ -213,83 +213,83 @@ static _enc_buffer_s *_enc_buffer_init(void) {
return buf; return buf;
} }
static void *_pcm_thread(void *v_audio) { static void *_pcm_thread(void *v_acap) {
US_THREAD_SETTLE("us_a_pcm"); US_THREAD_SETTLE("us_ac_pcm");
us_audio_s *const audio = v_audio; us_acap_s *const acap = v_acap;
u8 in[_MAX_BUF8]; u8 in[_MAX_BUF8];
while (!atomic_load(&audio->stop)) { while (!atomic_load(&acap->stop)) {
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames); const int frames = snd_pcm_readi(acap->pcm, in, acap->pcm_frames);
if (frames < 0) { if (frames < 0) {
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames"); _JLOG_PERROR_ALSA(frames, "acap", "Fatal: Can't capture PCM frames");
break; break;
} else if (frames < (int)audio->pcm_frames) { } else if (frames < (int)acap->pcm_frames) {
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured"); US_JLOG_ERROR("acap", "Fatal: Too few PCM frames captured");
break; break;
} }
const int ri = us_ring_producer_acquire(audio->pcm_ring, 0); const int ri = us_ring_producer_acquire(acap->pcm_ring, 0);
if (ri >= 0) { if (ri >= 0) {
_pcm_buffer_s *const out = audio->pcm_ring->items[ri]; _pcm_buffer_s *const out = acap->pcm_ring->items[ri];
memcpy(out->data, in, audio->pcm_size); memcpy(out->data, in, acap->pcm_size);
us_ring_producer_release(audio->pcm_ring, ri); us_ring_producer_release(acap->pcm_ring, ri);
} else { } else {
US_JLOG_ERROR("audio", "PCM ring is full"); US_JLOG_ERROR("acap", "PCM ring is full");
} }
} }
atomic_store(&audio->stop, true); atomic_store(&acap->stop, true);
return NULL; return NULL;
} }
static void *_encoder_thread(void *v_audio) { static void *_encoder_thread(void *v_acap) {
US_THREAD_SETTLE("us_a_enc"); US_THREAD_SETTLE("us_a_enc");
us_audio_s *const audio = v_audio; us_acap_s *const acap = v_acap;
s16 in_res[_MAX_BUF16]; s16 in_res[_MAX_BUF16];
while (!atomic_load(&audio->stop)) { while (!atomic_load(&acap->stop)) {
const int in_ri = us_ring_consumer_acquire(audio->pcm_ring, 0.1); const int in_ri = us_ring_consumer_acquire(acap->pcm_ring, 0.1);
if (in_ri < 0) { if (in_ri < 0) {
continue; continue;
} }
_pcm_buffer_s *const in = audio->pcm_ring->items[in_ri]; _pcm_buffer_s *const in = acap->pcm_ring->items[in_ri];
s16 *in_ptr; s16 *in_ptr;
if (audio->res != NULL) { if (acap->res != NULL) {
assert(audio->pcm_hz != _ENCODER_INPUT_HZ); assert(acap->pcm_hz != _ENCODER_INPUT_HZ);
u32 in_count = audio->pcm_frames; u32 in_count = acap->pcm_frames;
u32 out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ); u32 out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count); speex_resampler_process_interleaved_int(acap->res, in->data, &in_count, in_res, &out_count);
in_ptr = in_res; in_ptr = in_res;
} else { } else {
assert(audio->pcm_hz == _ENCODER_INPUT_HZ); assert(acap->pcm_hz == _ENCODER_INPUT_HZ);
in_ptr = in->data; in_ptr = in->data;
} }
const int out_ri = us_ring_producer_acquire(audio->enc_ring, 0); const int out_ri = us_ring_producer_acquire(acap->enc_ring, 0);
if (out_ri < 0) { if (out_ri < 0) {
US_JLOG_ERROR("audio", "OPUS encoder queue is full"); US_JLOG_ERROR("acap", "OPUS encoder queue is full");
us_ring_consumer_release(audio->pcm_ring, in_ri); us_ring_consumer_release(acap->pcm_ring, in_ri);
continue; continue;
} }
_enc_buffer_s *const out = audio->enc_ring->items[out_ri]; _enc_buffer_s *const out = acap->enc_ring->items[out_ri];
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data)); const int size = opus_encode(acap->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
us_ring_consumer_release(audio->pcm_ring, in_ri); us_ring_consumer_release(acap->pcm_ring, in_ri);
if (size >= 0) { if (size >= 0) {
out->used = size; out->used = size;
out->pts = audio->pts; out->pts = acap->pts;
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2 // https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ); acap->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
} else { } else {
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS"); _JLOG_PERROR_OPUS(size, "acap", "Fatal: Can't encode PCM frame to OPUS");
} }
us_ring_producer_release(audio->enc_ring, out_ri); us_ring_producer_release(acap->enc_ring, out_ri);
} }
atomic_store(&audio->stop, true); atomic_store(&acap->stop, true);
return NULL; return NULL;
} }

View File

@@ -50,12 +50,12 @@ typedef struct {
pthread_t enc_tid; pthread_t enc_tid;
bool tids_created; bool tids_created;
atomic_bool stop; atomic_bool stop;
} us_audio_s; } us_acap_s;
bool us_audio_probe(const char *name); bool us_acap_probe(const char *name);
us_audio_s *us_audio_init(const char *name, uint pcm_hz); us_acap_s *us_acap_init(const char *name, uint pcm_hz);
void us_audio_destroy(us_audio_s *audio); void us_acap_destroy(us_acap_s *acap);
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts); int us_acap_get_encoded(us_acap_s *acap, u8 *data, uz *size, u64 *pts);

View File

@@ -40,7 +40,7 @@
static void *_video_thread(void *v_client); static void *_video_thread(void *v_client);
static void *_audio_thread(void *v_client); static void *_acap_thread(void *v_client);
static void *_common_thread(void *v_client, bool video); static void *_common_thread(void *v_client, bool video);
@@ -50,7 +50,7 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
client->gw = gw; client->gw = gw;
client->session = session; client->session = session;
atomic_init(&client->transmit, false); atomic_init(&client->transmit, false);
atomic_init(&client->transmit_audio, false); atomic_init(&client->transmit_acap, false);
atomic_init(&client->video_orient, 0); atomic_init(&client->video_orient, 0);
atomic_init(&client->stop, false); atomic_init(&client->stop, false);
@@ -58,8 +58,8 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
US_RING_INIT_WITH_ITEMS(client->video_ring, 2048, us_rtp_init); US_RING_INIT_WITH_ITEMS(client->video_ring, 2048, us_rtp_init);
US_THREAD_CREATE(client->video_tid, _video_thread, client); US_THREAD_CREATE(client->video_tid, _video_thread, client);
US_RING_INIT_WITH_ITEMS(client->audio_ring, 64, us_rtp_init); US_RING_INIT_WITH_ITEMS(client->acap_ring, 64, us_rtp_init);
US_THREAD_CREATE(client->audio_tid, _audio_thread, client); US_THREAD_CREATE(client->acap_tid, _acap_thread, client);
return client; return client;
} }
@@ -70,8 +70,8 @@ void us_janus_client_destroy(us_janus_client_s *client) {
US_THREAD_JOIN(client->video_tid); US_THREAD_JOIN(client->video_tid);
US_RING_DELETE_WITH_ITEMS(client->video_ring, us_rtp_destroy); US_RING_DELETE_WITH_ITEMS(client->video_ring, us_rtp_destroy);
US_THREAD_JOIN(client->audio_tid); US_THREAD_JOIN(client->acap_tid);
US_RING_DELETE_WITH_ITEMS(client->audio_ring, us_rtp_destroy); US_RING_DELETE_WITH_ITEMS(client->acap_ring, us_rtp_destroy);
free(client); free(client);
} }
@@ -79,13 +79,13 @@ void us_janus_client_destroy(us_janus_client_s *client) {
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) { void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
if ( if (
atomic_load(&client->transmit) atomic_load(&client->transmit)
&& (rtp->video || atomic_load(&client->transmit_audio)) && (rtp->video || atomic_load(&client->transmit_acap))
) { ) {
us_ring_s *const ring = (rtp->video ? client->video_ring : client->audio_ring); us_ring_s *const ring = (rtp->video ? client->video_ring : client->acap_ring);
const int ri = us_ring_producer_acquire(ring, 0); const int ri = us_ring_producer_acquire(ring, 0);
if (ri < 0) { if (ri < 0) {
US_JLOG_ERROR("client", "Session %p %s ring is full", US_JLOG_ERROR("client", "Session %p %s ring is full",
client->session, (rtp->video ? "video" : "audio")); client->session, (rtp->video ? "video" : "acap"));
return; return;
} }
memcpy(ring->items[ri], rtp, sizeof(us_rtp_s)); memcpy(ring->items[ri], rtp, sizeof(us_rtp_s));
@@ -98,14 +98,14 @@ static void *_video_thread(void *v_client) {
return _common_thread(v_client, true); return _common_thread(v_client, true);
} }
static void *_audio_thread(void *v_client) { static void *_acap_thread(void *v_client) {
US_THREAD_SETTLE("us_c_audio"); US_THREAD_SETTLE("us_c_acap");
return _common_thread(v_client, false); return _common_thread(v_client, false);
} }
static void *_common_thread(void *v_client, bool video) { static void *_common_thread(void *v_client, bool video) {
us_janus_client_s *const client = v_client; us_janus_client_s *const client = v_client;
us_ring_s *const ring = (video ? client->video_ring : client->audio_ring); us_ring_s *const ring = (video ? client->video_ring : client->acap_ring);
assert(ring != NULL); // Audio may be NULL assert(ring != NULL); // Audio may be NULL
while (!atomic_load(&client->stop)) { while (!atomic_load(&client->stop)) {
@@ -119,7 +119,7 @@ static void *_common_thread(void *v_client, bool video) {
if ( if (
atomic_load(&client->transmit) atomic_load(&client->transmit)
&& (video || atomic_load(&client->transmit_audio)) && (video || atomic_load(&client->transmit_acap))
) { ) {
janus_plugin_rtp packet = { janus_plugin_rtp packet = {
.video = rtp.video, .video = rtp.video,

View File

@@ -38,15 +38,15 @@ typedef struct {
janus_callbacks *gw; janus_callbacks *gw;
janus_plugin_session *session; janus_plugin_session *session;
atomic_bool transmit; atomic_bool transmit;
atomic_bool transmit_audio; atomic_bool transmit_acap;
atomic_uint video_orient; atomic_uint video_orient;
pthread_t video_tid; pthread_t video_tid;
pthread_t audio_tid; pthread_t acap_tid;
atomic_bool stop; atomic_bool stop;
us_ring_s *video_ring; us_ring_s *video_ring;
us_ring_s *audio_ring; us_ring_s *acap_ring;
US_LIST_DECLARE; US_LIST_DECLARE;
} us_janus_client_s; } us_janus_client_s;

View File

@@ -62,7 +62,7 @@ us_config_s *us_config_init(const char *config_dir_path) {
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)"); US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
goto error; goto error;
} }
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) { if ((config->acap_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) { if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
US_JLOG_INFO("config", "Missing config value: audio.tc358743"); US_JLOG_INFO("config", "Missing config value: audio.tc358743");
goto error; goto error;
@@ -82,7 +82,7 @@ ok:
void us_config_destroy(us_config_s *config) { void us_config_destroy(us_config_s *config) {
US_DELETE(config->video_sink_name, free); US_DELETE(config->video_sink_name, free);
US_DELETE(config->audio_dev_name, free); US_DELETE(config->acap_dev_name, free);
US_DELETE(config->tc358743_dev_path, free); US_DELETE(config->tc358743_dev_path, free);
free(config); free(config);
} }

View File

@@ -26,7 +26,7 @@
typedef struct { typedef struct {
char *video_sink_name; char *video_sink_name;
char *audio_dev_name; char *acap_dev_name;
char *tc358743_dev_path; char *tc358743_dev_path;
} us_config_s; } us_config_s;

View File

@@ -48,7 +48,7 @@
#include "const.h" #include "const.h"
#include "logging.h" #include "logging.h"
#include "client.h" #include "client.h"
#include "audio.h" #include "acap.h"
#include "rtp.h" #include "rtp.h"
#include "rtpv.h" #include "rtpv.h"
#include "rtpa.h" #include "rtpa.h"
@@ -69,11 +69,11 @@ static pthread_t _g_video_rtp_tid;
static atomic_bool _g_video_rtp_tid_created = false; static atomic_bool _g_video_rtp_tid_created = false;
static pthread_t _g_video_sink_tid; static pthread_t _g_video_sink_tid;
static atomic_bool _g_video_sink_tid_created = false; static atomic_bool _g_video_sink_tid_created = false;
static pthread_t _g_audio_tid; static pthread_t _g_acap_tid;
static atomic_bool _g_audio_tid_created = false; static atomic_bool _g_acap_tid_created = false;
static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t _g_video_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t _g_audio_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t _g_acap_lock = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool _g_ready = false; static atomic_bool _g_ready = false;
static atomic_bool _g_stop = false; static atomic_bool _g_stop = false;
static atomic_bool _g_has_watchers = false; static atomic_bool _g_has_watchers = false;
@@ -84,11 +84,11 @@ static atomic_bool _g_key_required = false;
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock) #define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock) #define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
#define _LOCK_AUDIO US_MUTEX_LOCK(_g_audio_lock) #define _LOCK_ACAP US_MUTEX_LOCK(_g_acap_lock)
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock) #define _UNLOCK_ACAP US_MUTEX_UNLOCK(_g_acap_lock)
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; } #define _LOCK_ALL { _LOCK_VIDEO; _LOCK_ACAP; }
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; } #define _UNLOCK_ALL { _UNLOCK_ACAP; _UNLOCK_VIDEO; }
#define _READY atomic_load(&_g_ready) #define _READY atomic_load(&_g_ready)
#define _STOP atomic_load(&_g_stop) #define _STOP atomic_load(&_g_stop)
@@ -198,15 +198,15 @@ static void *_video_sink_thread(void *arg) {
return NULL; return NULL;
} }
static int _check_tc358743_audio(uint *audio_hz) { static int _check_tc358743_acap(uint *hz) {
int fd; int fd;
if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) { if ((fd = open(_g_config->tc358743_dev_path, O_RDWR)) < 0) {
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device"); US_JLOG_PERROR("acap", "Can't open TC358743 V4L2 device");
return -1; return -1;
} }
const int checked = us_tc358743_xioctl_get_audio_hz(fd, audio_hz); const int checked = us_tc358743_xioctl_get_audio_hz(fd, hz);
if (checked < 0) { if (checked < 0) {
US_JLOG_PERROR("audio", "Can't check TC358743 audio state (%d)", checked); US_JLOG_PERROR("acap", "Can't check TC358743 audio state (%d)", checked);
close(fd); close(fd);
return -1; return -1;
} }
@@ -214,12 +214,12 @@ static int _check_tc358743_audio(uint *audio_hz) {
return 0; return 0;
} }
static void *_audio_thread(void *arg) { static void *_acap_thread(void *arg) {
(void)arg; (void)arg;
US_THREAD_SETTLE("us_audio"); US_THREAD_SETTLE("us_acap");
atomic_store(&_g_audio_tid_created, true); atomic_store(&_g_acap_tid_created, true);
assert(_g_config->audio_dev_name != NULL); assert(_g_config->acap_dev_name != NULL);
assert(_g_config->tc358743_dev_path != NULL); assert(_g_config->tc358743_dev_path != NULL);
int once = 0; int once = 0;
@@ -230,42 +230,42 @@ static void *_audio_thread(void *arg) {
continue; continue;
} }
uint audio_hz = 0; uint hz = 0;
us_audio_s *audio = NULL; us_acap_s *acap = NULL;
if (_check_tc358743_audio(&audio_hz) < 0) { if (_check_tc358743_acap(&hz) < 0) {
goto close_audio; goto close_acap;
} }
if (audio_hz == 0) { if (hz == 0) {
US_ONCE({ US_JLOG_INFO("audio", "No audio presented from the host"); }); US_ONCE({ US_JLOG_INFO("acap", "No audio presented from the host"); });
goto close_audio; goto close_acap;
} }
US_ONCE({ US_JLOG_INFO("audio", "Detected host audio"); }); US_ONCE({ US_JLOG_INFO("acap", "Detected host audio"); });
if ((audio = us_audio_init(_g_config->audio_dev_name, audio_hz)) == NULL) { if ((acap = us_acap_init(_g_config->acap_dev_name, hz)) == NULL) {
goto close_audio; goto close_acap;
} }
once = 0; once = 0;
while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) { while (!_STOP && _HAS_WATCHERS && _HAS_LISTENERS) {
if (_check_tc358743_audio(&audio_hz) < 0 || audio->pcm_hz != audio_hz) { if (_check_tc358743_acap(&hz) < 0 || acap->pcm_hz != hz) {
goto close_audio; goto close_acap;
} }
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE; uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
u8 data[size]; u8 data[size];
u64 pts; u64 pts;
const int result = us_audio_get_encoded(audio, data, &size, &pts); const int result = us_acap_get_encoded(acap, data, &size, &pts);
if (result == 0) { if (result == 0) {
_LOCK_AUDIO; _LOCK_ACAP;
us_rtpa_wrap(_g_rtpa, data, size, pts); us_rtpa_wrap(_g_rtpa, data, size, pts);
_UNLOCK_AUDIO; _UNLOCK_ACAP;
} else if (result == -1) { } else if (result == -1) {
goto close_audio; goto close_acap;
} }
} }
close_audio: close_acap:
US_DELETE(audio, us_audio_destroy); US_DELETE(acap, us_acap_destroy);
sleep(1); // error_delay sleep(1); // error_delay
} }
return NULL; return NULL;
@@ -292,9 +292,9 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init); US_RING_INIT_WITH_ITEMS(_g_video_ring, 64, us_frame_init);
_g_rtpv = us_rtpv_init(_relay_rtp_clients); _g_rtpv = us_rtpv_init(_relay_rtp_clients);
if (_g_config->audio_dev_name != NULL && us_audio_probe(_g_config->audio_dev_name)) { if (_g_config->acap_dev_name != NULL && us_acap_probe(_g_config->acap_dev_name)) {
_g_rtpa = us_rtpa_init(_relay_rtp_clients); _g_rtpa = us_rtpa_init(_relay_rtp_clients);
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL); US_THREAD_CREATE(_g_acap_tid, _acap_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);
@@ -310,7 +310,7 @@ static void _plugin_destroy(void) {
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } } # define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
JOIN(_g_video_sink_tid); JOIN(_g_video_sink_tid);
JOIN(_g_video_rtp_tid); JOIN(_g_video_rtp_tid);
JOIN(_g_audio_tid); JOIN(_g_acap_tid);
# undef JOIN # undef JOIN
US_LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
@@ -351,7 +351,7 @@ static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
found = true; found = true;
} else { } else {
has_watchers = (has_watchers || atomic_load(&client->transmit)); has_watchers = (has_watchers || atomic_load(&client->transmit));
has_listeners = (has_listeners || atomic_load(&client->transmit_audio)); has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
} }
}); });
if (!found) { if (!found) {
@@ -458,15 +458,22 @@ static struct janus_plugin_result *_plugin_handle_message(
PUSH_STATUS("stopped", NULL, NULL); PUSH_STATUS("stopped", NULL, NULL);
} else if (!strcmp(request_str, "watch")) { } else if (!strcmp(request_str, "watch")) {
bool with_audio = false;
uint video_orient = 0; uint video_orient = 0;
bool with_acap = false;
bool with_aplay = false;
{ {
json_t *const params = json_object_get(msg, "params"); json_t *const params = json_object_get(msg, "params");
if (params != NULL) { if (params != NULL) {
{ {
json_t *const obj = json_object_get(params, "audio"); json_t *const obj = json_object_get(params, "audio");
if (obj != NULL && json_is_boolean(obj)) { if (obj != NULL && json_is_boolean(obj)) {
with_audio = (_g_rtpa != NULL && json_boolean_value(obj)); with_acap = (_g_rtpa != NULL && json_boolean_value(obj));
}
}
{
json_t *const obj = json_object_get(params, "microphone");
if (obj != NULL && json_is_boolean(obj)) {
with_aplay = (with_acap && json_boolean_value(obj)); // FIXME: also check playback
} }
} }
{ {
@@ -485,7 +492,7 @@ static struct janus_plugin_result *_plugin_handle_message(
{ {
char *sdp; char *sdp;
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv); char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
char *const audio_sdp = (with_audio ? us_rtpa_make_sdp(_g_rtpa) : us_strdup("")); char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_strdup(""));
US_ASPRINTF(sdp, US_ASPRINTF(sdp,
"v=0" RN "v=0" RN
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN "o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
@@ -516,17 +523,17 @@ static struct janus_plugin_result *_plugin_handle_message(
bool has_listeners = false; bool has_listeners = false;
US_LIST_ITERATE(_g_clients, client, { US_LIST_ITERATE(_g_clients, client, {
if (client->session == session) { if (client->session == session) {
atomic_store(&client->transmit_audio, with_audio); atomic_store(&client->transmit_acap, with_acap);
atomic_store(&client->video_orient, video_orient); atomic_store(&client->video_orient, video_orient);
} }
has_listeners = (has_listeners || atomic_load(&client->transmit_audio)); has_listeners = (has_listeners || atomic_load(&client->transmit_acap));
}); });
atomic_store(&_g_has_listeners, has_listeners); atomic_store(&_g_has_listeners, has_listeners);
_UNLOCK_ALL; _UNLOCK_ALL;
} }
} else if (!strcmp(request_str, "features")) { } else if (!strcmp(request_str, "features")) {
json_t *const features = json_pack("{sb}", "audio", (_g_rtpa != NULL)); json_t *const features = json_pack("{sbsb}", "audio", (_g_rtpa != NULL), "microphone", false);
PUSH_STATUS("features", features, NULL); PUSH_STATUS("features", features, NULL);
json_decref(features); json_decref(features);

View File

@@ -43,7 +43,7 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
free(rtpa); free(rtpa);
} }
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) { char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
const uint pl = rtpa->rtp->payload; const uint pl = rtpa->rtp->payload;
char *sdp; char *sdp;
US_ASPRINTF(sdp, US_ASPRINTF(sdp,
@@ -55,9 +55,10 @@ char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
"a=rtcp-fb:%u nack pli" RN "a=rtcp-fb:%u nack pli" RN
"a=rtcp-fb:%u goog-remb" RN "a=rtcp-fb:%u goog-remb" RN
"a=ssrc:%" PRIu32 " cname:ustreamer" RN "a=ssrc:%" PRIu32 " cname:ustreamer" RN
"a=sendonly" RN, "a=%s" RN,
pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl, pl,
rtpa->rtp->ssrc rtpa->rtp->ssrc,
(mic ? "sendrecv" : "sendonly")
); );
return sdp; return sdp;
} }

View File

@@ -36,5 +36,5 @@ typedef struct {
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback); us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
void us_rtpa_destroy(us_rtpa_s *rtpa); void us_rtpa_destroy(us_rtpa_s *rtpa);
char *us_rtpa_make_sdp(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); void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);

View File

@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true skipsdist = true
[testenv] [testenv]
basepython = python3.12 basepython = python3.13
changedir = /src changedir = /src
[testenv:cppcheck] [testenv:cppcheck]

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.19" "January 2021" .TH USTREAMER-DUMP 1 "version 6.23" "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.19" "November 2020" .TH USTREAMER 1 "version 6.23" "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.19 pkgver=6.23
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

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

View File

@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
if __name__ == "__main__": if __name__ == "__main__":
setup( setup(
name="ustreamer", name="ustreamer",
version="6.19", version="6.23",
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 19 #define US_VERSION_MINOR 23
#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)

View File

@@ -598,10 +598,10 @@ static void _stream_update_captured_fpsi(us_stream_s *stream, const us_frame_s *
us_stream_runtime_s *const run = stream->run; us_stream_runtime_s *const run = stream->run;
us_fpsi_meta_s meta = {0}; us_fpsi_meta_s meta = {0};
us_fpsi_frame_to_meta(run->blank->raw, &meta); us_fpsi_frame_to_meta(frame, &meta);
us_fpsi_update(run->http->captured_fpsi, false, &meta); us_fpsi_update(run->http->captured_fpsi, bump, &meta);
if (stream->notify_parent && !memcmp(&run->notify_meta, &meta, sizeof(us_fpsi_meta_s))) { if (stream->notify_parent && memcmp(&run->notify_meta, &meta, sizeof(us_fpsi_meta_s))) {
memcpy(&run->notify_meta, &meta, sizeof(us_fpsi_meta_s)); memcpy(&run->notify_meta, &meta, sizeof(us_fpsi_meta_s));
us_process_notify_parent(); us_process_notify_parent();
} }