diff --git a/build/Makefile.am b/build/Makefile.am index 25c6083..b7736f7 100644 --- a/build/Makefile.am +++ b/build/Makefile.am @@ -59,7 +59,9 @@ toxic_LDADD = $(LIBTOXCORE_LDFLAGS) \ if BUILD_AV toxic_SOURCES += $(top_srcdir)/src/audio_call.c \ - $(top_srcdir)/src/audio_call.h + $(top_srcdir)/src/audio_call.h \ + $(top_srcdir)/src/device.c \ + $(top_srcdir)/src/device.h toxic_CFLAGS += $(LIBTOXAV_CFLAGS) \ $(OPENAL_CFLAGS) \ diff --git a/configure.ac b/configure.ac index 3b1f181..a800b6a 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([toxic], [0.4.1], [https://tox.im/]) +AC_INIT([toxic], [0.4.2], [https://tox.im/]) AC_CONFIG_AUX_DIR(configure_aux) AC_CONFIG_SRCDIR([src/toxic.c]) AC_CONFIG_HEADERS([config.h]) @@ -37,7 +37,9 @@ if test -n "$DEPSEARCH"; then CFLAGS="$CFLAGS -I$DEPSEARCH/include" CPPFLAGS="$CPPFLAGS -I$DEPSEARCH/include" LDFLAGS="$LDFLAGS -L$DEPSEARCH/lib" - export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig:/usr/local/lib/pkgconfig + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig:/usr/local/lib/pkgconfig +else + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig fi AC_ARG_WITH(libtoxcore-headers, @@ -417,6 +419,8 @@ if test -n "$AV_SEARCH_DIR"; then CPPFLAGS="$CPPFLAGS -I$AV_SEARCH_DIR/include" LDFLAGS="$LDFLAGS -L$AV_SEARCH_DIR/lib" export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$AV_SEARCH_DIR/lib/pkgconfig +else + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" fi # Check if specified enable diff --git a/src/audio_call.c b/src/audio_call.c index 9b294a3..b4e80cf 100644 --- a/src/audio_call.c +++ b/src/audio_call.c @@ -27,6 +27,7 @@ #include "toxic.h" #include "windows.h" #include "audio_call.h" +#include "device.h" #include "chat_commands.h" #include "global_commands.h" #include "line_info.h" @@ -48,31 +49,34 @@ #define _cbend pthread_exit(NULL) -#define AUDIO_FRAME_SIZE (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000) - #define MAX_CALLS 10 -#define _True 1 -#define _False 0 - -typedef struct _DeviceIx { - - ALCdevice *dhndl; /* Handle of device selected/opened */ - ALCcontext *ctx; /* Device context */ - const char *devices[MAX_DEVICES]; /* Container of available devices */ - int size; /* Size of above container */ - int dix; /* Index of default device */ - int index; /* Current index */ -} DeviceIx; +#define frame_size (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000) typedef struct _Call { pthread_t ttid; /* Transmission thread id */ _Bool ttas; /* Transmission thread active status (0 - stopped, 1- running) */ + int in_idx, out_idx; + pthread_mutex_t mutex; } Call; -struct _ASettings { - DeviceIx device[2]; +void set_call(Call* call, _Bool start) +{ + call->in_idx = -1; + call->out_idx = -1; + + if ( start ) { + call->ttas = _True; + pthread_mutex_init(&call->mutex, NULL); + } + else { + call->ttid = 0; + pthread_mutex_destroy(&call->mutex); + } +} + +struct _ASettings { AudioError errors; ToxAv *av; @@ -82,17 +86,17 @@ struct _ASettings { Call calls[MAX_CALLS]; } ASettins; -void callback_recv_invite ( int32_t call_index, void *arg ); -void callback_recv_ringing ( int32_t call_index, void *arg ); +void callback_recv_invite ( int32_t call_index, void *arg ); +void callback_recv_ringing ( int32_t call_index, void *arg ); void callback_recv_starting ( int32_t call_index, void *arg ); -void callback_recv_ending ( int32_t call_index, void *arg ); -void callback_recv_error ( int32_t call_index, void *arg ); -void callback_call_started ( int32_t call_index, void *arg ); +void callback_recv_ending ( int32_t call_index, void *arg ); +void callback_recv_error ( int32_t call_index, void *arg ); +void callback_call_started ( int32_t call_index, void *arg ); void callback_call_canceled ( int32_t call_index, void *arg ); void callback_call_rejected ( int32_t call_index, void *arg ); -void callback_call_ended ( int32_t call_index, void *arg ); -void callback_requ_timeout ( int32_t call_index, void *arg ); -void callback_peer_timeout ( int32_t call_index, void *arg ); +void callback_call_ended ( int32_t call_index, void *arg ); +void callback_requ_timeout ( int32_t call_index, void *arg ); +void callback_peer_timeout ( int32_t call_index, void *arg ); static void print_err (ToxWindow *self, uint8_t *error_str) @@ -100,228 +104,45 @@ static void print_err (ToxWindow *self, uint8_t *error_str) line_info_add(self, NULL, NULL, NULL, error_str, SYS_MSG, 0, 0); } -/* Opens device under current index - */ -int device_open (ToxWindow *self, _Devices type) -{ - WINDOW *window = self->chatwin->history; - - /* Do not error if no device */ - if ( !ASettins.device[type].size ) return 0; - - ALCdevice *prev_device = ASettins.device[type].dhndl; - - uint8_t msg[MAX_STR_SIZE]; - uint8_t *error = NULL; - - if ( type == input ) { - ASettins.device[type].dhndl = alcCaptureOpenDevice( - ASettins.device[type].devices[ASettins.device[type].index], - av_DefaultSettings.audio_sample_rate, - AL_FORMAT_MONO16, - AUDIO_FRAME_SIZE * 4); - - if (alcGetError(ASettins.device[type].dhndl) != AL_NO_ERROR) { - - /* Now check if we have previous device and act acording to it */ - if ( !prev_device ) { - error = "Error starting input device!"; - - ASettins.errors |= ErrorStartingCaptureDevice; - } else { - error = "Could not start input device, falling back to previous"; - - /* NOTE: What if device is opened? */ - ASettins.device[type].dhndl = prev_device; - } - } else { - /* Close previous */ - if ( prev_device ) - alcCaptureCloseDevice(prev_device); - - if ( window ) { - snprintf(msg, sizeof(msg), "Input device: %s", ASettins.device[type].devices[ASettins.device[type].index]); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } - } - - ASettins.device[type].ctx = NULL; - } else { - ASettins.device[type].dhndl = alcOpenDevice(ASettins.device[type].devices[ASettins.device[type].index]); - - if (alcGetError(ASettins.device[type].dhndl) != AL_NO_ERROR) { - - /* Now check if we have previous device and act acording to it */ - if ( !prev_device ) { - error = "Error starting output device!"; - - ASettins.errors |= ErrorStartingOutputDevice; - ASettins.device[type].ctx = NULL; - } else { - error = "Could not start output device, falling back to previous"; - - /* NOTE: What if device is opened? */ - ASettins.device[type].dhndl = prev_device; - } - } else { - - /* Close previous */ - if ( prev_device ) { - alcCaptureCloseDevice(prev_device); - alcMakeContextCurrent(NULL); - alcDestroyContext(ASettins.device[type].ctx); - } - - ASettins.device[type].ctx = alcCreateContext(ASettins.device[type].dhndl, NULL); - - if ( window ) { - snprintf(msg, sizeof(msg), "Output device: %s", ASettins.device[type].devices[ASettins.device[type].index]); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } - } - } - - if ( error ) { - if ( window ) { - snprintf(msg, sizeof(msg), "Error: %s", error); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } - - return -1; - } else return 0; -} - -int device_close (ToxWindow *self, _Devices type) -{ - /* Only close if device opened */ - if ( ASettins.device[type].dhndl ) { - uint8_t *device = NULL; - - if (type == input) { - alcCaptureCloseDevice(ASettins.device[type].dhndl); - device = "input"; - } else { - alcCloseDevice(ASettins.device[type].dhndl); - alcMakeContextCurrent(NULL); - - if ( ASettins.device[type].ctx ) - alcDestroyContext(ASettins.device[type].ctx); - - device = "output"; - } - - ASettins.device[type].index = ASettins.device[type].dix; - - if ( device == NULL ) { - return -1; - } - - if ( self ) { - uint8_t msg[MAX_STR_SIZE]; - snprintf(msg, sizeof(msg), "Closed %s device", device); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } - } - - return 0; -} - - -int device_set(ToxWindow *self, _Devices type, long int selection) -{ - uint8_t str[MAX_STR_SIZE]; - uint8_t *s_type = type == input ? "input" : "output"; - - if ( selection < 0 || selection >= ASettins.device[type].size ) { - snprintf(str, sizeof(str), "Cannot set audio %s device: Invalid index: %ld", s_type, selection); - line_info_add(self, NULL, NULL, NULL, str, SYS_MSG, 0, 0); - return -1; - } - - ASettins.device[type].index = selection; - - if ( device_open(self, type) != 0 ) { - snprintf(str, sizeof(str), "Cannot open audio %s device index: %ld", s_type, selection); - line_info_add(self, NULL, NULL, NULL, str, SYS_MSG, 0, 0); - return -1; - } - - return 0; -} - - ToxAv *init_audio(ToxWindow *self, Tox *tox) { ASettins.cs = av_DefaultSettings; ASettins.cs.video_height = ASettins.cs.video_width = 0; - ASettins.errors = NoError; + ASettins.errors = ae_None; memset(ASettins.calls, 0, sizeof(Call) * 10); - /* Capture devices */ - const char *stringed_device_list = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); - ASettins.device[input].size = 0; + + /* Streaming stuff from core */ - if ( stringed_device_list ) { - const char *default_device = alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + ASettins.av = toxav_new(tox, MAX_CALLS); - for ( ; *stringed_device_list; ++ASettins.device[input].size ) { - ASettins.device[input].devices[ASettins.device[input].size] = stringed_device_list; - - if ( strcmp( default_device , ASettins.device[input].devices[ASettins.device[input].size] ) == 0 ) - ASettins.device[input].index = ASettins.device[input].dix = ASettins.device[input].size; - - stringed_device_list += strlen( stringed_device_list ) + 1; - } + if ( !ASettins.av ) { + ASettins.errors |= ae_StartingCoreAudio; + return NULL; + } + + if ( init_devices(ASettins.av) == de_InternalError ) { + line_info_add(self, NULL, NULL, NULL, "Failed to init devices", SYS_MSG, 0, 0); + toxav_kill(ASettins.av); + return ASettins.av = NULL; } + toxav_register_callstate_callback(callback_call_started, av_OnStart, self); + toxav_register_callstate_callback(callback_call_canceled, av_OnCancel, self); + toxav_register_callstate_callback(callback_call_rejected, av_OnReject, self); + toxav_register_callstate_callback(callback_call_ended, av_OnEnd, self); + toxav_register_callstate_callback(callback_recv_invite, av_OnInvite, self); - /* Output devices */ - stringed_device_list = alcGetString(NULL, ALC_DEVICE_SPECIFIER); - ASettins.device[output].size = 0; + toxav_register_callstate_callback(callback_recv_ringing, av_OnRinging, self); + toxav_register_callstate_callback(callback_recv_starting, av_OnStarting, self); + toxav_register_callstate_callback(callback_recv_ending, av_OnEnding, self); - if ( stringed_device_list ) { - const char *default_device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); - - for ( ; *stringed_device_list; ++ASettins.device[output].size ) { - ASettins.device[output].devices[ASettins.device[output].size] = stringed_device_list; - - if ( strcmp( default_device , ASettins.device[output].devices[ASettins.device[output].size] ) == 0 ) - ASettins.device[output].index = ASettins.device[output].dix = ASettins.device[output].size; - - stringed_device_list += strlen( stringed_device_list ) + 1; - } - } - - if (!ASettins.device[input].size && !ASettins.device[output].size) { - uint8_t *msg = "No devices: disabling audio!"; - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - ASettins.av = NULL; - } else { - /* Streaming stuff from core */ - - ASettins.av = toxav_new(tox, MAX_CALLS); - - if ( !ASettins.av ) { - ASettins.errors |= ErrorStartingCoreAudio; - return NULL; - } - - toxav_register_callstate_callback(callback_call_started, av_OnStart, self); - toxav_register_callstate_callback(callback_call_canceled, av_OnCancel, self); - toxav_register_callstate_callback(callback_call_rejected, av_OnReject, self); - toxav_register_callstate_callback(callback_call_ended, av_OnEnd, self); - toxav_register_callstate_callback(callback_recv_invite, av_OnInvite, self); - - toxav_register_callstate_callback(callback_recv_ringing, av_OnRinging, self); - toxav_register_callstate_callback(callback_recv_starting, av_OnStarting, self); - toxav_register_callstate_callback(callback_recv_ending, av_OnEnding, self); - - toxav_register_callstate_callback(callback_recv_error, av_OnError, self); - toxav_register_callstate_callback(callback_requ_timeout, av_OnRequestTimeout, self); - toxav_register_callstate_callback(callback_peer_timeout, av_OnPeerTimeout, self); - } + toxav_register_callstate_callback(callback_recv_error, av_OnError, self); + toxav_register_callstate_callback(callback_requ_timeout, av_OnRequestTimeout, self); + toxav_register_callstate_callback(callback_peer_timeout, av_OnPeerTimeout, self); + return ASettins.av; } @@ -335,159 +156,112 @@ void terminate_audio() if ( ASettins.av ) toxav_kill(ASettins.av); - device_close(NULL, input); - device_close(NULL, output); + terminate_devices(); } -int errors() +void read_device_callback (const int16_t* captured, uint32_t size, void* data) { - return ASettins.errors; + int32_t call_index = *((int32_t*)data); /* TODO: Or pass an array of call_idx's */ + + uint8_t encoded_payload[RTP_PAYLOAD_SIZE]; + int32_t payload_size = toxav_prepare_audio_frame(ASettins.av, call_index, encoded_payload, RTP_PAYLOAD_SIZE, captured, size); + if ( payload_size <= 0 || toxav_send_audio(ASettins.av, call_index, encoded_payload, payload_size) < 0 ) { + /*fprintf(stderr, "Could not encode audio packet\n");*/ + } } - - /* * Transmission */ void *transmission(void *arg) -{ +{ +#define lock pthread_mutex_lock(&this_call->mutex) +#define unlock pthread_mutex_unlock(&this_call->mutex) + ToxWindow* self = arg; - int32_t call_index = self->call_index; + int32_t call_index = self->call_idx; /* Missing audio support */ if ( !ASettins.av ) _cbend; Call* this_call = &ASettins.calls[call_index]; - - this_call->ttas = _True; - /* Prepare devices */ - alcCaptureStart(ASettins.device[input].dhndl); - alcMakeContextCurrent(ASettins.device[output].ctx); - int32_t dec_frame_len; - int16_t frame[4096]; - int32_t sample = 0; - uint32_t buffer; - int32_t ready; - int32_t openal_buffers = 5; - uint32_t source, *buffers; - uint8_t encoded_payload[RTP_PAYLOAD_SIZE]; - const int32_t frame_size = AUDIO_FRAME_SIZE; - - /* Prepare buffers */ - buffers = calloc(sizeof(uint32_t), openal_buffers); - alGenBuffers(openal_buffers, buffers); - alGenSources((uint32_t)1, &source); - alSourcei(source, AL_LOOPING, AL_FALSE); - - uint16_t zeros[frame_size]; - memset(zeros, 0, frame_size); int16_t PCM[frame_size]; - - int32_t i = 0; - - for (; i < openal_buffers; ++i) { - alBufferData(buffers[i], AL_FORMAT_MONO16, zeros, frame_size, 48000); - } - - alSourceQueueBuffers(source, openal_buffers, buffers); - alSourcePlay(source); - if (alGetError() != AL_NO_ERROR) { - /* Print something? */ - /*fprintf(stderr, "Error starting audio\n");*/ + + if ( open_primary_device(input, &this_call->in_idx) != de_None ) goto cleanup; + if ( register_device_callback(call_index, this_call->in_idx, read_device_callback, &call_index, _True) != de_None) + /* Set VAD as true for all; TODO: Make it more dynamic */ + goto cleanup; + + if ( open_primary_device(output, &this_call->out_idx) != de_None ) goto cleanup; -// goto cleanup; - } - /* Start transmission */ while (this_call->ttas) { - - alcGetIntegerv(ASettins.device[input].dhndl, ALC_CAPTURE_SAMPLES, (int32_t) sizeof(int32_t), &sample); - - /* RECORD AND SEND */ - if (sample >= frame_size) { - alcCaptureSamples(ASettins.device[input].dhndl, frame, frame_size); - - int32_t payload_size = toxav_prepare_audio_frame(ASettins.av, call_index, encoded_payload, RTP_PAYLOAD_SIZE, frame, frame_size); - if ( payload_size <= 0 || toxav_send_audio(ASettins.av, call_index, encoded_payload, payload_size) < 0 ) { - /*fprintf(stderr, "Could not encode audio packet\n");*/ - } + + lock; + + if (playback_device_ready(this_call->out_idx) == de_Busy) { + unlock; + continue; } - - /* PLAYBACK */ - - alGetSourcei(source, AL_BUFFERS_PROCESSED, &ready); - - if (ready <= 0) continue; - + dec_frame_len = toxav_recv_audio(ASettins.av, call_index, frame_size, PCM); /* Play the packet */ if (dec_frame_len > 0) { - alSourceUnqueueBuffers(source, 1, &buffer); - alBufferData(buffer, AL_FORMAT_MONO16, PCM, dec_frame_len * 2 * 1, 48000); - int32_t error = alGetError(); - - if (error != AL_NO_ERROR) { - fprintf(stderr, "Error setting buffer %d\n", error); - break; - } - - alSourceQueueBuffers(source, 1, &buffer); - - if (alGetError() != AL_NO_ERROR) { - fprintf(stderr, "Error: could not buffer audio\n"); - break; - } - - alGetSourcei(source, AL_SOURCE_STATE, &ready); - - if (ready != AL_PLAYING) alSourcePlay(source); + write_out(this_call->out_idx, PCM, dec_frame_len, av_DefaultSettings.audio_channels); } - else { - /* Error ignored */ + else if (dec_frame_len != 0) { + /* >implying it'll ever get an error */ } - + + unlock; + usleep(1000); } cleanup: - - alDeleteSources(1, &source); - alDeleteBuffers(openal_buffers, buffers); - /* - device_close(NULL, input); - device_close(NULL, output);*/ - + if ( this_call->in_idx != -1 ) if ( close_device(input, this_call->in_idx) != de_None ) + line_info_add(self, NULL, NULL, NULL, "Failed to close input device!", SYS_MSG, 0, 0); + + if ( this_call->out_idx != -1 ) if ( close_device(output, this_call->out_idx) != de_None ) + line_info_add(self, NULL, NULL, NULL, "Failed to close output device!", SYS_MSG, 0, 0); + + set_call(this_call, _False); + _cbend; } int start_transmission(ToxWindow *self) { - if ( !ASettins.av || self->call_index == -1 ) return -1; + if ( !ASettins.av || self->call_idx == -1 ) return -1; /* Don't provide support for video */ - if ( 0 != toxav_prepare_transmission(ASettins.av, self->call_index, &ASettins.cs, 0) ) { + if ( 0 != toxav_prepare_transmission(ASettins.av, self->call_idx, &ASettins.cs, 0) ) { line_info_add(self, NULL, NULL, NULL, "Could not prepare transmission", SYS_MSG, 0, 0); } - if ( !toxav_capability_supported(ASettins.av, self->call_index, AudioDecoding) || - !toxav_capability_supported(ASettins.av, self->call_index, AudioEncoding) ) + if ( !toxav_capability_supported(ASettins.av, self->call_idx, AudioDecoding) || + !toxav_capability_supported(ASettins.av, self->call_idx, AudioEncoding) ) return -1; - if ( 0 != pthread_create(&ASettins.calls[self->call_index].ttid, NULL, transmission, self ) && - 0 != pthread_detach(ASettins.calls[self->call_index].ttid) ) { + set_call(&ASettins.calls[self->call_idx], _True); + + if ( 0 != pthread_create(&ASettins.calls[self->call_idx].ttid, NULL, transmission, self ) && + 0 != pthread_detach(ASettins.calls[self->call_idx].ttid) ) { return -1; - } + } } int stop_transmission(int call_index) { - toxav_kill_transmission(ASettins.av, call_index); - ASettins.calls[call_index].ttas = _False; + if ( ASettins.calls[call_index].ttas ) { + toxav_kill_transmission(ASettins.av, call_index); + ASettins.calls[call_index].ttas = _False; + } } /* * End of transmission @@ -517,7 +291,7 @@ void callback_recv_starting ( int32_t call_index, void* arg ) ToxWindow* windows = arg; int i; for (i = 0; i < MAX_WINDOWS_NUM; ++i) - if (windows[i].onStarting != NULL && windows[i].call_index == call_index) { + if (windows[i].onStarting != NULL && windows[i].call_idx == call_index) { windows[i].onStarting(&windows[i], ASettins.av, call_index); if ( 0 != start_transmission(&windows[i]) ) {/* YEAH! */ line_info_add(&windows[i], NULL, NULL, NULL, "Error starting transmission!", SYS_MSG, 0, 0); @@ -540,7 +314,7 @@ void callback_call_started ( int32_t call_index, void* arg ) ToxWindow* windows = arg; int i; for (i = 0; i < MAX_WINDOWS_NUM; ++i) - if (windows[i].onStart != NULL && windows[i].call_index == call_index) { + if (windows[i].onStart != NULL && windows[i].call_idx == call_index) { windows[i].onStart(&windows[i], ASettins.av, call_index); if ( 0 != start_transmission(&windows[i]) ) {/* YEAH! */ line_info_add(&windows[i], NULL, NULL, NULL, "Error starting transmission!", SYS_MSG, 0, 0); @@ -601,7 +375,7 @@ void cmd_call(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA goto on_error; } - ToxAvError error = toxav_call(ASettins.av, &self->call_index, self->num, TypeAudio, 30); + ToxAvError error = toxav_call(ASettins.av, &self->call_idx, self->num, TypeAudio, 30); if ( error != ErrorNone ) { if ( error == ErrorAlreadyInCall ) error_str = "Already in a call!"; @@ -610,7 +384,7 @@ void cmd_call(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA goto on_error; } - snprintf(msg, sizeof(msg), "Calling... idx: %d", self->call_index); + snprintf(msg, sizeof(msg), "Calling... idx: %d", self->call_idx); line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); return; @@ -633,7 +407,7 @@ void cmd_answer(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ goto on_error; } - ToxAvError error = toxav_answer(ASettins.av, self->call_index, TypeAudio); + ToxAvError error = toxav_answer(ASettins.av, self->call_idx, TypeAudio); if ( error != ErrorNone ) { if ( error == ErrorInvalidState ) error_str = "Cannot answer in invalid state!"; @@ -664,7 +438,7 @@ void cmd_reject(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ goto on_error; } - ToxAvError error = toxav_reject(ASettins.av, self->call_index, "Why not?"); + ToxAvError error = toxav_reject(ASettins.av, self->call_idx, "Why not?"); if ( error != ErrorNone ) { if ( error == ErrorInvalidState ) error_str = "Cannot reject in invalid state!"; @@ -695,7 +469,7 @@ void cmd_hangup(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ goto on_error; } - ToxAvError error = toxav_hangup(ASettins.av, self->call_index); + ToxAvError error = toxav_hangup(ASettins.av, self->call_idx); if ( error != ErrorNone ) { if ( error == ErrorInvalidState ) error_str = "Cannot hangup in invalid state!"; @@ -724,7 +498,7 @@ void cmd_cancel(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ goto on_error; } - ToxAvError error = toxav_cancel(ASettins.av, self->call_index, self->num, + ToxAvError error = toxav_cancel(ASettins.av, self->call_idx, self->num, "Only those who appreciate small things know the beauty that is life"); if ( error != ErrorNone ) { @@ -754,7 +528,7 @@ void cmd_list_devices(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (* goto on_error; } - _Devices type; + DeviceType type; if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ type = input; @@ -768,18 +542,14 @@ void cmd_list_devices(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (* return; } - int i = 0; - - for ( ; i < ASettins.device[type].size; i ++) { - snprintf(msg, sizeof(msg), "%d: %s", i, ASettins.device[type].devices[i]); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } + print_devices(self, type); return; on_error: print_err (self, error_str); } +/* This changes primary device only */ void cmd_change_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { uint8_t msg[MAX_STR_SIZE]; @@ -793,12 +563,7 @@ void cmd_change_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char ( goto on_error; } - if ( ASettins.calls[self->call_index].ttas ) { /* Transmission is active */ - error_str = "Cannot change device while active transmission"; - goto on_error; - } - - _Devices type; + DeviceType type; if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ type = input; @@ -821,22 +586,151 @@ void cmd_change_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char ( goto on_error; } - if ( device_close(self, type) != 0 ) { - error_str = "Could not close device!"; + if ( set_primary_device(type, selection) == de_InvalidSelection ) { + error_str="Invalid selection!"; goto on_error; } - if ( device_set(self, type, selection ) == 0) { - /*snprintf(msg, sizeof(msg), "Selected: %s", ASettins.device[type].devices[selection]); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0);*/ - } - - if ( device_open(self, type) == 0 ) { - snprintf(msg, sizeof(msg), "Now using: %s", ASettins.device[type].devices[selection]); - line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - } - return; on_error: print_err (self, error_str); } + +void cmd_ccur_device(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + uint8_t msg[MAX_STR_SIZE]; + uint8_t *error_str; + + if ( argc != 2 ) { + if ( argc < 1 ) error_str = "Type must be specified!"; + else if ( argc < 2 ) error_str = "Must have id!"; + else error_str = "Only two arguments allowed!"; + + goto on_error; + } + + DeviceType type; + + if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ + type = input; + + else if ( strcmp(argv[1], "out") == 0 ) /* Output devices */ + type = output; + + else { + snprintf(msg, sizeof(msg), "Invalid type: %s", argv[1]); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + return; + } + + + char *end; + long int selection = strtol(argv[2], &end, 10); + + if ( *end ) { + error_str = "Invalid input"; + goto on_error; + } + + if ( selection_valid(type, selection) == de_InvalidSelection ) { + error_str="Invalid selection!"; + goto on_error; + } + + /* If call is active, change device */ + if ( self->call_idx > -1) { + Call* this_call = &ASettins.calls[self->call_idx]; + if (this_call->ttas) { + if (type == output) { + pthread_mutex_lock(&this_call->mutex); + close_device(output, this_call->out_idx); + open_device(output, selection, &this_call->out_idx); + pthread_mutex_unlock(&this_call->mutex); + } + else { + /* TODO: check for failure */ + close_device(input, this_call->in_idx); + open_device(input, selection, &this_call->in_idx); + /* Set VAD as true for all; TODO: Make it more dynamic */ + register_device_callback(self->call_idx, this_call->in_idx, read_device_callback, &self->call_idx, _True); + } + } + } + + self->device_selection[type] = selection; + + return; + on_error: + print_err (self, error_str); +} + +void cmd_mute(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + uint8_t msg[MAX_STR_SIZE]; + uint8_t *error_str; + + if ( argc != 1 ) { + if ( argc < 1 ) error_str = "Type must be specified!"; + else error_str = "Only two arguments allowed!"; + + goto on_error; + } + + DeviceType type; + + if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ + type = input; + + else if ( strcmp(argv[1], "out") == 0 ) /* Output devices */ + type = output; + + else { + snprintf(msg, sizeof(msg), "Invalid type: %s", argv[1]); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + return; + } + + + /* If call is active, use this_call values */ + if ( self->call_idx > -1) { + Call* this_call = &ASettins.calls[self->call_idx]; + + pthread_mutex_lock(&this_call->mutex); + device_mute(type, type == input ? this_call->in_idx : this_call->out_idx); + pthread_mutex_unlock(&this_call->mutex); + } + + return; + + on_error: + print_err (self, error_str); +} + +void cmd_sense(WINDOW * window, ToxWindow * self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + uint8_t msg[MAX_STR_SIZE]; + uint8_t *error_str; + + if ( argc != 1 ) { + if ( argc < 1 ) error_str = "Must have value!"; + else error_str = "Only two arguments allowed!"; + + goto on_error; + } + + char *end; + float value = strtof(argv[1], &end); + + if ( *end ) { + error_str = "Invalid input"; + goto on_error; + } + + /* Call must be active */ + if ( self->call_idx > -1) device_set_VAD_treshold(ASettins.calls[self->call_idx].in_idx, value); + + return; + +on_error: + print_err (self, error_str); +} diff --git a/src/audio_call.h b/src/audio_call.h index f360aae..ce7edfe 100644 --- a/src/audio_call.h +++ b/src/audio_call.h @@ -25,22 +25,15 @@ #include -#include "toxic.h" -#include "windows.h" - -#define MAX_DEVICES 32 +#include "device.h" typedef enum _AudioError { - NoError = 0, - ErrorStartingCaptureDevice = 1 << 0, - ErrorStartingOutputDevice = 1 << 1, - ErrorStartingCoreAudio = 1 << 2 + ae_None = 0, + ae_StartingCaptureDevice = 1 << 0, + ae_StartingOutputDevice = 1 << 1, + ae_StartingCoreAudio = 1 << 2 } AudioError; -typedef enum _Devices { - input, - output, -} _Devices; /* You will have to pass pointer to first member of 'windows' * declared in windows.c otherwise undefined behaviour will @@ -48,10 +41,8 @@ typedef enum _Devices { ToxAv *init_audio(ToxWindow *self, Tox *tox); void terminate_audio(); -int errors(); - int start_transmission(ToxWindow *self); int stop_transmission(int call_index); -int device_set(ToxWindow *self, _Devices type, long int selection); + #endif /* _audio_h */ diff --git a/src/chat.c b/src/chat.c index 443ace5..190d67d 100644 --- a/src/chat.c +++ b/src/chat.c @@ -54,7 +54,7 @@ extern struct _Winthread Winthread; extern struct user_settings *user_settings; #ifdef _SUPPORT_AUDIO -#define AC_NUM_CHAT_COMMANDS 23 +#define AC_NUM_CHAT_COMMANDS 26 #else #define AC_NUM_CHAT_COMMANDS 18 #endif /* _SUPPORT_AUDIO */ @@ -87,6 +87,9 @@ static const uint8_t chat_cmd_list[AC_NUM_CHAT_COMMANDS][MAX_CMDNAME_SIZE] = { { "/answer" }, { "/reject" }, { "/hangup" }, + { "/sdev" }, + { "/mute" }, + { "/sense" }, #endif /* _SUPPORT_AUDIO */ }; @@ -430,7 +433,7 @@ void chat_onInvite (ToxWindow *self, ToxAv *av, int call_index) /* call_index is set here and reset on call end */ - self->call_index = call_index; + self->call_idx = call_index; line_info_add(self, NULL, NULL, NULL, "Incoming audio call! Type: \"/answer\" or \"/reject\"", SYS_MSG, 0, 0); @@ -439,7 +442,7 @@ void chat_onInvite (ToxWindow *self, ToxAv *av, int call_index) void chat_onRinging (ToxWindow *self, ToxAv *av, int call_index) { - if ( self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if ( self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; line_info_add(self, NULL, NULL, NULL, "Ringing...\"cancel\" ?", SYS_MSG, 0, 0); @@ -447,7 +450,7 @@ void chat_onRinging (ToxWindow *self, ToxAv *av, int call_index) void chat_onStarting (ToxWindow *self, ToxAv *av, int call_index) { - if ( self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if ( self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; line_info_add(self, NULL, NULL, NULL, "Call started! Type: \"/hangup\" to end it.", SYS_MSG, 0, 0); @@ -455,23 +458,25 @@ void chat_onStarting (ToxWindow *self, ToxAv *av, int call_index) void chat_onEnding (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Call ended!", SYS_MSG, 0, 0); } void chat_onError (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Error!", SYS_MSG, 0, 0); } void chat_onStart (ToxWindow *self, ToxAv *av, int call_index) { - if ( self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if ( self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; line_info_add(self, NULL, NULL, NULL, "Call started! Type: \"/hangup\" to end it.", SYS_MSG, 0, 0); @@ -479,41 +484,46 @@ void chat_onStart (ToxWindow *self, ToxAv *av, int call_index) void chat_onCancel (ToxWindow *self, ToxAv *av, int call_index) { - if ( self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if ( self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Call canceled!", SYS_MSG, 0, 0); } void chat_onReject (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Rejected!", SYS_MSG, 0, 0); } void chat_onEnd (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Call ended!", SYS_MSG, 0, 0); } void chat_onRequestTimeout (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "No answer!", SYS_MSG, 0, 0); } void chat_onPeerTimeout (ToxWindow *self, ToxAv *av, int call_index) { - if (self->call_index != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) return; + self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, "Peer disconnected; call ended!", SYS_MSG, 0, 0); } @@ -949,7 +959,8 @@ ToxWindow new_chat(Tox *m, int32_t friendnum) ret.onRequestTimeout = &chat_onRequestTimeout; ret.onPeerTimeout = &chat_onPeerTimeout; - ret.call_index = -1; + ret.call_idx = -1; + ret.device_selection[0] = ret.device_selection[1] = -1; #endif /* _SUPPORT_AUDIO */ uint8_t name[TOX_MAX_NAME_LENGTH] = {'\0'}; diff --git a/src/chat_commands.c b/src/chat_commands.c index a92d867..8e8202f 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -59,7 +59,7 @@ void cmd_chat_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 1, CYAN); #ifdef _SUPPORT_AUDIO -#define NUMLINES 13 +#define NUMLINES 16 #else #define NUMLINES 9 #endif @@ -72,6 +72,9 @@ void cmd_chat_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg { " /answer : Answer incomming call" }, { " /reject : Reject incoming call" }, { " /hangup : Hangup active call" }, + { " /sdev : Change active device" }, + { " /mute : Mute active device if in call" }, + { " /sense : VAD sensitivity treshold" }, #endif /* _SUPPORT_AUDIO */ { " /invite : Invite friend to a group chat" }, { " /join : Join a pending group chat" }, diff --git a/src/chat_commands.h b/src/chat_commands.h index 0cab454..d1fc34f 100644 --- a/src/chat_commands.h +++ b/src/chat_commands.h @@ -39,6 +39,9 @@ void cmd_answer(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZ void cmd_reject(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_hangup(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_cancel(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_ccur_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_mute(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_sense(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); #endif /* _SUPPORT_AUDIO */ #endif /* #define _chat_commands_h */ diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..d7290e3 --- /dev/null +++ b/src/device.c @@ -0,0 +1,420 @@ +#include "audio_call.h" +#include "line_info.h" + + +#ifdef __APPLE__ +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#define openal_bufs 5 +#define sample_rate 48000 +#define inline__ inline __attribute__((always_inline)) +#define frame_size (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000) + +typedef struct _Device { + ALCdevice *dhndl; /* Handle of device selected/opened */ + ALCcontext *ctx; /* Device context */ + DataHandleCallback cb; /* Use this to handle data from input device usually */ + void* cb_data; /* Data to be passed to callback */ + int32_t call_idx; /* ToxAv call index */ + + uint32_t source, buffers[openal_bufs]; /* Playback source/buffers */ + size_t ref_count; + int32_t selection; + _Bool enable_VAD; + _Bool muted; + float VAD_treshold; /* 40 is usually recommended value */ +} Device; + +const char *ddevice_names[2]; /* Default device */ +const char *devices_names[2][MAX_DEVICES]; /* Container of available devices */ +static int size[2]; /* Size of above containers */ +Device *running[2][MAX_DEVICES]={NULL}; /* Running devices */ +uint32_t primary_device[2] = {0}; /* Primary device */ + +static ToxAv* av = NULL; + +/* q_mutex */ +#define lock pthread_mutex_lock(&mutex) +#define unlock pthread_mutex_unlock(&mutex) +pthread_mutex_t mutex; + + +_Bool thread_running = _True, + thread_paused = _True; /* Thread control */ + +void* thread_poll(void*); +/* Meet devices */ +DeviceError init_devices(ToxAv* av_) +{ + const char *stringed_device_list; + + + + size[input] = 0; + if ( (stringed_device_list = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER)) ) { + ddevice_names[input] = alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + + for ( ; *stringed_device_list && size[input] < MAX_DEVICES; ++size[input] ) { + devices_names[input][size[input]] = stringed_device_list; + stringed_device_list += strlen( stringed_device_list ) + 1; + } + } + + + + size[output] = 0; + if ( (stringed_device_list = alcGetString(NULL, ALC_DEVICE_SPECIFIER)) ) { + ddevice_names[output] = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + + for ( ; *stringed_device_list && size[output] < MAX_DEVICES; ++size[output] ) { + devices_names[output][size[output]] = stringed_device_list; + stringed_device_list += strlen( stringed_device_list ) + 1; + } + } + + // Start poll thread + + pthread_mutex_init(&mutex, NULL); + + pthread_t thread_id; + if ( pthread_create(&thread_id, NULL, thread_poll, NULL) != 0 || pthread_detach(thread_id) != 0) + return de_InternalError; + + av = av_; + + return ae_None; +} + +DeviceError terminate_devices() +{ + /* Cleanup if needed */ + thread_running = false; + usleep(20000); + + pthread_mutex_destroy(&mutex); + + return ae_None; +} + +DeviceError device_mute(DeviceType type, uint32_t device_idx) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + lock; + + Device* device = running[type][device_idx]; + + if (!device) { + unlock; + return de_DeviceNotActive; + } + + device->muted = !device->muted; + + unlock; + return de_None; +} + +DeviceError device_set_VAD_treshold(uint32_t device_idx, float value) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + lock; + + Device* device = running[input][device_idx]; + + if (!device) { + unlock; + return de_DeviceNotActive; + } + + device->VAD_treshold = value; + + unlock; + return de_None; +} + +DeviceError set_primary_device(DeviceType type, int32_t selection) +{ + if (size[type] <= selection || selection < 0) return de_InvalidSelection; + primary_device[type] = selection; + + return de_None; +} + +DeviceError open_primary_device(DeviceType type, uint32_t* device_idx) +{ + return open_device(type, primary_device[type], device_idx); +} + + +// TODO: generate buffers separately +DeviceError open_device(DeviceType type, int32_t selection, uint32_t* device_idx) +{ + if (size[type] <= selection || selection < 0) return de_InvalidSelection; + + lock; + + uint32_t i; + for (i = 0; i < MAX_DEVICES && running[type][i] != NULL; i ++); + + if (i == size[type]) { unlock; return de_AllDevicesBusy; } + else *device_idx = i; + + Device* device = running[type][*device_idx] = calloc(1, sizeof(Device));; + device->selection = selection; + + for (i = 0; i < *device_idx; i ++) { /* Check if any previous has the same selection */ + if ( running[type][i]->selection == selection ) { + device->dhndl = running[type][i]->dhndl; + if (type == output) { + device->ctx = running[type][i]->ctx; + memcpy(device->buffers, running[type][i]->buffers, sizeof(running[type][i]->buffers)); + device->source = running[type][i]->source; + } + device->ref_count++; + unlock; + return de_None; + } + } + + if (type == input) { + device->dhndl = alcCaptureOpenDevice(devices_names[type][selection], + av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16, frame_size * 4); + device->VAD_treshold = 40.0; + } + else { + device->dhndl = alcOpenDevice(devices_names[type][selection]); + if ( !device->dhndl ) { + free(device); + running[type][*device_idx] = NULL; + unlock; + return de_FailedStart; + } + + device->ctx = alcCreateContext(device->dhndl, NULL); + alcMakeContextCurrent(device->ctx); + + alGenBuffers(openal_bufs, device->buffers); + alGenSources((uint32_t)1, &device->source); + alSourcei(device->source, AL_LOOPING, AL_FALSE); + + uint16_t zeros[frame_size]; + memset(zeros, 0, frame_size); + + for ( i =0; i < openal_bufs; ++i) { + alBufferData(device->buffers[i], AL_FORMAT_MONO16, zeros, frame_size, sample_rate); + } + + alSourceQueueBuffers(device->source, openal_bufs, device->buffers); + alSourcePlay(device->source); + } + + if (alcGetError(device->dhndl) != AL_NO_ERROR) { + free(device); + running[type][*device_idx] = NULL; + unlock; + return de_FailedStart; + } + + if (type == input) { + alcCaptureStart(device->dhndl); + thread_paused = _False; + } + + unlock; + return de_None; +} + +DeviceError close_device(DeviceType type, uint32_t device_idx) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + + lock; + Device* device = running[type][device_idx]; + + if (!device) { + unlock; + return de_DeviceNotActive; + } + + if ( !(device->ref_count--) ) { + running[type][device_idx] = NULL; + unlock; + + DeviceError rc = de_None; + + if (type == input) { + if ( !alcCaptureCloseDevice(device->dhndl) ) rc = de_AlError; + } + else { + if (alcGetCurrentContext() != device->ctx) alcMakeContextCurrent(device->ctx); + + alDeleteSources(1, &device->source); + alDeleteBuffers(openal_bufs, device->buffers); + + if ( !alcCloseDevice(device->dhndl) ) rc = de_AlError; + alcMakeContextCurrent(NULL); + if ( device->ctx ) alcDestroyContext(device->ctx); + } + + free(device); + return rc; + } + + return de_None; +} + +DeviceError register_device_callback( int32_t call_idx, uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD) +{ + if (size[input] <= device_idx || !running[input][device_idx] || running[input][device_idx]->dhndl == NULL) + return de_InvalidSelection; + + lock; + running[input][device_idx]->cb = callback; + running[input][device_idx]->cb_data = data; + running[input][device_idx]->enable_VAD = enable_VAD; + running[input][device_idx]->call_idx = call_idx; + unlock; + + return de_None; +} + +inline__ DeviceError playback_device_ready(uint32_t device_idx) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + + Device* device = running[output][device_idx]; + + if (!device) return de_DeviceNotActive; + + int32_t ready; + alGetSourcei(device->source, AL_BUFFERS_PROCESSED, &ready); + + return ready <= 0 ? de_Busy : de_None; +} + +/* TODO: thread safety? */ +inline__ DeviceError write_out(uint32_t device_idx, int16_t* data, uint32_t lenght, uint8_t channels) +{ + if (device_idx >= MAX_DEVICES) return de_InvalidSelection; + + Device* device = running[output][device_idx]; + + if (!device || device->muted) return de_DeviceNotActive; + + alcMakeContextCurrent(device->ctx); /* TODO: Check for error */ + + uint32_t buffer; + int32_t ready; + + + alSourceUnqueueBuffers(device->source, 1, &buffer); + alBufferData(buffer, AL_FORMAT_MONO16, data, lenght * 2 * 1 /*channels*/, sample_rate); // TODO: Frequency must be set dynamically + + int rc = alGetError(); + if (rc != AL_NO_ERROR) { + fprintf(stderr, "Error setting buffer %d\n", rc); + return de_BufferError; + } + + alSourceQueueBuffers(device->source, 1, &buffer); + + rc = alGetError(); + if (alGetError() != AL_NO_ERROR) { + fprintf(stderr, "Error: could not buffer audio: %d\n", rc); + return de_BufferError; + } + + alGetSourcei(device->source, AL_SOURCE_STATE, &ready); + + if (ready != AL_PLAYING) { + alSourcePlay(device->source); + return de_None; + } + + return de_Busy; +} + +void* thread_poll (void* arg) // TODO: maybe use thread for every input source +{ + /* + * NOTE: We only need to poll input devices for data. + */ + (void)arg; + uint32_t i; + int32_t sample = 0; + + int f_size = frame_size; + + while (thread_running) + { + if (thread_paused) usleep(10000); /* Wait for unpause. */ + else + { + for (i = 0; i < size[input]; i ++) + { + lock; + if (running[input][i] != NULL) + { + alcGetIntegerv(running[input][i]->dhndl, ALC_CAPTURE_SAMPLES, sizeof(int32_t), &sample); + + if (sample < f_size) { + unlock; + continue; + } + Device* device = running[input][i]; + + int16_t frame[4096]; + alcCaptureSamples(device->dhndl, frame, f_size); + + if ( device->muted || + (device->enable_VAD && !toxav_has_activity(av, device->call_idx, frame, f_size, device->VAD_treshold))) + { unlock; continue; } /* Skip if no voice activity */ + + if ( device->cb ) device->cb(frame, f_size, device->cb_data); + } + unlock; + } + usleep(5000); + } + } + + pthread_exit(NULL); +} + +void print_devices(ToxWindow* self, DeviceType type) +{ + int i = 0; + for ( ; i < size[type]; i ++) { + uint8_t msg[MAX_STR_SIZE]; + snprintf(msg, sizeof(msg), "%d: %s", i, devices_names[type][i]); + line_info_add(self, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); + } + + return; +} + +DeviceError selection_valid(DeviceType type, int32_t selection) +{ + return (size[type] <= selection || selection < 0) ? de_InvalidSelection : de_None; +} + +void* get_device_callback_data(uint32_t device_idx) +{ + if (size[input] <= device_idx || !running[input][device_idx] || running[input][device_idx]->dhndl == NULL) + return NULL; + + return running[input][device_idx]->cb_data; +} \ No newline at end of file diff --git a/src/device.h b/src/device.h new file mode 100644 index 0000000..9715854 --- /dev/null +++ b/src/device.h @@ -0,0 +1,68 @@ +/* + * Toxic -- Tox Curses Client + */ + +/* + * You can have multiple sources (Input devices) but only one output device. + * Pass buffers to output device via write(); + * Read from running input device(s) via select()/callback combo. + */ + + +#ifndef _device_h +#define _device_h + +#define MAX_DEVICES 32 +#include +#include "windows.h" + +#define _True 1 +#define _False 0 + +typedef enum DeviceType { + input, + output, +} DeviceType; + +typedef enum DeviceError { + de_None, + de_InternalError = -1, + de_InvalidSelection = -2, + de_FailedStart = -3, + de_Busy = -4, + de_AllDevicesBusy = -5, + de_DeviceNotActive = -6, + de_BufferError = -7, + de_AlError = -8, +} DeviceError; + +typedef void (*DataHandleCallback) (const int16_t*, uint32_t size, void* data); + + +DeviceError init_devices(ToxAv* av); +DeviceError terminate_devices(); + +/* Callback handles ready data from INPUT device */ +DeviceError register_device_callback(int32_t call_idx, uint32_t device_idx, DataHandleCallback callback, void* data, _Bool enable_VAD); +void* get_device_callback_data(uint32_t device_idx); + +/* toggle device mute */ +DeviceError device_mute(DeviceType type, uint32_t device_idx); + +DeviceError device_set_VAD_treshold(uint32_t device_idx, float value); + +DeviceError set_primary_device(DeviceType type, int32_t selection); +DeviceError open_primary_device(DeviceType type, uint32_t* device_idx); +/* Start device */ +DeviceError open_device(DeviceType type, int32_t selection, uint32_t* device_idx); +/* Stop device */ +DeviceError close_device(DeviceType type, uint32_t device_idx); + +DeviceError playback_device_ready(uint32_t device_idx); +/* Write data to device */ +DeviceError write_out(uint32_t device_idx, int16_t* data, uint32_t lenght, uint8_t channels); + +void print_devices(ToxWindow* self, DeviceType type); + +DeviceError selection_valid(DeviceType type, int32_t selection); +#endif /* _device_h */ \ No newline at end of file diff --git a/src/execute.c b/src/execute.c index a383c8e..f85fd63 100644 --- a/src/execute.c +++ b/src/execute.c @@ -76,6 +76,9 @@ static struct cmd_func chat_commands[] = { { "/answer", cmd_answer }, { "/reject", cmd_reject }, { "/hangup", cmd_hangup }, + { "/sdev", cmd_ccur_device }, + { "/mute", cmd_mute }, + { "/sense", cmd_sense }, #endif /* _SUPPORT_AUDIO */ }; diff --git a/src/execute.h b/src/execute.h index 80a6a00..20c55a3 100644 --- a/src/execute.h +++ b/src/execute.h @@ -30,7 +30,7 @@ #ifdef _SUPPORT_AUDIO #define GLOBAL_NUM_COMMANDS 16 -#define CHAT_NUM_COMMANDS 10 +#define CHAT_NUM_COMMANDS 13 #else #define GLOBAL_NUM_COMMANDS 14 #define CHAT_NUM_COMMANDS 5 diff --git a/src/friendlist.c b/src/friendlist.c index 19e35c7..a1573de 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -627,7 +627,8 @@ ToxWindow new_friendlist(void) ret.onRequestTimeout = &friendlist_onAv; ret.onPeerTimeout = &friendlist_onAv; - ret.call_index = -1; + ret.call_idx = -1; + ret.device_selection[0] = ret.device_selection[1] = -1; #endif /* _SUPPORT_AUDIO */ strcpy(ret.name, "friends"); diff --git a/src/settings.c b/src/settings.c index 47ecaca..8964723 100644 --- a/src/settings.c +++ b/src/settings.c @@ -32,8 +32,8 @@ #include "configdir.h" #ifdef _SUPPORT_AUDIO -#include "audio_call.h" -#endif + #include "device.h" +#endif /* _SUPPORT_AUDIO */ #include "settings.h" #include "line_info.h" @@ -209,7 +209,7 @@ int settings_load(struct user_settings *s, char *path) int i; for (i = 0; i < NUM_SETTINGS; ++i) { - if (!strcmp(user_settings_list[i].key, key)) { + if (strcmp(user_settings_list[i].key, key) == 0) { (user_settings_list[i].func)(s, val); break; } diff --git a/src/settings.h b/src/settings.h index 946d2e6..3400466 100644 --- a/src/settings.h +++ b/src/settings.h @@ -29,7 +29,7 @@ #define NUM_SETTINGS 8 #else #define NUM_SETTINGS 6 -#endif +#endif /* _SUPPORT_AUDIO */ /* holds user setting values */ struct user_settings { diff --git a/src/toxic.c b/src/toxic.c index 45871a9..4a4a097 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -606,16 +606,10 @@ int main(int argc, char *argv[]) av = init_audio(prompt, m); - device_set(prompt, input, user_settings->audio_in_dev); - device_set(prompt, output, user_settings->audio_out_dev); - - if ( errors() == NoError ) - msg = "Audio initiated with no problems."; - else /* Get error code and stuff */ - msg = "Error initiating audio!"; - - line_info_add(prompt, NULL, NULL, NULL, msg, SYS_MSG, 0, 0); - + + set_primary_device(input, user_settings->audio_in_dev); + set_primary_device(output, user_settings->audio_out_dev); + #endif /* _SUPPORT_AUDIO */ if (config_err) { diff --git a/src/windows.h b/src/windows.h index a32d922..7cd8aa2 100644 --- a/src/windows.h +++ b/src/windows.h @@ -107,8 +107,9 @@ struct ToxWindow { void(*onRequestTimeout)(ToxWindow *, ToxAv *, int); void(*onPeerTimeout)(ToxWindow *, ToxAv *, int); - int call_index; /* If in a call will have this index set, otherwise it's -1. - * Don't modify outside av callbacks. */ + int call_idx; /* If in a call will have this index set, otherwise it's -1. + * Don't modify outside av callbacks. */ + int device_selection[2]; /* -1 if not set, if set uses these selections instead of primary device */ #endif /* _SUPPORT_AUDIO */ char name[TOX_MAX_NAME_LENGTH];