1
0
mirror of https://github.com/Tha14/toxic.git synced 2024-06-26 20:57:48 +02:00

Implement simplistic VAD

This commit is contained in:
zugz (tox) 2020-05-07 00:00:00 +00:00
parent ddcf224db2
commit 7dead5ec96
No known key found for this signature in database
GPG Key ID: 6F2BDA289D04F249
18 changed files with 316 additions and 159 deletions

View File

@ -11,6 +11,7 @@ endif
CHECK_AUDIO_LIBS := $(shell $(PKG_CONFIG) --exists $(AUDIO_LIBS) || echo -n "error") CHECK_AUDIO_LIBS := $(shell $(PKG_CONFIG) --exists $(AUDIO_LIBS) || echo -n "error")
ifneq ($(CHECK_AUDIO_LIBS), error) ifneq ($(CHECK_AUDIO_LIBS), error)
LIBS += $(AUDIO_LIBS) LIBS += $(AUDIO_LIBS)
LDFLAGS += -lm
CFLAGS += $(AUDIO_CFLAGS) CFLAGS += $(AUDIO_CFLAGS)
OBJ += $(AUDIO_OBJ) OBJ += $(AUDIO_OBJ)
else ifneq ($(MAKECMDGOALS), clean) else ifneq ($(MAKECMDGOALS), clean)

View File

@ -217,9 +217,9 @@ Audio output device\&. Integer value\&. Number corresponds to
/lsdev out /lsdev out
.RE .RE
.PP .PP
\fBVAD_treshold\fR \fBVAD_threshold\fR
.RS 4 .RS 4
Voice Activity Detection treshold\&. Float value\&. Recommended values are around 40\&.0 Voice Activity Detection threshold\&. Float value\&. Recommended values are 1\&.0-40\&.0
.RE .RE
.RE .RE
.PP .PP

View File

@ -135,9 +135,9 @@ OPTIONS
*output_device*;; *output_device*;;
Audio output device. Integer value. Number corresponds to `/lsdev out` Audio output device. Integer value. Number corresponds to `/lsdev out`
*VAD_treshold*;; *VAD_threshold*;;
Voice Activity Detection treshold. Float value. Recommended values are Voice Activity Detection threshold. Float value. Recommended values are
around 40.0 1.0-40.0
*tox*:: *tox*::
Configuration related to paths. Configuration related to paths.

View File

