janus: plug audio devices dynamically

This commit is contained in:
Maxim Devaev 2025-10-22 19:35:35 +03:00
parent 472673ea90
commit c4ac67acba
5 changed files with 85 additions and 21 deletions

View File

@ -47,19 +47,6 @@ static void *_pcm_thread(void *v_acap);
static void *_encoder_thread(void *v_acap); static void *_encoder_thread(void *v_acap);
bool us_acap_probe(const char *name) {
snd_pcm_t *dev;
int err;
US_JLOG_INFO("acap", "Probing PCM capture ...");
if ((err = snd_pcm_open(&dev, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
US_JLOG_PERROR_ALSA(err, "acap", "Can't probe PCM capture");
return false;
}
snd_pcm_close(dev);
US_JLOG_INFO("acap", "PCM capture is available");
return true;
}
us_acap_s *us_acap_init(const char *name, uint pcm_hz) { us_acap_s *us_acap_init(const char *name, uint pcm_hz) {
us_acap_s *acap; us_acap_s *acap;
US_CALLOC(acap, 1); US_CALLOC(acap, 1);

View File

@ -53,8 +53,6 @@ typedef struct {
} us_acap_s; } us_acap_s;
bool us_acap_probe(const char *name);
us_acap_s *us_acap_init(const char *name, uint pcm_hz); us_acap_s *us_acap_init(const char *name, uint pcm_hz);
void us_acap_destroy(us_acap_s *acap); void us_acap_destroy(us_acap_s *acap);

View File

@ -23,10 +23,78 @@
#include "au.h" #include "au.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "uslibs/tools.h" #include "uslibs/tools.h"
bool us_au_probe(const char *name) {
// This function is very limited. It takes something like:
// hw:0,0 or hw:tc358743,0 or plughw:UAC2Gadget,0
// parses card name (0, tc358743, UAC2Gadget) and checks
// the existence of it in /proc/asound/.
// It's enough for our case.
if (name == NULL) {
return false;
}
if (strchr(name, '/') || strchr(name, '.')) {
return false;
}
const char *begin = strchr(name, ':');
if (begin == NULL) {
return false;
}
begin += 1;
if (*begin == '\0') {
return false;
}
const char *end = strchr(begin, ',');
if (end == NULL) {
return false;
}
if (end - begin < 1) {
return false;
}
char *card = us_strdup(begin);
card[end - begin] = '\0';
bool numeric = true;
for (uz index = 0; card[index] != '\0'; ++index) {
if (!isdigit(card[index])) {
numeric = false;
break;
}
}
char *path;
if (numeric) {
US_ASPRINTF(path, "/proc/asound/card%s", card);
} else {
US_ASPRINTF(path, "/proc/asound/%s", card);
}
bool ok = false;
struct stat st;
if (lstat(path, &st) == 0) {
if (numeric && S_ISDIR(st.st_mode)) {
ok = true;
} else if (!numeric && S_ISLNK(st.st_mode)) {
ok = true;
}
}
free(path);
free(card);
return ok;
}
us_au_pcm_s *us_au_pcm_init(void) { us_au_pcm_s *us_au_pcm_init(void) {
us_au_pcm_s *pcm; us_au_pcm_s *pcm;
US_CALLOC(pcm, 1); US_CALLOC(pcm, 1);

View File

@ -51,6 +51,7 @@ typedef struct {
u64 pts; u64 pts;
} us_au_encoded_s; } us_au_encoded_s;
bool us_au_probe(const char *name);
us_au_pcm_s *us_au_pcm_init(void); us_au_pcm_s *us_au_pcm_init(void);
void us_au_pcm_destroy(us_au_pcm_s *pcm); void us_au_pcm_destroy(us_au_pcm_s *pcm);

View File

@ -69,7 +69,7 @@ static us_janus_client_s *_g_clients = NULL;
static janus_callbacks *_g_gw = NULL; static janus_callbacks *_g_gw = NULL;
static us_ring_s *_g_video_ring = NULL; static us_ring_s *_g_video_ring = NULL;
static us_rtpv_s *_g_rtpv = NULL; static us_rtpv_s *_g_rtpv = NULL;
static us_rtpa_s *_g_rtpa = NULL; // Also indicates "audio capture is available" static us_rtpa_s *_g_rtpa = NULL;
static pthread_t _g_video_rtp_tid; 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;
@ -250,6 +250,10 @@ static void *_acap_thread(void *arg) {
uint hz = 0; uint hz = 0;
us_acap_s *acap = NULL; us_acap_s *acap = NULL;
if (!us_au_probe(_g_config->acap_dev_name)) {
US_ONCE({ US_JLOG_ERROR("acap", "No PCM capture device"); });
goto close_acap;
}
if (_check_tc358743_acap(&hz) < 0) { if (_check_tc358743_acap(&hz) < 0) {
goto close_acap; goto close_acap;
} }
@ -339,6 +343,11 @@ static void *_aplay_thread(void *arg) {
} }
if (dev == NULL) { if (dev == NULL) {
if (!us_au_probe(_g_config->aplay_dev_name)) {
US_ONCE({ US_JLOG_ERROR("aplay", "No PCM playback device"); });
goto close_aplay;
}
int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0); int err = snd_pcm_open(&dev, _g_config->aplay_dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) { if (err < 0) {
US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); }); US_ONCE({ US_JLOG_PERROR_ALSA(err, "aplay", "Can't open PCM playback"); });
@ -424,7 +433,7 @@ 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->acap_dev_name != NULL && us_acap_probe(_g_config->acap_dev_name)) { 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) { if (_g_config->aplay_dev_name != NULL) {
@ -602,13 +611,13 @@ static struct janus_plugin_result *_plugin_handle_message(
{ {
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_acap = (_g_rtpa != NULL && json_boolean_value(obj)); with_acap = (us_au_probe(_g_config->acap_dev_name) && json_boolean_value(obj));
} }
} }
{ {
json_t *const obj = json_object_get(params, "mic"); json_t *const obj = json_object_get(params, "mic");
if (obj != NULL && json_is_boolean(obj)) { if (obj != NULL && json_is_boolean(obj)) {
with_aplay = (_g_config->aplay_dev_name != NULL && with_acap && json_boolean_value(obj)); with_aplay = (us_au_probe(_g_config->aplay_dev_name) && json_boolean_value(obj));
} }
} }
{ {
@ -673,10 +682,11 @@ 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", (_g_rtpa != NULL), "audio", acap_avail,
"mic", (_g_rtpa != NULL && _g_config->aplay_dev_name != NULL), "mic", (acap_avail && 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);