diff --git a/Makefile b/Makefile index 392b257..b567ad5 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,10 @@ $(BUILD_DIR)/toxic: $(OBJ) @echo " LD $(@:$(BUILD_DIR)/%=%)" @$(CC) $(CFLAGS) -o $(BUILD_DIR)/toxic $(OBJ) $(LDFLAGS) +$(BUILD_DIR)/osx_video.o: $(SRC_DIR)/$(OSX_VIDEO) + @echo " CC $(@:$(BUILD_DIR)/)osx_video.o" + @$(CC) $(CFLAGS) -o $(BUILD_DIR)/osx_video.o -c $(SRC_DIR)/$(OSX_VIDEO) + $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c @if [ ! -e $(BUILD_DIR) ]; then \ mkdir -p $(BUILD_DIR) ;\ diff --git a/cfg/checks/av.mk b/cfg/checks/audio.mk similarity index 88% rename from cfg/checks/av.mk rename to cfg/checks/audio.mk index 338cb4c..eceba9e 100644 --- a/cfg/checks/av.mk +++ b/cfg/checks/audio.mk @@ -1,10 +1,10 @@ # Variables for audio call support AUDIO_LIBS = libtoxav openal AUDIO_CFLAGS = -DAUDIO -ifneq (, $(findstring device.o, $(OBJ))) +ifneq (, $(findstring audio_device.o, $(OBJ))) AUDIO_OBJ = audio_call.o else - AUDIO_OBJ = audio_call.o device.o + AUDIO_OBJ = audio_call.o audio_device.o endif # Check if we can build audio support @@ -18,4 +18,4 @@ else ifneq ($(MAKECMDGOALS), clean) $(warning WARNING -- Toxic will be compiled without audio support) $(warning WARNING -- You need these libraries for audio support) $(warning WARNING -- $(MISSING_AUDIO_LIBS)) -endif +endif \ No newline at end of file diff --git a/cfg/checks/check_features.mk b/cfg/checks/check_features.mk index 1b613db..783b2f2 100644 --- a/cfg/checks/check_features.mk +++ b/cfg/checks/check_features.mk @@ -9,7 +9,15 @@ endif # Check if we want build audio support AUDIO = $(shell if [ -z "$(DISABLE_AV)" ] || [ "$(DISABLE_AV)" = "0" ] ; then echo enabled ; else echo disabled ; fi) ifneq ($(AUDIO), disabled) - -include $(CHECKS_DIR)/av.mk + -include $(CHECKS_DIR)/audio.mk +endif + +# Check if we want build video support +VIDEO = $*shell if [ -z "$(DISABLE_AV)" ] || [ "$(DISABLE_AV)" = "0" ] ; then echo enabled ; else echo disabled ; fi) +ifneq ($(AUDIO), disabled) +ifneq ($(VIDEO), disabled) + -include $(CHECKS_DIR)/video.mk +endif endif # Check if we want build sound notifications support diff --git a/cfg/checks/video.mk b/cfg/checks/video.mk new file mode 100644 index 0000000..1495c4b --- /dev/null +++ b/cfg/checks/video.mk @@ -0,0 +1,21 @@ +# Variables for video call support +VIDEO_LIBS = libtoxav vpx x11 +VIDEO_CFLAGS = -DVIDEO +ifneq (, $(findstring video_device.o, $(OBJ))) + VIDEO_OBJ = video_call.o +else + VIDEO_OBJ = video_call.o video_device.o +endif + +# Check if we can build video support +CHECK_VIDEO_LIBS = $(shell pkg-config --exists $(VIDEO_LIBS) || echo -n "error") +ifneq ($(CHECK_VIDEO_LIBS), error) + LIBS += $(VIDEO_LIBS) + CFLAGS += $(VIDEO_CFLAGS) + OBJ += $(VIDEO_OBJ) +else ifneq ($(MAKECMDGOALS), clean) + MISSING_VIDEO_LIBS = $(shell for lib in $(VIDEO_LIBS) ; do if ! pkg-config --exists $$lib ; then echo $$lib ; fi ; done) + $(warning WARNING -- Toxic will be compiled without video support) + $(warning WARNING -- You will need these libraries for video support) + $(warning WARNING -- $(MISSING_VIDEO_LIBS)) +endif \ No newline at end of file diff --git a/cfg/systems/Darwin.mk b/cfg/systems/Darwin.mk index f33a4be..d228b6a 100644 --- a/cfg/systems/Darwin.mk +++ b/cfg/systems/Darwin.mk @@ -8,3 +8,11 @@ LIBS := $(filter-out ncursesw, $(LIBS)) # OS X ships a usable, recent version of ncurses, but calls it ncurses not ncursesw. LDFLAGS += -lncurses -lalut -ltoxav -ltoxcore -ltoxdns -lresolv -lconfig -ltoxencryptsave -g CFLAGS += -I/usr/local/opt/freealut/include/AL -I/usr/local/opt/glib/include/glib-2.0 -g + +OSX_LIBRARIES = -lobjc -lresolv +OSX_FRAMEWORKS = -framework Foundation -framework CoreFoundation -framework AVFoundation \ + -framework QuartzCore -framework CoreMedia +OSX_VIDEO = osx_video.m + +LDFLAGS += $(OSX_LIBRARIES) $(OSX_FRAMEWORKS) +OBJ += osx_video.o \ No newline at end of file diff --git a/src/audio_call.c b/src/audio_call.c index 54b865b..8cd6d9b 100644 --- a/src/audio_call.c +++ b/src/audio_call.c @@ -23,12 +23,16 @@ #include "toxic.h" #include "windows.h" #include "audio_call.h" -#include "device.h" +#include "audio_device.h" #include "chat_commands.h" #include "global_commands.h" #include "line_info.h" #include "notify.h" +#ifdef VIDEO +#include "video_call.h" +#endif /* VIDEO */ + #include #include #include @@ -51,172 +55,169 @@ #define cbend pthread_exit(NULL) -#define MAX_CALLS 10 - -#define frame_size (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000) +#define frame_size (CallControl.audio_sample_rate * CallControl.audio_frame_duration / 1000) static int set_call(Call* call, bool start) { call->in_idx = -1; call->out_idx = -1; +#ifdef VIDEO + call->vin_idx = -1; + call->vout_idx = -1; +#endif /* VIDEO */ if ( start ) { call->ttas = true; - if (pthread_mutex_init(&call->mutex, NULL) != 0) + if ( pthread_mutex_init(&call->mutex, NULL) != 0 ) return -1; } else { call->ttid = 0; - if (pthread_mutex_destroy(&call->mutex) != 0) - return -1; + if ( pthread_mutex_destroy(&call->mutex) != 0 ) + return -1; } return 0; } -struct ASettings { - AudioError errors; +void call_cb ( ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data ); +void callstate_cb ( ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data ); +void receive_audio_frame_cb ( ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data ); +void audio_bit_rate_status_cb( ToxAV *av, uint32_t friend_number, + bool stable, uint32_t bit_rate, void *user_data ); +void receive_video_frame_cb ( ToxAV *av, uint32_t friend_number, + uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, uint8_t const *a, + int32_t ystride, int32_t ustride, int32_t vstride, int32_t astride, + void *user_data ); +void video_bit_rate_status_cb( ToxAV *av, uint32_t friend_number, + bool stable, uint32_t bit_rate, void *user_data); - ToxAv *av; +void callback_recv_invite ( uint32_t friend_number ); +void callback_recv_ringing ( uint32_t friend_number ); +void callback_recv_starting ( uint32_t friend_number ); +void callback_recv_ending ( uint32_t friend_number ); +void callback_call_started ( uint32_t friend_number ); +void callback_call_canceled ( uint32_t friend_number ); +void callback_call_rejected ( uint32_t friend_number ); +void callback_call_ended ( uint32_t friend_number ); +void callback_requ_timeout ( uint32_t friend_number ); +void callback_peer_timeout ( uint32_t friend_number ); +void callback_media_change ( uint32_t friend_number ); - ToxAvCSettings cs; - - Call calls[MAX_CALLS]; -} ASettins; - -void callback_recv_invite ( void* av, int32_t call_index, void *arg ); -void callback_recv_ringing ( void* av, int32_t call_index, void *arg ); -void callback_recv_starting ( void* av, int32_t call_index, void *arg ); -void callback_recv_ending ( void* av, int32_t call_index, void *arg ); -void callback_call_started ( void* av, int32_t call_index, void *arg ); -void callback_call_canceled ( void* av, int32_t call_index, void *arg ); -void callback_call_rejected ( void* av, int32_t call_index, void *arg ); -void callback_call_ended ( void* av, int32_t call_index, void *arg ); -void callback_requ_timeout ( void* av, int32_t call_index, void *arg ); -void callback_peer_timeout ( void* av, int32_t call_index, void *arg ); -void callback_media_change ( void* av, int32_t call_index, void *arg ); - -void write_device_callback( void* agent, int32_t call_index, const int16_t* PCM, uint16_t size, void* arg ); +void write_device_callback( uint32_t friend_number, const int16_t* PCM, uint16_t size ); static void print_err (ToxWindow *self, const char *error_str) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str); } -ToxAv *init_audio(ToxWindow *self, Tox *tox) +ToxAV *init_audio(ToxWindow *self, Tox *tox) { - ASettins.cs = av_DefaultSettings; - ASettins.cs.max_video_height = ASettins.cs.max_video_width = 0; + TOXAV_ERR_NEW error; + CallControl.audio_errors = ae_None; + CallControl.prompt = self; + CallControl.pending_call = false; - ASettins.errors = ae_None; + CallControl.av = toxav_new(tox, &error); - memset(ASettins.calls, 0, sizeof(ASettins.calls)); + CallControl.audio_enabled = true; + CallControl.audio_bit_rate = 48; + CallControl.audio_sample_rate = 48000; + CallControl.audio_frame_duration = 10; + CallControl.audio_channels = 1; +#ifndef VIDEO + CallControl.video_enabled = false; + CallControl.video_bit_rate = 0; + CallControl.video_frame_duration = 0; +#endif /* VIDEO */ - /* Streaming stuff from core */ + memset(CallControl.calls, 0, sizeof(CallControl.calls)); - ASettins.av = toxav_new(tox, MAX_CALLS); + if ( !CallControl.av ) { + CallControl.audio_errors |= ae_StartingCoreAudio; + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init ToxAV"); - if ( !ASettins.av ) { - ASettins.errors |= ae_StartingCoreAudio; return NULL; } - if ( init_devices(ASettins.av) == de_InternalError ) { + if ( init_devices(CallControl.av) == de_InternalError ) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init devices"); - toxav_kill(ASettins.av); - return ASettins.av = NULL; + toxav_kill(CallControl.av); + + return CallControl.av = NULL; } - toxav_register_callstate_callback(ASettins.av, callback_call_started, av_OnStart, self); - toxav_register_callstate_callback(ASettins.av, callback_call_canceled, av_OnCancel, self); - toxav_register_callstate_callback(ASettins.av, callback_call_rejected, av_OnReject, self); - toxav_register_callstate_callback(ASettins.av, callback_call_ended, av_OnEnd, self); - toxav_register_callstate_callback(ASettins.av, callback_recv_invite, av_OnInvite, self); + toxav_callback_call(CallControl.av, call_cb, &CallControl); + toxav_callback_call_state(CallControl.av, callstate_cb, &CallControl); + toxav_callback_audio_receive_frame(CallControl.av, receive_audio_frame_cb, &CallControl); + toxav_callback_audio_bit_rate_status(CallControl.av, audio_bit_rate_status_cb, &CallControl); - toxav_register_callstate_callback(ASettins.av, callback_recv_ringing, av_OnRinging, self); - toxav_register_callstate_callback(ASettins.av, callback_recv_starting, av_OnStart, self); - toxav_register_callstate_callback(ASettins.av, callback_recv_ending, av_OnEnd, self); - - toxav_register_callstate_callback(ASettins.av, callback_requ_timeout, av_OnRequestTimeout, self); - toxav_register_callstate_callback(ASettins.av, callback_peer_timeout, av_OnPeerTimeout, self); - //toxav_register_callstate_callback(ASettins.av, callback_media_change, av_OnMediaChange, self); - - toxav_register_audio_callback(ASettins.av, write_device_callback, NULL); - - return ASettins.av; + return CallControl.av; } void terminate_audio() { int i; for (i = 0; i < MAX_CALLS; ++i) - stop_transmission(&ASettins.calls[i], i); + stop_transmission(&CallControl.calls[i], i); - if ( ASettins.av ) - toxav_kill(ASettins.av); + if ( CallControl.av ) + toxav_kill(CallControl.av); terminate_devices(); } -void read_device_callback (const int16_t* captured, uint32_t size, void* data) +void read_device_callback(const int16_t* captured, uint32_t size, void* data) { - int32_t call_index = *((int32_t*)data); /* TODO: Or pass an array of call_idx's */ + TOXAV_ERR_SEND_FRAME error; + uint32_t friend_number = *((uint32_t*)data); /* TODO: Or pass an array of call_idx's */ + int64_t sample_count = CallControl.audio_sample_rate * CallControl.audio_frame_duration / 1000; - 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");*/ - } + if ( sample_count <= 0 || toxav_audio_send_frame(CallControl.av, friend_number, + captured, sample_count, + CallControl.audio_channels, + CallControl.audio_sample_rate, &error) == false ) + {} } -void write_device_callback(void *agent, int32_t call_index, const int16_t* PCM, uint16_t size, void* arg) +void write_device_callback(uint32_t friend_number, const int16_t* PCM, uint16_t size) { - (void)arg; - (void)agent; - - if (call_index >= 0 && ASettins.calls[call_index].ttas) { - ToxAvCSettings csettings = ASettins.cs; - toxav_get_peer_csettings(ASettins.av, call_index, 0, &csettings); - write_out(ASettins.calls[call_index].out_idx, PCM, size, csettings.audio_channels); - } + if ( CallControl.calls[friend_number].ttas ) + write_out(CallControl.calls[friend_number].out_idx, PCM, size, CallControl.audio_channels); } int start_transmission(ToxWindow *self, Call *call) { - if ( !self || !ASettins.av || self->call_idx == -1 ) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Could not prepare transmission"); + if ( !self || !CallControl.av ) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare transmission"); return -1; } - /* Don't provide support for video */ - if ( 0 != toxav_prepare_transmission(ASettins.av, self->call_idx, 0) ) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Could not prepare transmission"); - return -1; - } - - if ( !toxav_capability_supported(ASettins.av, self->call_idx, av_AudioDecoding) || - !toxav_capability_supported(ASettins.av, self->call_idx, av_AudioEncoding) ) - return -1; - if (set_call(call, true) == -1) return -1; - ToxAvCSettings csettings; - toxav_get_peer_csettings(ASettins.av, self->call_idx, 0, &csettings); + DeviceError error = open_primary_device(input, &call->in_idx, + CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels); - if ( open_primary_device(input, &call->in_idx, - csettings.audio_sample_rate, csettings.audio_frame_duration, csettings.audio_channels) != de_None ) - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open input device!"); + if ( error != de_None ) { + if ( error == de_FailedStart) + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to start input device"); - if ( register_device_callback(self->call_idx, call->in_idx, - read_device_callback, &self->call_idx, true) != de_None) + if ( error == de_InternalError ) + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Internal error with opening input device"); + } + + if ( register_device_callback(self->num, call->in_idx, + read_device_callback, &self->num, true) != de_None) /* Set VAD as true for all; TODO: Make it more dynamic */ line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to register input handler!"); if ( open_primary_device(output, &call->out_idx, - csettings.audio_sample_rate, csettings.audio_frame_duration, csettings.audio_channels) != de_None ) { + CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels) != de_None ) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open output device!"); call->has_output = 0; } @@ -224,22 +225,31 @@ int start_transmission(ToxWindow *self, Call *call) return 0; } -int stop_transmission(Call *call, int32_t call_index) +int stop_transmission(Call *call, uint32_t friend_number) { if ( call->ttas ) { - toxav_kill_transmission(ASettins.av, call_index); - call->ttas = false; + TOXAV_ERR_CALL_CONTROL error = TOXAV_ERR_CALL_CONTROL_OK; - if ( call->in_idx != -1 ) - close_device(input, call->in_idx); + if ( CallControl.call_state != TOXAV_FRIEND_CALL_STATE_FINISHED ) + toxav_call_control(CallControl.av, friend_number, TOXAV_CALL_CONTROL_CANCEL, &error); - if ( call->out_idx != -1 ) - close_device(output, call->out_idx); + if ( error == TOXAV_ERR_CALL_CONTROL_OK ) { + call->ttas = false; + + if ( call->in_idx != -1 ) + close_device(input, call->in_idx); + + if ( call->out_idx != -1 ) + close_device(output, call->out_idx); + + if ( set_call(call, false) == -1 ) + return -1; + + return 0; + } else { - if (set_call(call, false) == -1) return -1; - - return 0; + } } return -1; @@ -255,82 +265,158 @@ int stop_transmission(Call *call, int32_t call_index) /* * Callbacks */ - -#define CB_BODY(call_idx, Arg, onFunc) do { ToxWindow* windows = (Arg); int i;\ -for (i = 0; i < MAX_WINDOWS_NUM; ++i) if (windows[i].onFunc != NULL) windows[i].onFunc(&windows[i], ASettins.av, call_idx); } while (0) - -void callback_recv_invite ( void* av, int32_t call_index, void* arg ) +void call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) { - CB_BODY(call_index, arg, onInvite); + CallControl.pending_call = true; + callback_recv_invite(friend_number); } -void callback_recv_ringing ( void* av, int32_t call_index, void* arg ) + +void callstate_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) { - CB_BODY(call_index, arg, onRinging); -} -void callback_recv_starting ( void* av, 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_idx == call_index) { - windows[i].onStarting(&windows[i], ASettins.av, call_index); - if ( 0 != start_transmission(&windows[i], &ASettins.calls[call_index])) {/* YEAH! */ - line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0 , "Error starting transmission!"); + CallControl.call_state = state; + + switch ( state ) { + case ( TOXAV_FRIEND_CALL_STATE_ERROR ): + line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "ToxAV callstate error!"); + +#ifdef VIDEO + callback_video_end(friend_number); +#endif /* VIDEO */ + + stop_transmission(&CallControl.calls[friend_number], friend_number); + callback_call_ended(friend_number); + CallControl.pending_call = false; + + break; + case ( TOXAV_FRIEND_CALL_STATE_FINISHED ): + if ( CallControl.pending_call ) + callback_call_rejected(friend_number); + else + callback_call_ended(friend_number); + +#ifdef VIDEO + callback_recv_video_end(friend_number); + callback_video_end(friend_number); +#endif /* VIDEO */ + + stop_transmission(&CallControl.calls[friend_number], friend_number); + + /* Reset stored call state after finishing */ + CallControl.call_state = 0; + CallControl.pending_call = false; + + break; + default: + if ( CallControl.pending_call ) { + /* Start answered call */ + callback_call_started(friend_number); + CallControl.pending_call = false; + + } else { +#ifdef VIDEO + /* Handle receiving client video call states */ + if ( state & TOXAV_FRIEND_CALL_STATE_SENDING_V ) + callback_recv_video_starting(friend_number); + else if ( state & ~TOXAV_FRIEND_CALL_STATE_SENDING_V ) + callback_recv_video_end(friend_number); + +#endif /* VIDEO */ } + + break; + } +} + +void receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, + int16_t const *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data) +{ + write_device_callback(friend_number, pcm, frame_size); +} + +void audio_bit_rate_status_cb(ToxAV *av, uint32_t friend_number, + bool stable, uint32_t bit_rate, void *user_data) +{ + if ( stable ) + CallControl.audio_bit_rate = bit_rate; +} + + +#define CB_BODY(friend_number, onFunc) do { ToxWindow* windows = CallControl.prompt; int i;\ +for (i = 0; i < MAX_WINDOWS_NUM; ++i) if ( windows[i].onFunc != NULL && windows[i].num == friend_number )\ +windows[i].onFunc(&windows[i], CallControl.av, friend_number, CallControl.call_state); } while (0) + +void callback_recv_invite(uint32_t friend_number) +{ + CB_BODY(friend_number, onInvite); +} +void callback_recv_ringing(uint32_t friend_number) +{ + CB_BODY(friend_number, onRinging); +} +void callback_recv_starting(uint32_t friend_number) +{ + ToxWindow* windows = CallControl.prompt; + + int i; + for (i = 0; i < MAX_WINDOWS_NUM; ++i) { + if ( windows[i].onStarting != NULL && windows[i].num == friend_number ) { + windows[i].onStarting(&windows[i], CallControl.av, friend_number, CallControl.call_state); + if ( 0 != start_transmission(&windows[i], &CallControl.calls[friend_number]) ) /* YEAH! */ + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0 , "Error starting transmission!"); + return; } + } } -void callback_recv_ending ( void* av, int32_t call_index, void* arg ) +void callback_recv_ending(uint32_t friend_number) { - CB_BODY(call_index, arg, onEnding); - stop_transmission(&ASettins.calls[call_index], call_index); + CB_BODY(friend_number, onEnding); } +void callback_call_started(uint32_t friend_number) +{ + ToxWindow* windows = CallControl.prompt; -void callback_call_started ( void* av, 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_idx == call_index) { - windows[i].onStart(&windows[i], ASettins.av, call_index); - if ( 0 != start_transmission(&windows[i], &ASettins.calls[call_index]) ) {/* YEAH! */ + if ( windows[i].onStart != NULL && windows[i].num == friend_number ) { + windows[i].onStart(&windows[i], CallControl.av, friend_number, CallControl.call_state); + if ( 0 != start_transmission(&windows[i], &CallControl.calls[friend_number]) ) {/* YEAH! */ line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Error starting transmission!"); return; } } } -void callback_call_canceled ( void* av, int32_t call_index, void* arg ) +void callback_call_canceled(uint32_t friend_number) { - CB_BODY(call_index, arg, onCancel); + CB_BODY(friend_number, onCancel); +} +void callback_call_rejected(uint32_t friend_number) +{ + CB_BODY(friend_number, onReject); +} +void callback_call_ended(uint32_t friend_number) +{ + CB_BODY(friend_number, onEnd); +} +void callback_requ_timeout(uint32_t friend_number) +{ + CB_BODY(friend_number, onRequestTimeout); +} +void callback_peer_timeout(uint32_t friend_number) +{ + CB_BODY(friend_number, onPeerTimeout); - /* In case call is active */ - stop_transmission(&ASettins.calls[call_index], call_index); -} -void callback_call_rejected ( void* av, int32_t call_index, void* arg ) -{ - CB_BODY(call_index, arg, onReject); -} -void callback_call_ended ( void* av, int32_t call_index, void* arg ) -{ - CB_BODY(call_index, arg, onEnd); - stop_transmission(&ASettins.calls[call_index], call_index); -} - -void callback_requ_timeout ( void* av, int32_t call_index, void* arg ) -{ - CB_BODY(call_index, arg, onRequestTimeout); -} -void callback_peer_timeout ( void* av, int32_t call_index, void* arg ) -{ - CB_BODY(call_index, arg, onPeerTimeout); - stop_transmission(&ASettins.calls[call_index], call_index); + callback_video_end(friend_number); + callback_recv_video_end(friend_number); + stop_transmission(&CallControl.calls[friend_number], friend_number); /* Call is stopped manually since there might be some other * actions that one can possibly take on timeout */ - toxav_stop_call(ASettins.av, call_index); + toxav_call_control(CallControl.av, friend_number, TOXAV_CALL_CONTROL_CANCEL, NULL); } -// void callback_media_change(void* av, int32_t call_index, void* arg) +// void callback_media_change(void* av, uint32_t friend_number, void* arg) // { /*... TODO cancel all media change requests */ // } @@ -345,33 +431,42 @@ void callback_peer_timeout ( void* av, int32_t call_index, void* arg ) */ void cmd_call(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { + TOXAV_ERR_CALL error; const char *error_str; - if (argc != 0) { + if ( argc != 0 ) { error_str = "Unknown arguments."; goto on_error; } - if ( !ASettins.av ) { - error_str = "Audio not supported!"; + if ( !CallControl.av ) { + error_str = "ToxAV not supported!"; goto on_error; } - if (!self->stb->connection) { + if ( !self->stb->connection ) { error_str = "Friend is offline."; goto on_error; } - ToxAvError error = toxav_call(ASettins.av, &self->call_idx, self->num, &ASettins.cs, 30); + if ( CallControl.pending_call ) { + error_str = "Already a pending call!"; + goto on_error; + } - if ( error != av_ErrorNone ) { - if ( error == av_ErrorAlreadyInCallWithPeer ) error_str = "Already in a call!"; + toxav_call(CallControl.av, self->num, CallControl.audio_bit_rate, CallControl.video_bit_rate, &error); + if ( error != TOXAV_ERR_CALL_OK ) { + if ( error == TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL ) error_str = "Already in a call!"; + else if ( error == TOXAV_ERR_CALL_MALLOC ) error_str = "Memory allocation issue"; + else if ( error == TOXAV_ERR_CALL_FRIEND_NOT_FOUND ) error_str = "Friend number invalid"; + else if ( error == TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED ) error_str = "Friend is valid but not currently connected"; else error_str = "Internal error!"; goto on_error; } - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Calling... idx: %d", self->call_idx); + CallControl.pending_call = true; + callback_recv_ringing(self->num); return; on_error: @@ -380,29 +475,38 @@ on_error: void cmd_answer(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { + TOXAV_ERR_ANSWER error; const char *error_str; - if (argc != 0) { + if ( argc != 0 ) { error_str = "Unknown arguments."; goto on_error; } - if ( !ASettins.av ) { + if ( !CallControl.av ) { error_str = "Audio not supported!"; goto on_error; } - ToxAvError error = toxav_answer(ASettins.av, self->call_idx, &ASettins.cs); + if ( !CallControl.pending_call ) { + error_str = "No incoming call!"; + goto on_error; + } - if ( error != av_ErrorNone ) { - if ( error == av_ErrorInvalidState ) error_str = "Cannot answer in invalid state!"; - else if ( error == av_ErrorNoCall ) error_str = "No incoming call!"; + toxav_answer(CallControl.av, self->num, CallControl.audio_bit_rate, CallControl.video_bit_rate, &error); + if ( error != TOXAV_ERR_ANSWER_OK ) { + if ( error == TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING ) error_str = "No incoming call!"; + else if ( error == TOXAV_ERR_ANSWER_CODEC_INITIALIZATION ) error_str = "Failed to initialize codecs!"; + else if ( error == TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND ) error_str = "Friend not found!"; + else if ( error == TOXAV_ERR_ANSWER_INVALID_BIT_RATE ) error_str = "Invalid bit rate!"; else error_str = "Internal error!"; goto on_error; } /* Callback will print status... */ + callback_recv_starting(self->num); + CallControl.pending_call = false; return; on_error: @@ -413,27 +517,27 @@ void cmd_reject(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ { const char *error_str; - if (argc != 0) { + if ( argc != 0 ) { error_str = "Unknown arguments."; goto on_error; } - if ( !ASettins.av ) { + if ( !CallControl.av ) { error_str = "Audio not supported!"; goto on_error; } - ToxAvError error = toxav_reject(ASettins.av, self->call_idx, "Why not?"); - - if ( error != av_ErrorNone ) { - if ( error == av_ErrorInvalidState ) error_str = "Cannot reject in invalid state!"; - else if ( error == av_ErrorNoCall ) error_str = "No incoming call!"; - else error_str = "Internal error!"; - + if ( !CallControl.pending_call ) { + error_str = "No incoming call!"; goto on_error; } + /* Manually send a cancel call control because call hasn't started */ + toxav_call_control(CallControl.av, self->num, TOXAV_CALL_CONTROL_CANCEL, NULL); + CallControl.pending_call = false; + /* Callback will print status... */ + callback_call_rejected(self->num); return; on_error: @@ -444,36 +548,34 @@ void cmd_hangup(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[ { const char *error_str; - if (argc != 0) { + if ( argc != 0 ) { error_str = "Unknown arguments."; goto on_error; } - if ( !ASettins.av ) { + if ( !CallControl.av ) { error_str = "Audio not supported!"; goto on_error; } - ToxAvError error; +#ifdef VIDEO + callback_video_end(self->num); - if (toxav_get_call_state(ASettins.av, self->call_idx) == av_CallInviting) { - error = toxav_cancel(ASettins.av, self->call_idx, self->num, - "Only those who appreciate small things know the beauty that is life"); -#ifdef SOUND_NOTIFY - stop_sound(self->ringing_sound); -#endif - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call canceled!"); - } else { - error = toxav_hangup(ASettins.av, self->call_idx); +#endif /* VIDEO */ + + + + if ( CallControl.pending_call ) { + /* Manually send a cancel call control because call hasn't started */ + toxav_call_control(CallControl.av, self->num, TOXAV_CALL_CONTROL_CANCEL, NULL); + callback_call_canceled(self->num); + } + else { + stop_transmission(&CallControl.calls[self->num], self->num); + callback_call_ended(self->num); } - if ( error != av_ErrorNone ) { - if ( error == av_ErrorInvalidState ) error_str = "Cannot hangup in invalid state!"; - else if ( error == av_ErrorNoCall ) error_str = "No call!"; - else error_str = "Internal error!"; - - goto on_error; - } + CallControl.pending_call = false; return; on_error: @@ -596,28 +698,26 @@ void cmd_ccur_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*a } /* If call is active, change device */ - if ( self->call_idx > -1) { - Call* this_call = &ASettins.calls[self->call_idx]; - if (this_call->ttas) { + if ( self->is_call ) { + Call* this_call = &CallControl.calls[self->num]; + if ( this_call->ttas ) { - ToxAvCSettings csettings; - toxav_get_peer_csettings(ASettins.av, self->call_idx, 0, &csettings); - if (type == output) { + if ( type == output ) { pthread_mutex_lock(&this_call->mutex); close_device(output, this_call->out_idx); this_call->has_output = open_device(output, selection, &this_call->out_idx, - csettings.audio_sample_rate, csettings.audio_frame_duration, csettings.audio_channels) + CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels) == de_None ? 1 : 0; 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, csettings.audio_sample_rate, - csettings.audio_frame_duration, csettings.audio_channels); + open_device(input, selection, &this_call->in_idx, CallControl.audio_sample_rate, + CallControl.audio_frame_duration, CallControl.audio_channels); /* 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); + register_device_callback(self->num, this_call->in_idx, read_device_callback, &self->num, true); } } } @@ -655,11 +755,11 @@ void cmd_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA /* If call is active, use this_call values */ - if ( self->call_idx > -1) { - Call* this_call = &ASettins.calls[self->call_idx]; + if ( self->is_call ) { + Call* this_call = &CallControl.calls[self->num]; pthread_mutex_lock(&this_call->mutex); - if (type == input) { + if ( type == input ) { device_mute(type, this_call->in_idx); self->chatwin->infobox.in_is_muted ^= 1; } else { @@ -695,8 +795,8 @@ void cmd_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[M } /* Call must be active */ - if ( self->call_idx > -1) { - device_set_VAD_treshold(ASettins.calls[self->call_idx].in_idx, value); + if ( self->is_call ) { + device_set_VAD_treshold(CallControl.calls[self->num].in_idx, value); self->chatwin->infobox.vad_lvl = value; } @@ -709,23 +809,6 @@ on_error: void stop_current_call(ToxWindow* self) { - ToxAvCallState callstate; - if ( ASettins.av != NULL && self->call_idx != -1 && - ( callstate = toxav_get_call_state(ASettins.av, self->call_idx) ) != av_CallNonExistent) { - switch (callstate) - { - case av_CallActive: - case av_CallHold: - toxav_hangup(ASettins.av, self->call_idx); - break; - case av_CallInviting: - toxav_cancel(ASettins.av, self->call_idx, 0, "Not interested anymore"); - break; - case av_CallStarting: - toxav_reject(ASettins.av, self->call_idx, "Not interested"); - break; - default: - break; - } - } + TOXAV_ERR_CALL_CONTROL error; + toxav_call_control(CallControl.av, self->num, TOXAV_CALL_CONTROL_CANCEL, &error); } diff --git a/src/audio_call.h b/src/audio_call.h index 68509b9..608d8b3 100644 --- a/src/audio_call.h +++ b/src/audio_call.h @@ -20,12 +20,14 @@ * */ -#ifndef AUDIO_H -#define AUDIO_H +#ifndef AUDIO_CALL_H +#define AUDIO_CALL_H #include -#include "device.h" +#include "audio_device.h" + +#define MAX_CALLS 10 typedef enum _AudioError { ae_None = 0, @@ -34,18 +36,61 @@ typedef enum _AudioError { ae_StartingCoreAudio = 1 << 2 } AudioError; +#ifdef VIDEO +typedef enum _VideoError { + ve_None = 0, + ve_StartingCaptureDevice = 1 << 0, + ve_StartingOutputDevice = 1 << 1, + ve_StartingCoreVideo = 1 << 2 +} VideoError; + +#endif /* VIDEO */ + typedef struct Call { pthread_t ttid; /* Transmission thread id */ bool ttas, has_output; /* Transmission thread active status (0 - stopped, 1- running) */ - uint32_t in_idx, out_idx; + uint32_t in_idx, out_idx; /* Audio Index */ +#ifdef VIDEO + uint32_t vin_idx, vout_idx; /* Video Index */ +#endif /* VIDEO */ pthread_mutex_t mutex; } Call; +struct CallControl { + AudioError audio_errors; +#ifdef VIDEO + VideoError video_errors; +#endif /* VIDEO */ + + ToxAV *av; + ToxWindow *prompt; + + Call calls[MAX_CALLS]; + uint32_t call_state; + bool pending_call; + bool audio_enabled; + bool video_enabled; + + uint32_t audio_bit_rate; + int32_t audio_frame_duration; + uint32_t audio_sample_rate; + uint8_t audio_channels; + +#ifdef VIDEO + uint32_t video_bit_rate; + int32_t video_frame_duration; + +#endif /* VIDEO */ + +} CallControl; + +struct CallControl CallControl; + /* You will have to pass pointer to first member of 'windows' declared in windows.c */ -ToxAv *init_audio(ToxWindow *self, Tox *tox); +ToxAV *init_audio(ToxWindow *self, Tox *tox); void terminate_audio(); int start_transmission(ToxWindow *self, Call *call); -int stop_transmission(Call *call, int call_index); +int stop_transmission(Call *call, uint32_t friend_number); void stop_current_call(ToxWindow *self); -#endif /* AUDIO_H */ +#endif /* AUDIO_CALL_H */ diff --git a/src/device.c b/src/audio_device.c similarity index 97% rename from src/device.c rename to src/audio_device.c index 9ef26de..fabed10 100644 --- a/src/device.c +++ b/src/audio_device.c @@ -1,4 +1,4 @@ -/* device.c +/* audio_device.c * * * Copyright (C) 2014 Toxic All Rights Reserved. @@ -20,7 +20,7 @@ * */ -#include "device.h" +#include "audio_device.h" #ifdef AUDIO #include "audio_call.h" @@ -57,7 +57,7 @@ typedef struct Device { 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 */ + int32_t friend_number; /* ToxAV friend number */ uint32_t source, buffers[OPENAL_BUFS]; /* Playback source/buffers */ uint32_t ref_count; @@ -80,7 +80,7 @@ Device *running[2][MAX_DEVICES] = {{NULL}}; /* Running devices */ uint32_t primary_device[2]; /* Primary device */ #ifdef AUDIO -static ToxAv* av = NULL; +static ToxAV* av = NULL; #endif /* AUDIO */ /* q_mutex */ @@ -95,7 +95,7 @@ bool thread_running = true, void* thread_poll(void*); /* Meet devices */ #ifdef AUDIO -DeviceError init_devices(ToxAv* av_) +DeviceError init_devices(ToxAV* av_) #else DeviceError init_devices() #endif /* AUDIO */ @@ -342,7 +342,7 @@ DeviceError close_device(DeviceType type, uint32_t device_idx) return rc; } -DeviceError register_device_callback( int32_t call_idx, uint32_t device_idx, DataHandleCallback callback, void* data, bool enable_VAD) +DeviceError register_device_callback( int32_t friend_number, 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; @@ -351,7 +351,7 @@ DeviceError register_device_callback( int32_t call_idx, uint32_t device_idx, Dat 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; + running[input][device_idx]->friend_number = friend_number; unlock; return de_None; diff --git a/src/device.h b/src/audio_device.h similarity index 90% rename from src/device.h rename to src/audio_device.h index aa80a53..cc1b268 100644 --- a/src/device.h +++ b/src/audio_device.h @@ -1,4 +1,4 @@ -/* device.h +/* audio_device.h * * * Copyright (C) 2014 Toxic All Rights Reserved. @@ -26,8 +26,8 @@ * Read from running input device(s) via select()/callback combo. */ -#ifndef DEVICE_H -#define DEVICE_H +#ifndef AUDIO_DEVICE_H +#define AUDIO_DEVICE_H #define OPENAL_BUFS 5 #define MAX_DEVICES 32 @@ -56,7 +56,7 @@ typedef void (*DataHandleCallback) (const int16_t*, uint32_t size, void* data); #ifdef AUDIO -DeviceError init_devices(ToxAv* av); +DeviceError init_devices(ToxAV* av); #else DeviceError init_devices(); #endif /* AUDIO */ @@ -64,7 +64,7 @@ DeviceError init_devices(); 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); +DeviceError register_device_callback(int32_t friend_number, uint32_t device_idx, DataHandleCallback callback, void* data, bool enable_VAD); void* get_device_callback_data(uint32_t device_idx); /* toggle device mute */ @@ -88,4 +88,4 @@ void print_devices(ToxWindow* self, DeviceType type); void get_primary_device_name(DeviceType type, char *buf, int size); DeviceError selection_valid(DeviceType type, int32_t selection); -#endif /* DEVICE_H */ +#endif /* AUDIO_DEVICE_H */ diff --git a/src/chat.c b/src/chat.c index 97ab127..ac6835c 100644 --- a/src/chat.c +++ b/src/chat.c @@ -623,32 +623,31 @@ static void chat_onGroupInvite(ToxWindow *self, Tox *m, int32_t friendnumber, ui line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat."); } -/* Av Stuff */ +/* AV Stuff */ #ifdef AUDIO -void chat_onInvite (ToxWindow *self, ToxAv *av, int call_index) +void chat_onInvite (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; - /* call_index is set here and reset on call end */ + /* call is flagged active here */ + self->is_call = true; - self->call_idx = call_index; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Incoming audio call! Type: \"/answer\" or \"/reject\""); if (self->ringing_sound == -1) sound_notify(self, call_incoming, NT_LOOP, &self->ringing_sound); - if (self->active_box != -1) box_silent_notify2(self, NT_NOFOCUS | NT_WNDALERT_0, self->active_box, "Incoming audio call!"); else box_silent_notify(self, NT_NOFOCUS | NT_WNDALERT_0, &self->active_box, self->name, "Incoming audio call!"); } -void chat_onRinging (ToxWindow *self, ToxAv *av, int call_index) +void chat_onRinging (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if ( !self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Ringing...type \"/hangup\" to cancel it."); @@ -659,40 +658,44 @@ void chat_onRinging (ToxWindow *self, ToxAv *av, int call_index) #endif /* SOUND_NOTIFY */ } -void chat_onStarting (ToxWindow *self, ToxAv *av, int call_index) +void chat_onStarting (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if ( !self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; init_infobox(self); line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it."); + /* call is flagged active here */ + self->is_call = true; + #ifdef SOUND_NOTIFY stop_sound(self->ringing_sound); #endif /* SOUND_NOTIFY */ } -void chat_onEnding (ToxWindow *self, ToxAv *av, int call_index) +void chat_onEnding (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; kill_infobox(self); - self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call ended!"); + self->is_call = false; + #ifdef SOUND_NOTIFY stop_sound(self->ringing_sound); #endif /* SOUND_NOTIFY */ } -void chat_onError (ToxWindow *self, ToxAv *av, int call_index) +void chat_onError (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; - self->call_idx = -1; + self->is_call = false; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Error!"); #ifdef SOUND_NOTIFY @@ -700,11 +703,14 @@ void chat_onError (ToxWindow *self, ToxAv *av, int call_index) #endif /* SOUND_NOTIFY */ } -void chat_onStart (ToxWindow *self, ToxAv *av, int call_index) +void chat_onStart (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if ( !self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; + /* call is flagged active here */ + self->is_call = true; + init_infobox(self); line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it."); @@ -714,13 +720,13 @@ void chat_onStart (ToxWindow *self, ToxAv *av, int call_index) #endif /* SOUND_NOTIFY */ } -void chat_onCancel (ToxWindow *self, ToxAv *av, int call_index) +void chat_onCancel (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if ( !self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; + self->is_call = false; kill_infobox(self); - self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call canceled!"); #ifdef SOUND_NOTIFY @@ -728,39 +734,39 @@ void chat_onCancel (ToxWindow *self, ToxAv *av, int call_index) #endif /* SOUND_NOTIFY */ } -void chat_onReject (ToxWindow *self, ToxAv *av, int call_index) +void chat_onReject (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; - self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Rejected!"); + self->is_call = false; #ifdef SOUND_NOTIFY stop_sound(self->ringing_sound); #endif /* SOUND_NOTIFY */ } -void chat_onEnd (ToxWindow *self, ToxAv *av, int call_index) +void chat_onEnd (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; kill_infobox(self); - self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call ended!"); + self->is_call = false; #ifdef SOUND_NOTIFY stop_sound(self->ringing_sound); #endif /* SOUND_NOTIFY */ } -void chat_onRequestTimeout (ToxWindow *self, ToxAv *av, int call_index) +void chat_onRequestTimeout (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; - self->call_idx = -1; + self->is_call = false; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No answer!"); #ifdef SOUND_NOTIFY @@ -768,13 +774,13 @@ void chat_onRequestTimeout (ToxWindow *self, ToxAv *av, int call_index) #endif /* SOUND_NOTIFY */ } -void chat_onPeerTimeout (ToxWindow *self, ToxAv *av, int call_index) +void chat_onPeerTimeout (ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - if (!self || self->call_idx != call_index || self->num != toxav_get_peer_id(av, call_index, 0)) + if (!self || self->num != friend_number) return; + self->is_call = false; kill_infobox(self); - self->call_idx = -1; line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Peer disconnected; call ended!"); #ifdef SOUND_NOTIFY @@ -1209,7 +1215,7 @@ ToxWindow new_chat(Tox *m, uint32_t friendnum) ret.onRequestTimeout = &chat_onRequestTimeout; ret.onPeerTimeout = &chat_onPeerTimeout; - ret.call_idx = -1; + ret.is_call = false; ret.device_selection[0] = ret.device_selection[1] = -1; ret.ringing_sound = -1; #endif /* AUDIO */ diff --git a/src/chat_commands.c b/src/chat_commands.c index 3e479cc..523ad60 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -120,11 +120,11 @@ void cmd_join_group(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar if (type == TOX_GROUPCHAT_TYPE_TEXT) groupnum = tox_join_groupchat(m, self->num, (uint8_t *) groupkey, length); -#ifdef AUDIO +/*#ifdef AUDIO else groupnum = toxav_join_av_groupchat(m, self->num, (uint8_t *) groupkey, length, NULL, NULL); -#endif +#endif*/ if (groupnum == -1) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat instance failed to initialize."); diff --git a/src/chat_commands.h b/src/chat_commands.h index c66b77a..9389329 100644 --- a/src/chat_commands.h +++ b/src/chat_commands.h @@ -43,4 +43,9 @@ 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 /* AUDIO */ +#ifdef VIDEO +void cmd_video(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_ccur_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +#endif /* VIDEO */ + #endif /* #define CHAT_COMMANDS_H */ diff --git a/src/execute.c b/src/execute.c index 9e83a83..c4c53f6 100644 --- a/src/execute.c +++ b/src/execute.c @@ -61,6 +61,10 @@ static struct cmd_func global_commands[] = { { "/lsdev", cmd_list_devices }, { "/sdev", cmd_change_device }, #endif /* AUDIO */ +#ifdef VIDEO + { "/lsvdev", cmd_list_video_devices }, + { "/svdev" , cmd_change_video_device }, +#endif /* VIDEO */ { NULL, NULL }, }; @@ -78,6 +82,9 @@ static struct cmd_func chat_commands[] = { { "/mute", cmd_mute }, { "/sense", cmd_sense }, #endif /* AUDIO */ +#ifdef VIDEO + { "/video", cmd_video }, +#endif /* VIDEO */ { NULL, NULL }, }; diff --git a/src/friendlist.c b/src/friendlist.c index 2e235cf..46183a3 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -1089,23 +1089,22 @@ void disable_chatwin(uint32_t f_num) } #ifdef AUDIO -static void friendlist_onAv(ToxWindow *self, ToxAv *av, int call_index) +static void friendlist_onAV(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state) { - int id = toxav_get_peer_id(av, call_index, 0); - - if ( id != av_ErrorUnknown && id >= Friends.max_idx) + if( friend_number >= Friends.max_idx) return; Tox *m = toxav_get_tox(av); - if (Friends.list[id].chatwin == -1) { + if (Friends.list[friend_number].chatwin == -1) { if (get_num_active_windows() < MAX_WINDOWS_NUM) { - if (toxav_get_call_state(av, call_index) == av_CallStarting) { /* Only open windows when call is incoming */ - Friends.list[id].chatwin = add_window(m, new_chat(m, Friends.list[id].num)); + if(state != TOXAV_FRIEND_CALL_STATE_FINISHED) { + Friends.list[friend_number].chatwin = add_window(m, new_chat(m, Friends.list[friend_number].num)); + set_active_window(Friends.list[friend_number].chatwin); } } else { char nick[TOX_MAX_NAME_LENGTH]; - get_nick_truncate(m, nick, Friends.list[id].num); + get_nick_truncate(m, nick, Friends.list[friend_number].num); line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio action from: %s!", nick); const char *errmsg = "* Warning: Too many windows are open."; @@ -1137,22 +1136,23 @@ ToxWindow new_friendlist(void) ret.onGroupInvite = &friendlist_onGroupInvite; #ifdef AUDIO - ret.onInvite = &friendlist_onAv; - ret.onRinging = &friendlist_onAv; - ret.onStarting = &friendlist_onAv; - ret.onEnding = &friendlist_onAv; - ret.onError = &friendlist_onAv; - ret.onStart = &friendlist_onAv; - ret.onCancel = &friendlist_onAv; - ret.onReject = &friendlist_onAv; - ret.onEnd = &friendlist_onAv; - ret.onRequestTimeout = &friendlist_onAv; - ret.onPeerTimeout = &friendlist_onAv; + ret.onInvite = &friendlist_onAV; + ret.onRinging = &friendlist_onAV; + ret.onStarting = &friendlist_onAV; + ret.onEnding = &friendlist_onAV; + ret.onError = &friendlist_onAV; + ret.onStart = &friendlist_onAV; + ret.onCancel = &friendlist_onAV; + ret.onReject = &friendlist_onAV; + ret.onEnd = &friendlist_onAV; + ret.onRequestTimeout = &friendlist_onAV; + ret.onPeerTimeout = &friendlist_onAV; - ret.call_idx = -1; + ret.is_call = false; ret.device_selection[0] = ret.device_selection[1] = -1; #endif /* AUDIO */ + ret.num = -1; ret.active_box = -1; Help *help = calloc(1, sizeof(Help)); diff --git a/src/global_commands.c b/src/global_commands.c index 1ce3b21..619224c 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -344,10 +344,10 @@ void cmd_groupchat(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg if (type == TOX_GROUPCHAT_TYPE_TEXT) groupnum = tox_add_groupchat(m); -#ifdef AUDIO +/*#ifdef AUDIO else groupnum = toxav_add_av_groupchat(m, NULL, NULL); -#endif +#endif*/ if (groupnum == -1) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat instance failed to initialize."); diff --git a/src/global_commands.h b/src/global_commands.h index 277f0f1..7749c45 100644 --- a/src/global_commands.h +++ b/src/global_commands.h @@ -49,4 +49,9 @@ void cmd_list_devices(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_S void cmd_change_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); #endif /* AUDIO */ +#ifdef VIDEO +void cmd_list_video_devices(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_change_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]); +#endif /* VIDEO */ + #endif /* #define GLOBAL_COMMANDS_H */ diff --git a/src/groupchat.c b/src/groupchat.c index ce568d8..09b17c7 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -59,7 +59,7 @@ #include "help.h" #include "notify.h" #include "autocomplete.h" -#include "device.h" +#include "audio_device.h" extern char *DATA_FILE; diff --git a/src/help.c b/src/help.c index 8989c11..1f6ff66 100644 --- a/src/help.c +++ b/src/help.c @@ -167,6 +167,15 @@ static void help_draw_global(ToxWindow *self) wprintw(win, " /sdev : Set active device\n"); #endif /* AUDIO */ +#ifdef VIDEO + wattron(win, A_BOLD); + wprintw(win, "\n Video:\n"); + wattroff(win, A_BOLD); + + wprintw(win, " /lsvdev : List video devices where type: in|out\n"); + wprintw(win, " /svdev : Set active video device\n"); +#endif /* VIDEO */ + help_draw_bottom_menu(win); box(win, ACS_VLINE, ACS_HLINE); @@ -203,6 +212,13 @@ static void help_draw_chat(ToxWindow *self) wprintw(win, " /sense : VAD sensitivity threshold\n"); #endif /* AUDIO */ +#ifdef VIDEO + wattron(win, A_BOLD); + wprintw(win, "\n Video:\n"); + wattroff(win, A_BOLD); + wprintw(win, " /video : Toggle video call\n"); +#endif /* VIDEO */ + help_draw_bottom_menu(win); box(win, ACS_VLINE, ACS_HLINE); @@ -282,7 +298,9 @@ void help_onKey(ToxWindow *self, wint_t key) break; case 'c': -#ifdef AUDIO +#ifdef VIDEO + help_init_window(self, 22, 80); +#elif AUDIO help_init_window(self, 19, 80); #else help_init_window(self, 9, 80); @@ -291,7 +309,9 @@ void help_onKey(ToxWindow *self, wint_t key) break; case 'g': -#ifdef AUDIO +#ifdef VIDEO + help_init_window(self, 28, 80); +#elif AUDIO help_init_window(self, 24, 80); #else help_init_window(self, 20, 80); diff --git a/src/notify.c b/src/notify.c index b7aae76..f6a2fcf 100644 --- a/src/notify.c +++ b/src/notify.c @@ -31,7 +31,7 @@ #include #include "notify.h" -#include "device.h" +#include "audio_device.h" #include "settings.h" #include "line_info.h" #include "misc_tools.h" diff --git a/src/osx_video.h b/src/osx_video.h new file mode 100644 index 0000000..fce78fe --- /dev/null +++ b/src/osx_video.h @@ -0,0 +1,54 @@ +/* osx_video.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifndef OSX_VIDEO_H +#define OSX_VIDEO_H + +#include + +#ifdef __OBJC__ +#import +#import +#endif /* __OBJC__ */ + +#define RELEASE_CHK(func, obj) if ((obj))\ + func((obj)); + +void bgrtoyuv420(uint8_t *plane_y, uint8_t *plane_u, uint8_t *plane_v, uint8_t *rgb, uint16_t width, uint16_t height); + +#ifdef __OBJC__ +@interface OSXVideo : NSObject +- (instancetype)initWithDeviceNames:(char **)device_names AmtDevices:(int *)size; +@end +#endif /* __OBJC__ */ + +int osx_video_init(char **device_names, int *size); +void osx_video_release(); +/* Start device */ +int osx_video_open_device(uint32_t selection, uint16_t *width, uint16_t *height); +/* Stop device */ +void osx_video_close_device(uint32_t device_idx); +/* Read data from device */ +int osx_video_read_device(uint8_t *y, uint8_t *u, uint8_t *v, uint16_t *width, uint16_t *height); + + +#endif /* OSX_VIDEO_H */ \ No newline at end of file diff --git a/src/osx_video.m b/src/osx_video.m new file mode 100644 index 0000000..befbc4c --- /dev/null +++ b/src/osx_video.m @@ -0,0 +1,308 @@ +/* osx_video.m + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifdef __OBJC__ +#include "osx_video.h" + +#import +#import + +#include "line_info.h" +#include "settings.h" + +#include + +#include +#include +#include +#include +#include +#include + +/* + * Helper video format functions + */ +static uint8_t rgb_to_y(int r, int g, int b) +{ + int y = ((9798 * r + 19235 * g + 3736 * b) >> 15); + return y>255? 255 : y<0 ? 0 : y; +} + +static uint8_t rgb_to_u(int r, int g, int b) +{ + int u = ((-5538 * r + -10846 * g + 16351 * b) >> 15) + 128; + return u>255? 255 : u<0 ? 0 : u; +} + +static uint8_t rgb_to_v(int r, int g, int b) +{ + int v = ((16351 * r + -13697 * g + -2664 * b) >> 15) + 128; + return v>255? 255 : v<0 ? 0 : v; +} + +void bgrxtoyuv420(uint8_t *plane_y, uint8_t *plane_u, uint8_t *plane_v, uint8_t *rgb, uint16_t width, uint16_t height) +{ + uint16_t x, y; + uint8_t *p; + uint8_t r, g, b; + + for(y = 0; y != height; y += 2) { + p = rgb; + for(x = 0; x != width; x++) { + b = *rgb++; + g = *rgb++; + r = *rgb++; + rgb++; + + *plane_y++ = rgb_to_y(r, g, b); + } + + for(x = 0; x != width / 2; x++) { + b = *rgb++; + g = *rgb++; + r = *rgb++; + rgb++; + + *plane_y++ = rgb_to_y(r, g, b); + + b = *rgb++; + g = *rgb++; + r = *rgb++; + rgb++; + + *plane_y++ = rgb_to_y(r, g, b); + + b = ((int)b + (int)*(rgb - 8) + (int)*p + (int)*(p + 4) + 2) / 4; p++; + g = ((int)g + (int)*(rgb - 7) + (int)*p + (int)*(p + 4) + 2) / 4; p++; + r = ((int)r + (int)*(rgb - 6) + (int)*p + (int)*(p + 4) + 2) / 4; p++; + p++; + + *plane_u++ = rgb_to_u(r, g, b); + *plane_v++ = rgb_to_v(r, g, b); + + p += 4; + } + } +} +/* + * End of helper video format functions + */ + + + +/* + * Implementation for OSXVideo + */ +@implementation OSXVideo { + dispatch_queue_t _processingQueue; + AVCaptureSession *_session; + AVCaptureVideoDataOutput *_linkerVideo; + + CVImageBufferRef _currentFrame; + pthread_mutex_t _frameLock; + BOOL _shouldMangleDimensions; +} + +- (instancetype)initWithDeviceNames: (char **)device_names AmtDevices: (int *)size { + _session = [[AVCaptureSession alloc] init]; + + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + int i; + for (i = 0; i < [devices count]; ++i) { + AVCaptureDevice *device = [devices objectAtIndex:i]; + char *video_input_name; + NSString *localizedName = [device localizedName]; + video_input_name = (char*)malloc(strlen([localizedName cStringUsingEncoding:NSUTF8StringEncoding]) + 1); + strcpy(video_input_name, (char*)[localizedName cStringUsingEncoding:NSUTF8StringEncoding]); + device_names[i] = video_input_name; + } + + if ( i <= 0 ) + return nil; + *size = i; + + return self; +} + +- (void)dealloc { + pthread_mutex_destroy(&_frameLock); + [_session release]; + [_linkerVideo release]; + dispatch_release(_processingQueue); + [super dealloc]; +} + +- (int)openVideoDeviceIndex: (uint32_t)device_idx Width: (uint16_t *)width Height: (uint16_t *)height { + pthread_mutex_init(&_frameLock, NULL); + pthread_mutex_lock(&_frameLock); + _processingQueue = dispatch_queue_create("Toxic processing queue", DISPATCH_QUEUE_SERIAL); + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + AVCaptureDevice *device = [devices objectAtIndex:device_idx]; + NSError *error = NULL; + AVCaptureInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error]; + + if ( error != NULL ) { + [input release]; + return -1; + } + + [_session beginConfiguration]; + [_session addInput:input]; + //_session.sessionPreset = AVCaptureSessionPreset640x480; + //*width = 640; + //*height = 480; + _shouldMangleDimensions = YES; + [_session commitConfiguration]; + [input release]; + [device release]; + + /* Obtain device resolution */ + AVCaptureInputPort *port = [input.ports objectAtIndex:0]; + CMFormatDescriptionRef format_description = port.formatDescription; + if ( format_description ) { + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format_description); + *width = dimensions.width; + *height = dimensions.height; + } else { + *width = 0; + *height = 0; + } + + _linkerVideo = [[AVCaptureVideoDataOutput alloc] init]; + [_linkerVideo setSampleBufferDelegate:self queue:_processingQueue]; + // TODO possibly get a better pixel format + if (_shouldMangleDimensions) { + [_linkerVideo setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA), + (id)kCVPixelBufferWidthKey: @640, + (id)kCVPixelBufferHeightKey: @480}]; + } else { + [_linkerVideo setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)}]; + } + [_session addOutput:_linkerVideo]; + [_session startRunning]; + + pthread_mutex_unlock(&_frameLock); + return 0; +} + +- (void)closeVideoDeviceIndex: (uint32_t)device_idx { + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + AVCaptureDevice *device = [devices objectAtIndex:device_idx]; + NSError *error = NULL; + AVCaptureInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error]; + [_session stopRunning]; + [_session removeOutput:_linkerVideo]; + [_session removeInput:input]; + [_linkerVideo release]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + pthread_mutex_lock(&_frameLock); + CVImageBufferRef img = CMSampleBufferGetImageBuffer(sampleBuffer); + if (!img) { + NSLog(@"Toxic WARNING: Bad sampleBuffer from AVfoundation!"); + } else { + CVPixelBufferUnlockBaseAddress(_currentFrame, kCVPixelBufferLock_ReadOnly); + RELEASE_CHK(CFRelease, _currentFrame); + + _currentFrame = (CVImageBufferRef)CFRetain(img); + // we're not going to do anything to it, so it's safe to lock it always + CVPixelBufferLockBaseAddress(_currentFrame, kCVPixelBufferLock_ReadOnly); + } + pthread_mutex_unlock(&_frameLock); +} + +- (int)getVideoFrameY: (uint8_t *)y U: (uint8_t *)u V: (uint8_t *)v Width: (uint16_t *)width Height: (uint16_t *)height { + if (!_currentFrame) { + return -1; + } + + pthread_mutex_lock(&_frameLock); + CFRetain(_currentFrame); + + CFTypeID imageType = CFGetTypeID(_currentFrame); + if (imageType == CVPixelBufferGetTypeID()) { + // TODO maybe handle other formats + bgrxtoyuv420(y, u, v, CVPixelBufferGetBaseAddress(_currentFrame), *width, *height); + } else if (imageType == CVOpenGLBufferGetTypeID()) { + // OpenGL pbuffer + } else if (imageType == CVOpenGLTextureGetTypeID()) { + // OpenGL Texture (Do we need to handle these?) + } + + CVPixelBufferRelease(_currentFrame); + pthread_mutex_unlock(&_frameLock); + return 0; +} + +@end +/* + * End of implementation for OSXVideo + */ + + +/* + * C-interface for OSXVideo + */ +static OSXVideo* _OSXVideo = nil; + +int osx_video_init(char **device_names, int *size) +{ + _OSXVideo = [[OSXVideo alloc] initWithDeviceNames: device_names AmtDevices: size]; + + if ( _OSXVideo == nil ) + return -1; + + return 0; +} + +void osx_video_release() +{ + [_OSXVideo release]; + _OSXVideo = nil; +} + +int osx_video_open_device(uint32_t selection, uint16_t *width, uint16_t *height) +{ + if ( _OSXVideo == nil ) + return -1; + + return [_OSXVideo openVideoDeviceIndex: selection Width: width Height: height]; +} + +void osx_video_close_device(uint32_t device_idx) +{ + [_OSXVideo closeVideoDeviceIndex: device_idx]; +} + +int osx_video_read_device(uint8_t *y, uint8_t *u, uint8_t *v, uint16_t *width, uint16_t *height) +{ + if ( _OSXVideo == nil ) + return -1; + + return [_OSXVideo getVideoFrameY: y U: u V: v Width: width Height: height]; +} +/* + * End of C-interface for OSXVideo + */ + +#endif /* __OBJC__ */ \ No newline at end of file diff --git a/src/prompt.c b/src/prompt.c index 872481b..f0cb6c3 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -49,12 +49,13 @@ extern struct Winthread Winthread; extern FriendsList Friends; FriendRequests FrndRequests; - -#ifdef AUDIO +#ifdef VIDEO +#define AC_NUM_GLOB_COMMANDS 20 +#elif AUDIO #define AC_NUM_GLOB_COMMANDS 18 #else #define AC_NUM_GLOB_COMMANDS 16 -#endif /* AUDIO */ +#endif /* Array of global command names used for tab completion. */ static const char glob_cmd_list[AC_NUM_GLOB_COMMANDS][MAX_CMDNAME_SIZE] = { @@ -81,6 +82,14 @@ static const char glob_cmd_list[AC_NUM_GLOB_COMMANDS][MAX_CMDNAME_SIZE] = { { "/sdev" }, #endif /* AUDIO */ + +#ifdef VIDEO + + { "/lsvdev" }, + { "/svdev" }, + +#endif /* VIDEO */ + }; void kill_prompt_window(ToxWindow *self) @@ -510,6 +519,7 @@ ToxWindow new_prompt(void) ToxWindow ret; memset(&ret, 0, sizeof(ret)); + ret.num = -1; ret.active = true; ret.is_prompt = true; diff --git a/src/settings.c b/src/settings.c index 73a293f..95e20a6 100644 --- a/src/settings.c +++ b/src/settings.c @@ -32,7 +32,7 @@ #include "misc_tools.h" #ifdef AUDIO - #include "device.h" + #include "audio_device.h" #endif /* AUDIO */ #include "settings.h" diff --git a/src/toxic.c b/src/toxic.c index 0a0c302..0d49557 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -54,7 +54,7 @@ #include "settings.h" #include "log.h" #include "notify.h" -#include "device.h" +#include "audio_device.h" #include "message_queue.h" #include "execute.h" #include "term_mplex.h" @@ -65,7 +65,10 @@ #ifdef AUDIO #include "audio_call.h" -ToxAv *av; +#ifdef VIDEO +#include "video_call.h" +#endif /* VIDEO */ +ToxAV *av; #endif /* AUDIO */ #ifndef PACKAGE_DATADIR @@ -86,7 +89,7 @@ ToxWindow *prompt = NULL; struct Winthread Winthread; struct cqueue_thread cqueue_thread; -struct audio_thread audio_thread; +struct av_thread av_thread; struct arg_opts arg_opts; struct user_settings *user_settings = NULL; @@ -149,6 +152,11 @@ void exit_toxic_success(Tox *m) terminate_notify(); #ifdef AUDIO + +#ifdef VIDEO + terminate_video(); +#endif /* VIDEO */ + terminate_audio(); #endif /* AUDIO */ @@ -876,16 +884,16 @@ void *thread_cqueue(void *data) } #ifdef AUDIO -void *thread_audio(void *data) +void *thread_av(void *data) { - ToxAv *av = (ToxAv *) data; - + ToxAV *av = (ToxAV *) data; + while (true) { pthread_mutex_lock(&Winthread.lock); - toxav_do(av); + toxav_iterate(av); pthread_mutex_unlock(&Winthread.lock); - usleep(toxav_do_interval(av) * 1000); + usleep(toxav_iteration_interval(av) * 1000); } } #endif /* AUDIO */ @@ -1256,9 +1264,14 @@ int main(int argc, char **argv) #ifdef AUDIO av = init_audio(prompt, m); + +#ifdef VIDEO + init_video(prompt, m); - /* audio thread */ - if (pthread_create(&audio_thread.tid, NULL, thread_audio, (void *) av) != 0) +#endif /* VIDEO */ + + /* AV thread */ + if (pthread_create(&av_thread.tid, NULL, thread_av, (void *) av) != 0) exit_toxic_err("failed in main", FATALERR_THREAD_CREATE); set_primary_device(input, user_settings->audio_in_dev); diff --git a/src/video_call.c b/src/video_call.c new file mode 100644 index 0000000..99f810b --- /dev/null +++ b/src/video_call.c @@ -0,0 +1,419 @@ +/* video_call.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#include "toxic.h" +#include "windows.h" +#include "video_call.h" +#include "video_device.h" +#include "chat_commands.h" +#include "global_commands.h" +#include "line_info.h" +#include "notify.h" + +#include +#include +#include +#include +#include +#include +#include + +#define default_video_bit_rate 5000 + +void receive_video_frame_cb( ToxAV *av, uint32_t friend_number, + uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data ); +void video_bit_rate_status_cb( ToxAV *av, uint32_t friend_number, + bool stable, uint32_t bit_rate, void *user_data); + +static void print_err (ToxWindow *self, const char *error_str) +{ + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str); +} + +ToxAV *init_video(ToxWindow *self, Tox *tox) +{ + CallControl.video_errors = ve_None; + + CallControl.video_enabled = true; + CallControl.video_bit_rate = 0; + CallControl.video_frame_duration = 10; + + if ( !CallControl.av ) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Video failed to init with ToxAV instance"); + + return NULL; + } + + if ( init_video_devices(CallControl.av) == vde_InternalError ) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init video devices"); + + return NULL; + } + + toxav_callback_video_receive_frame(CallControl.av, receive_video_frame_cb, &CallControl); + toxav_callback_video_bit_rate_status(CallControl.av, video_bit_rate_status_cb, &CallControl); + + return CallControl.av; +} + +void terminate_video() +{ + int i; + for (i = 0; i < MAX_CALLS; ++i) { + Call* this_call = &CallControl.calls[i]; + + stop_video_transmission(this_call, i); + + if( this_call->vout_idx != -1 ) + close_video_device(vdt_output, this_call->vout_idx); + } + + terminate_video_devices(); +} + +void read_video_device_callback(int16_t width, int16_t height, const uint8_t* y, const uint8_t* u, const uint8_t* v, void* data) +{ + uint32_t friend_number = *((uint32_t*)data); /* TODO: Or pass an array of call_idx's */ + Call* this_call = &CallControl.calls[friend_number]; + TOXAV_ERR_SEND_FRAME error; + + /* Drop frame if video sending is disabled */ + if ( CallControl.video_bit_rate == 0 || this_call->vin_idx == -1 ) { + line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Video frame dropped."); + return; + } + + if ( toxav_video_send_frame(CallControl.av, friend_number, width, height, y, u, v, &error ) == false ) { + line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to send video frame"); + + if ( error == TOXAV_ERR_SEND_FRAME_NULL ) + line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to capture video frame"); + else if ( error == TOXAV_ERR_SEND_FRAME_INVALID ) + line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare video frame"); + } +} + +void write_video_device_callback(uint32_t friend_number, uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) +{ + write_video_out(width, height, y, u, v, ystride, ustride, vstride, user_data); +} + +int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call) +{ + if ( !self || !av) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare transmission"); + return -1; + } + + CallControl.video_bit_rate = default_video_bit_rate; + if ( toxav_video_bit_rate_set(CallControl.av, self->num, CallControl.video_bit_rate, true, NULL) == false ) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set video bit rate"); + return -1; + } + + if ( open_primary_video_device(vdt_input, &call->vin_idx) != vde_None ) { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open input video device!"); + return -1; + } + + if ( register_video_device_callback(self->num, call->vin_idx, read_video_device_callback, &self->num) != vde_None ) + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to register input video handler!"); + + return 0; +} + +int stop_video_transmission(Call *call, int friend_number) +{ + CallControl.video_bit_rate = 0; + toxav_video_bit_rate_set(CallControl.av, friend_number, CallControl.video_bit_rate, true, NULL); + + if ( call->vin_idx != -1 ) + close_video_device(vdt_input, call->vin_idx); + call->vin_idx = -1; + + return 0; +} +/* + * End of transmission + */ + + + + + +/* + * Callbacks + */ +void receive_video_frame_cb(ToxAV *av, uint32_t friend_number, + uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) +{ + write_video_device_callback(friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data); +} + +void video_bit_rate_status_cb(ToxAV *av, uint32_t friend_number, + bool stable, uint32_t bit_rate, void *user_data) +{ + if ( stable ) { + CallControl.video_bit_rate = bit_rate; + toxav_video_bit_rate_set(CallControl.av, friend_number, CallControl.video_bit_rate, false, NULL); + } +} + +void callback_recv_video_starting(uint32_t friend_number) +{ + Call* this_call = &CallControl.calls[friend_number]; + + if ( this_call->vout_idx != -1 ) + return; + + open_primary_video_device(vdt_output, &this_call->vout_idx); +} +void callback_recv_video_end(uint32_t friend_number) +{ + Call* this_call = &CallControl.calls[friend_number]; + + close_video_device(vdt_output, this_call->vout_idx); + this_call->vout_idx = -1; +} +void callback_video_starting(uint32_t friend_number) +{ + ToxWindow* windows = CallControl.prompt; + Call* this_call = &CallControl.calls[friend_number]; + + TOXAV_ERR_CALL_CONTROL error = TOXAV_ERR_CALL_CONTROL_OK; + toxav_call_control(CallControl.av, friend_number, TOXAV_CALL_CONTROL_SHOW_VIDEO, &error); + + if (error == TOXAV_ERR_CALL_CONTROL_OK) { + int i; + for (i = 0; i < MAX_WINDOWS_NUM; ++i) { + if ( windows[i].is_call && windows[i].num == friend_number ) { + if(0 != start_video_transmission(&windows[i], CallControl.av, this_call)) { + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Error starting transmission!"); + return; + } + + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video capture starting."); + } + } + } +} +void callback_video_end(uint32_t friend_number) +{ + ToxWindow* windows = CallControl.prompt; + + int i; + for (i = 0; i < MAX_WINDOWS_NUM; ++i) + if ( windows[i].is_call && windows[i].num == friend_number ) + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video capture ending."); + + stop_video_transmission(&CallControl.calls[friend_number], friend_number); +} +/* + * End of Callbacks + */ + + + +/* + * Commands from chat_commands.h + */ +void cmd_video(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + const char *error_str; + Call* this_call = &CallControl.calls[self->num]; + + if ( argc != 0 ) { + error_str = "Unknown arguments."; + goto on_error; + } + + if ( !CallControl.av ) { + error_str = "ToxAV not supported!"; + goto on_error; + } + + if ( !self->stb->connection ) { + error_str = "Friend is offline."; + goto on_error; + } + + if ( !self->is_call ) { + error_str = "Not in call!"; + goto on_error; + } + + if ( this_call->vin_idx == -1 ) + callback_video_starting(self->num); + else + callback_video_end(self->num); + + return; +on_error: + print_err (self, error_str); +} + +void cmd_list_video_devices(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + const char *error_str; + + if ( argc != 1 ) { + if ( argc < 1 ) error_str = "Type must be specified!"; + else error_str = "Only one argument allowed!"; + + goto on_error; + } + + VideoDeviceType type; + + if ( strcasecmp(argv[1], "in") == 0 ) /* Input devices */ + type = vdt_input; + + else if ( strcasecmp(argv[1], "out") == 0 ) /* Output devices */ + type = vdt_output; + + else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]); + return; + } + + print_video_devices(self, type); + + return; +on_error: + print_err (self, error_str); +} + +/* This changes primary video device only */ +void cmd_change_video_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + const char *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; + } + + VideoDeviceType type; + + if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ + type = vdt_input; + + else if ( strcmp(argv[1], "out") == 0 ) /* Output devices */ + type = vdt_output; + + else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]); + return; + } + + + char *end; + long int selection = strtol(argv[2], &end, 10); + + if ( *end ) { + error_str = "Invalid input"; + goto on_error; + } + + if ( set_primary_video_device(type, selection) == vde_InvalidSelection ) { + error_str = "Invalid selection!"; + goto on_error; + } + + return; +on_error: + print_err (self, error_str); +} + +void cmd_ccur_video_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) +{ + const char *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; + } + + VideoDeviceType type; + + if ( strcmp(argv[1], "in") == 0 ) /* Input devices */ + type = vdt_input; + + else if ( strcmp(argv[1], "out") == 0 ) /* Output devices */ + type = vdt_output; + + else { + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]); + return; + } + + + char *end; + long int selection = strtol(argv[2], &end, 10); + + if ( *end ) { + error_str = "Invalid input"; + goto on_error; + } + + if ( video_selection_valid(type, selection) == vde_InvalidSelection ) { + error_str="Invalid selection!"; + goto on_error; + } + + /* If call is active, change device */ + if ( self->is_call ) { + Call* this_call = &CallControl.calls[self->num]; + if ( this_call->ttas ) { + + if ( type == vdt_output ) { + } + else { + /* TODO: check for failure */ + close_video_device(vdt_input, this_call->vin_idx); + open_video_device(vdt_input, selection, &this_call->vin_idx); + register_video_device_callback(self->num, this_call->vin_idx, read_video_device_callback, &self->num); + } + } + } + + self->video_device_selection[type] = selection; + + return; + on_error: + print_err (self, error_str); +} \ No newline at end of file diff --git a/src/video_call.h b/src/video_call.h new file mode 100644 index 0000000..00a0ce0 --- /dev/null +++ b/src/video_call.h @@ -0,0 +1,43 @@ +/* video_call.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifndef VIDEO_CALL_H +#define VIDEO_CALL_H + +#include + +#include "audio_call.h" + +#include "video_device.h" + +/* You will have to pass pointer to first member of 'windows' declared in windows.c */ +ToxAV *init_video(ToxWindow *self, Tox *tox); +void terminate_video(); +int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call); +int stop_video_transmission(Call *call, int friend_number); + +void callback_recv_video_starting(uint32_t friend_number); +void callback_recv_video_end(uint32_t friend_number); +void callback_video_starting(uint32_t friend_number); +void callback_video_end(uint32_t friend_number); + +#endif /* VIDEO_CALL_H */ \ No newline at end of file diff --git a/src/video_device.c b/src/video_device.c new file mode 100644 index 0000000..c0217d4 --- /dev/null +++ b/src/video_device.c @@ -0,0 +1,772 @@ +/* video_device.c + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#include "video_device.h" +#include "video_call.h" + +#include +#include +#include +#include + +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#else /* __OSX__ */ +#import "osx_video.h" +#endif + +#include "line_info.h" +#include "settings.h" + +#include + +#include +#include +#include +#include +#include +#include + +#define inline__ inline __attribute__((always_inline)) + +extern struct user_settings *user_settings; + +struct VideoBuffer { + void *start; + size_t length; +}; + +typedef struct VideoDevice { + VideoDataHandleCallback cb; /* Use this to handle data from input device usually */ + void* cb_data; /* Data to be passed to callback */ + int32_t friend_number; /* ToxAV friend number */ + +#ifdef __linux__ + int fd; /* File descriptor of video device selected/opened */ + struct v4l2_format fmt; + struct VideoBuffer *buffers; + uint32_t n_buffers; +#endif + + uint32_t ref_count; + int32_t selection; + pthread_mutex_t mutex[1]; + uint16_t video_width; + uint16_t video_height; + + vpx_image_t input; + + Display *x_display; + Window x_window; + GC x_gc; + +} VideoDevice; + +const char *dvideo_device_names[2]; /* Default device */ +const char *video_devices_names[2][MAX_DEVICES]; /* Container of available devices */ +static int size[2]; /* Size of above containers */ +VideoDevice *video_devices_running[2][MAX_DEVICES] = {{NULL}}; /* Running devices */ +uint32_t primary_video_device[2]; /* Primary device */ + +#ifdef VIDEO +static ToxAV* av = NULL; +#endif /* VIDEO */ + +/* q_mutex */ +#define lock pthread_mutex_lock(&video_mutex); +#define unlock pthread_mutex_unlock(&video_mutex); +pthread_mutex_t video_mutex; + +bool video_thread_running = true, + video_thread_paused = true; /* Thread control */ + +void* video_thread_poll(void*); + +static void yuv420tobgr(uint16_t width, uint16_t height, const uint8_t *y, + const uint8_t *u, const uint8_t *v, unsigned int ystride, + unsigned int ustride, unsigned int vstride, uint8_t *out) +{ + unsigned long int i, j; + for (i = 0; i < height; ++i) { + for (j = 0; j < width; ++j) { + uint8_t *point = out + 4 * ((i * width) + j); + int t_y = y[((i * ystride) + j)]; + int t_u = u[(((i / 2) * ustride) + (j / 2))]; + int t_v = v[(((i / 2) * vstride) + (j / 2))]; + t_y = t_y < 16 ? 16 : t_y; + + int r = (298 * (t_y - 16) + 409 * (t_v - 128) + 128) >> 8; + int g = (298 * (t_y - 16) - 100 * (t_u - 128) - 208 * (t_v - 128) + 128) >> 8; + int b = (298 * (t_y - 16) + 516 * (t_u - 128) + 128) >> 8; + + point[2] = r>255? 255 : r<0 ? 0 : r; + point[1] = g>255? 255 : g<0 ? 0 : g; + point[0] = b>255? 255 : b<0 ? 0 : b; + point[3] = ~0; + } + } +} + +#ifdef __linux__ +static void yuv422to420(uint8_t *plane_y, uint8_t *plane_u, uint8_t *plane_v, + uint8_t *input, uint16_t width, uint16_t height) +{ + uint8_t *end = input + width * height * 2; + while (input != end) { + uint8_t *line_end = input + width * 2; + while (input != line_end) { + *plane_y++ = *input++; + *plane_u++ = *input++; + *plane_y++ = *input++; + *plane_v++ = *input++; + } + + line_end = input + width * 2; + while (input != line_end) { + *plane_y++ = *input++; + input++;//u + *plane_y++ = *input++; + input++;//v + } + } +} + +static int xioctl(int fh, unsigned long request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +#endif /* __linux__ */ + +/* Meet devices */ +#ifdef VIDEO +VideoDeviceError init_video_devices(ToxAV* av_) +#else +VideoDeviceError init_video_devices() +#endif /* VIDEO */ +{ + size[vdt_input] = 0; + +#ifdef __linux__ + for (; size[vdt_input] <= MAX_DEVICES; ++size[vdt_input]) { + int fd; + char device_address[] = "/dev/videoXX"; + snprintf(device_address + 10, sizeof(char) * strlen(device_address) - 10, "%i", size[vdt_input]); + + fd = open(device_address, O_RDWR | O_NONBLOCK, 0); + if ( fd == -1 ) { + break; + } else { + struct v4l2_capability cap; + char* video_input_name; + + /* Query V4L for capture capabilities */ + if ( -1 != ioctl(fd, VIDIOC_QUERYCAP, &cap) ) { + video_input_name = (char*)malloc(strlen((const char*)cap.card) + strlen(device_address) + 4); + strcpy(video_input_name, (char*)cap.card); + strcat(video_input_name, " ("); + strcat(video_input_name, (char*)device_address); + strcat(video_input_name, ")"); + } else { + video_input_name = (char*)malloc(strlen(device_address) + 3); + strcpy(video_input_name, "("); + strcat(video_input_name, device_address); + strcat(video_input_name, ")"); + } + video_devices_names[vdt_input][size[vdt_input]] = video_input_name; + + close(fd); + } + } + +#else /* __OSX__ */ + if( osx_video_init((char**)video_devices_names[vdt_input], &size[vdt_input]) != 0 ) + return vde_InternalError; +#endif + + size[vdt_output] = 1; + char* video_output_name = "Toxic Video Receiver"; + video_devices_names[vdt_output][0] = video_output_name; + + // Start poll thread + if ( pthread_mutex_init(&video_mutex, NULL) != 0 ) + return vde_InternalError; + + pthread_t thread_id; + if ( pthread_create(&thread_id, NULL, video_thread_poll, NULL) != 0 || pthread_detach(thread_id) != 0 ) + return vde_InternalError; + +#ifdef VIDEO + av = av_; +#endif /* VIDEO */ + + return (VideoDeviceError) vde_None; +} + +VideoDeviceError terminate_video_devices() +{ + /* Cleanup if needed */ + video_thread_running = false; + usleep(20000); + + int i; + for (i = 0; i < size[vdt_input]; ++i) { + free((void*)video_devices_names[vdt_input][i]); + } + + if ( pthread_mutex_destroy(&video_mutex) != 0 ) + return (VideoDeviceError) vde_InternalError; + +#ifdef __OSX__ + osx_video_release(); +#endif /* __OSX__ */ + + return (VideoDeviceError) vde_None; +} + +VideoDeviceError register_video_device_callback(int32_t friend_number, uint32_t device_idx, + VideoDataHandleCallback callback, void* data) +{ +#ifdef __linux__ + if ( size[vdt_input] <= device_idx || !video_devices_running[vdt_input][device_idx] || !video_devices_running[vdt_input][device_idx]->fd ) + return vde_InvalidSelection; +#else /* __OSX__ */ + if ( size[vdt_input] <= device_idx || !video_devices_running[vdt_input][device_idx] ) + return vde_InvalidSelection; +#endif + + lock; + video_devices_running[vdt_input][device_idx]->cb = callback; + video_devices_running[vdt_input][device_idx]->cb_data = data; + video_devices_running[vdt_input][device_idx]->friend_number = friend_number; + unlock; + + return vde_None; +} + +VideoDeviceError set_primary_video_device(VideoDeviceType type, int32_t selection) +{ + if ( size[type] <= selection || selection < 0 ) return vde_InvalidSelection; + + primary_video_device[type] = selection; + + return vde_None; +} + +VideoDeviceError open_primary_video_device(VideoDeviceType type, uint32_t* device_idx) +{ + return open_video_device(type, primary_video_device[type], device_idx); +} + +void get_primary_video_device_name(VideoDeviceType type, char *buf, int size) +{ + memcpy(buf, dvideo_device_names[type], size); +} + +VideoDeviceError open_video_device(VideoDeviceType type, int32_t selection, uint32_t* device_idx) +{ + if ( size[type] <= selection || selection < 0 ) return vde_InvalidSelection; + + lock; + + uint32_t i; + for (i = 0; i < MAX_DEVICES && video_devices_running[type][i]; ++i); + + if (i == MAX_DEVICES) { unlock; return vde_AllDevicesBusy; } + else *device_idx = i; + + for (i = 0; i < MAX_DEVICES; i ++) { /* Check if any device has the same selection */ + if ( video_devices_running[type][i] && video_devices_running[type][i]->selection == selection ) { + + video_devices_running[type][*device_idx] = video_devices_running[type][i]; + video_devices_running[type][i]->ref_count ++; + + unlock; + return vde_None; + } + } + + VideoDevice* device = video_devices_running[type][*device_idx] = calloc(1, sizeof(VideoDevice)); + device->selection = selection; + + if ( pthread_mutex_init(device->mutex, NULL) != 0 ) { + free(device); + unlock; + return vde_InternalError; + } + + if ( type == vdt_input ) { + video_thread_paused = true; + +#ifdef __linux__ + /* Open selected device */ + char device_address[] = "/dev/videoXX"; + snprintf(device_address + 10 , sizeof(device_address) - 10, "%i", selection); + + device->fd = open(device_address, O_RDWR); + if ( device->fd == -1 ) { + return vde_FailedStart; + } + + /* Obtain video device capabilities */ + struct v4l2_capability cap; + if ( -1 == xioctl(device->fd, VIDIOC_QUERYCAP, &cap) ) { + close(device->fd); + free(device); + + return vde_FailedStart; + } + + /* Setup video format */ + struct v4l2_format fmt; + memset(&(fmt), 0, sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + if( -1 == xioctl(device->fd, VIDIOC_G_FMT, &fmt) ) { + close(device->fd); + free(device); + + return vde_FailedStart; + } + + device->video_width = fmt.fmt.pix.width; + device->video_height = fmt.fmt.pix.height; + + /* Request buffers */ + struct v4l2_requestbuffers req; + memset(&(req), 0, sizeof(req)); + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + if ( -1 == xioctl(device->fd, VIDIOC_REQBUFS, &req) ) { + close(device->fd); + free(device); + + return vde_FailedStart; + } + + if ( req.count < 2 ) { + close(device->fd); + free(device); + + return vde_FailedStart; + } + + device->buffers = calloc(req.count, sizeof(struct VideoBuffer)); + + for (i = 0; i < req.count; ++i) { + struct v4l2_buffer buf; + memset(&(buf), 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if ( -1 == xioctl(device->fd, VIDIOC_QUERYBUF, &buf) ) { + close(device->fd); + free(device); + + return vde_FailedStart; + } + + device->buffers[i].length = buf.length; + device->buffers[i].start = mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + device->fd, buf.m.offset); + + if ( MAP_FAILED == device->buffers[i].start ) { + for (i = 0; i < buf.index; ++i) + munmap(device->buffers[i].start, device->buffers[i].length); + close(device->fd); + free(device); + + return vde_FailedStart; + } + } + device->n_buffers = i; + + enum v4l2_buf_type type; + + for (i = 0; i < device->n_buffers; ++i) { + struct v4l2_buffer buf; + memset(&(buf), 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if ( -1 == xioctl(device->fd, VIDIOC_QBUF, &buf) ) { + for (i = 0; i < device->n_buffers; ++i) + munmap(device->buffers[i].start, device->buffers[i].length); + close(device->fd); + free(device); + + return vde_FailedStart; + } + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Turn on video stream */ + if ( -1 == xioctl(device->fd, VIDIOC_STREAMON, &type) ) { + close_video_device(vdt_input, *device_idx); + + return vde_FailedStart; + } + +#else /* __OSX__ */ + if ( osx_video_open_device(selection, &device->video_width, &device->video_height) != 0 ) { + free(device); + + return vde_FailedStart; + } +#endif + + /* Create X11 window associated to device */ + if ( (device->x_display = XOpenDisplay(NULL)) == NULL ) { + close_video_device(vdt_input, *device_idx); + + return vde_FailedStart; + } + + int screen = DefaultScreen(device->x_display); + + if ( !(device->x_window = XCreateSimpleWindow(device->x_display, RootWindow(device->x_display, screen), 0, 0, + device->video_width, device->video_height, 0, BlackPixel(device->x_display, screen), + BlackPixel(device->x_display, screen))) ) { + close_video_device(vdt_input, *device_idx); + + return vde_FailedStart; + } + + XStoreName(device->x_display, device->x_window, "Video Preview"); + XSelectInput(device->x_display, device->x_window, ExposureMask|ButtonPressMask|KeyPressMask); + + if ( (device->x_gc = DefaultGC(device->x_display, screen)) == NULL ) { + close_video_device(vdt_input, *device_idx); + + return vde_FailedStart; + } + + /* Disable user from manually closing the X11 window */ + Atom wm_delete_window = XInternAtom(device->x_display, "WM_DELETE_WINDOW", false); + XSetWMProtocols(device->x_display, device->x_window, &wm_delete_window, 1); + + XMapWindow(device->x_display, device->x_window); + XClearWindow(device->x_display, device->x_window); + XMapRaised(device->x_display, device->x_window); + XFlush(device->x_display); + + vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, device->video_width, device->video_height, 1); + + video_thread_paused = false; + } else { /* vdt_output */ + + /* Create X11 window associated to device */ + if ( (device->x_display = XOpenDisplay(NULL)) == NULL ) { + close_video_device(vdt_output, *device_idx); + + return vde_FailedStart; + } + + int screen = DefaultScreen(device->x_display); + + if ( !(device->x_window = XCreateSimpleWindow(device->x_display, RootWindow(device->x_display, screen), 0, 0, + 100, 100, 0, BlackPixel(device->x_display, screen), BlackPixel(device->x_display, screen))) ) { + close_video_device(vdt_output, *device_idx); + + return vde_FailedStart; + } + + XStoreName(device->x_display, device->x_window, "Video Receive"); + XSelectInput(device->x_display, device->x_window, ExposureMask|ButtonPressMask|KeyPressMask); + + if ( (device->x_gc = DefaultGC(device->x_display, screen)) == NULL ) { + close_video_device(vdt_output, *device_idx); + + return vde_FailedStart; + } + + /* Disable user from manually closing the X11 window */ + Atom wm_delete_window = XInternAtom(device->x_display, "WM_DELETE_WINDOW", false); + XSetWMProtocols(device->x_display, device->x_window, &wm_delete_window, 1); + + XMapWindow(device->x_display, device->x_window); + XClearWindow(device->x_display, device->x_window); + XMapRaised(device->x_display, device->x_window); + XFlush(device->x_display); + + vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, device->video_width, device->video_height, 1); + } + + unlock; + return vde_None; +} + +__inline VideoDeviceError write_video_out(uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) +{ + VideoDevice* device = video_devices_running[vdt_output][0]; + + if ( !device ) return vde_DeviceNotActive; + + if( !device->x_window ) return vde_DeviceNotActive; + + pthread_mutex_lock(device->mutex); + + /* Resize X11 window to correct size */ + if ( device->video_width != width || device->video_height != height ) { + device->video_width = width; + device->video_height = height; + XResizeWindow(device->x_display, device->x_window, width, height); + + vpx_img_free(&device->input); + vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, width, height, 1); + } + + /* Convert YUV420 data to BGR */ + ystride = abs(ystride); + ustride = abs(ustride); + vstride = abs(vstride); + uint8_t *img_data = malloc(width * height * 4); + yuv420tobgr(width, height, y, u, v, ystride, ustride, vstride, img_data); + + /* Allocate image data in X11 */ + XImage image = { + .width = width, + .height = height, + .depth = 24, + .bits_per_pixel = 32, + .format = ZPixmap, + .byte_order = LSBFirst, + .bitmap_unit = 8, + .bitmap_bit_order = LSBFirst, + .bytes_per_line = width * 4, + .red_mask = 0xFF0000, + .green_mask = 0xFF00, + .blue_mask = 0xFF, + .data = (char*)img_data + }; + + /* Render image data */ + Pixmap pixmap = XCreatePixmap(device->x_display, device->x_window, width, height, 24); + XPutImage(device->x_display, pixmap, device->x_gc, &image, 0, 0, 0, 0, width, height); + XCopyArea(device->x_display, pixmap, device->x_window, device->x_gc, 0, 0, width, height, 0, 0); + XFreePixmap(device->x_display, pixmap); + XFlush(device->x_display); + free(img_data); + + pthread_mutex_unlock(device->mutex); + return vde_None; +} + +void* video_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; + + while (video_thread_running) + { + if ( video_thread_paused ) usleep(10000); /* Wait for unpause. */ + else + { + for (i = 0; i < size[vdt_input]; ++i) + { + lock; + if ( video_devices_running[vdt_input][i] != NULL ) + { + /* Obtain frame image data from device buffers */ + VideoDevice* device = video_devices_running[vdt_input][i]; + uint16_t video_width = device->video_width; + uint16_t video_height = device->video_height; + uint8_t *y = device->input.planes[0]; + uint8_t *u = device->input.planes[1]; + uint8_t *v = device->input.planes[2]; + +#ifdef __linux__ + struct v4l2_buffer buf; + memset(&(buf), 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if ( -1 == ioctl(device->fd, VIDIOC_DQBUF, &buf) ) { + unlock; + continue; + } + + void *data = (void*)device->buffers[buf.index].start; + + /* Convert frame image data to YUV420 for ToxAV */ + yuv422to420(y, u, v, data, video_width, video_height); + +#else /* __OSX__*/ + if ( osx_video_read_device(y, u, v, &video_width, &video_height) != 0 ) { + unlock; + continue; + } +#endif + + /* Send frame data to friend through ToxAV */ + if ( device->cb ) + device->cb(video_width, video_height, y, u, v, device->cb_data); + + /* Convert YUV420 data to BGR */ + uint8_t *img_data = malloc(video_width * video_height * 4); + yuv420tobgr(video_width, video_height, y, u, v, + video_width, video_width/2, video_width/2, img_data); + + /* Allocate image data in X11 */ + XImage image = { + .width = video_width, + .height = video_height, + .depth = 24, + .bits_per_pixel = 32, + .format = ZPixmap, + .byte_order = LSBFirst, + .bitmap_unit = 8, + .bitmap_bit_order = LSBFirst, + .bytes_per_line = video_width * 4, + .red_mask = 0xFF0000, + .green_mask = 0xFF00, + .blue_mask = 0xFF, + .data = (char*)img_data + }; + + /* Render image data */ + Pixmap pixmap = XCreatePixmap(device->x_display, device->x_window, video_width, video_height, 24); + XPutImage(device->x_display, pixmap, device->x_gc, &image, 0, 0, 0, 0, video_width, video_height); + XCopyArea(device->x_display, pixmap, device->x_window, device->x_gc, 0, 0, video_width, video_height, 0, 0); + XFreePixmap(device->x_display, pixmap); + XFlush(device->x_display); + free(img_data); + +#ifdef __linux__ + if ( -1 == xioctl(device->fd, VIDIOC_QBUF, &buf) ) { + unlock; + continue; + } +#endif /* __linux__ */ + + } + unlock; + } + usleep(1000 * 1000 / 24); + } + } + + pthread_exit(NULL); +} + +VideoDeviceError close_video_device(VideoDeviceType type, uint32_t device_idx) +{ + if ( device_idx >= MAX_DEVICES ) return vde_InvalidSelection; + + lock; + VideoDevice *device = video_devices_running[type][device_idx]; + VideoDeviceError rc = vde_None; + + if ( !device ) { + unlock; + return vde_DeviceNotActive; + } + + video_devices_running[type][device_idx] = NULL; + + if ( !device->ref_count ) { + + if ( type == vdt_input ) { +#ifdef __linux__ + enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if( -1 == xioctl(device->fd, VIDIOC_STREAMOFF, &buf_type) ) {} + + int i; + for (i = 0; i < device->n_buffers; ++i) { + if ( -1 == munmap(device->buffers[i].start, device->buffers[i].length) ) { + } + } + close(device->fd); + +#else /* __OSX__ */ + osx_video_close_device(device_idx); +#endif + vpx_img_free(&device->input); + XDestroyWindow(device->x_display, device->x_window); + XFlush(device->x_display); + XCloseDisplay(device->x_display); + pthread_mutex_destroy(device->mutex); + +#ifdef __linux__ + free(device->buffers); +#endif /* __linux__ */ + + free(device); + } else { + vpx_img_free(&device->input); + XDestroyWindow(device->x_display, device->x_window); + XFlush(device->x_display); + XCloseDisplay(device->x_display); + pthread_mutex_destroy(device->mutex); + free(device); + } + + } + else device->ref_count--; + + unlock; + return rc; +} + +void print_video_devices(ToxWindow* self, VideoDeviceType type) +{ + int i; + + for (i = 0; i < size[type]; ++i) + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i, video_devices_names[type][i]); + + return; +} + +VideoDeviceError video_selection_valid(VideoDeviceType type, int32_t selection) +{ + return (size[type] <= selection || selection < 0) ? vde_InvalidSelection : vde_None; +} \ No newline at end of file diff --git a/src/video_device.h b/src/video_device.h new file mode 100644 index 0000000..c32cffc --- /dev/null +++ b/src/video_device.h @@ -0,0 +1,76 @@ +/* video_device.h + * + * + * Copyright (C) 2014 Toxic All Rights Reserved. + * + * This file is part of Toxic. + * + * Toxic is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Toxic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toxic. If not, see . + * + */ + +#ifndef VIDEO_DEVICE_H +#define VIDEO_DEVICE_H + +#define MAX_DEVICES 32 +#include +#include "windows.h" + +typedef enum VideoDeviceType { + vdt_input, + vdt_output, +} VideoDeviceType; + +typedef enum VideoDeviceError { + vde_None, + vde_InternalError = -1, + vde_InvalidSelection = -2, + vde_FailedStart = -3, + vde_Busy = -4, + vde_AllDevicesBusy = -5, + vde_DeviceNotActive = -6, + vde_BufferError = -7, + vde_UnsupportedMode = -8, + vde_CaptureError = -9, +} VideoDeviceError; + +typedef void (*VideoDataHandleCallback) (int16_t width, int16_t height, const uint8_t* y, const uint8_t* u, const uint8_t* v, void* data); + +#ifdef VIDEO +VideoDeviceError init_video_devices(ToxAV* av); +#else +VideoDeviceError init_video_devices(); +#endif /* VIDEO */ + +VideoDeviceError terminate_video_devices(); + +/* Callback handles ready data from INPUT device */ +VideoDeviceError register_video_device_callback(int32_t call_idx, uint32_t device_idx, VideoDataHandleCallback callback, void* data); +void* get_video_device_callback_data(uint32_t device_idx); + +VideoDeviceError set_primary_video_device(VideoDeviceType type, int32_t selection); +VideoDeviceError open_primary_video_device(VideoDeviceType type, uint32_t* device_idx); +/* Start device */ +VideoDeviceError open_video_device(VideoDeviceType type, int32_t selection, uint32_t* device_idx); +/* Stop device */ +VideoDeviceError close_video_device(VideoDeviceType type, uint32_t device_idx); + +/* Write data to device */ +VideoDeviceError write_video_out(uint16_t width, uint16_t height, uint8_t const *y, uint8_t const *u, uint8_t const *v, int32_t ystride, int32_t ustride, int32_t vstride, void *user_data); + +void print_video_devices(ToxWindow* self, VideoDeviceType type); +void get_primary_video_device_name(VideoDeviceType type, char *buf, int size); + +VideoDeviceError video_selection_valid(VideoDeviceType type, int32_t selection); +#endif /* VIDEO_DEVICE_H */ \ No newline at end of file diff --git a/src/windows.h b/src/windows.h index 21e90bf..c27b433 100644 --- a/src/windows.h +++ b/src/windows.h @@ -76,7 +76,7 @@ struct cqueue_thread { pthread_t tid; }; -struct audio_thread { +struct av_thread { pthread_t tid; }; @@ -135,24 +135,29 @@ struct ToxWindow { #ifdef AUDIO - void(*onInvite)(ToxWindow *, ToxAv *, int); - void(*onRinging)(ToxWindow *, ToxAv *, int); - void(*onStarting)(ToxWindow *, ToxAv *, int); - void(*onEnding)(ToxWindow *, ToxAv *, int); - void(*onError)(ToxWindow *, ToxAv *, int); - void(*onStart)(ToxWindow *, ToxAv *, int); - void(*onCancel)(ToxWindow *, ToxAv *, int); - void(*onReject)(ToxWindow *, ToxAv *, int); - void(*onEnd)(ToxWindow *, ToxAv *, int); - void(*onRequestTimeout)(ToxWindow *, ToxAv *, int); - void(*onPeerTimeout)(ToxWindow *, ToxAv *, int); - void(*onWriteDevice)(ToxWindow *, Tox *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int); + void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onRinging)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onStarting)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onEnding)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onError)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onStart)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onCancel)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onReject)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onEnd)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onRequestTimeout)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onPeerTimeout)(ToxWindow *, ToxAV *, uint32_t, int); + void(*onWriteDevice)(ToxWindow *, Tox *, uint32_t, int, const int16_t *, unsigned int, uint8_t, unsigned int); - 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 */ - + bool is_call; int ringing_sound; + +#ifdef VIDEO + + int video_device_selection[2]; /* -1 if not set, if set uses these selections instead of primary video device */ + +#endif /* VIDEO */ + #endif /* AUDIO */ int active_box; /* For box notify */