@ -82,8 +82,8 @@ audio = {
// preferred audio output device; numbers correspond to /lsdev out // preferred audio output device; numbers correspond to /lsdev out
output_device=0; output_device=0;
// default VAD treshold; float (recommended values are around 40) // default VAD threshold; float (recommended values are 1.0-40.0)
VAD_treshold=40.0; VAD_threshold=5.0;
}; };
tox = { tox = {

View File

@ -213,9 +213,8 @@ int start_transmission(ToxWindow *self, Call *call)
return -1; return -1;
} }
DeviceError error = open_input_device(&call->in_idx, read_device_callback, &self->num, true, DeviceError error = open_input_device(&call->in_idx, read_device_callback, &self->num, false,
CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels); CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels);
/* Set VAD as true for all; TODO: Make it more dynamic */
if (error != de_None) { if (error != de_None) {
if (error == de_FailedStart) { if (error == de_FailedStart) {
@ -819,7 +818,7 @@ void cmd_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[M
/* Call must be active */ /* Call must be active */
if (self->is_call) { if (self->is_call) {
device_set_VAD_treshold(CallControl.calls[self->num].in_idx, value); device_set_VAD_threshold(CallControl.calls[self->num].in_idx, value);
self->chatwin->infobox.vad_lvl = value; self->chatwin->infobox.vad_lvl = value;
} }

View File

@ -22,10 +22,6 @@
#include "audio_device.h" #include "audio_device.h"
#ifdef AUDIO
#include "audio_call.h"
#endif
#include "line_info.h" #include "line_info.h"
#include "settings.h" #include "settings.h"
#include "misc_tools.h" #include "misc_tools.h"
@ -43,6 +39,7 @@
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <math.h>
extern struct user_settings *user_settings; extern struct user_settings *user_settings;
extern struct Winthread Winthread; extern struct Winthread Winthread;
@ -70,11 +67,8 @@ typedef struct Device {
// used only by input devices: // used only by input devices:
DataHandleCallback cb; DataHandleCallback cb;
void *cb_data; void *cb_data;
// TODO: implement VAD, and fix the typo, or remove these. float VAD_threshold;
bool enable_VAD; uint32_t VAD_samples_remaining;
#ifdef AUDIO
float VAD_treshold; /* 40 is usually recommended value */
#endif
// used only by output devices: // used only by output devices:
uint32_t source; uint32_t source;
@ -89,9 +83,11 @@ typedef struct AudioState {
uint32_t num_devices[2]; uint32_t num_devices[2];
FrameInfo capture_frame_info; FrameInfo capture_frame_info;
float input_volume;
// mutexes to prevent changes to input resp. output devices and al_devices // mutexes to prevent changes to input resp. output devices and al_devices
// during poll_input iterations resp. calls to write_out // during poll_input iterations resp. calls to write_out;
// mutex[input] also used to lock input_volume which poll_input writes to.
pthread_mutex_t mutex[2]; pthread_mutex_t mutex[2];
// TODO: unused // TODO: unused
@ -118,7 +114,9 @@ static void unlock(DeviceType type)
static bool thread_running = true, static bool thread_running = true,
thread_paused = true; /* Thread control */ thread_paused = true; /* Thread control */
#ifdef AUDIO
static void *poll_input(void *); static void *poll_input(void *);
#endif
static uint32_t sound_mode(bool stereo) static uint32_t sound_mode(bool stereo)
{ {
@ -148,6 +146,7 @@ DeviceError init_devices(void)
} }
} }
#ifdef AUDIO
// Start poll thread // Start poll thread
pthread_t thread_id; pthread_t thread_id;
@ -156,6 +155,8 @@ DeviceError init_devices(void)
return de_InternalError; return de_InternalError;
} }
#endif
return de_None; return de_None;
} }
@ -247,8 +248,7 @@ bool device_is_muted(DeviceType type, uint32_t device_idx)
return device->muted; return device->muted;
} }
#ifdef AUDIO DeviceError device_set_VAD_threshold(uint32_t device_idx, float value)
DeviceError device_set_VAD_treshold(uint32_t device_idx, float value)
{ {
if (device_idx >= MAX_DEVICES) { if (device_idx >= MAX_DEVICES) {
return de_InvalidSelection; return de_InvalidSelection;
@ -260,14 +260,32 @@ DeviceError device_set_VAD_treshold(uint32_t device_idx, float value)
return de_DeviceNotActive; return de_DeviceNotActive;
} }
if (value <= 0.0f) {
value = 0.0f;
}
lock(input); lock(input);
device->VAD_treshold = value; device->VAD_threshold = value;
unlock(input); unlock(input);
return de_None; return de_None;
} }
#endif
float device_get_VAD_threshold(uint32_t device_idx)
{
if (device_idx >= MAX_DEVICES) {
return 0.0;
}
Device *device = &audio_state->devices[input][device_idx];
if (!device->active) {
return 0.0;
}
return device->VAD_threshold;
}
DeviceError set_source_position(uint32_t device_idx, float x, float y, float z) DeviceError set_source_position(uint32_t device_idx, float x, float y, float z)
{ {
@ -511,9 +529,10 @@ static DeviceError open_device(DeviceType type, uint32_t *device_idx,
if (type == input) { if (type == input) {
device->cb = cb; device->cb = cb;
device->cb_data = cb_data; device->cb_data = cb_data;
device->enable_VAD = enable_VAD;
#ifdef AUDIO #ifdef AUDIO
device->VAD_treshold = user_settings->VAD_treshold; device->VAD_threshold = enable_VAD ? user_settings->VAD_threshold : 0.0f;
#else
device->VAD_threshold = 0.0f;
#endif #endif
} else { } else {
if (open_source(device) != de_None) { if (open_source(device) != de_None) {
@ -606,7 +625,7 @@ DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t sample_
ALuint *bufids = malloc(processed * sizeof(ALuint)); ALuint *bufids = malloc(processed * sizeof(ALuint));
if (bufids == NULL) { if (bufids == NULL) {
pthread_mutex_unlock(device->mutex); unlock(output);
return de_InternalError; return de_InternalError;
} }
@ -639,6 +658,33 @@ DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t sample_
return de_None; return de_None;
} }
#ifdef AUDIO
/* Adapted from qtox,
* Copyright © 2014-2019 by The qTox Project Contributors
*
* return normalized volume of buffer in range 0.0-100.0
*/
float volume(int16_t *frame, uint32_t samples)
{
float sum_of_squares = 0;
for (uint32_t i = 0; i < samples; i++) {
const float sample = (float)(frame[i]) / INT16_MAX;
sum_of_squares += powf(sample, 2);
}
const float root_mean_square = sqrtf(sum_of_squares / samples);
const float root_two = 1.414213562;
// normalizedVolume == 1.0 corresponds to a sine wave of maximal amplitude
const float normalized_volume = root_mean_square * root_two;
return 100.0f * fminf(1.0f, normalized_volume);
}
// Time in ms for which we continue to capture audio after VAD is triggered:
#define VAD_TIME 250
#define FRAME_BUF_SIZE 16000 #define FRAME_BUF_SIZE 16000
static void *poll_input(void *arg) static void *poll_input(void *arg)
@ -679,9 +725,23 @@ static void *poll_input(void *arg)
pthread_mutex_lock(&Winthread.lock); pthread_mutex_lock(&Winthread.lock);
lock(input); lock(input);
float frame_volume = volume(frame_buf, f_size);
audio_state->input_volume = frame_volume;
for (int i = 0; i < MAX_DEVICES; i++) { for (int i = 0; i < MAX_DEVICES; i++) {
Device *device = &audio_state->devices[input][i]; Device *device = &audio_state->devices[input][i];
if (device->VAD_threshold != 0.0f) {
if (frame_volume >= device->VAD_threshold) {
device->VAD_samples_remaining = VAD_TIME * (audio_state->capture_frame_info.sample_rate / 1000);
} else if (device->VAD_samples_remaining < f_size) {
continue;
} else {
device->VAD_samples_remaining -= f_size;
}
}
if (device->active && !device->muted && device->cb) { if (device->active && !device->muted && device->cb) {
device->cb(frame_buf, f_size, device->cb_data); device->cb(frame_buf, f_size, device->cb_data);
} }
@ -697,6 +757,20 @@ static void *poll_input(void *arg)
pthread_exit(NULL); pthread_exit(NULL);
} }
#endif
float get_input_volume(void)
{
float ret = 0.0f;
if (audio_state->al_device[input] != NULL) {
lock(input);
ret = audio_state->input_volume;
unlock(input);
}
return ret;
}
void print_al_devices(ToxWindow *self, DeviceType type) void print_al_devices(ToxWindow *self, DeviceType type)
{ {

View File

@ -66,9 +66,9 @@ DeviceError device_mute(DeviceType type, uint32_t device_idx);
bool device_is_muted(DeviceType type, uint32_t device_idx); bool device_is_muted(DeviceType type, uint32_t device_idx);
#ifdef AUDIO DeviceError device_set_VAD_threshold(uint32_t device_idx, float value);
DeviceError device_set_VAD_treshold(uint32_t device_idx, float value);
#endif float device_get_VAD_threshold(uint32_t device_idx);
DeviceError set_source_position(uint32_t device_idx, float x, float y, float z); DeviceError set_source_position(uint32_t device_idx, float x, float y, float z);
@ -88,6 +88,9 @@ DeviceError close_device(DeviceType type, uint32_t device_idx);
DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t length, uint8_t channels, DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t length, uint8_t channels,
uint32_t sample_rate); uint32_t sample_rate);
/* return current input volume as float in range 0.0-100.0 */
float get_input_volume(void);
void print_al_devices(ToxWindow *self, DeviceType type); void print_al_devices(ToxWindow *self, DeviceType type);
DeviceError selection_valid(DeviceType type, int32_t selection); DeviceError selection_valid(DeviceType type, int32_t selection);

View File

@ -950,7 +950,7 @@ static void init_infobox(ToxWindow *self)
ctx->infobox.win = newwin(INFOBOX_HEIGHT, INFOBOX_WIDTH + 1, 1, x2 - INFOBOX_WIDTH); ctx->infobox.win = newwin(INFOBOX_HEIGHT, INFOBOX_WIDTH + 1, 1, x2 - INFOBOX_WIDTH);
ctx->infobox.starttime = get_unix_time(); ctx->infobox.starttime = get_unix_time();
ctx->infobox.vad_lvl = user_settings->VAD_treshold; ctx->infobox.vad_lvl = 0.0f;
ctx->infobox.active = true; ctx->infobox.active = true;
strcpy(ctx->infobox.timestr, "00"); strcpy(ctx->infobox.timestr, "00");
} }

View File

@ -353,12 +353,14 @@ uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *pre
return 0; return 0;
} }
const int len = strlen(prefix); const size_t len = strlen(prefix);
if (len == 2 * TOX_PUBLIC_KEY_SIZE) { if (len == 2 * TOX_PUBLIC_KEY_SIZE) {
for (uint32_t i = 0; i < chat->num_peers; ++i) { for (uint32_t i = 0; i < chat->num_peers; ++i) {
if (strcasecmp(prefix, chat->name_list[i].pubkey_str) == 0) { NameListEntry *entry = &chat->name_list[i];
entries[0] = &chat->name_list[i];
if (strcasecmp(prefix, entry->pubkey_str) == 0) {
entries[0] = entry;
return 1; return 1;
} }
} }
@ -367,9 +369,11 @@ uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *pre
uint32_t n = 0; uint32_t n = 0;
for (uint32_t i = 0; i < chat->num_peers; ++i) { for (uint32_t i = 0; i < chat->num_peers; ++i) {
if (strncmp(prefix, chat->name_list[i].name, len) == 0 NameListEntry *entry = &chat->name_list[i];
|| strncasecmp(prefix, chat->name_list[i].pubkey_str, len) == 0) {
entries[n] = &chat->name_list[i]; if (strncmp(prefix, entry->name, len) == 0
|| strncasecmp(prefix, entry->pubkey_str, len) == 0) {
entries[n] = entry;
++n; ++n;
if (n == maxpeers) { if (n == maxpeers) {
@ -418,11 +422,14 @@ static void conference_update_name_list(uint32_t conferencenum)
uint32_t count = 0; uint32_t count = 0;
for (uint32_t i = 0; i < chat->max_idx; ++i) { for (uint32_t i = 0; i < chat->max_idx; ++i) {
if (chat->peer_list[i].active) { const ConferencePeer *peer = &chat->peer_list[i];
memcpy(chat->name_list[count].name, chat->peer_list[i].name, chat->peer_list[i].name_length + 1); NameListEntry *entry = &chat->name_list[count];
bin_pubkey_to_string(chat->peer_list[i].pubkey, sizeof(chat->peer_list[i].pubkey),
chat->name_list[count].pubkey_str, sizeof(chat->name_list[count].pubkey_str)); if (peer->active) {
chat->name_list[count].peernum = i; memcpy(entry->name, peer->name, peer->name_length + 1);
bin_pubkey_to_string(peer->pubkey, sizeof(peer->pubkey),
entry->pubkey_str, sizeof(entry->pubkey_str));
entry->peernum = i;
++count; ++count;
} }
} }
@ -451,7 +458,7 @@ static int realloc_peer_list(ConferenceChat *chat, uint32_t num_peers)
return 0; return 0;
} }
struct ConferencePeer *tmp_list = realloc(chat->peer_list, num_peers * sizeof(struct ConferencePeer)); ConferencePeer *tmp_list = realloc(chat->peer_list, num_peers * sizeof(ConferencePeer));
if (!tmp_list) { if (!tmp_list) {
return -1; return -1;
@ -462,13 +469,35 @@ static int realloc_peer_list(ConferenceChat *chat, uint32_t num_peers)
return 0; return 0;
} }
/* return NULL if peer or conference doesn't exist */
static ConferencePeer *peer_in_conference(uint32_t conferencenum, uint32_t peernum)
{
if (conferencenum >= MAX_CONFERENCE_NUM) {
return NULL;
}
const ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active || peernum > chat->max_idx) {
return NULL;
}
ConferencePeer *peer = &chat->peer_list[peernum];
if (!peer->active) {
return NULL;
}
return peer;
}
#ifdef AUDIO #ifdef AUDIO
static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t peernum) static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t peernum)
{ {
ConferenceChat *chat = &conferences[conferencenum]; ConferenceChat *chat = &conferences[conferencenum];
ConferencePeer *peer = &chat->peer_list[peernum]; ConferencePeer *peer = &chat->peer_list[peernum];
if (!peer->sending_audio) { if (peer == NULL || !peer->sending_audio) {
return; return;
} }
@ -497,10 +526,10 @@ static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t pee
#endif #endif
static bool find_peer_by_pubkey(ConferencePeer *list, uint32_t num_peers, uint8_t *pubkey, uint32_t *idx) static bool find_peer_by_pubkey(const ConferencePeer *list, uint32_t num_peers, uint8_t *pubkey, uint32_t *idx)
{ {
for (uint32_t i = 0; i < num_peers; ++i) { for (uint32_t i = 0; i < num_peers; ++i) {
ConferencePeer *peer = &list[i]; const ConferencePeer *peer = &list[i];
if (peer->active && memcmp(peer->pubkey, pubkey, TOX_PUBLIC_KEY_SIZE) == 0) { if (peer->active && memcmp(peer->pubkey, pubkey, TOX_PUBLIC_KEY_SIZE) == 0) {
*idx = i; *idx = i;
@ -567,7 +596,7 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers,
peer->active = true; peer->active = true;
peer->name_length = length; peer->name_length = length;
peer->peernumber = i; peer->peernum = i;
#ifdef AUDIO #ifdef AUDIO
set_peer_audio_position(m, conferencenum, i); set_peer_audio_position(m, conferencenum, i);
@ -628,28 +657,19 @@ static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint3
return; return;
} }
ConferenceChat *chat = &conferences[conferencenum]; const ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
if (!chat->active) { if (peer != NULL && peer->name_length > 0) {
return; ChatContext *ctx = self->chatwin;
} char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
for (uint32_t i = 0; i < chat->max_idx; ++i) { char tmp_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
ConferencePeer *peer = &chat->peer_list[i]; snprintf(tmp_event, sizeof(tmp_event), "is now known as %s", (const char *) name);
if (peer->active && peer->peernumber == peernum && peer->name_length > 0) { write_to_log(tmp_event, peer->name, ctx->log, true);
ChatContext *ctx = self->chatwin; line_info_add(self, timefrmt, peer->name, (const char *) name, NAME_CHANGE, 0, 0, " is now known as ");
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
char tmp_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
snprintf(tmp_event, sizeof(tmp_event), "is now known as %s", (const char *) name);
write_to_log(tmp_event, peer->name, ctx->log, true);
line_info_add(self, timefrmt, peer->name, (const char *) name, NAME_CHANGE, 0, 0, " is now known as ");
break;
}
} }
conference_onConferenceNameListChange(self, m, conferencenum); conference_onConferenceNameListChange(self, m, conferencenum);
@ -716,6 +736,7 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
} }
bool input_ret = false; bool input_ret = false;
ConferenceChat *chat = &conferences[self->num];
if (key == L'\t') { /* TAB key: auto-completes peer name or command */ if (key == L'\t') { /* TAB key: auto-completes peer name or command */
input_ret = true; input_ret = true;
@ -725,14 +746,14 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
/* TODO: make this not suck */ /* TODO: make this not suck */
if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) { if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) {
const char **complete_strs = calloc(conferences[self->num].num_peers, sizeof(const char *)); const char **complete_strs = calloc(chat->num_peers, sizeof(const char *));
if (complete_strs) { if (complete_strs) {
for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { for (uint32_t i = 0; i < chat->num_peers; ++i) {
complete_strs[i] = (const char *) conferences[self->num].name_list[i].name; complete_strs[i] = (const char *) chat->name_list[i].name;
} }
diff = complete_line(self, complete_strs, conferences[self->num].num_peers); diff = complete_line(self, complete_strs, chat->num_peers);
free(complete_strs); free(complete_strs);
} }
} else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) { } else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) {
@ -746,20 +767,20 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
#endif #endif
else if (wcsncmp(ctx->line, L"/mute ", wcslen(L"/mute ")) == 0) { else if (wcsncmp(ctx->line, L"/mute ", wcslen(L"/mute ")) == 0) {
const char **complete_strs = calloc(conferences[self->num].num_peers, sizeof(const char *)); const char **complete_strs = calloc(chat->num_peers, sizeof(const char *));
if (complete_strs) { if (complete_strs) {
for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { for (uint32_t i = 0; i < chat->num_peers; ++i) {
complete_strs[i] = (const char *) conferences[self->num].name_list[i].name; complete_strs[i] = (const char *) chat->name_list[i].name;
} }
diff = complete_line(self, complete_strs, conferences[self->num].num_peers); diff = complete_line(self, complete_strs, chat->num_peers);
if (diff == -1) { if (diff == -1) {
for (uint32_t i = 0; i < conferences[self->num].num_peers; ++i) { for (uint32_t i = 0; i < chat->num_peers; ++i) {
complete_strs[i] = (const char *) conferences[self->num].name_list[i].pubkey_str; complete_strs[i] = (const char *) chat->name_list[i].pubkey_str;
} }
diff = complete_line(self, complete_strs, conferences[self->num].num_peers); diff = complete_line(self, complete_strs, chat->num_peers);
} }
free(complete_strs); free(complete_strs);
@ -784,14 +805,14 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
input_ret = true; input_ret = true;
const int L = y2 - CHATBOX_HEIGHT - sidebar_offset(self->num); const int L = y2 - CHATBOX_HEIGHT - sidebar_offset(self->num);
if (conferences[self->num].side_pos < (int64_t) conferences[self->num].num_peers - L) { if (chat->side_pos < (int64_t) chat->num_peers - L) {
++conferences[self->num].side_pos; ++chat->side_pos;
} }
} else if (key == T_KEY_C_UP) { } else if (key == T_KEY_C_UP) {
input_ret = true; input_ret = true;
if (conferences[self->num].side_pos > 0) { if (chat->side_pos > 0) {
--conferences[self->num].side_pos; --chat->side_pos;
} }
} else if (key == L'\r') { } else if (key == L'\r') {
input_ret = true; input_ret = true;
@ -850,14 +871,14 @@ static void draw_peer(ToxWindow *self, Tox *m, ChatContext *ctx, uint32_t i)
if (audio) { if (audio) {
#ifdef AUDIO #ifdef AUDIO
pthread_mutex_lock(&Winthread.lock); pthread_mutex_lock(&Winthread.lock);
const ConferencePeer *peer = &conferences[self->num].peer_list[peernum]; const ConferencePeer *peer = peer_in_conference(self->num, peernum);
const bool audio_active = is_self const bool audio_active = is_self
? !timed_out(conferences[self->num].last_sent_audio, 2) ? !timed_out(conferences[self->num].last_sent_audio, 2)
: peer->active && peer->sending_audio && !timed_out(peer->last_audio_time, 2); : peer != NULL && peer->sending_audio && !timed_out(peer->last_audio_time, 2);
const bool mute = audio_active && const bool mute = audio_active &&
(is_self (is_self
? device_is_muted(input, conferences[self->num].audio_in_idx) ? device_is_muted(input, conferences[self->num].audio_in_idx)
: device_is_muted(output, peer->audio_out_idx)); : peer != NULL && device_is_muted(output, peer->audio_out_idx));
pthread_mutex_unlock(&Winthread.lock); pthread_mutex_unlock(&Winthread.lock);
const int aud_attr = A_BOLD | COLOR_PAIR(audio_active && !mute ? GREEN : RED); const int aud_attr = A_BOLD | COLOR_PAIR(audio_active && !mute ? GREEN : RED);
@ -929,14 +950,27 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
#ifdef AUDIO #ifdef AUDIO
pthread_mutex_lock(&Winthread.lock); pthread_mutex_lock(&Winthread.lock);
const bool mic_on = !device_is_muted(input, conferences[self->num].audio_in_idx); const bool mic_on = !device_is_muted(input, conferences[self->num].audio_in_idx);
const float volume = get_input_volume();
const float threshold = device_get_VAD_threshold(conferences[self->num].audio_in_idx);
pthread_mutex_unlock(&Winthread.lock); pthread_mutex_unlock(&Winthread.lock);
wmove(ctx->sidebar, line, 1); wmove(ctx->sidebar, line, 1);
wattron(ctx->sidebar, A_BOLD); wattron(ctx->sidebar, A_BOLD);
wprintw(ctx->sidebar, "Mic: "); wprintw(ctx->sidebar, "Mic: ");
const int color = mic_on ? GREEN : RED; const int color = mic_on && volume > threshold ? GREEN : RED;
wattron(ctx->sidebar, COLOR_PAIR(color)); wattron(ctx->sidebar, COLOR_PAIR(color));
wprintw(ctx->sidebar, mic_on ? "ON" : "OFF");
if (mic_on) {
float v = volume;
while (v > 0.0f) {
wprintw(ctx->sidebar, v > 10.0f ? (v > 15.0f ? "*" : "+") : (v > 5.0f ? "-" : "."));
v -= 20.0f;
}
} else {
wprintw(ctx->sidebar, "OFF");
}
wattroff(ctx->sidebar, COLOR_PAIR(color)); wattroff(ctx->sidebar, COLOR_PAIR(color));
wattroff(ctx->sidebar, A_BOLD); wattroff(ctx->sidebar, A_BOLD);
++line; ++line;
@ -953,10 +987,9 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
mvwhline(ctx->sidebar, line, 1, ACS_HLINE, SIDEBAR_WIDTH - 1); mvwhline(ctx->sidebar, line, 1, ACS_HLINE, SIDEBAR_WIDTH - 1);
++line; ++line;
int maxlines = y2 - header_lines - CHATBOX_HEIGHT; for (uint32_t i = 0;
uint32_t i; i < num_peers && i < y2 - header_lines - CHATBOX_HEIGHT;
++i) {
for (i = 0; i < num_peers && i < maxlines; ++i) {
wmove(ctx->sidebar, i + header_lines, 1); wmove(ctx->sidebar, i + header_lines, 1);
draw_peer(self, m, ctx, i); draw_peer(self, m, ctx, i);
} }
@ -1060,40 +1093,32 @@ static ToxWindow *new_conference_chat(uint32_t conferencenum)
#define CONFAV_AUDIO_CHANNELS 1 #define CONFAV_AUDIO_CHANNELS 1
#define CONFAV_SAMPLES_PER_FRAME (CONFAV_SAMPLE_RATE * CONFAV_FRAME_DURATION / 1000) #define CONFAV_SAMPLES_PER_FRAME (CONFAV_SAMPLE_RATE * CONFAV_FRAME_DURATION / 1000)
void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernumber, const int16_t *pcm, void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernum, const int16_t *pcm,
unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata)
{ {
ConferenceChat *chat = &conferences[conferencenum]; ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
if (!chat->active) { if (peer == NULL) {
return; return;
} }
for (uint32_t i = 0; i < chat->max_idx; ++i) { if (!peer->sending_audio) {
ConferencePeer *peer = &chat->peer_list[i]; if (open_output_device(&peer->audio_out_idx,
sample_rate, CONFAV_FRAME_DURATION, channels) != de_None) {
if (!peer->active || peer->peernumber != peernumber) { // TODO: error message?
continue; return;
} }
if (!peer->sending_audio) { peer->sending_audio = true;
if (open_output_device(&peer->audio_out_idx,
sample_rate, CONFAV_FRAME_DURATION, channels) != de_None) {
// TODO: error message?
return;
}
peer->sending_audio = true; set_peer_audio_position(tox, conferencenum, peernum);
set_peer_audio_position(tox, conferencenum, i);
}
write_out(peer->audio_out_idx, pcm, samples, channels, sample_rate);
peer->last_audio_time = get_unix_time();
return;
} }
write_out(peer->audio_out_idx, pcm, samples, channels, sample_rate);
peer->last_audio_time = get_unix_time();
return;
} }
static void conference_read_device_callback(const int16_t *captured, uint32_t size, void *data) static void conference_read_device_callback(const int16_t *captured, uint32_t size, void *data)
@ -1155,12 +1180,12 @@ bool disable_conference_audio(Tox *tox, uint32_t conferencenum)
chat->audio_enabled = false; chat->audio_enabled = false;
} }
return (toxav_groupchat_disable_av(tox, conferencenum) == 0); return toxav_groupchat_disable_av(tox, conferencenum) == 0;
} }
bool conference_mute_self(uint32_t conferencenum) bool conference_mute_self(uint32_t conferencenum)
{ {
ConferenceChat *chat = &conferences[conferencenum]; const ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active || !chat->audio_enabled) { if (!chat->active || !chat->audio_enabled) {
return false; return false;
@ -1177,20 +1202,42 @@ bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum
return conference_mute_self(conferencenum); return conference_mute_self(conferencenum);
} }
ConferenceChat *chat = &conferences[conferencenum]; const ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active || !chat->audio_enabled if (!chat->active || !chat->audio_enabled
|| peernum > chat->max_idx) { || peernum > chat->max_idx) {
return false; return false;
} }
const ConferencePeer *peer = &chat->peer_list[peernum]; const ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
if (!peer->active || !peer->sending_audio) { if (peer == NULL || !peer->sending_audio) {
return false; return false;
} }
device_mute(output, peer->audio_out_idx); device_mute(output, peer->audio_out_idx);
return true; return true;
} }
bool conference_set_VAD_threshold(uint32_t conferencenum, float threshold)
{
const ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active || !chat->audio_enabled) {
return false;
}
return (device_set_VAD_threshold(chat->audio_in_idx, threshold) == de_None);
}
float conference_get_VAD_threshold(uint32_t conferencenum)
{
const ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active || !chat->audio_enabled) {
return 0.0f;
}
return device_get_VAD_threshold(chat->audio_in_idx);
}
#endif #endif

View File

@ -34,7 +34,7 @@ typedef struct ConferencePeer {
bool active; bool active;
uint8_t pubkey[TOX_PUBLIC_KEY_SIZE]; uint8_t pubkey[TOX_PUBLIC_KEY_SIZE];
uint32_t peernumber; uint32_t peernum; /* index in chat->peer_list */
char name[TOX_MAX_NAME_LENGTH]; char name[TOX_MAX_NAME_LENGTH];
size_t name_length; size_t name_length;
@ -96,11 +96,13 @@ uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *pre
bool init_conference_audio_input(Tox *tox, uint32_t conferencenum); bool init_conference_audio_input(Tox *tox, uint32_t conferencenum);
bool enable_conference_audio(Tox *tox, uint32_t conferencenum); bool enable_conference_audio(Tox *tox, uint32_t conferencenum);
bool disable_conference_audio(Tox *tox, uint32_t conferencenum); bool disable_conference_audio(Tox *tox, uint32_t conferencenum);
void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernumber, void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernum,
const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t
sample_rate, void *userdata); sample_rate, void *userdata);
bool conference_mute_self(uint32_t conferencenum); bool conference_mute_self(uint32_t conferencenum);
bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum); bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum);
bool conference_set_VAD_threshold(uint32_t conferencenum, float threshold);
float conference_get_VAD_threshold(uint32_t conferencenum);
#endif /* CONFERENCE_H */ #endif /* CONFERENCE_H */

View File

@ -21,6 +21,7 @@
*/ */
#include <string.h> #include <string.h>
#include <stdlib.h>
#include "toxic.h" #include "toxic.h"
#include "windows.h" #include "windows.h"
@ -29,6 +30,11 @@
#include "log.h" #include "log.h"
#include "conference.h" #include "conference.h"
static void print_err(ToxWindow *self, const char *error_str)
{
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
}
void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{ {
UNUSED_VAR(window); UNUSED_VAR(window);
@ -40,12 +46,12 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc,
size_t tlen = tox_conference_get_title_size(m, self->num, &err); size_t tlen = tox_conference_get_title_size(m, self->num, &err);
if (err != TOX_ERR_CONFERENCE_TITLE_OK || tlen >= sizeof(title)) { if (err != TOX_ERR_CONFERENCE_TITLE_OK || tlen >= sizeof(title)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title is not set"); print_err(self, "Title is not set");
return; return;
} }
if (!tox_conference_get_title(m, self->num, (uint8_t *) title, &err)) { if (!tox_conference_get_title(m, self->num, (uint8_t *) title, &err)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title is not set"); print_err(self, "Title is not set");
return; return;
} }
@ -81,11 +87,11 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc,
write_to_log(tmp_event, selfnick, self->chatwin->log, true); write_to_log(tmp_event, selfnick, self->chatwin->log, true);
} }
#ifdef AUDIO
void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{ {
UNUSED_VAR(window); UNUSED_VAR(window);
#ifdef AUDIO
bool enable; bool enable;
if (argc == 1 && !strcasecmp(argv[1], "on")) { if (argc == 1 && !strcasecmp(argv[1], "on")) {
@ -93,43 +99,38 @@ void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*
} else if (argc == 1 && !strcasecmp(argv[1], "off")) { } else if (argc == 1 && !strcasecmp(argv[1], "off")) {
enable = false; enable = false;
} else { } else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Please specify: on | off"); print_err(self, "Please specify: on | off");
return; return;
} }
if ((enable ? enable_conference_audio : disable_conference_audio)(m, self->num)) { if (enable ? enable_conference_audio(m, self->num) : disable_conference_audio(m, self->num)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, enable ? "Enabled conference audio" : "Disabled conference audio"); print_err(self, enable ? "Enabled conference audio" : "Disabled conference audio");
} else { } else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, enable ? "Failed to enable audio" : "Failed to disable audio"); print_err(self, enable ? "Failed to enable audio" : "Failed to disable audio");
} }
#endif
} }
void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{ {
UNUSED_VAR(window); UNUSED_VAR(window);
#ifdef AUDIO
if (argc < 1) { if (argc < 1) {
if (conference_mute_self(self->num)) { if (conference_mute_self(self->num)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled self audio mute status"); print_err(self, "Toggled self audio mute status");
} else { } else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No audio input to mute"); print_err(self, "No audio input to mute");
} }
} else { } else {
NameListEntry *entries[16]; NameListEntry *entries[16];
uint32_t n = get_name_list_entries_by_prefix(self->num, argv[1], entries, 16); uint32_t n = get_name_list_entries_by_prefix(self->num, argv[1], entries, 16);
if (n == 0) { if (n == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No such peer"); print_err(self, "No such peer");
return; return;
} }
if (n > 1) { if (n > 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, print_err(self, "Multiple matching peers (use /mute [public key] to disambiguate):");
"Multiple matching peers (use /mute [public key] to disambiguate):");
for (uint32_t i = 0; i < n; ++i) { for (uint32_t i = 0; i < n; ++i) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s: %s", entries[i]->pubkey_str, entries[i]->name); line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s: %s", entries[i]->pubkey_str, entries[i]->name);
@ -141,9 +142,39 @@ void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
if (conference_mute_peer(m, self->num, entries[0]->peernum)) { if (conference_mute_peer(m, self->num, entries[0]->peernum)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name); line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name);
} else { } else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No such audio peer"); print_err(self, "No such audio peer");
} }
} }
#endif
} }
void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
if (argc == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Current VAD threshold: %.1f",
(double) conference_get_VAD_threshold(self->num));
return;
}
if (argc > 1) {
print_err(self, "Only one argument allowed.");
return;
}
char *end;
float value = strtof(argv[1], &end);
if (*end) {
print_err(self, "Invalid input");
return;
}
if (conference_set_VAD_threshold(self->num, value)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Set VAD threshold to %.1f", (double) value);
} else {
print_err(self, "Failed to set conference audio input sensitivity.");
}
}
#endif /* AUDIO */

View File

@ -29,5 +29,6 @@
void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* CONFERENCE_COMMANDS_H */ #endif /* CONFERENCE_COMMANDS_H */

View File

@ -103,7 +103,7 @@ static struct cmd_func conference_commands[] = {
#ifdef AUDIO #ifdef AUDIO
{ "/audio", cmd_enable_audio }, { "/audio", cmd_enable_audio },
{ "/mute", cmd_conference_mute }, { "/mute", cmd_conference_mute },
{ "/sense", cmd_sense }, { "/sense", cmd_conference_sense },
#endif /* AUDIO */ #endif /* AUDIO */
{ NULL, NULL }, { NULL, NULL },
}; };

View File

@ -311,7 +311,8 @@ static void help_draw_conference(ToxWindow *self)
wattroff(win, A_BOLD); wattroff(win, A_BOLD);
wprintw(win, " /audio <on> or <off> : Enable/disable audio in an audio conference\n"); wprintw(win, " /audio <on> or <off> : Enable/disable audio in an audio conference\n");
wprintw(win, " /mute : Toggle self audio mute status\n"); wprintw(win, " /mute : Toggle self audio mute status\n");
wprintw(win, " /mute <nick> or <pubkey> : Toggle peer audio mute status\n\n"); wprintw(win, " /mute <nick> or <pubkey> : Toggle peer audio mute status\n");
wprintw(win, " /sense <n> : VAD sensitivity threshold\n\n");
#endif #endif
help_draw_bottom_menu(win); help_draw_bottom_menu(win);

View File

@ -197,9 +197,7 @@ int bin_pubkey_to_string(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char
return -1; return -1;
} }
size_t i; for (size_t i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i) {
for (i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i) {
snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_pubkey[i] & 0xff); snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_pubkey[i] & 0xff);
} }

View File

@ -204,19 +204,19 @@ static const struct audio_strings {
const char *self; const char *self;
const char *input_device; const char *input_device;
const char *output_device; const char *output_device;
const char *VAD_treshold; const char *VAD_threshold;
} audio_strings = { } audio_strings = {
"audio", "audio",
"input_device", "input_device",
"output_device", "output_device",
"VAD_treshold", "VAD_threshold",
}; };
static void audio_defaults(struct user_settings *settings) static void audio_defaults(struct user_settings *settings)
{ {
settings->audio_in_dev = 0; settings->audio_in_dev = 0;
settings->audio_out_dev = 0; settings->audio_out_dev = 0;
settings->VAD_treshold = 40.0; settings->VAD_threshold = 5.0;
} }
#endif #endif
@ -505,7 +505,7 @@ int settings_load(struct user_settings *s, const char *patharg)
config_setting_lookup_int(setting, audio_strings.output_device, &s->audio_out_dev); config_setting_lookup_int(setting, audio_strings.output_device, &s->audio_out_dev);
s->audio_out_dev = s->audio_out_dev < 0 || s->audio_out_dev > MAX_DEVICES ? 0 : s->audio_out_dev; s->audio_out_dev = s->audio_out_dev < 0 || s->audio_out_dev > MAX_DEVICES ? 0 : s->audio_out_dev;
config_setting_lookup_float(setting, audio_strings.VAD_treshold, &s->VAD_treshold); config_setting_lookup_float(setting, audio_strings.VAD_threshold, &s->VAD_threshold);
} }
#endif #endif

View File

@ -84,7 +84,7 @@ struct user_settings {
#ifdef AUDIO #ifdef AUDIO
int audio_in_dev; int audio_in_dev;
int audio_out_dev; int audio_out_dev;
double VAD_treshold; double VAD_threshold;
#endif #endif
}; };

View File

@ -1440,20 +1440,10 @@ int main(int argc, char **argv)
prompt_init_statusbar(prompt, m, !datafile_exists); prompt_init_statusbar(prompt, m, !datafile_exists);
load_conferences(m); load_conferences(m);
/* thread for ncurses stuff */
if (pthread_mutex_init(&Winthread.lock, NULL) != 0) { if (pthread_mutex_init(&Winthread.lock, NULL) != 0) {
exit_toxic_err("failed in main", FATALERR_MUTEX_INIT); exit_toxic_err("failed in main", FATALERR_MUTEX_INIT);
} }
if (pthread_create(&Winthread.tid, NULL, thread_winref, (void *) m) != 0) {
exit_toxic_err("failed in main", FATALERR_THREAD_CREATE);
}
/* thread for message queue */
if (pthread_create(&cqueue_thread.tid, NULL, thread_cqueue, (void *) m) != 0) {
exit_toxic_err("failed in main", FATALERR_THREAD_CREATE);
}
#ifdef AUDIO #ifdef AUDIO
av = init_audio(prompt, m); av = init_audio(prompt, m);
@ -1479,6 +1469,16 @@ int main(int argc, char **argv)
#endif /* AUDIO */ #endif /* AUDIO */
/* thread for ncurses stuff */
if (pthread_create(&Winthread.tid, NULL, thread_winref, (void *) m) != 0) {
exit_toxic_err("failed in main", FATALERR_THREAD_CREATE);
}
/* thread for message queue */
if (pthread_create(&cqueue_thread.tid, NULL, thread_cqueue, (void *) m) != 0) {
exit_toxic_err("failed in main", FATALERR_THREAD_CREATE);
}
#ifdef PYTHON #ifdef PYTHON
init_python(m); init_python(m);