diff --git a/auto_tests/toxav_basic_test.c b/auto_tests/toxav_basic_test.c index af8d91e97..5821a6d45 100644 --- a/auto_tests/toxav_basic_test.c +++ b/auto_tests/toxav_basic_test.c @@ -2,23 +2,31 @@ #include "config.h" #endif +#ifndef HAVE_LIBCHECK +# include + +# define ck_assert(X) assert(X); +# define START_TEST(NAME) void NAME () +# define END_TEST +#else +# include "helpers.h" +#endif + #include #include #include #include -#include #include #include -#include #include #include "../toxcore/tox.h" +#include "../toxcore/util.h" #include "../toxcore/logger.h" #include "../toxcore/crypto_core.h" #include "../toxav/toxav.h" -#include "helpers.h" #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) #define c_sleep(x) Sleep(1*x) @@ -28,604 +36,564 @@ #endif - -typedef enum _CallStatus { - none, - InCall, - Ringing, - Ended, - Rejected, - Canceled, - TimedOut - -} CallStatus; - -typedef struct _Party { - CallStatus status; - ToxAv *av; - time_t *CallStarted; - int call_index; -} Party; - -typedef struct _Status { - Party Alice; - Party Bob; -} Status; - -/* My default settings */ -static ToxAvCSettings muhcaps; - -void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) -{ - if (length == 7 && memcmp("gentoo", data, 7) == 0) { - tox_friend_add_norequest(m, public_key, 0); - } -} +#define TEST_REGULAR_AV 1 +#define TEST_REGULAR_A 1 +#define TEST_REGULAR_V 1 +#define TEST_REJECT 1 +#define TEST_CANCEL 1 +#define TEST_MUTE_UNMUTE 1 +#define TEST_STOP_RESUME_PAYLOAD 1 +#define TEST_PAUSE_RESUME_SEND 1 -/******************************************************************************/ -void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (cast->Alice.av == av) { - // ... - } else if (cast->Bob.av == av) { - /* Bob always receives invite */ - cast->Bob.status = Ringing; - cast->Bob.call_index = call_index; - } -} -void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (cast->Alice.av == av) { - /* Alice always sends invite */ - cast->Alice.status = Ringing; - } else if (cast->Bob.av == av) { - // ... - } -} +typedef struct { + bool incoming; + uint32_t state; + +} CallControl; -void callback_call_started ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (cast->Alice.av == av) { - printf("Call started on Alices side...\n"); - cast->Alice.status = InCall; - toxav_prepare_transmission(av, call_index, 1); - } else if (cast->Bob.av == av) { - printf("Call started on Bob side...\n"); - cast->Bob.status = InCall; - toxav_prepare_transmission(av, call_index, 1); - } -} -void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (cast->Alice.av == av) { - // ... - } else if (cast->Bob.av == av) { - printf ( "Call Canceled for Bob!\n" ); - cast->Bob.status = Canceled; - } -} -void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - printf ( "Call rejected by Bob!\n" - "Call ended for Alice!\n" ); - - /* If Bob rejects, call is ended for alice and she sends ending */ - if (cast->Alice.av == av) { - cast->Alice.status = Rejected; - } else if (cast->Bob.av == av) { - //... ignor - } -} -void callback_call_ended ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (cast->Alice.av == av) { - printf ( "Call ended for Alice!\n" ); - cast->Alice.status = Ended; - } else if (cast->Bob.av == av) { - printf ( "Call ended for Bob!\n" ); - cast->Bob.status = Ended; - } -} - -void callback_peer_cs_change ( void *av, int32_t call_index, void *_arg ) -{ - ToxAvCSettings csettings; - toxav_get_peer_csettings(av, call_index, 0, &csettings); - - printf("Peer changing settings to: \n" - "Type: %u \n" - "Video bitrate: %u \n" - "Video height: %u \n" - "Video width: %u \n" - "Audio bitrate: %u \n" - "Audio framedur: %u \n" - "Audio sample rate: %u \n" - "Audio channels: %u \n", - csettings.call_type, - csettings.video_bitrate, - csettings.max_video_height, - csettings.max_video_width, - csettings.audio_bitrate, - csettings.audio_frame_duration, - csettings.audio_sample_rate, - csettings.audio_channels - ); -} - -void callback_self_cs_change ( void *av, int32_t call_index, void *_arg ) -{ - ToxAvCSettings csettings; - toxav_get_peer_csettings(av, call_index, 0, &csettings); - - printf("Changed settings to: \n" - "Type: %u \n" - "Video bitrate: %u \n" - "Video height: %u \n" - "Video width: %u \n" - "Audio bitrate: %u \n" - "Audio framedur: %u \n" - "Audio sample rate: %u \n" - "Audio channels: %u \n", - csettings.call_type, - csettings.video_bitrate, - csettings.max_video_height, - csettings.max_video_width, - csettings.audio_bitrate, - csettings.audio_frame_duration, - csettings.audio_sample_rate, - csettings.audio_channels - ); -} - -void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - printf("Call timed-out!\n"); - - if (cast->Alice.av == av) { - cast->Alice.status = TimedOut; - } else if (cast->Bob.av == av) { - cast->Bob.status = TimedOut; - } -} - -void callback_audio (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data) -{} - -void callback_video (void *agent, int32_t call_idx, const vpx_image_t *img, void *data) -{} - -void register_callbacks(ToxAv *av, void *data) -{ - toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); - toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); - toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); - toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); - toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); - toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); - toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); - toxav_register_callstate_callback(av, callback_peer_cs_change, av_OnPeerCSChange, data); - toxav_register_callstate_callback(av, callback_self_cs_change, av_OnSelfCSChange, data); - toxav_register_audio_callback(av, callback_audio, NULL); - toxav_register_video_callback(av, callback_video, NULL); -} - - -/*************************************************************************************************/ - -/* Alice calls bob and the call starts. - * What happens during the call is defined after. To quit the loop use: step++; +/** + * Callbacks */ -#define CALL_AND_START_LOOP(AliceCallType, BobCallType) \ -{ int step = 0, running = 1; while (running) {\ - tox_iterate(bootstrap_node); tox_iterate(Alice); tox_iterate(Bob); \ - toxav_do(status_control.Bob.av); toxav_do(status_control.Alice.av); \ - switch ( step ) {\ - case 0: /* Alice */ printf("Alice is calling...\n");\ - toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); step++; break;\ - case 1: /* Bob */ if (status_control.Bob.status == Ringing) { printf("Bob answers...\n");\ - cur_time = time(NULL); toxav_answer(status_control.Bob.av, status_control.Bob.call_index, &muhcaps); step++; } break; \ - case 2: /* Rtp transmission */ \ - if (status_control.Bob.status == InCall && status_control.Alice.status == InCall) +void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + (void) av; + (void) friend_number; + (void) audio_enabled; + (void) video_enabled; + + printf("Handling CALL callback\n"); + ((CallControl*)user_data)->incoming = true; +} +void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + (void) av; + (void) friend_number; + + printf("Handling CALL STATE callback: %d\n", state); + ((CallControl*)user_data)->state = state; +} +void t_toxav_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) av; + (void) friend_number; + (void) width; + (void) height; + (void) y; + (void) u; + (void) v; + (void) ystride; + (void) ustride; + (void) vstride; + (void) user_data; + printf("Received video payload\n"); +} +void t_toxav_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) av; + (void) friend_number; + (void) pcm; + (void) sample_count; + (void) channels; + (void) sampling_rate; + (void) user_data; + printf("Received audio payload\n"); +} +void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + (void) userdata; + + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); + } +} + + +/** + * Iterate helper + */ +int iterate_tox(Tox* bootstrap, Tox* Alice, Tox* Bob) +{ + tox_iterate(bootstrap); + tox_iterate(Alice); + tox_iterate(Bob); + + return MIN(tox_iteration_interval(Alice), tox_iteration_interval(Bob)); +} -#define TERMINATE_SCOPE() break;\ -case 3: /* Wait for Both to have status ended */\ -if (status_control.Alice.status == Ended && status_control.Bob.status == Ended) running = 0; break; } c_sleep(20); } } printf("\n"); START_TEST(test_AV_flows) { + Tox* Alice, *Bob, *bootstrap; + ToxAV* AliceAV, *BobAV; + + CallControl AliceCC, BobCC; + + { + TOX_ERR_NEW error; + + bootstrap = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Alice = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Bob = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + } + + printf("Created 3 instances of Tox\n"); + printf("Preparing network...\n"); long long unsigned int cur_time = time(NULL); - Tox *bootstrap_node = tox_new(0, 0); - Tox *Alice = tox_new(0, 0); - Tox *Bob = tox_new(0, 0); - - ck_assert_msg(bootstrap_node || Alice || Bob, "Failed to create 3 tox instances"); - + uint32_t to_compare = 974536; - tox_callback_friend_request(Alice, accept_friend_request, &to_compare); uint8_t address[TOX_ADDRESS_SIZE]; + + tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); tox_self_get_address(Alice, address); - uint32_t test = tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, 0); - - ck_assert_msg(test == 0, "Failed to add friend error code: %i", test); - + + + ck_assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); + uint8_t off = 1; - + while (1) { - tox_iterate(bootstrap_node); - tox_iterate(Alice); - tox_iterate(Bob); - - if (tox_self_get_connection_status(bootstrap_node) && tox_self_get_connection_status(Alice) - && tox_self_get_connection_status(Bob) - && off) { + iterate_tox(bootstrap, Alice, Bob); + + if (tox_self_get_connection_status(bootstrap) && + tox_self_get_connection_status(Alice) && + tox_self_get_connection_status(Bob) && off) { printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); off = 0; } - - if (tox_friend_get_connection_status(Alice, 0, 0) && tox_friend_get_connection_status(Bob, 0, 0)) + + if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP) break; - + c_sleep(20); } - - printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); - - muhcaps = av_DefaultSettings; - muhcaps.max_video_height = muhcaps.max_video_width = 128; - - Status status_control = { - {none, toxav_new(Alice, 1), NULL, -1}, - {none, toxav_new(Bob, 1), NULL, -1}, - }; - - ck_assert_msg(status_control.Alice.av || status_control.Bob.av, "Failed to create 2 toxav instances"); - - - register_callbacks(status_control.Alice.av, &status_control); - register_callbacks(status_control.Bob.av, &status_control); - - const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); - int16_t sample_payload[frame_size]; - randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); - - uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; - int payload_size; - - vpx_image_t *sample_image = vpx_img_alloc(NULL, VPX_IMG_FMT_I420, 128, 128, 1); - - memcpy(sample_image->planes[VPX_PLANE_Y], sample_payload, 10); - memcpy(sample_image->planes[VPX_PLANE_U], sample_payload, 10); - memcpy(sample_image->planes[VPX_PLANE_V], sample_payload, 10); - - - /************************************************************************************************* - * Successful flows (when call starts) - */ - - /* - * Call with audio only on both sides. Alice calls Bob. - */ - - - CALL_AND_START_LOOP(TypeAudio, TypeAudio) { - /* Both send */ - payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, - 1000, sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); - - payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, - sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); - - if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ - step++; /* This terminates the loop */ - toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); - toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); - - /* Call over Alice hangs up */ - toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); - } - } - TERMINATE_SCOPE() - - - /* - * Call with audio on both sides and video on one side. Alice calls Bob. - */ - CALL_AND_START_LOOP(TypeAudio, TypeVideo) { - /* Both send */ - payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, - 1000, sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); - - payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, - sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); - -// toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); - - if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ - step++; /* This terminates the loop */ - toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); - toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); - - /* Call over Alice hangs up */ - toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); - } - } - TERMINATE_SCOPE() - - - /* - * Call with audio and video on both sides. Alice calls Bob. - */ - CALL_AND_START_LOOP(TypeVideo, TypeVideo) { - /* Both send */ - - payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, - 1000, sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); - - payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, - sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); - -// toxav_send_video(status_control.Alice.av, status_control.Alice.call_index, sample_image); -// toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); - - - if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ - step++; /* This terminates the loop */ - toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); - toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); - - /* Call over Alice hangs up */ - toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); - } - } - TERMINATE_SCOPE() - - - uint64_t times_they_are_a_changin = time(NULL); - /* Media change */ - CALL_AND_START_LOOP(TypeAudio, TypeAudio) { - /* Both send */ - payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, - 1000, sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); - - payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, - sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); - - /* Wait 2 seconds and change transmission type */ - if (time(NULL) - times_they_are_a_changin > 2) { - times_they_are_a_changin = time(NULL); - muhcaps.audio_bitrate ++; - toxav_change_settings(status_control.Alice.av, status_control.Alice.call_index, &muhcaps); - } - - if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ - step++; /* This terminates the loop */ - toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); - toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); - - /* Call over Alice hangs up */ - toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); - } - } - TERMINATE_SCOPE() - - - /************************************************************************************************* - * Other flows - */ - - /* - * Call and reject - */ + + { - int step = 0; - int running = 1; - - while (running) { - tox_iterate(bootstrap_node); - tox_iterate(Alice); - tox_iterate(Bob); - - switch ( step ) { - case 0: /* Alice */ - printf("Alice is calling...\n"); - toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); - step++; - break; - - case 1: /* Bob */ - if (status_control.Bob.status == Ringing) { - printf("Bob rejects...\n"); - toxav_reject(status_control.Bob.av, status_control.Bob.call_index, "Who likes D's anyway?"); - step++; - } - - break; - - case 2: /* Wait for Both to have status ended */ - if (status_control.Alice.status == Rejected && status_control.Bob.status == Ended) running = 0; - - break; - } - - c_sleep(20); - } - - printf("\n"); + TOXAV_ERR_NEW error; + AliceAV = toxav_new(Alice, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); + + BobAV = toxav_new(Bob, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); } - - - /* - * Call and cancel - */ - { - int step = 0; - int running = 1; - - while (running) { - tox_iterate(bootstrap_node); - tox_iterate(Alice); - tox_iterate(Bob); - - toxav_do(status_control.Alice.av); - toxav_do(status_control.Bob.av); - - - switch ( step ) { - case 0: /* Alice */ - printf("Alice is calling...\n"); - toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); - step++; - break; - - - case 1: /* Alice again */ - if (status_control.Bob.status == Ringing) { - printf("Alice cancels...\n"); - toxav_cancel(status_control.Alice.av, status_control.Alice.call_index, 0, "Who likes D's anyway?"); - step++; - } - - break; - - case 2: /* Wait for Both to have status ended */ - if (status_control.Bob.status == Canceled) running = 0; - - break; - } - - c_sleep(20); - } - - printf("\n"); + + toxav_callback_call(AliceAV, t_toxav_call_cb, &AliceCC); + toxav_callback_call_state(AliceAV, t_toxav_call_state_cb, &AliceCC); + toxav_callback_video_receive_frame(AliceAV, t_toxav_receive_video_frame_cb, &AliceCC); + toxav_callback_audio_receive_frame(AliceAV, t_toxav_receive_audio_frame_cb, &AliceCC); + + toxav_callback_call(BobAV, t_toxav_call_cb, &BobCC); + toxav_callback_call_state(BobAV, t_toxav_call_state_cb, &BobCC); + toxav_callback_video_receive_frame(BobAV, t_toxav_receive_video_frame_cb, &BobCC); + toxav_callback_audio_receive_frame(BobAV, t_toxav_receive_audio_frame_cb, &BobCC); + + printf("Created 2 instances of ToxAV\n"); + printf("All set after %llu seconds!\n", time(NULL) - cur_time); + + +#define REGULAR_CALL_FLOW(A_BR, V_BR) \ + do { \ + memset(&AliceCC, 0, sizeof(CallControl)); \ + memset(&BobCC, 0, sizeof(CallControl)); \ + \ + TOXAV_ERR_CALL rc; \ + toxav_call(AliceAV, 0, A_BR, V_BR, &rc); \ + \ + if (rc != TOXAV_ERR_CALL_OK) { \ + printf("toxav_call failed: %d\n", rc); \ + ck_assert(0); \ + } \ + \ + \ + long long unsigned int start_time = time(NULL); \ + \ + \ + while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) { \ + \ + if (BobCC.incoming) { \ + TOXAV_ERR_ANSWER rc; \ + toxav_answer(BobAV, 0, A_BR, V_BR, &rc); \ + \ + if (rc != TOXAV_ERR_ANSWER_OK) { \ + printf("toxav_answer failed: %d\n", rc); \ + ck_assert(0); \ + } \ + BobCC.incoming = false; \ + } else { \ + /* TODO rtp */ \ + \ + if (time(NULL) - start_time >= 1) { \ + \ + TOXAV_ERR_CALL_CONTROL rc; \ + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); \ + \ + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { \ + printf("toxav_call_control failed: %d\n", rc); \ + ck_assert(0); \ + } \ + } \ + } \ + \ + iterate_tox(bootstrap, Alice, Bob); \ + } \ + printf("Success!\n");\ + } while(0) + + if (TEST_REGULAR_AV) { + printf("\nTrying regular call (Audio and Video)...\n"); + REGULAR_CALL_FLOW(48, 4000); } - - /* - * Timeout - */ - { - int step = 0; - int running = 1; - - while (running) { - tox_iterate(bootstrap_node); - tox_iterate(Alice); - tox_iterate(Bob); - - toxav_do(status_control.Alice.av); - toxav_do(status_control.Bob.av); - - switch ( step ) { - case 0: - printf("Alice is calling...\n"); - toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); - step++; - break; - - case 1: - if (status_control.Alice.status == TimedOut) running = 0; - - break; - } - - c_sleep(20); - } - - printf("\n"); + + if (TEST_REGULAR_A) { + printf("\nTrying regular call (Audio only)...\n"); + REGULAR_CALL_FLOW(48, 0); } - - vpx_img_free(sample_image); - toxav_kill(status_control.Alice.av); - toxav_kill(status_control.Bob.av); - tox_kill(bootstrap_node); - tox_kill(Alice); + + if (TEST_REGULAR_V) { + printf("\nTrying regular call (Video only)...\n"); + REGULAR_CALL_FLOW(0, 4000); + } + +#undef REGULAR_CALL_FLOW + + if (TEST_REJECT) { /* Alice calls; Bob rejects */ + printf("\nTrying reject flow...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + { + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, Alice, Bob); + + /* Reject */ + { + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(BobAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + ck_assert(0); + } + } + + while (AliceCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) + iterate_tox(bootstrap, Alice, Bob); + + printf("Success!\n"); + } + + if (TEST_CANCEL) { /* Alice calls; Alice cancels while ringing */ + printf("\nTrying cancel (while ringing) flow...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + { + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, Alice, Bob); + + /* Cancel */ + { + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + ck_assert(0); + } + } + + /* Alice will not receive end state */ + while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) + iterate_tox(bootstrap, Alice, Bob); + + printf("Success!\n"); + } + + if (TEST_MUTE_UNMUTE) { /* Check Mute-Unmute etc */ + printf("\nTrying mute functionality...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + /* Assume sending audio and video */ + { + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 1000, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, Alice, Bob); + + /* At first try all stuff while in invalid state */ + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL)); + + { + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 48, 4000, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + ck_assert(0); + } + } + + iterate_tox(bootstrap, Alice, Bob); + + /* Pause and Resume */ + printf("Pause and Resume\n"); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == 0); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & (TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V)); + + /* Mute/Unmute single */ + printf("Mute/Unmute single\n"); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + + /* Mute/Unmute both */ + printf("Mute/Unmute both\n"); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); + + { + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + ck_assert(0); + } + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_STOP_RESUME_PAYLOAD) { /* Stop and resume audio/video payload */ + printf("\nTrying stop/resume functionality...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + /* Assume sending audio and video */ + { + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, Alice, Bob); + + { + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + ck_assert(0); + } + } + + iterate_tox(bootstrap, Alice, Bob); + + printf("Call started as audio only\n"); + printf("Turning on video for Alice...\n"); + ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 1000, NULL)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V); + + printf("Turning off video for Alice...\n"); + ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 0, NULL)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)); + + printf("Turning off audio for Alice...\n"); + ck_assert(toxav_bit_rate_set(AliceAV, 0, 0, -1, NULL)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_A)); + + { + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + ck_assert(0); + } + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_PAUSE_RESUME_SEND) { /* Stop and resume audio/video payload and test send options */ + printf("\nTrying stop/resume functionality...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + /* Assume sending audio and video */ + { + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, Alice, Bob); + + { + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + ck_assert(0); + } + } + + int16_t PCM[5670]; + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL)); + ck_assert(!toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL)); + ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL)); + ck_assert(toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL)); + iterate_tox(bootstrap, Alice, Bob); + + { + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + ck_assert(0); + } + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + toxav_kill(BobAV); + toxav_kill(AliceAV); tox_kill(Bob); - - printf("Calls ended!\n"); + tox_kill(Alice); + tox_kill(bootstrap); + + printf("\nTest successful!\n"); } END_TEST -/*************************************************************************************************/ - - -/*************************************************************************************************/ - -/*************************************************************************************************/ - - +#ifndef HAVE_LIBCHECK +int main(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + test_AV_flows(); + return 0; +} +#else Suite *tox_suite(void) { Suite *s = suite_create("ToxAV"); DEFTESTCASE_SLOW(AV_flows, 200); - return s; } int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + Suite *tox = tox_suite(); SRunner *test_runner = srunner_create(tox); @@ -637,6 +605,5 @@ int main(int argc, char *argv[]) srunner_free(test_runner); return number_failed; - -// return test_AV_flows(); } +#endif diff --git a/auto_tests/toxav_many_test.c b/auto_tests/toxav_many_test.c index 6017e5262..9819b6949 100644 --- a/auto_tests/toxav_many_test.c +++ b/auto_tests/toxav_many_test.c @@ -2,18 +2,27 @@ #include "config.h" #endif +#ifndef HAVE_LIBCHECK +# include + +# define ck_assert(X) assert(X); +# define START_TEST(NAME) void NAME () +# define END_TEST +#else +# include "helpers.h" +#endif + #include #include #include #include -#include #include #include -#include #include #include "../toxcore/tox.h" +#include "../toxcore/util.h" #include "../toxcore/logger.h" #include "../toxcore/crypto_core.h" #include "../toxav/toxav.h" @@ -26,365 +35,318 @@ #define c_sleep(x) usleep(1000*x) #endif -pthread_mutex_t muhmutex; -typedef enum _CallStatus { - none, - InCall, - Ringing, - Ended, - Rejected, - Canceled +typedef struct { + bool incoming; + uint32_t state; +} CallControl; -} CallStatus; +typedef struct { + ToxAV* AliceAV; + ToxAV* BobAV; + CallControl* AliceCC; + CallControl* BobCC; + uint32_t friend_number; +} thread_data; -typedef struct _Party { - CallStatus status; - ToxAv *av; - int id; -} Party; - -typedef struct _ACall { - pthread_t tid; - int idx; - - Party Caller; - Party Callee; -} ACall; - -typedef struct _Status { - ACall calls[3]; /* Make 3 calls for this test */ -} Status; - -Status status_control; - -void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +/** + * Callbacks + */ +void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) { + (void) av; + (void) audio_enabled; + (void) video_enabled; + + printf("Handling CALL callback\n"); + ((CallControl*)user_data)[friend_number].incoming = true; +} +void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + printf("Handling CALL STATE callback: %d %p\n", state, av); + ((CallControl*)user_data)[friend_number].state = state; +} +void t_toxav_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) av; + (void) friend_number; + (void) width; + (void) height; + (void) y; + (void) u; + (void) v; + (void) ystride; + (void) ustride; + (void) vstride; + (void) user_data; +} +void t_toxav_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) av; + (void) friend_number; + (void) pcm; + (void) sample_count; + (void) channels; + (void) sampling_rate; + (void) user_data; +} +void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + (void) userdata; if (length == 7 && memcmp("gentoo", data, 7) == 0) { - tox_friend_add_norequest(m, public_key, 0); + ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); } } -/******************************************************************************/ -void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) +/** + * Iterate helper + */ +ToxAV* setup_av_instance(Tox* tox, CallControl *CC) { - Status *cast = _arg; - cast->calls[call_index].Callee.status = Ringing; + TOXAV_ERR_NEW error; + + ToxAV* av = toxav_new(tox, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, t_toxav_call_cb, CC); + toxav_callback_call_state(av, t_toxav_call_state_cb, CC); + toxav_callback_video_receive_frame(av, t_toxav_receive_video_frame_cb, CC); + toxav_callback_audio_receive_frame(av, t_toxav_receive_audio_frame_cb, CC); + + return av; } -void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) +void* call_thread(void* pd) { - Status *cast = _arg; - cast->calls[call_index].Caller.status = Ringing; -} -void callback_call_ended ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (av == cast->calls[call_index].Caller.av) - cast->calls[call_index].Caller.status = Ended; - else - cast->calls[call_index].Callee.status = Ended; -} -void callback_call_started ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - - if (av == cast->calls[call_index].Caller.av) - cast->calls[call_index].Caller.status = InCall; - else - cast->calls[call_index].Callee.status = InCall; -} -void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) -{ -} -void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) -{ - Status *cast = _arg; - cast->calls[call_index].Caller.status = Rejected; -} - -void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) -{ - ck_assert_msg(0, "No answer!"); -} - -void callback_audio (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data) -{} - -void callback_video (void *agent, int32_t call_idx, const vpx_image_t *img, void *data) -{} - -void register_callbacks(ToxAv *av, void *data) -{ - toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); - toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); - toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); - toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); - toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); - toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); - toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); - - - toxav_register_audio_callback(av, callback_audio, NULL); - toxav_register_video_callback(av, callback_video, NULL); -} -/*************************************************************************************************/ - -int call_running[3]; - -void *in_thread_call (void *arg) -{ -#define call_print(call, what, args...) printf("[%d] " what "\n", call, ##args) - - ACall *this_call = arg; - uint64_t start = 0; - int step = 0; - int call_idx; - - const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); - int16_t sample_payload[frame_size]; - randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); - - uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; - - register_callbacks(this_call->Caller.av, &status_control); - register_callbacks(this_call->Callee.av, arg); - - /* NOTE: CALLEE WILL ALWAHYS NEED CALL_IDX == 0 */ - pthread_mutex_lock(&muhmutex); - - while (call_running[this_call->idx]) { - - pthread_mutex_unlock(&muhmutex); - - switch ( step ) { - case 0: /* CALLER */ - toxav_call(this_call->Caller.av, &call_idx, this_call->Callee.id, &av_DefaultSettings, 10); - call_print(call_idx, "Calling ..."); - step++; - break; - - case 1: /* CALLEE */ - pthread_mutex_lock(&muhmutex); - - if (this_call->Caller.status == Ringing) { - call_print(call_idx, "Callee answers ..."); - pthread_mutex_unlock(&muhmutex); - toxav_answer(this_call->Callee.av, 0, &av_DefaultSettings); - step++; - start = time(NULL); - pthread_mutex_lock(&muhmutex); - } - - pthread_mutex_unlock(&muhmutex); - break; - - case 2: /* Rtp transmission */ - pthread_mutex_lock(&muhmutex); - - if (this_call->Caller.status == InCall) { /* I think this is okay */ - call_print(call_idx, "Sending rtp ..."); - pthread_mutex_unlock(&muhmutex); - - c_sleep(1000); /* We have race condition here */ - toxav_prepare_transmission(this_call->Callee.av, 0, 1); - toxav_prepare_transmission(this_call->Caller.av, call_idx, 1); - - int payload_size = toxav_prepare_audio_frame(this_call->Caller.av, call_idx, prepared_payload, RTP_PAYLOAD_SIZE, - sample_payload, frame_size); - - if ( payload_size < 0 ) { - ck_assert_msg ( 0, "Failed to encode payload" ); - } - - - while (time(NULL) - start < 10) { /* 10 seconds */ - /* Both send */ - toxav_send_audio(this_call->Caller.av, call_idx, prepared_payload, payload_size); - - toxav_send_audio(this_call->Callee.av, 0, prepared_payload, payload_size); - - /* Both receive */ - int16_t storage[RTP_PAYLOAD_SIZE]; - int recved; - - c_sleep(20); - } - - step++; /* This terminates the loop */ - - pthread_mutex_lock(&muhmutex); - toxav_kill_transmission(this_call->Callee.av, 0); - toxav_kill_transmission(this_call->Caller.av, call_idx); - pthread_mutex_unlock(&muhmutex); - - /* Call over CALLER hangs up */ - toxav_hangup(this_call->Caller.av, call_idx); - call_print(call_idx, "Hanging up ..."); - - pthread_mutex_lock(&muhmutex); - } - - pthread_mutex_unlock(&muhmutex); - break; - - case 3: /* Wait for Both to have status ended */ - pthread_mutex_lock(&muhmutex); - - if (this_call->Caller.status == Ended) { - pthread_mutex_unlock(&muhmutex); - c_sleep(1000); /* race condition */ - pthread_mutex_lock(&muhmutex); - this_call->Callee.status = Ended; - call_running[this_call->idx] = 0; - } - - pthread_mutex_unlock(&muhmutex); - - break; - + ToxAV* AliceAV = ((thread_data*) pd)->AliceAV; + ToxAV* BobAV = ((thread_data*) pd)->BobAV; + CallControl* AliceCC = ((thread_data*) pd)->AliceCC; + CallControl* BobCC = ((thread_data*) pd)->BobCC; + uint32_t friend_number = ((thread_data*) pd)->friend_number; + + + memset(AliceCC, 0, sizeof(CallControl)); + memset(BobCC, 0, sizeof(CallControl)); + + { /* Call */ + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, friend_number, 48, 3000, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); } - - c_sleep(20); - - pthread_mutex_lock(&muhmutex); } - - pthread_mutex_unlock(&muhmutex); - call_print(call_idx, "Call ended successfully!"); + + while (!BobCC->incoming) + c_sleep(10); + + { /* Answer */ + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 8, 500, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + ck_assert(0); + } + } + + c_sleep(30); + + int16_t PCM[960]; + uint8_t video_y[800*600]; + uint8_t video_u[800*600 / 2]; + uint8_t video_v[800*600 / 2]; + + memset(PCM, 0, sizeof(PCM)); + memset(video_y, 0, sizeof(video_y)); + memset(video_u, 0, sizeof(video_u)); + memset(video_v, 0, sizeof(video_v)); + + time_t start_time = time(NULL); + while(time(NULL) - start_time < 4) { + toxav_iterate(AliceAV); + toxav_iterate(BobAV); + + toxav_audio_send_frame(AliceAV, friend_number, PCM, 960, 1, 48000, NULL); + toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL); + + toxav_video_send_frame(AliceAV, friend_number, 800, 600, video_y, video_u, video_v, NULL); + toxav_video_send_frame(BobAV, 0, 800, 600, video_y, video_u, video_v, NULL); + + c_sleep(10); + } + + { /* Hangup */ + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, friend_number, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d %p %p\n", rc, AliceAV, BobAV); + } + } + + c_sleep(30); + + printf ("Closing thread\n"); pthread_exit(NULL); } - - - START_TEST(test_AV_three_calls) -// void test_AV_three_calls() { + Tox* Alice, *bootstrap, *Bobs[3]; + ToxAV* AliceAV, *BobsAV[3]; + + CallControl AliceCC[3], BobsCC[3]; + + { + TOX_ERR_NEW error; + + bootstrap = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Alice = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Bobs[0] = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Bobs[1] = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + + Bobs[2] = tox_new(NULL, &error); + ck_assert(error == TOX_ERR_NEW_OK); + } + + printf("Created 5 instances of Tox\n"); + printf("Preparing network...\n"); long long unsigned int cur_time = time(NULL); - Tox *bootstrap_node = tox_new(0, 0); - Tox *caller = tox_new(0, 0); - Tox *callees[3] = { - tox_new(0, 0), - tox_new(0, 0), - tox_new(0, 0), - }; - - - ck_assert_msg(bootstrap_node != NULL, "Failed to create bootstrap node"); - - int i = 0; - - for (; i < 3; i ++) { - ck_assert_msg(callees[i] != NULL, "Failed to create 3 tox instances"); - } - - for ( i = 0; i < 3; i ++ ) { - uint32_t to_compare = 974536; - tox_callback_friend_request(callees[i], accept_friend_request, &to_compare); - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(callees[i], address); - - uint32_t test = tox_friend_add(caller, address, (uint8_t *)"gentoo", 7, 0); - ck_assert_msg( test == i, "Failed to add friend error code: %i", test); - } - + + uint32_t to_compare = 974536; + uint8_t address[TOX_ADDRESS_SIZE]; + + tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); + tox_self_get_address(Alice, address); + + + ck_assert(tox_friend_add(Bobs[0], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); + ck_assert(tox_friend_add(Bobs[1], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); + ck_assert(tox_friend_add(Bobs[2], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); + uint8_t off = 1; - + while (1) { - tox_iterate(bootstrap_node); - tox_iterate(caller); - - for (i = 0; i < 3; i ++) { - tox_iterate(callees[i]); - } - - - if (tox_self_get_connection_status(bootstrap_node) && - tox_self_get_connection_status(caller) && - tox_self_get_connection_status(callees[0]) && - tox_self_get_connection_status(callees[1]) && - tox_self_get_connection_status(callees[2]) && off) { + tox_iterate(bootstrap); + tox_iterate(Alice); + tox_iterate(Bobs[0]); + tox_iterate(Bobs[1]); + tox_iterate(Bobs[2]); + + if (tox_self_get_connection_status(bootstrap) && + tox_self_get_connection_status(Alice) && + tox_self_get_connection_status(Bobs[0]) && + tox_self_get_connection_status(Bobs[1]) && + tox_self_get_connection_status(Bobs[2]) && off) { printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); off = 0; } - - - if (tox_friend_get_connection_status(caller, 0, 0) && - tox_friend_get_connection_status(caller, 1, 0) && - tox_friend_get_connection_status(caller, 2, 0) ) + + if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Alice, 1, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Alice, 2, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[0], 0, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[1], 0, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[2], 0, NULL) == TOX_CONNECTION_UDP) break; - + c_sleep(20); } - - printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); - - ToxAv *uniqcallerav = toxav_new(caller, 3); - - for (i = 0; i < 3; i ++) { - status_control.calls[i].idx = i; - - status_control.calls[i].Caller.av = uniqcallerav; - status_control.calls[i].Caller.id = 0; - status_control.calls[i].Caller.status = none; - - status_control.calls[i].Callee.av = toxav_new(callees[i], 1); - status_control.calls[i].Callee.id = i; - status_control.calls[i].Callee.status = none; - } - - pthread_mutex_init(&muhmutex, NULL); - - for ( i = 0; i < 3; i++ ) { - call_running[i] = 1; - pthread_create(&status_control.calls[i].tid, NULL, in_thread_call, &status_control.calls[i]); - } - - /* Now start 3 calls and they'll run for 10 s */ - - for ( i = 0; i < 3; i++ ) - pthread_detach(status_control.calls[i].tid); - - while (call_running[0] || call_running[1] || call_running[2]) { - pthread_mutex_lock(&muhmutex); - - tox_iterate(bootstrap_node); - tox_iterate(caller); - tox_iterate(callees[0]); - tox_iterate(callees[1]); - tox_iterate(callees[2]); - - for ( i = 0; i < 3; i++ ) - toxav_do(status_control.calls[0].Caller.av); - - toxav_do(status_control.calls[0].Callee.av); - toxav_do(status_control.calls[1].Callee.av); - toxav_do(status_control.calls[2].Callee.av); - - pthread_mutex_unlock(&muhmutex); + + AliceAV = setup_av_instance(Alice, AliceCC); + BobsAV[0] = setup_av_instance(Bobs[0], BobsCC + 0); + BobsAV[1] = setup_av_instance(Bobs[1], BobsCC + 1); + BobsAV[2] = setup_av_instance(Bobs[2], BobsCC + 2); + + printf("Created 4 instances of ToxAV\n"); + printf("All set after %llu seconds!\n", time(NULL) - cur_time); + + thread_data tds[3]; + tds[0].AliceAV = AliceAV; + tds[0].BobAV = BobsAV[0]; + tds[0].AliceCC = AliceCC + 0; + tds[0].BobCC = BobsCC + 0; + tds[0].friend_number = 0; + + tds[1].AliceAV = AliceAV; + tds[1].BobAV = BobsAV[1]; + tds[1].AliceCC = AliceCC + 1; + tds[1].BobCC = BobsCC + 1; + tds[1].friend_number = 1; + + tds[2].AliceAV = AliceAV; + tds[2].BobAV = BobsAV[2]; + tds[2].AliceCC = AliceCC + 2; + tds[2].BobCC = BobsCC + 2; + tds[2].friend_number = 2; + + pthread_t tids[3]; + (void) pthread_create(tids + 0, NULL, call_thread, tds + 0); + (void) pthread_create(tids + 1, NULL, call_thread, tds + 1); + (void) pthread_create(tids + 2, NULL, call_thread, tds + 2); + + (void) pthread_detach(tids[0]); + (void) pthread_detach(tids[1]); + (void) pthread_detach(tids[2]); + + time_t start_time = time(NULL); + while (time(NULL) - start_time < 5) { + tox_iterate(Alice); + tox_iterate(Bobs[0]); + tox_iterate(Bobs[1]); + tox_iterate(Bobs[2]); c_sleep(20); } - - toxav_kill(status_control.calls[0].Caller.av); - toxav_kill(status_control.calls[0].Callee.av); - toxav_kill(status_control.calls[1].Callee.av); - toxav_kill(status_control.calls[2].Callee.av); - - tox_kill(bootstrap_node); - tox_kill(caller); - - for ( i = 0; i < 3; i ++) - tox_kill(callees[i]); - + + (void) pthread_join(tids[0], NULL); + (void) pthread_join(tids[1], NULL); + (void) pthread_join(tids[2], NULL); + + printf ("Killing all instances\n"); + toxav_kill(BobsAV[0]); + toxav_kill(BobsAV[1]); + toxav_kill(BobsAV[2]); + toxav_kill(AliceAV); + tox_kill(Bobs[0]); + tox_kill(Bobs[1]); + tox_kill(Bobs[2]); + tox_kill(Alice); + tox_kill(bootstrap); + + printf("\nTest successful!\n"); } END_TEST - - +#ifndef HAVE_LIBCHECK +int main(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + test_AV_three_calls(); + return 0; +} +#else Suite *tox_suite(void) { Suite *s = suite_create("ToxAV"); @@ -399,6 +361,9 @@ Suite *tox_suite(void) int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + Suite *tox = tox_suite(); SRunner *test_runner = srunner_create(tox); @@ -410,8 +375,5 @@ int main(int argc, char *argv[]) srunner_free(test_runner); return number_failed; - -// test_AV_three_calls(); - -// return 0; } +#endif diff --git a/configure.ac b/configure.ac index 2b7f3a2e2..01680ac63 100644 --- a/configure.ac +++ b/configure.ac @@ -33,7 +33,7 @@ BUILD_TESTS="yes" BUILD_AV="yes" BUILD_TESTING="yes" -LOGGING="no" +TOX_LOGGER="no" LOGGING_OUTNAM="libtoxcore.log" NCURSES_FOUND="no" @@ -82,13 +82,13 @@ AC_ARG_ENABLE([randombytes-stir], ] ) -AC_ARG_ENABLE([log], - [AC_HELP_STRING([--enable-log], [enable logging (default: auto)]) ], +AC_ARG_ENABLE([logging], + [AC_HELP_STRING([--enable-logging], [enable logging (default: auto)]) ], [ if test "x$enableval" = "xyes"; then - LOGGING="yes" + TOX_LOGGER="yes" - AC_DEFINE([LOGGING], [], [If logging enabled]) + AC_DEFINE([TOX_LOGGER], [], [If logging enabled]) AC_DEFINE([LOGGER_LEVEL], [LOG_DEBUG], [LOG_LEVEL value]) AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$LOGGING_OUTNAM"], [Output of logger]) fi @@ -99,7 +99,7 @@ AC_ARG_WITH(log-level, AC_HELP_STRING([--with-log-level=LEVEL], [Logger levels: TRACE; DEBUG; INFO; WARNING; ERROR ]), [ - if test "x$LOGGING" = "xno"; then + if test "x$TOX_LOGGER" = "xno"; then AC_MSG_WARN([Logging disabled!]) else if test "x$withval" = "xTRACE"; then @@ -127,7 +127,7 @@ AC_ARG_WITH(log-path, AC_HELP_STRING([--with-log-path=DIR], [Path of logger output]), [ - if test "x$LOGGING" = "xno"; then + if test "x$TOX_LOGGER" = "xno"; then AC_MSG_WARN([Logging disabled!]) else AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$withval""/""$LOGGING_OUTNAM"], [Output of logger]) diff --git a/other/apidsl/toxav.in.h b/other/apidsl/toxav.in.h new file mode 100644 index 000000000..ab89b0ea1 --- /dev/null +++ b/other/apidsl/toxav.in.h @@ -0,0 +1,615 @@ +%{ +/* toxav.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifndef TOXAV_H +#define TOXAV_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +%} + +/** \page av Public audio/video API for Tox clients. + * + * This API can handle multiple calls. Each call has its state, in very rare + * occasions the library can change the state of the call without apps knowledge. + * + */ + +/** \subsection events Events and callbacks + * + * As in Core API, events are handled by callbacks. One callback can be + * registered per event. All events have a callback function type named + * `toxav_{event}_cb` and a function to register it named `tox_callback_{event}`. + * Passing a NULL callback will result in no callback being registered for that + * event. Only one callback per event can be registered, so if a client needs + * multiple event listeners, it needs to implement the dispatch functionality + * itself. Unlike Core API, lack of some event handlers will cause the the + * library to drop calls before they are started. Hanging up call from a + * callback causes undefined behaviour. + * + */ + +/** \subsection threading Threading implications + * + * Unlike the Core API, this API is fully thread-safe. The library will ensure + * the proper synchronization of parallel calls. + * + * A common way to run ToxAV (multiple or single instance) is to have a thread, + * separate from tox instance thread, running a simple ${toxAV.iterate} loop, + * sleeping for ${toxAV.iteration_interval} * milliseconds on each iteration. + * + * An important thing to note is that events are triggered from both tox and + * toxav thread (see above). audio and video receive frame events are triggered + * from toxav thread while all the other events are triggered from tox thread. + * + * Tox thread has priority with mutex mechanisms. Any api function can + * fail if mutexes are held by tox thread in which case they will set SYNC + * error code. + */ + +/** + * External Tox type. + */ +class tox { + struct this; +} + +/** + * ToxAV. + */ +class toxAV { + +/** + * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox + * instance, and Tox instance can have only one ToxAV instance. One must make + * sure to close ToxAV instance prior closing Tox instance otherwise undefined + * behaviour occurs. Upon closing of ToxAV instance, all active calls will be + * forcibly terminated without notifying peers. + * + */ +struct this; +/******************************************************************************* + * + * :: API version + * + ******************************************************************************/ +/** + * The major version number. Incremented when the API or ABI changes in an + * incompatible way. + */ +#define TOXAV_VERSION_MAJOR 0u +/** + * The minor version number. Incremented when functionality is added without + * breaking the API or ABI. Set to 0 when the major version number is + * incremented. + */ +#define TOXAV_VERSION_MINOR 0u +/** + * The patch or revision number. Incremented when bugfixes are applied without + * changing any functionality or API or ABI. + */ +#define TOXAV_VERSION_PATCH 0u + +/** + * A macro to check at preprocessing time whether the client code is compatible + * with the installed version of ToxAV. + */ +#define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ + (TOXAV_VERSION_MAJOR == MAJOR && \ + (TOXAV_VERSION_MINOR > MINOR || \ + (TOXAV_VERSION_MINOR == MINOR && \ + TOXAV_VERSION_PATCH >= PATCH))) + +/** + * A macro to make compilation fail if the client code is not compatible with + * the installed version of ToxAV. + */ +#define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ + typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] + +/** + * A convenience macro to call ${version.is_compatible} with the currently + * compiling API version. + */ +#define TOXAV_VERSION_IS_ABI_COMPATIBLE() \ + toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH) + + +static namespace version { + + /** + * Return the major version number of the library. Can be used to display the + * ToxAV library version or to check whether the client is compatible with the + * dynamically linked version of ToxAV. + */ + uint32_t major(); + + /** + * Return the minor version number of the library. + */ + uint32_t minor(); + + /** + * Return the patch number of the library. + */ + uint32_t patch(); + + /** + * Return whether the compiled library version is compatible with the passed + * version numbers. + */ + bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch); + +} +/******************************************************************************* + * + * :: Creation and destruction + * + ******************************************************************************/ +/** + * Start new A/V session. There can only be only one session per Tox instance. + */ +static this new (tox::this *tox) { + NULL, + /** + * Memory allocation failure while trying to allocate structures required for + * the A/V session. + */ + MALLOC, + /** + * Attempted to create a second session for the same Tox instance. + */ + MULTIPLE, +} +/** + * Releases all resources associated with the A/V session. + * + * If any calls were ongoing, these will be forcibly terminated without + * notifying peers. After calling this function, no other functions may be + * called and the av pointer becomes invalid. + */ +void kill(); +/** + * Returns the Tox instance the A/V object was created for. + */ +tox::this *tox { get(); } +/******************************************************************************* + * + * :: A/V event loop + * + ******************************************************************************/ +/** + * Returns the interval in milliseconds when the next toxav_iterate call should + * be. If no call is active at the moment, this function returns 200. + */ +const uint32_t iteration_interval(); +/** + * Main loop for the session. This function needs to be called in intervals of + * toxav_iteration_interval() milliseconds. It is best called in the separate + * thread from tox_iterate. + */ +void iterate(); +/******************************************************************************* + * + * :: Call setup + * + ******************************************************************************/ +/** + * Call a friend. This will start ringing the friend. + * + * It is the client's responsibility to stop ringing after a certain timeout, + * if such behaviour is desired. If the client does not stop ringing, the + * library will not stop until the friend is disconnected. Audio and video + * receiving are both enabled by default. + * + * @param friend_number The friend number of the friend that should be called. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. + */ +bool call(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { + /** + * A resource allocation error occurred while trying to create the structures + * required for the call. + */ + MALLOC, + /** + * Synchronization error occurred. + */ + SYNC, + /** + * The friend number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * The friend was valid, but not currently connected. + */ + FRIEND_NOT_CONNECTED, + /** + * Attempted to call a friend while already in an audio or video call with + * them. + */ + FRIEND_ALREADY_IN_CALL, + /** + * Audio or video bit rate is invalid. + */ + INVALID_BIT_RATE, +} +event call { + /** + * The function type for the ${event call} callback. + * + * @param friend_number The friend number from which the call is incoming. + * @param audio_enabled True if friend is sending audio. + * @param video_enabled True if friend is sending video. + */ + typedef void(uint32_t friend_number, bool audio_enabled, bool video_enabled); +} +/** + * Accept an incoming call. + * + * If answering fails for any reason, the call will still be pending and it is + * possible to try and answer it later. Audio and video receiving are both + * enabled by default. + * + * @param friend_number The friend number of the friend that is calling. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. + */ +bool answer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { + /** + * Synchronization error occurred. + */ + SYNC, + /** + * Failed to initialize codecs for call session. Note that codec initiation + * will fail if there is no receive callback registered for either audio or + * video. + */ + CODEC_INITIALIZATION, + /** + * The friend number did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * The friend was valid, but they are not currently trying to initiate a call. + * This is also returned if this client is already in a call with the friend. + */ + FRIEND_NOT_CALLING, + /** + * Audio or video bit rate is invalid. + */ + INVALID_BIT_RATE, +} +/******************************************************************************* + * + * :: Call state graph + * + ******************************************************************************/ +bitmask FRIEND_CALL_STATE { + /** + * Set by the AV core if an error occurred on the remote end or if friend + * timed out. This is the final state after which no more state + * transitions can occur for the call. This call state will never be triggered + * in combination with other call states. + */ + ERROR, + /** + * The call has finished. This is the final state after which no more state + * transitions can occur for the call. This call state will never be + * triggered in combination with other call states. + */ + FINISHED, + /** + * The flag that marks that friend is sending audio. + */ + SENDING_A, + /** + * The flag that marks that friend is sending video. + */ + SENDING_V, + /** + * The flag that marks that friend is receiving audio. + */ + ACCEPTING_A, + /** + * The flag that marks that friend is receiving video. + */ + ACCEPTING_V, +} +event call_state { + /** + * The function type for the ${event call_state} callback. + * + * @param friend_number The friend number for which the call state changed. + * @param state The bitmask of the new call state which is guaranteed to be + * different than the previous state. The state is set to 0 when the call is + * paused. The bitmask represents all the activities currently performed by the + * friend. + */ + typedef void(uint32_t friend_number, uint32_t state); +} +/******************************************************************************* + * + * :: Call control + * + ******************************************************************************/ +enum class CALL_CONTROL { + /** + * Resume a previously paused call. Only valid if the pause was caused by this + * client, if not, this control is ignored. Not valid before the call is accepted. + */ + RESUME, + /** + * Put a call on hold. Not valid before the call is accepted. + */ + PAUSE, + /** + * Reject a call if it was not answered, yet. Cancel a call after it was + * answered. + */ + CANCEL, + /** + * Request that the friend stops sending audio. Regardless of the friend's + * compliance, this will cause the ${event audio.receive_frame} event to stop being + * triggered on receiving an audio frame from the friend. + */ + MUTE_AUDIO, + /** + * Calling this control will notify client to start sending audio again. + */ + UNMUTE_AUDIO, + /** + * Request that the friend stops sending video. Regardless of the friend's + * compliance, this will cause the ${event video.receive_frame} event to stop being + * triggered on receiving a video frame from the friend. + */ + HIDE_VIDEO, + /** + * Calling this control will notify client to start sending video again. + */ + SHOW_VIDEO, +} +/** + * Sends a call control command to a friend. + * + * @param friend_number The friend number of the friend this client is in a call + * with. + * @param control The control command to send. + * + * @return true on success. + */ +bool call_control (uint32_t friend_number, CALL_CONTROL control) { + /** + * Synchronization error occurred. + */ + SYNC, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. Before the call is + * answered, only CANCEL is a valid control. + */ + FRIEND_NOT_IN_CALL, + /** + * Happens if user tried to pause an already paused call or if trying to + * resume a call that is not paused. + */ + INVALID_TRANSITION, +} +/******************************************************************************* + * + * :: Controlling bit rates + * + ******************************************************************************/ +namespace bit_rate { + /** + * Set the audio bit rate to be used in subsequent audio/video frames. + * + * @param friend_number The friend number. + * @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable + * audio sending. Set to -1 to leave unchanged. + * @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable + * video sending. Set to -1 to leave unchanged. + * + */ + bool set(uint32_t friend_number, int32_t audio_bit_rate, int32_t video_bit_rate) { + /** + * Synchronization error occurred. + */ + SYNC, + /** + * The bit rate passed was not one of the supported values. + */ + INVALID, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. + */ + FRIEND_NOT_IN_CALL, + } + event status { + /** + * The function type for the ${event status} callback. The event is triggered + * when the network becomes too saturated for current bit rates at which + * point core suggests new bit rates. + * + * @param friend_number The friend number. + * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. + * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. + */ + typedef void(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate); + } +} +/******************************************************************************* + * + * :: A/V sending + * + ******************************************************************************/ +error for send_frame { + /** + * In case of video, one of Y, U, or V was NULL. In case of audio, the samples + * data pointer was NULL. + */ + NULL, + /** + * The friend_number passed did not designate a valid friend. + */ + FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. + */ + FRIEND_NOT_IN_CALL, + /** + * Synchronization error occurred. + */ + SYNC, + /** + * One of the frame parameters was invalid. E.g. the resolution may be too + * small or too large, or the audio sampling rate may be unsupported. + */ + INVALID, + /** + * Either friend turned off audio or video receiving or we turned off sending + * for the said payload. + */ + PAYLOAD_TYPE_DISABLED, + /** + * Failed to push frame through rtp interface. + */ + RTP_FAILED, +} +namespace audio { + /** + * Send an audio frame to a friend. + * + * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... + * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... + * For mono audio, this has no meaning, every sample is subsequent. For stereo, + * this means the expected format is LRLRLR... with samples for left and right + * alternating. + * + * @param friend_number The friend number of the friend to which to send an + * audio frame. + * @param pcm An array of audio samples. The size of this array must be + * sample_count * channels. + * @param sample_count Number of samples in this frame. Valid numbers here are + * ((sample rate) * (audio length) / 1000), where audio length can be + * 2.5, 5, 10, 20, 40 or 60 millseconds. + * @param channels Number of audio channels. Supported values are 1 and 2. + * @param sampling_rate Audio sampling rate used in this frame. Valid sampling + * rates are 8000, 12000, 16000, 24000, or 48000. + */ + bool send_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate) with error for send_frame; +} +namespace video { + /** + * Send a video frame to a friend. + * + * Y - plane should be of size: height * width + * U - plane should be of size: (height/2) * (width/2) + * V - plane should be of size: (height/2) * (width/2) + * + * @param friend_number The friend number of the friend to which to send a video + * frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y Y (Luminance) plane data. + * @param u U (Chroma) plane data. + * @param v V (Chroma) plane data. + */ + bool send_frame(uint32_t friend_number, uint16_t width, uint16_t height, + const uint8_t *y, const uint8_t *u, const uint8_t *v) with error for send_frame; +} +/******************************************************************************* + * + * :: A/V receiving + * + ******************************************************************************/ +namespace audio { + event receive_frame { + /** + * The function type for the ${event receive_frame} callback. The callback can be + * called multiple times per single iteration depending on the amount of queued + * frames in the buffer. The received format is the same as in send function. + * + * @param friend_number The friend number of the friend who sent an audio frame. + * @param pcm An array of audio samples (sample_count * channels elements). + * @param sample_count The number of audio samples per channel in the PCM array. + * @param channels Number of audio channels. + * @param sampling_rate Sampling rate used in this frame. + * + */ + typedef void(uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate); + } +} +namespace video { + event receive_frame { + /** + * The function type for the ${event receive_frame} callback. + * + * @param friend_number The friend number of the friend who sent a video frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y + * @param u + * @param v Plane data. + * The size of plane data is derived from width and height where + * Y = MAX(width, abs(ystride)) * height, + * U = MAX(width/2, abs(ustride)) * (height/2) and + * V = MAX(width/2, abs(vstride)) * (height/2). + * @param ystride + * @param ustride + * @param vstride Strides data. Strides represent padding for each plane + * that may or may not be present. You must handle strides in + * your image processing code. Strides are negative if the + * image is bottom-up hence why you MUST abs() it when + * calculating plane buffer size. + */ + typedef void(uint32_t friend_number, uint16_t width, uint16_t height, + const uint8_t *y, const uint8_t *u, const uint8_t *v, + int32_t ystride, int32_t ustride, int32_t vstride); + } +} + +} +%{ +#ifdef __cplusplus +} +#endif +#endif /* TOXAV_H */ +%} diff --git a/testing/av_test.c b/testing/av_test.c new file mode 100644 index 000000000..fa6a831f8 --- /dev/null +++ b/testing/av_test.c @@ -0,0 +1,731 @@ +/** av_test.c + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + * Compile with (Linux only; in newly created directory toxcore/dir_name): + * gcc -o av_test ../toxav/av_test.c ../build/.libs/libtox*.a -lopencv_core \ + * -lopencv_highgui -lopencv_imgproc -lsndfile -pthread -lvpx -lopus -lsodium -lportaudio + */ + + +#include "../toxav/toxav.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "../toxcore/network.h" /* current_time_monotonic() */ + +/* Playing audio data */ +#include +/* Reading audio */ +#include + +/* Reading and Displaying video data */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define c_sleep(x) usleep(1000*x) + + +#define CLIP(X) ((X) > 255 ? 255 : (X) < 0 ? 0 : X) + +// RGB -> YUV +#define RGB2Y(R, G, B) CLIP((( 66 * (R) + 129 * (G) + 25 * (B) + 128) >> 8) + 16) +#define RGB2U(R, G, B) CLIP(((-38 * (R) - 74 * (G) + 112 * (B) + 128) >> 8) + 128) +#define RGB2V(R, G, B) CLIP(((112 * (R) - 94 * (G) - 18 * (B) + 128) >> 8) + 128) + +// YUV -> RGB +#define C(Y) ((Y) - 16 ) +#define D(U) ((U) - 128 ) +#define E(V) ((V) - 128 ) + +#define YUV2R(Y, U, V) CLIP((298 * C(Y) + 409 * E(V) + 128) >> 8) +#define YUV2G(Y, U, V) CLIP((298 * C(Y) - 100 * D(U) - 208 * E(V) + 128) >> 8) +#define YUV2B(Y, U, V) CLIP((298 * C(Y) + 516 * D(U) + 128) >> 8) + + +#define TEST_TRANSFER_A 0 +#define TEST_TRANSFER_V 1 + + +typedef struct { + bool incoming; + uint32_t state; + pthread_mutex_t arb_mutex[1]; + RingBuffer* arb; /* Audio ring buffer */ + +} CallControl; + +struct toxav_thread_data { + ToxAV* AliceAV; + ToxAV* BobAV; + int32_t sig; +}; + +const char* vdout = "AV Test"; /* Video output */ +PaStream* adout = NULL; /* Audio output */ + +typedef struct { + uint16_t size; + int16_t data[]; +} frame; + +void* pa_write_thread (void* d) +{ + /* The purpose of this thread is to make sure Pa_WriteStream will not block + * toxav_iterate thread + */ + CallControl* cc = d; + + while (Pa_IsStreamActive(adout)) { + frame* f; + pthread_mutex_lock(cc->arb_mutex); + if (rb_read(cc->arb, (void**)&f)) { + pthread_mutex_unlock(cc->arb_mutex); + Pa_WriteStream(adout, f->data, f->size); + free(f); + } else { + pthread_mutex_unlock(cc->arb_mutex); + c_sleep(10); + } + } +} + +/** + * Callbacks + */ +void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + printf("Handling CALL callback\n"); + ((CallControl*)user_data)->incoming = true; +} +void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + printf("Handling CALL STATE callback: %d\n", state); + ((CallControl*)user_data)->state = state; +} +void t_toxav_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) +{ + ystride = abs(ystride); + ustride = abs(ustride); + vstride = abs(vstride); + + uint16_t *img_data = malloc(height * width * 6); + + unsigned long int i, j; + for (i = 0; i < height; ++i) { + for (j = 0; j < width; ++j) { + uint8_t *point = (uint8_t*) img_data + 3 * ((i * width) + j); + int yx = y[(i * ystride) + j]; + int ux = u[((i / 2) * ustride) + (j / 2)]; + int vx = v[((i / 2) * vstride) + (j / 2)]; + + point[0] = YUV2R(yx, ux, vx); + point[1] = YUV2G(yx, ux, vx); + point[2] = YUV2B(yx, ux, vx); + } + } + + + CvMat mat = cvMat(height, width, CV_8UC3, img_data); + + CvSize sz = {.height = height, .width = width}; + + IplImage* header = cvCreateImageHeader(sz, 1, 3); + IplImage* img = cvGetImage(&mat, header); + cvShowImage(vdout, img); + free(img_data); +} +void t_toxav_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) +{ + CallControl* cc = user_data; + frame* f = malloc(sizeof(uint16_t) + sample_count * sizeof(int16_t) * channels); + memcpy(f->data, pcm, sample_count * sizeof(int16_t) * channels); + f->size = sample_count; + + pthread_mutex_lock(cc->arb_mutex); + free(rb_write(cc->arb, f)); + pthread_mutex_unlock(cc->arb_mutex); +} +void t_toxav_bit_rate_status_cb(ToxAV *av, uint32_t friend_number, + uint32_t audio_bit_rate, uint32_t video_bit_rate, + void *user_data) +{ + printf ("Suggested bit rates: audio: %d video: %d\n", audio_bit_rate, video_bit_rate); +} +void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); + } +} + +/** + */ +void initialize_tox(Tox** bootstrap, ToxAV** AliceAV, CallControl* AliceCC, ToxAV** BobAV, CallControl* BobCC) +{ + Tox* Alice; + Tox* Bob; + + struct Tox_Options opts; + tox_options_default(&opts); + + opts.end_port = 0; + opts.ipv6_enabled = false; + + { + TOX_ERR_NEW error; + + opts.start_port = 33445; + *bootstrap = tox_new(&opts, &error); + assert(error == TOX_ERR_NEW_OK); + + opts.start_port = 33455; + Alice = tox_new(&opts, &error); + assert(error == TOX_ERR_NEW_OK); + + opts.start_port = 33465; + Bob = tox_new(&opts, &error); + assert(error == TOX_ERR_NEW_OK); + } + + printf("Created 3 instances of Tox\n"); + printf("Preparing network...\n"); + long long unsigned int cur_time = time(NULL); + + uint32_t to_compare = 974536; + uint8_t address[TOX_ADDRESS_SIZE]; + + tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); + tox_self_get_address(Alice, address); + + + assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); + + uint8_t off = 1; + + while (1) { + tox_iterate(*bootstrap); + tox_iterate(Alice); + tox_iterate(Bob); + + if (tox_self_get_connection_status(*bootstrap) && + tox_self_get_connection_status(Alice) && + tox_self_get_connection_status(Bob) && off) { + printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); + off = 0; + } + + if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP) + break; + + c_sleep(20); + } + + + TOXAV_ERR_NEW rc; + *AliceAV = toxav_new(Alice, &rc); + assert(rc == TOXAV_ERR_NEW_OK); + + *BobAV = toxav_new(Bob, &rc); + assert(rc == TOXAV_ERR_NEW_OK); + + + /* Alice */ + toxav_callback_call(*AliceAV, t_toxav_call_cb, AliceCC); + toxav_callback_call_state(*AliceAV, t_toxav_call_state_cb, AliceCC); + toxav_callback_bit_rate_status(*AliceAV, t_toxav_bit_rate_status_cb, AliceCC); + toxav_callback_video_receive_frame(*AliceAV, t_toxav_receive_video_frame_cb, AliceCC); + toxav_callback_audio_receive_frame(*AliceAV, t_toxav_receive_audio_frame_cb, AliceCC); + + /* Bob */ + toxav_callback_call(*BobAV, t_toxav_call_cb, BobCC); + toxav_callback_call_state(*BobAV, t_toxav_call_state_cb, BobCC); + toxav_callback_bit_rate_status(*BobAV, t_toxav_bit_rate_status_cb, BobCC); + toxav_callback_video_receive_frame(*BobAV, t_toxav_receive_video_frame_cb, BobCC); + toxav_callback_audio_receive_frame(*BobAV, t_toxav_receive_audio_frame_cb, BobCC); + + + printf("Created 2 instances of ToxAV\n"); + printf("All set after %llu seconds!\n", time(NULL) - cur_time); +} +int iterate_tox(Tox* bootstrap, ToxAV* AliceAV, ToxAV* BobAV) +{ + tox_iterate(bootstrap); + tox_iterate(toxav_get_tox(AliceAV)); + tox_iterate(toxav_get_tox(BobAV)); + + return MIN(tox_iteration_interval(toxav_get_tox(AliceAV)), tox_iteration_interval(toxav_get_tox(BobAV))); +} +void* iterate_toxav (void * data) +{ + struct toxav_thread_data* data_cast = data; +#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 + cvNamedWindow(vdout, CV_WINDOW_AUTOSIZE); +#endif + + while (data_cast->sig == 0) { + toxav_iterate(data_cast->AliceAV); + toxav_iterate(data_cast->BobAV); + int rc = MIN(toxav_iteration_interval(data_cast->AliceAV), toxav_iteration_interval(data_cast->BobAV)); + + printf("\rIteration interval: %d ", rc); + fflush(stdout); + +#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 + if (!rc) + rc = 1; + + cvWaitKey(rc); +#else + c_sleep(rc); +#endif + } + + data_cast->sig = 1; + +#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 + cvDestroyWindow(vdout); +#endif + + pthread_exit(NULL); +} + +int send_opencv_img(ToxAV* av, uint32_t friend_number, const IplImage* img) +{ + int32_t strides[3] = { 1280, 640, 640 }; + uint8_t* planes[3] = { + malloc(img->height * img->width), + malloc(img->height * img->width / 4), + malloc(img->height * img->width / 4), + }; + + int x_chroma_shift = 1; + int y_chroma_shift = 1; + + int x, y; + for (y = 0; y < img->height; ++y) { + for (x = 0; x < img->width; ++x) { + uint8_t r = img->imageData[(x + y * img->width) * 3 + 0]; + uint8_t g = img->imageData[(x + y * img->width) * 3 + 1]; + uint8_t b = img->imageData[(x + y * img->width) * 3 + 2]; + + planes[0][x + y * strides[0]] = RGB2Y(r, g, b); + if (!(x % (1 << x_chroma_shift)) && !(y % (1 << y_chroma_shift))) { + const int i = x / (1 << x_chroma_shift); + const int j = y / (1 << y_chroma_shift); + planes[1][i + j * strides[1]] = RGB2U(r, g, b); + planes[2][i + j * strides[2]] = RGB2V(r, g, b); + } + } + } + + int rc = toxav_video_send_frame(av, friend_number, img->width, img->height, + planes[0], planes[1], planes[2], NULL); + free(planes[0]); + free(planes[1]); + free(planes[2]); + return rc; +} +int print_audio_devices() +{ + int i = 0; + for (i = 0; i < Pa_GetDeviceCount(); ++i) { + const PaDeviceInfo* info = Pa_GetDeviceInfo(i); + if (info) + printf("%d) %s\n", i, info->name); + } + + return 0; +} +int print_help (const char* name) +{ + printf("Usage: %s -[a:v:o:dh]\n" + "-a audio input file\n" + "-b audio frame duration\n" + "-v video input file\n" + "-x video frame duration\n" + "-o output audio device index\n" + "-d print output audio devices\n" + "-h print this help\n", name); + + return 0; +} + +int main (int argc, char** argv) +{ + freopen("/dev/zero", "w", stderr); + Pa_Initialize(); + + struct stat st; + + /* AV files for testing */ + const char* af_name = NULL; + const char* vf_name = NULL; + long audio_out_dev_idx = -1; + + int32_t audio_frame_duration = 20; + int32_t video_frame_duration = 10; + + /* Parse settings */ + CHECK_ARG: switch (getopt(argc, argv, "a:b:v:x:o:dh")) { + case 'a': + af_name = optarg; + goto CHECK_ARG; + case 'b':{ + char *d; + audio_frame_duration = strtol(optarg, &d, 10); + if (*d) { + printf("Invalid value for argument: 'b'"); + exit(1); + } + goto CHECK_ARG; + } + case 'v': + vf_name = optarg; + goto CHECK_ARG; + case 'x':{ + char *d; + video_frame_duration = strtol(optarg, &d, 10); + if (*d) { + printf("Invalid value for argument: 'x'"); + exit(1); + } + goto CHECK_ARG; + } + case 'o': { + char *d; + audio_out_dev_idx = strtol(optarg, &d, 10); + if (*d) { + printf("Invalid value for argument: 'o'"); + exit(1); + } + goto CHECK_ARG; + } + case 'd': + return print_audio_devices(); + case 'h': + return print_help(argv[0]); + case '?': + exit(1); + case -1:; + } + + { /* Check files */ + if (!af_name) { + printf("Required audio input file!\n"); + exit(1); + } + + if (!vf_name) { + printf("Required video input file!\n"); + exit(1); + } + + /* Check for files */ + if(stat(af_name, &st) != 0 || !S_ISREG(st.st_mode)) + { + printf("%s doesn't seem to be a regular file!\n", af_name); + exit(1); + } + + if(stat(vf_name, &st) != 0 || !S_ISREG(st.st_mode)) + { + printf("%s doesn't seem to be a regular file!\n", vf_name); + exit(1); + } + } + + if (audio_out_dev_idx < 0) + audio_out_dev_idx = Pa_GetDefaultOutputDevice(); + + const PaDeviceInfo* audio_dev = Pa_GetDeviceInfo(audio_out_dev_idx); + if (!audio_dev) { + fprintf(stderr, "Device under index: %ld invalid", audio_out_dev_idx); + return 1; + } + + printf("Using audio device: %s\n", audio_dev->name); + printf("Using audio file: %s\n", af_name); + printf("Using video file: %s\n", vf_name); + + /* START TOX NETWORK */ + + Tox *bootstrap; + ToxAV *AliceAV; + ToxAV *BobAV; + + CallControl AliceCC; + CallControl BobCC; + + initialize_tox(&bootstrap, &AliceAV, &AliceCC, &BobAV, &BobCC); + + if (TEST_TRANSFER_A) { + SNDFILE* af_handle; + SF_INFO af_info; + + printf("\nTrying audio enc/dec...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + pthread_mutex_init(AliceCC.arb_mutex, NULL); + pthread_mutex_init(BobCC.arb_mutex, NULL); + + AliceCC.arb = rb_new(16); + BobCC.arb = rb_new(16); + + { /* Call */ + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + exit(1); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, AliceAV, BobAV); + + { /* Answer */ + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 48, 0, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + exit(1); + } + } + + while (AliceCC.state == 0) + iterate_tox(bootstrap, AliceAV, BobAV); + + /* Open audio file */ + af_handle = sf_open(af_name, SFM_READ, &af_info); + if (af_handle == NULL) { + printf("Failed to open the file.\n"); + exit(1); + } + + int16_t PCM[5760]; + + time_t start_time = time(NULL); + time_t expected_time = af_info.frames / af_info.samplerate + 2; + + + /* Start decode thread */ + struct toxav_thread_data data = { + .AliceAV = AliceAV, + .BobAV = BobAV, + .sig = 0 + }; + + pthread_t dect; + pthread_create(&dect, NULL, iterate_toxav, &data); + pthread_detach(dect); + + int frame_size = (af_info.samplerate * audio_frame_duration / 1000) * af_info.channels; + + struct PaStreamParameters output; + output.device = audio_out_dev_idx; + output.channelCount = af_info.channels; + output.sampleFormat = paInt16; + output.suggestedLatency = audio_dev->defaultHighOutputLatency; + output.hostApiSpecificStreamInfo = NULL; + + PaError err = Pa_OpenStream(&adout, NULL, &output, af_info.samplerate, frame_size, paNoFlag, NULL, NULL); + assert(err == paNoError); + + err = Pa_StartStream(adout); + assert(err == paNoError); + +// toxav_audio_bit_rate_set(AliceAV, 0, 64, false, NULL); + + /* Start write thread */ + pthread_t t; + pthread_create(&t, NULL, pa_write_thread, &BobCC); + pthread_detach(t); + + printf("Sample rate %d\n", af_info.samplerate); + while (start_time + expected_time > time(NULL) ) { + uint64_t enc_start_time = current_time_monotonic(); + int64_t count = sf_read_short(af_handle, PCM, frame_size); + if (count > 0) { + TOXAV_ERR_SEND_FRAME rc; + if (toxav_audio_send_frame(AliceAV, 0, PCM, count/af_info.channels, af_info.channels, af_info.samplerate, &rc) == false) { + printf("Error sending frame of size %ld: %d\n", count, rc); + } + } + iterate_tox(bootstrap, AliceAV, BobAV); + c_sleep(abs(audio_frame_duration - (current_time_monotonic() - enc_start_time) - 1)); + } + + printf("Played file in: %lu; stopping stream...\n", time(NULL) - start_time); + + Pa_StopStream(adout); + sf_close(af_handle); + + { /* Hangup */ + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + exit(1); + } + } + + iterate_tox(bootstrap, AliceAV, BobAV); + assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + /* Stop decode thread */ + data.sig = -1; + while(data.sig != 1) + pthread_yield(); + + pthread_mutex_destroy(AliceCC.arb_mutex); + pthread_mutex_destroy(BobCC.arb_mutex); + + void* f = NULL; + while(rb_read(AliceCC.arb, &f)) + free(f); + + while(rb_read(BobCC.arb, &f)) + free(f); + + printf("Success!"); + } + + if (TEST_TRANSFER_V) { + printf("\nTrying video enc/dec...\n"); + + memset(&AliceCC, 0, sizeof(CallControl)); + memset(&BobCC, 0, sizeof(CallControl)); + + { /* Call */ + TOXAV_ERR_CALL rc; + toxav_call(AliceAV, 0, 0, 2000, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + exit(1); + } + } + + while (!BobCC.incoming) + iterate_tox(bootstrap, AliceAV, BobAV); + + { /* Answer */ + TOXAV_ERR_ANSWER rc; + toxav_answer(BobAV, 0, 0, 5000, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + exit(1); + } + } + + iterate_tox(bootstrap, AliceAV, BobAV); + + /* Start decode thread */ + struct toxav_thread_data data = { + .AliceAV = AliceAV, + .BobAV = BobAV, + .sig = 0 + }; + + pthread_t dect; + pthread_create(&dect, NULL, iterate_toxav, &data); + pthread_detach(dect); + + CvCapture* capture = cvCreateFileCapture(vf_name); + if (!capture) { + printf("Failed to open video file: %s\n", vf_name); + exit(1); + } + +// toxav_video_bit_rate_set(AliceAV, 0, 5000, false, NULL); + + time_t start_time = time(NULL); + while(start_time + 90 > time(NULL)) { + IplImage* frame = cvQueryFrame(capture ); + if (!frame) + break; + + send_opencv_img(AliceAV, 0, frame); + iterate_tox(bootstrap, AliceAV, BobAV); + c_sleep(10); + } + + cvReleaseCapture(&capture); + + { /* Hangup */ + TOXAV_ERR_CALL_CONTROL rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d\n", rc); + exit(1); + } + } + + iterate_tox(bootstrap, AliceAV, BobAV); + assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + /* Stop decode thread */ + printf("Stopping decode thread\n"); + data.sig = -1; + while(data.sig != 1) + pthread_yield(); + + printf("Success!"); + } + + + Tox* Alice = toxav_get_tox(AliceAV); + Tox* Bob = toxav_get_tox(BobAV); + toxav_kill(BobAV); + toxav_kill(AliceAV); + tox_kill(Bob); + tox_kill(Alice); + tox_kill(bootstrap); + + printf("\nTest successful!\n"); + + Pa_Terminate(); + return 0; +} diff --git a/toxav/Makefile.inc b/toxav/Makefile.inc index 0b4b869d4..083f862fd 100644 --- a/toxav/Makefile.inc +++ b/toxav/Makefile.inc @@ -1,38 +1,42 @@ if BUILD_AV -lib_LTLIBRARIES += libtoxav.la -libtoxav_la_include_HEADERS = ../toxav/toxav.h -libtoxav_la_includedir = $(includedir)/tox +lib_LTLIBRARIES += libtoxav.la + libtoxav_la_include_HEADERS = ../toxav/toxav.h + libtoxav_la_includedir = $(includedir)/tox libtoxav_la_SOURCES = ../toxav/rtp.h \ - ../toxav/rtp.c \ - ../toxav/msi.h \ - ../toxav/msi.c \ - ../toxav/group.h \ - ../toxav/group.c \ - ../toxav/codec.h \ - ../toxav/codec.c \ - ../toxav/toxav.h \ - ../toxav/toxav.c - + ../toxav/rtp.c \ + ../toxav/msi.h \ + ../toxav/msi.c \ + ../toxav/group.h \ + ../toxav/group.c \ + ../toxav/audio.h \ + ../toxav/audio.c \ + ../toxav/video.h \ + ../toxav/video.c \ + ../toxav/bwcontroler.h \ + ../toxav/bwcontroler.c \ + ../toxav/toxav.h \ + ../toxav/toxav.c \ + ../toxav/toxav_old.c libtoxav_la_CFLAGS = -I../toxcore \ - -I../toxav \ - $(LIBSODIUM_CFLAGS) \ - $(NACL_CFLAGS) \ - $(AV_CFLAGS) \ - $(PTHREAD_CFLAGS) + -I../toxav \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(AV_CFLAGS) \ + $(PTHREAD_CFLAGS) libtoxav_la_LDFLAGS = $(TOXAV_LT_LDFLAGS) \ - $(LIBSODIUM_LDFLAGS) \ - $(NACL_LDFLAGS) \ - $(EXTRA_LT_LDFLAGS) \ - $(WINSOCK2_LIBS) + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + $(EXTRA_LT_LDFLAGS) \ + $(WINSOCK2_LIBS) libtoxav_la_LIBADD = libtoxcore.la \ - $(LIBSODIUM_LIBS) \ - $(NACL_LIBS) \ - $(PTHREAD_LIBS) \ - $(AV_LIBS) + $(LIBSODIUM_LIBS) \ + $(NACL_LIBS) \ + $(PTHREAD_LIBS) \ + $(AV_LIBS) endif \ No newline at end of file diff --git a/toxav/audio.c b/toxav/audio.c new file mode 100644 index 000000000..4f9d35625 --- /dev/null +++ b/toxav/audio.c @@ -0,0 +1,439 @@ +/** audio.c + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include + +#include "audio.h" +#include "rtp.h" + +#include "../toxcore/logger.h" + +static struct JitterBuffer *jbuf_new(uint32_t capacity); +static void jbuf_clear(struct JitterBuffer *q); +static void jbuf_free(struct JitterBuffer *q); +static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m); +static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); +OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count); +bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, + int32_t *old_br, int32_t *old_sr, int32_t *old_ch); +bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels); + + + +ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data) +{ + ACSession *ac = calloc(sizeof(ACSession), 1); + + if (!ac) { + LOGGER_WARNING("Allocation failed! Application might misbehave!"); + return NULL; + } + + if (create_recursive_mutex(ac->queue_mutex) != 0) { + LOGGER_WARNING("Failed to create recursive mutex!"); + free(ac); + return NULL; + } + + int status; + ac->decoder = opus_decoder_create(48000, 2, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(status)); + goto BASE_CLEANUP; + } + + if (!(ac->j_buf = jbuf_new(3))) { + LOGGER_WARNING("Jitter buffer creaton failed!"); + opus_decoder_destroy(ac->decoder); + goto BASE_CLEANUP; + } + + /* Initialize encoders with default values */ + ac->encoder = create_audio_encoder(48000, 48000, 2); + + if (ac->encoder == NULL) + goto DECODER_CLEANUP; + + ac->le_bit_rate = 48000; + ac->le_sample_rate = 48000; + ac->le_channel_count = 2; + + ac->ld_channel_count = 2; + ac->ld_sample_rate = 48000; + ac->ldrts = 0; /* Make it possible to reconfigure straight away */ + + /* These need to be set in order to properly + * do error correction with opus */ + ac->lp_frame_duration = 120; + ac->lp_sampling_rate = 48000; + ac->lp_channel_count = 1; + + ac->av = av; + ac->friend_number = friend_number; + ac->acb.first = cb; + ac->acb.second = cb_data; + + return ac; + +DECODER_CLEANUP: + opus_decoder_destroy(ac->decoder); + jbuf_free(ac->j_buf); +BASE_CLEANUP: + pthread_mutex_destroy(ac->queue_mutex); + free(ac); + return NULL; +} +void ac_kill(ACSession *ac) +{ + if (!ac) + return; + + opus_encoder_destroy(ac->encoder); + opus_decoder_destroy(ac->decoder); + jbuf_free(ac->j_buf); + + pthread_mutex_destroy(ac->queue_mutex); + + LOGGER_DEBUG("Terminated audio handler: %p", ac); + free(ac); +} +void ac_iterate(ACSession *ac) +{ + if (!ac) + return; + + /* TODO fix this and jitter buffering */ + + /* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */ + int16_t tmp[5760 * 2]; + + struct RTPMessage *msg; + int rc = 0; + + pthread_mutex_lock(ac->queue_mutex); + + while ((msg = jbuf_read(ac->j_buf, &rc)) || rc == 2) { + pthread_mutex_unlock(ac->queue_mutex); + + if (rc == 2) { + LOGGER_DEBUG("OPUS correction"); + int fs = (ac->lp_sampling_rate * ac->lp_frame_duration) / 1000; + rc = opus_decode(ac->decoder, NULL, 0, tmp, fs, 1); + } else { + /* Get values from packet and decode. */ + /* NOTE: This didn't work very well + rc = convert_bw_to_sampling_rate(opus_packet_get_bandwidth(msg->data)); + if (rc != -1) { + cs->last_packet_sampling_rate = rc; + } else { + LOGGER_WARNING("Failed to load packet values!"); + rtp_free_msg(msg); + continue; + }*/ + + + /* Pick up sampling rate from packet */ + memcpy(&ac->lp_sampling_rate, msg->data, 4); + ac->lp_sampling_rate = ntohl(ac->lp_sampling_rate); + + ac->lp_channel_count = opus_packet_get_nb_channels(msg->data + 4); + + /** NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa, + * it didn't work quite well. + */ + if (!reconfigure_audio_decoder(ac, ac->lp_sampling_rate, ac->lp_channel_count)) { + LOGGER_WARNING("Failed to reconfigure decoder!"); + free(msg); + continue; + } + + rc = opus_decode(ac->decoder, msg->data + 4, msg->len - 4, tmp, 5760, 0); + free(msg); + } + + if (rc < 0) { + LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); + } else if (ac->acb.first) { + ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; + + ac->acb.first(ac->av, ac->friend_number, tmp, rc, ac->lp_channel_count, + ac->lp_sampling_rate, ac->acb.second); + } + + return; + } + + pthread_mutex_unlock(ac->queue_mutex); +} +int ac_queue_message(void *acp, struct RTPMessage *msg) +{ + if (!acp || !msg) + return -1; + + if ((msg->header.pt & 0x7f) == (rtp_TypeAudio + 2) % 128) { + LOGGER_WARNING("Got dummy!"); + free(msg); + return 0; + } + + if ((msg->header.pt & 0x7f) != rtp_TypeAudio % 128) { + LOGGER_WARNING("Invalid payload type!"); + free(msg); + return -1; + } + + ACSession *ac = acp; + + pthread_mutex_lock(ac->queue_mutex); + int rc = jbuf_write(ac->j_buf, msg); + pthread_mutex_unlock(ac->queue_mutex); + + if (rc == -1) { + LOGGER_WARNING("Could not queue the message!"); + free(msg); + return -1; + } + + return 0; +} +int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels) +{ + if (!ac || !reconfigure_audio_encoder(&ac->encoder, bit_rate, + sampling_rate, channels, + &ac->le_bit_rate, + &ac->le_sample_rate, + &ac->le_channel_count)) + return -1; + + return 0; +} + + + +struct JitterBuffer { + struct RTPMessage **queue; + uint32_t size; + uint32_t capacity; + uint16_t bottom; + uint16_t top; +}; + +static struct JitterBuffer *jbuf_new(uint32_t capacity) +{ + unsigned int size = 1; + + while (size <= (capacity * 4)) { + size *= 2; + } + + struct JitterBuffer *q; + + if (!(q = calloc(sizeof(struct JitterBuffer), 1))) return NULL; + + if (!(q->queue = calloc(sizeof(struct RTPMessage *), size))) { + free(q); + return NULL; + } + + q->size = size; + q->capacity = capacity; + return q; +} +static void jbuf_clear(struct JitterBuffer *q) +{ + for (; q->bottom != q->top; ++q->bottom) { + if (q->queue[q->bottom % q->size]) { + free(q->queue[q->bottom % q->size]); + q->queue[q->bottom % q->size] = NULL; + } + } +} +static void jbuf_free(struct JitterBuffer *q) +{ + if (!q) return; + + jbuf_clear(q); + free(q->queue); + free(q); +} +static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m) +{ + uint16_t sequnum = m->header.sequnum; + + unsigned int num = sequnum % q->size; + + if ((uint32_t)(sequnum - q->bottom) > q->size) { + LOGGER_DEBUG("Clearing filled jitter buffer: %p", q); + + jbuf_clear(q); + q->bottom = sequnum - q->capacity; + q->queue[num] = m; + q->top = sequnum + 1; + return 0; + } + + if (q->queue[num]) + return -1; + + q->queue[num] = m; + + if ((sequnum - q->bottom) >= (q->top - q->bottom)) + q->top = sequnum + 1; + + return 0; +} +static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success) +{ + if (q->top == q->bottom) { + *success = 0; + return NULL; + } + + unsigned int num = q->bottom % q->size; + + if (q->queue[num]) { + struct RTPMessage *ret = q->queue[num]; + q->queue[num] = NULL; + ++q->bottom; + *success = 1; + return ret; + } + + if ((uint32_t)(q->top - q->bottom) > q->capacity) { + ++q->bottom; + *success = 2; + return NULL; + } + + *success = 0; + return NULL; +} +OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count) +{ + int status = OPUS_OK; + OpusEncoder *rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_VOIP, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(status)); + return NULL; + } + + status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate)); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + /* Enable in-band forward error correction in codec */ + status = opus_encoder_ctl(rc, OPUS_SET_INBAND_FEC(1)); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + /* Make codec resistant to up to 10% packet loss + * NOTE This could also be adjusted on the fly, rather than hard-coded, + * with feedback from the receiving client. + */ + status = opus_encoder_ctl(rc, OPUS_SET_PACKET_LOSS_PERC(10)); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + /* Set algorithm to the highest complexity, maximizing compression */ + status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(10)); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); + goto FAILURE; + } + + return rc; + +FAILURE: + opus_encoder_destroy(rc); + return NULL; +} +bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, + int32_t *old_br, int32_t *old_sr, int32_t *old_ch) +{ + /* Values are checked in toxav.c */ + if (*old_sr != new_sr || *old_ch != new_ch) { + OpusEncoder *new_encoder = create_audio_encoder(new_br, new_sr, new_ch); + + if (new_encoder == NULL) + return false; + + opus_encoder_destroy(*e); + *e = new_encoder; + } else if (*old_br == new_br) + return true; /* Nothing changed */ + else { + int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br)); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); + return false; + } + } + + *old_br = new_br; + *old_sr = new_sr; + *old_ch = new_ch; + + LOGGER_DEBUG ("Reconfigured audio encoder br: %d sr: %d cc:%d", new_br, new_sr, new_ch); + return true; +} +bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels) +{ + if (sampling_rate != ac->ld_sample_rate || channels != ac->ld_channel_count) { + if (current_time_monotonic() - ac->ldrts < 500) + return false; + + int status; + OpusDecoder *new_dec = opus_decoder_create(sampling_rate, channels, &status); + + if (status != OPUS_OK) { + LOGGER_ERROR("Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status)); + return false; + } + + ac->ld_sample_rate = sampling_rate; + ac->ld_channel_count = channels; + ac->ldrts = current_time_monotonic(); + + opus_decoder_destroy(ac->decoder); + ac->decoder = new_dec; + + LOGGER_DEBUG("Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels); + } + + return true; +} diff --git a/toxav/audio.h b/toxav/audio.h new file mode 100644 index 000000000..b1db7448a --- /dev/null +++ b/toxav/audio.h @@ -0,0 +1,64 @@ +/** audio.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifndef AUDIO_H +#define AUDIO_H + +#include +#include + +#include "toxav.h" + +#include "../toxcore/util.h" + +struct RTPMessage; + +typedef struct ACSession_s { + /* encoding */ + OpusEncoder *encoder; + int32_t le_sample_rate; /* Last encoder sample rate */ + int32_t le_channel_count; /* Last encoder channel count */ + int32_t le_bit_rate; /* Last encoder bit rate */ + + /* decoding */ + OpusDecoder *decoder; + int32_t lp_channel_count; /* Last packet channel count */ + int32_t lp_sampling_rate; /* Last packet sample rate */ + int32_t lp_frame_duration; /* Last packet frame duration */ + int32_t ld_sample_rate; /* Last decoder sample rate */ + int32_t ld_channel_count; /* Last decoder channel count */ + uint64_t ldrts; /* Last decoder reconfiguration time stamp */ + void *j_buf; + + pthread_mutex_t queue_mutex[1]; + + ToxAV *av; + uint32_t friend_number; + PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ +} ACSession; + +ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data); +void ac_kill(ACSession *ac); +void ac_iterate(ACSession *ac); +int ac_queue_message(void *acp, struct RTPMessage *msg); +int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels); + +#endif /* AUDIO_H */ diff --git a/toxav/bwcontroler.c b/toxav/bwcontroler.c new file mode 100644 index 000000000..2530e7fd2 --- /dev/null +++ b/toxav/bwcontroler.c @@ -0,0 +1,205 @@ +/** bwcontroler.c + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include "bwcontroler.h" +#include "../toxcore/logger.h" +#include "../toxcore/util.h" + +#define BWC_PACKET_ID 196 +#define BWC_SEND_INTERVAL_MS 1000 +#define BWC_REFRESH_INTERVAL_MS 10000 +#define BWC_AVG_PKT_COUNT 20 + +/** + * + */ + +struct BWControler_s { + void (*mcb) (BWControler *, uint32_t, float, void *); + void *mcb_data; + + Messenger *m; + uint32_t friend_number; + + struct { + uint32_t lru; /* Last recv update time stamp */ + uint32_t lsu; /* Last sent update time stamp */ + uint32_t lfu; /* Last refresh time stamp */ + + uint32_t lost; + uint32_t recv; + } cycle; + + struct { + uint32_t rb_s[BWC_AVG_PKT_COUNT]; + RingBuffer *rb; + } rcvpkt; /* To calculate average received packet */ +}; + +int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); +void send_update(BWControler *bwc); + +BWControler *bwc_new(Messenger *m, uint32_t friendnumber, + void (*mcb) (BWControler *, uint32_t, float, void *), + void *udata) +{ + BWControler *retu = calloc(sizeof(struct BWControler_s), 1); + + retu->mcb = mcb; + retu->mcb_data = udata; + retu->m = m; + retu->friend_number = friendnumber; + retu->cycle.lsu = retu->cycle.lfu = current_time_monotonic(); + retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); + + /* Fill with zeros */ + int i = 0; + for (; i < BWC_AVG_PKT_COUNT; i ++) + rb_write(retu->rcvpkt.rb, retu->rcvpkt.rb_s + i); + + m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); + + return retu; +} +void bwc_kill(BWControler *bwc) +{ + if (!bwc) + return; + + m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, NULL, NULL); + + rb_kill(bwc->rcvpkt.rb); + free(bwc); +} +void bwc_feed_avg(BWControler* bwc, uint32_t bytes) +{ + uint32_t *p; + + rb_read(bwc->rcvpkt.rb, (void**) &p); + rb_write(bwc->rcvpkt.rb, p); + + *p = bytes; +} +void bwc_add_lost(BWControler *bwc, uint32_t bytes) +{ + if (!bwc) + return; + + if (!bytes) { + uint32_t* t_avg[BWC_AVG_PKT_COUNT], c = 1; + + rb_data(bwc->rcvpkt.rb, (void**) t_avg); + + int i = 0; + for (; i < BWC_AVG_PKT_COUNT; i ++) { + bytes += *(t_avg[i]); + + if (*(t_avg[i])) + c++; + } + + bytes /= c; + } + + bwc->cycle.lost += bytes; + send_update(bwc); +} +void bwc_add_recv(BWControler *bwc, uint32_t bytes) +{ + if (!bwc || !bytes) + return; + + bwc->cycle.recv += bytes; + send_update(bwc); +} + + +struct BWCMessage { + uint32_t lost; + uint32_t recv; +}; + +void send_update(BWControler *bwc) +{ + if (current_time_monotonic() - bwc->cycle.lfu > BWC_REFRESH_INTERVAL_MS) { + + bwc->cycle.lost /= 10; + bwc->cycle.recv /= 10; + bwc->cycle.lfu = current_time_monotonic(); + } + else if (current_time_monotonic() - bwc->cycle.lsu > BWC_SEND_INTERVAL_MS) { + + if (bwc->cycle.lost) + { + LOGGER_DEBUG ("%p Sent update rcv: %u lost: %u", + bwc, bwc->cycle.recv, bwc->cycle.lost); + + uint8_t p_msg[sizeof(struct BWCMessage) + 1]; + struct BWCMessage* b_msg = (struct BWCMessage*)(p_msg + 1); + + p_msg[0] = BWC_PACKET_ID; + b_msg->lost = htonl(bwc->cycle.lost); + b_msg->recv = htonl(bwc->cycle.recv); + + if (-1 == send_custom_lossy_packet(bwc->m, bwc->friend_number, p_msg, sizeof(p_msg))) + LOGGER_WARNING("BWC send failed (len: %d)! std error: %s", sizeof(p_msg), strerror(errno)); + } + + bwc->cycle.lsu = current_time_monotonic(); + } +} +int on_update (BWControler *bwc, struct BWCMessage *msg) +{ + LOGGER_DEBUG ("%p Got update from peer", bwc); + + /* Peer must respect time boundary */ + if (current_time_monotonic() < bwc->cycle.lru + BWC_SEND_INTERVAL_MS) { + LOGGER_DEBUG("%p Rejecting extra update", bwc); + return -1; + } + + bwc->cycle.lru = current_time_monotonic(); + + msg->recv = ntohl(msg->recv); + msg->lost = ntohl(msg->lost); + + LOGGER_DEBUG ("recved: %u lost: %u", msg->recv, msg->lost); + + if (msg->lost && bwc->mcb) + bwc->mcb(bwc, bwc->friend_number, + ((float) (msg->lost) / (msg->recv + msg->lost)), + bwc->mcb_data); + + return 0; +} +int bwc_handle_data(Messenger* m, uint32_t friendnumber, const uint8_t* data, uint16_t length, void* object) +{ + if (length - 1 != sizeof(struct BWCMessage)) + return -1; + + /* NOTE the data is mutable */ + return on_update(object, (struct BWCMessage *) (data + 1)); +} diff --git a/toxav/bwcontroler.h b/toxav/bwcontroler.h new file mode 100644 index 000000000..53b07d380 --- /dev/null +++ b/toxav/bwcontroler.h @@ -0,0 +1,37 @@ +/** bwcontroler.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifndef BWCONROLER_H +#define BWCONROLER_H +#include "../toxcore/Messenger.h" + +typedef struct BWControler_s BWControler; + +BWControler *bwc_new(Messenger *m, uint32_t friendnumber, + void (*mcb) (BWControler *, uint32_t, float, void *), + void *udata); +void bwc_kill(BWControler *bwc); + +void bwc_feed_avg(BWControler *bwc, uint32_t bytes); +void bwc_add_lost(BWControler *bwc, uint32_t bytes); +void bwc_add_recv(BWControler *bwc, uint32_t bytes); + +#endif /* BWCONROLER_H */ diff --git a/toxav/codec.c b/toxav/codec.c deleted file mode 100644 index 8940aa251..000000000 --- a/toxav/codec.c +++ /dev/null @@ -1,705 +0,0 @@ -/** codec.c - * - * Audio and video codec intitialization, encoding/decoding and playback - * - * Copyright (C) 2013 Tox project All Rights Reserved. - * - * This file is part of Tox. - * - * Tox 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. - * - * Tox 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 Tox. If not, see . - * - */ - - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif /* HAVE_CONFIG_H */ - -#include "codec.h" -#include "../toxcore/logger.h" -#include "../toxcore/util.h" - -#include -#include -#include -#include -#include - -#include "msi.h" -#include "rtp.h" - -/* Good quality encode. */ -#define MAX_DECODE_TIME_US 0 - -// TODO this has to be exchanged in msi -#define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */ -#define VIDEOFRAME_PIECE_SIZE 0x500 /* 1.25 KiB*/ -#define VIDEOFRAME_HEADER_SIZE 0x2 - -/* FIXME: Might not be enough */ -#define VIDEO_DECODE_BUFFER_SIZE 20 - -#define ARRAY(TYPE__) struct { uint16_t size; TYPE__ data[]; } - -typedef ARRAY(uint8_t) Payload; - -typedef struct { - uint16_t size; /* Max size */ - uint16_t start; - uint16_t end; - Payload **packets; -} PayloadBuffer; - -static _Bool buffer_full(const PayloadBuffer *b) -{ - return (b->end + 1) % b->size == b->start; -} - -static _Bool buffer_empty(const PayloadBuffer *b) -{ - return b->end == b->start; -} - -static void buffer_write(PayloadBuffer *b, Payload *p) -{ - b->packets[b->end] = p; - b->end = (b->end + 1) % b->size; - - if (b->end == b->start) b->start = (b->start + 1) % b->size; /* full, overwrite */ -} - -static void buffer_read(PayloadBuffer *b, Payload **p) -{ - *p = b->packets[b->start]; - b->start = (b->start + 1) % b->size; -} - -static void buffer_clear(PayloadBuffer *b) -{ - while (!buffer_empty(b)) { - Payload *p; - buffer_read(b, &p); - free(p); - } -} - -static PayloadBuffer *buffer_new(int size) -{ - PayloadBuffer *buf = calloc(sizeof(PayloadBuffer), 1); - - if (!buf) return NULL; - - buf->size = size + 1; /* include empty elem */ - - if (!(buf->packets = calloc(buf->size, sizeof(Payload *)))) { - free(buf); - return NULL; - } - - return buf; -} - -static void buffer_free(PayloadBuffer *b) -{ - if (b) { - buffer_clear(b); - free(b->packets); - free(b); - } -} - -/* JITTER BUFFER WORK */ -typedef struct _JitterBuffer { - RTPMessage **queue; - uint32_t size; - uint32_t capacity; - uint16_t bottom; - uint16_t top; -} JitterBuffer; - -static JitterBuffer *jbuf_new(uint32_t capacity) -{ - unsigned int size = 1; - - while (size <= (capacity * 4)) { - size *= 2; - } - - JitterBuffer *q; - - if ( !(q = calloc(sizeof(JitterBuffer), 1)) ) return NULL; - - if (!(q->queue = calloc(sizeof(RTPMessage *), size))) { - free(q); - return NULL; - } - - q->size = size; - q->capacity = capacity; - return q; -} - -static void jbuf_clear(JitterBuffer *q) -{ - for (; q->bottom != q->top; ++q->bottom) { - if (q->queue[q->bottom % q->size]) { - rtp_free_msg(NULL, q->queue[q->bottom % q->size]); - q->queue[q->bottom % q->size] = NULL; - } - } -} - -static void jbuf_free(JitterBuffer *q) -{ - if (!q) return; - - jbuf_clear(q); - free(q->queue); - free(q); -} - -static int jbuf_write(JitterBuffer *q, RTPMessage *m) -{ - uint16_t sequnum = m->header->sequnum; - - unsigned int num = sequnum % q->size; - - if ((uint32_t)(sequnum - q->bottom) > q->size) { - jbuf_clear(q); - q->bottom = sequnum - q->capacity; - q->queue[num] = m; - q->top = sequnum + 1; - return 0; - } - - if (q->queue[num]) - return -1; - - q->queue[num] = m; - - if ((sequnum - q->bottom) >= (q->top - q->bottom)) - q->top = sequnum + 1; - - return 0; -} - -/* Success is 0 when there is nothing to dequeue, - * 1 when there's a good packet, - * 2 when there's a lost packet */ -static RTPMessage *jbuf_read(JitterBuffer *q, int32_t *success) -{ - if (q->top == q->bottom) { - *success = 0; - return NULL; - } - - unsigned int num = q->bottom % q->size; - - if (q->queue[num]) { - RTPMessage *ret = q->queue[num]; - q->queue[num] = NULL; - ++q->bottom; - *success = 1; - return ret; - } - - if ((uint32_t)(q->top - q->bottom) > q->capacity) { - ++q->bottom; - *success = 2; - return NULL; - } - - *success = 0; - return NULL; -} - -static int init_video_decoder(CSSession *cs) -{ - int rc = vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0, VPX_DECODER_ABI_VERSION); - - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); - return -1; - } - - return 0; -} - -static int init_audio_decoder(CSSession *cs) -{ - int rc; - cs->audio_decoder = opus_decoder_create(cs->audio_decoder_sample_rate, cs->audio_decoder_channels, &rc ); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); - return -1; - } - - return 0; -} - -static int init_video_encoder(CSSession *cs, uint16_t max_width, uint16_t max_height, uint32_t video_bitrate) -{ - vpx_codec_enc_cfg_t cfg; - int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); - - if (rc != VPX_CODEC_OK) { - LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc)); - return -1; - } - - cfg.rc_target_bitrate = video_bitrate; - cfg.g_w = max_width; - cfg.g_h = max_height; - cfg.g_pass = VPX_RC_ONE_PASS; - cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; - cfg.g_lag_in_frames = 0; - cfg.kf_min_dist = 0; - cfg.kf_max_dist = 48; - cfg.kf_mode = VPX_KF_AUTO; - - rc = vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, VPX_ENCODER_ABI_VERSION); - - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); - return -1; - } - - rc = vpx_codec_control(&cs->v_encoder, VP8E_SET_CPUUSED, 8); - - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); - return -1; - } - - cs->max_width = max_width; - cs->max_height = max_height; - cs->video_bitrate = video_bitrate; - - return 0; -} - -static int init_audio_encoder(CSSession *cs) -{ - int rc = OPUS_OK; - cs->audio_encoder = opus_encoder_create(cs->audio_encoder_sample_rate, - cs->audio_encoder_channels, OPUS_APPLICATION_VOIP, &rc); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); - return -1; - } - - rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_encoder_bitrate)); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); - return -1; - } - - /* Enable in-band forward error correction in codec */ - rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_INBAND_FEC(1)); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); - return -1; - } - - /* Make codec resistant to up to 10% packet loss */ - rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_PACKET_LOSS_PERC(10)); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); - return -1; - } - - /* Set algorithm to the highest complexity, maximizing compression */ - rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10)); - - if ( rc != OPUS_OK ) { - LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); - return -1; - } - - return 0; -} - -/* PUBLIC */ -int cs_split_video_payload(CSSession *cs, const uint8_t *payload, uint16_t length) -{ - if (!cs || !length || length > cs->max_video_frame_size) { - LOGGER_ERROR("Invalid CodecState or video frame size: %u", length); - return cs_ErrorSplittingVideoPayload; - } - - cs->split_video_frame[0] = cs->frameid_out++; - cs->split_video_frame[1] = 0; - cs->processing_video_frame = payload; - cs->processing_video_frame_size = length; - - return ((length - 1) / cs->video_frame_piece_size) + 1; -} - -const uint8_t *cs_get_split_video_frame(CSSession *cs, uint16_t *size) -{ - if (!cs || !size) return NULL; - - if (cs->processing_video_frame_size > cs->video_frame_piece_size) { - memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, - cs->processing_video_frame, - cs->video_frame_piece_size); - - cs->processing_video_frame += cs->video_frame_piece_size; - cs->processing_video_frame_size -= cs->video_frame_piece_size; - - *size = cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE; - } else { - memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, - cs->processing_video_frame, - cs->processing_video_frame_size); - - *size = cs->processing_video_frame_size + VIDEOFRAME_HEADER_SIZE; - } - - cs->split_video_frame[1]++; - - return cs->split_video_frame; -} - -void cs_do(CSSession *cs) -{ - /* Codec session should always be protected by call mutex so no need to check for cs validity - */ - - if (!cs) return; - - Payload *p; - int rc; - - int success = 0; - - pthread_mutex_lock(cs->queue_mutex); - RTPMessage *msg; - - while ((msg = jbuf_read(cs->j_buf, &success)) || success == 2) { - pthread_mutex_unlock(cs->queue_mutex); - - uint16_t fsize = ((cs->audio_decoder_sample_rate * cs->audio_decoder_frame_duration) / 1000); - int16_t tmp[fsize * cs->audio_decoder_channels]; - - if (success == 2) { - rc = opus_decode(cs->audio_decoder, 0, 0, tmp, fsize, 1); - } else { - rc = opus_decode(cs->audio_decoder, msg->data, msg->length, tmp, fsize, 0); - rtp_free_msg(NULL, msg); - } - - if (rc < 0) { - LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); - } else if (cs->acb.first) { - /* Play */ - cs->acb.first(cs->agent, cs->call_idx, tmp, rc, cs->acb.second); - } - - pthread_mutex_lock(cs->queue_mutex); - } - - if (cs->vbuf_raw && !buffer_empty(cs->vbuf_raw)) { - /* Decode video */ - buffer_read(cs->vbuf_raw, &p); - - /* Leave space for (possibly) other thread to queue more data after we read it here */ - pthread_mutex_unlock(cs->queue_mutex); - - rc = vpx_codec_decode(&cs->v_decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US); - free(p); - - if (rc != VPX_CODEC_OK) { - LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); - } else { - vpx_codec_iter_t iter = NULL; - vpx_image_t *dest = vpx_codec_get_frame(&cs->v_decoder, &iter); - - /* Play decoded images */ - for (; dest; dest = vpx_codec_get_frame(&cs->v_decoder, &iter)) { - if (cs->vcb.first) - cs->vcb.first(cs->agent, cs->call_idx, dest, cs->vcb.second); - - vpx_img_free(dest); - } - } - - return; - } - - pthread_mutex_unlock(cs->queue_mutex); -} - -int cs_set_video_encoder_resolution(CSSession *cs, uint16_t width, uint16_t height) -{ - vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; - - if (cfg.g_w == width && cfg.g_h == height) - return 0; - - if (width * height > cs->max_width * cs->max_height) { - vpx_codec_ctx_t v_encoder = cs->v_encoder; - - if (init_video_encoder(cs, width, height, cs->video_bitrate) == -1) { - cs->v_encoder = v_encoder; - return cs_ErrorSettingVideoResolution; - } - - vpx_codec_destroy(&v_encoder); - return 0; - } - - LOGGER_DEBUG("New video resolution: %u %u", width, height); - cfg.g_w = width; - cfg.g_h = height; - int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); - - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); - return cs_ErrorSettingVideoResolution; - } - - return 0; -} - -int cs_set_video_encoder_bitrate(CSSession *cs, uint32_t video_bitrate) -{ - vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; - - if (cfg.rc_target_bitrate == video_bitrate) - return 0; - - LOGGER_DEBUG("New video bitrate: %u", video_bitrate); - cfg.rc_target_bitrate = video_bitrate; - int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); - - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); - return cs_ErrorSettingVideoBitrate; - } - - cs->video_bitrate = video_bitrate; - return 0; -} - -CSSession *cs_new(const ToxAvCSettings *cs_self, const ToxAvCSettings *cs_peer, uint32_t jbuf_size, int has_video) -{ - CSSession *cs = calloc(sizeof(CSSession), 1); - - if (!cs) { - LOGGER_WARNING("Allocation failed! Application might misbehave!"); - return NULL; - } - - if (create_recursive_mutex(cs->queue_mutex) != 0) { - LOGGER_WARNING("Failed to create recursive mutex!"); - free(cs); - return NULL; - } - - if ( !(cs->j_buf = jbuf_new(jbuf_size)) ) { - LOGGER_WARNING("Jitter buffer creaton failed!"); - goto error; - } - - cs->audio_encoder_bitrate = cs_self->audio_bitrate; - cs->audio_encoder_sample_rate = cs_self->audio_sample_rate; - cs->audio_encoder_channels = cs_self->audio_channels; - cs->audio_encoder_frame_duration = cs_self->audio_frame_duration; - - cs->audio_decoder_bitrate = cs_peer->audio_bitrate; - cs->audio_decoder_sample_rate = cs_peer->audio_sample_rate; - cs->audio_decoder_channels = cs_peer->audio_channels; - cs->audio_decoder_frame_duration = cs_peer->audio_frame_duration; - - - cs->capabilities |= ( 0 == init_audio_encoder(cs) ) ? cs_AudioEncoding : 0; - cs->capabilities |= ( 0 == init_audio_decoder(cs) ) ? cs_AudioDecoding : 0; - - if ( !(cs->capabilities & cs_AudioEncoding) || !(cs->capabilities & cs_AudioDecoding) ) goto error; - - if ((cs->support_video = has_video)) { - cs->max_video_frame_size = MAX_VIDEOFRAME_SIZE; - cs->video_frame_piece_size = VIDEOFRAME_PIECE_SIZE; - - cs->capabilities |= ( 0 == init_video_encoder(cs, cs_self->max_video_width, - cs_self->max_video_height, cs_self->video_bitrate) ) ? cs_VideoEncoding : 0; - cs->capabilities |= ( 0 == init_video_decoder(cs) ) ? cs_VideoDecoding : 0; - - if ( !(cs->capabilities & cs_VideoEncoding) || !(cs->capabilities & cs_VideoDecoding) ) goto error; - - if ( !(cs->frame_buf = calloc(cs->max_video_frame_size, 1)) ) goto error; - - if ( !(cs->split_video_frame = calloc(cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE, 1)) ) - goto error; - - if ( !(cs->vbuf_raw = buffer_new(VIDEO_DECODE_BUFFER_SIZE)) ) goto error; - } - - return cs; - -error: - LOGGER_WARNING("Error initializing codec session! Application might misbehave!"); - - pthread_mutex_destroy(cs->queue_mutex); - - if ( cs->audio_encoder ) opus_encoder_destroy(cs->audio_encoder); - - if ( cs->audio_decoder ) opus_decoder_destroy(cs->audio_decoder); - - - if (has_video) { - if ( cs->capabilities & cs_VideoDecoding ) vpx_codec_destroy(&cs->v_decoder); - - if ( cs->capabilities & cs_VideoEncoding ) vpx_codec_destroy(&cs->v_encoder); - - buffer_free(cs->vbuf_raw); - - free(cs->frame_buf); - free(cs->split_video_frame); - } - - jbuf_free(cs->j_buf); - free(cs); - - return NULL; -} - -void cs_kill(CSSession *cs) -{ - if (!cs) return; - - /* queue_message will not be called since it's unregistered before cs_kill is called */ - pthread_mutex_destroy(cs->queue_mutex); - - - if ( cs->audio_encoder ) - opus_encoder_destroy(cs->audio_encoder); - - if ( cs->audio_decoder ) - opus_decoder_destroy(cs->audio_decoder); - - if ( cs->capabilities & cs_VideoDecoding ) - vpx_codec_destroy(&cs->v_decoder); - - if ( cs->capabilities & cs_VideoEncoding ) - vpx_codec_destroy(&cs->v_encoder); - - jbuf_free(cs->j_buf); - buffer_free(cs->vbuf_raw); - free(cs->frame_buf); - free(cs->split_video_frame); - - LOGGER_DEBUG("Terminated codec state: %p", cs); - free(cs); -} - - - - -/* Called from RTP */ -void queue_message(RTPSession *session, RTPMessage *msg) -{ - /* This function is unregistered during call termination befor destroing - * Codec session so no need to check for validity of cs - */ - CSSession *cs = session->cs; - - if (!cs) return; - - /* Audio */ - if (session->payload_type == msi_TypeAudio % 128) { - pthread_mutex_lock(cs->queue_mutex); - int ret = jbuf_write(cs->j_buf, msg); - pthread_mutex_unlock(cs->queue_mutex); - - if (ret == -1) { - rtp_free_msg(NULL, msg); - } - } - /* Video */ - else { - uint8_t *packet = msg->data; - uint32_t packet_size = msg->length; - - if (packet_size < VIDEOFRAME_HEADER_SIZE) - goto end; - - uint8_t diff = packet[0] - cs->frameid_in; - - if (diff != 0) { - if (diff < 225) { /* New frame */ - /* Flush last frames' data and get ready for this frame */ - Payload *p = malloc(sizeof(Payload) + cs->frame_size); - - if (p) { - pthread_mutex_lock(cs->queue_mutex); - - if (buffer_full(cs->vbuf_raw)) { - LOGGER_DEBUG("Dropped video frame"); - Payload *tp; - buffer_read(cs->vbuf_raw, &tp); - free(tp); - } else { - p->size = cs->frame_size; - memcpy(p->data, cs->frame_buf, cs->frame_size); - } - - buffer_write(cs->vbuf_raw, p); - pthread_mutex_unlock(cs->queue_mutex); - } else { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); - goto end; - } - - cs->last_timestamp = msg->header->timestamp; - cs->frameid_in = packet[0]; - memset(cs->frame_buf, 0, cs->frame_size); - cs->frame_size = 0; - - } else { /* Old frame; drop */ - LOGGER_DEBUG("Old packet: %u", packet[0]); - goto end; - } - } - - uint8_t piece_number = packet[1]; - - uint32_t length_before_piece = ((piece_number - 1) * cs->video_frame_piece_size); - uint32_t framebuf_new_length = length_before_piece + (packet_size - VIDEOFRAME_HEADER_SIZE); - - if (framebuf_new_length > cs->max_video_frame_size) { - goto end; - } - - /* Otherwise it's part of the frame so just process */ - /* LOGGER_DEBUG("Video Packet: %u %u", packet[0], packet[1]); */ - - memcpy(cs->frame_buf + length_before_piece, - packet + VIDEOFRAME_HEADER_SIZE, - packet_size - VIDEOFRAME_HEADER_SIZE); - - if (framebuf_new_length > cs->frame_size) { - cs->frame_size = framebuf_new_length; - } - -end: - rtp_free_msg(NULL, msg); - } -} diff --git a/toxav/codec.h b/toxav/codec.h deleted file mode 100644 index 6018e5df6..000000000 --- a/toxav/codec.h +++ /dev/null @@ -1,176 +0,0 @@ -/** codec.h - * - * Audio and video codec intitialization, encoding/decoding and playback - * - * Copyright (C) 2013 Tox project All Rights Reserved. - * - * This file is part of Tox. - * - * Tox 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. - * - * Tox 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 Tox. If not, see . - * - */ - -#ifndef _CODEC_H_ -#define _CODEC_H_ - -#include "toxav.h" -#include "rtp.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) -#define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) - -/* Audio encoding/decoding */ -#include - -#define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } - -typedef void (*CSAudioCallback) (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data); -typedef void (*CSVideoCallback) (void *agent, int32_t call_idx, const vpx_image_t *img, void *data); - -/** - * Codec capabilities - */ -typedef enum { - cs_AudioEncoding = 1 << 0, - cs_AudioDecoding = 1 << 1, - cs_VideoEncoding = 1 << 2, - cs_VideoDecoding = 1 << 3 -} CSCapabilities; - -/** - * Codec errors. - */ -typedef enum { - cs_ErrorSettingVideoResolution = -30, - cs_ErrorSettingVideoBitrate = -31, - cs_ErrorSplittingVideoPayload = -32, -} CSError; - -/** - * Codec session - controling codec - */ -typedef struct _CSSession { - - /* VIDEO - * - * - */ - int support_video; - - /* video encoding */ - vpx_codec_ctx_t v_encoder; - uint32_t frame_counter; - - /* video decoding */ - vpx_codec_ctx_t v_decoder; - int max_width; - int max_height; - unsigned int video_bitrate; - - - /* Data handling */ - uint8_t *frame_buf; /* buffer for split video payloads */ - uint32_t frame_size; /* largest address written to in frame_buf for current input frame*/ - uint8_t frameid_in, frameid_out; /* id of input and output video frame */ - uint32_t last_timestamp; /* calculating cycles */ - - /* Limits */ - uint32_t video_frame_piece_size; - uint32_t max_video_frame_size; - - /* Reassembling */ - uint8_t *split_video_frame; - const uint8_t *processing_video_frame; - uint16_t processing_video_frame_size; - - - - /* AUDIO - * - * - */ - - /* audio encoding */ - OpusEncoder *audio_encoder; - int audio_encoder_bitrate; - int audio_encoder_sample_rate; - int audio_encoder_frame_duration; - int audio_encoder_channels; - - /* audio decoding */ - OpusDecoder *audio_decoder; - int audio_decoder_bitrate; - int audio_decoder_sample_rate; - int audio_decoder_frame_duration; - int audio_decoder_channels; - - struct _JitterBuffer *j_buf; - - - /* Voice activity detection */ - uint32_t EVAD_tolerance; /* In frames */ - uint32_t EVAD_tolerance_cr; - - - - /* OTHER - * - * - */ - - uint64_t capabilities; /* supports*/ - - /* Callbacks */ - PAIR(CSAudioCallback, void *) acb; - PAIR(CSVideoCallback, void *) vcb; - - /* Buffering */ - void *vbuf_raw; /* Un-decoded data */ - pthread_mutex_t queue_mutex[1]; - - void *agent; /* Pointer to ToxAv */ - int32_t call_idx; -} CSSession; - -/* Make sure to be called BEFORE corresponding rtp_new */ -CSSession *cs_new(const ToxAvCSettings *cs_self, const ToxAvCSettings *cs_peer, uint32_t jbuf_size, int has_video); -/* Make sure to be called AFTER corresponding rtp_kill */ -void cs_kill(CSSession *cs); - -int cs_split_video_payload(CSSession *cs, const uint8_t *payload, uint16_t length); -const uint8_t *cs_get_split_video_frame(CSSession *cs, uint16_t *size); - -/** - * Call playback callbacks - */ -void cs_do(CSSession *cs); - - -/* Reconfigure video encoder; return 0 on success or -1 on failure. */ -int cs_set_video_encoder_resolution(CSSession *cs, uint16_t width, uint16_t height); -int cs_set_video_encoder_bitrate(CSSession *cs, uint32_t video_bitrate); - - -/* Internal. Called from rtp_handle_message */ -void queue_message(RTPSession *session, RTPMessage *msg); -#endif /* _CODEC_H_ */ diff --git a/toxav/group.c b/toxav/group.c index 817ee6e69..190c2c3d7 100644 --- a/toxav/group.c +++ b/toxav/group.c @@ -20,7 +20,7 @@ #ifdef HAVE_CONFIG_H #include "config.h" -#endif +#endif /* HAVE_CONFIG_H */ #include "group.h" #include "../toxcore/util.h" @@ -54,7 +54,7 @@ static Group_JitterBuffer *create_queue(unsigned int capacity) Group_JitterBuffer *q; - if ( !(q = calloc(sizeof(Group_JitterBuffer), 1)) ) return NULL; + if (!(q = calloc(sizeof(Group_JitterBuffer), 1))) return NULL; if (!(q->queue = calloc(sizeof(Group_Audio_Packet *), size))) { free(q); @@ -190,7 +190,7 @@ static int recreate_encoder(Group_AV *group_av) group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels, OPUS_APPLICATION_AUDIO, &rc); - if ( rc != OPUS_OK ) { + if (rc != OPUS_OK) { LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); group_av->audio_encoder = NULL; return -1; @@ -198,7 +198,7 @@ static int recreate_encoder(Group_AV *group_av) rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate)); - if ( rc != OPUS_OK ) { + if (rc != OPUS_OK) { LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); opus_encoder_destroy(group_av->audio_encoder); group_av->audio_encoder = NULL; @@ -207,7 +207,7 @@ static int recreate_encoder(Group_AV *group_av) rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10)); - if ( rc != OPUS_OK ) { + if (rc != OPUS_OK) { LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); opus_encoder_destroy(group_av->audio_encoder); group_av->audio_encoder = NULL; @@ -306,7 +306,7 @@ static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, int g int rc; peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc); - if ( rc != OPUS_OK ) { + if (rc != OPUS_OK) { LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); free(pk); return -1; diff --git a/toxav/msi.c b/toxav/msi.c index 97ba936c5..7ad39a54e 100644 --- a/toxav/msi.c +++ b/toxav/msi.c @@ -1,6 +1,6 @@ /** msi.c * - * Copyright (C) 2013 Tox project All Rights Reserved. + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * * This file is part of Tox. * @@ -32,161 +32,345 @@ #include #include #include +#include #define MSI_MAXMSG_SIZE 256 -/* Define default timeout for a request. - * There is no behavior specified by the msi on what will - * client do on timeout, but to call timeout callback. - */ -#define m_deftout 10000 /* in milliseconds */ - /** * Protocol: * * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| */ -typedef uint8_t MSIRawCSettingsType[23]; - typedef enum { IDRequest = 1, - IDResponse, - IDReason, - IDCallId, - IDCSettings, + IDError, + IDCapabilities, } MSIHeaderID; -typedef enum { - TypeRequest, - TypeResponse, - -} MSIMessageType; typedef enum { - invite, - start, - cancel, - reject, - end, - + requ_init, + requ_push, + requ_pop, } MSIRequest; -typedef enum { - ringing, - starting, - ending, - error - -} MSIResponse; - #define GENERIC_HEADER(header, val_type) \ -typedef struct _MSIHeader##header { \ -val_type value; \ -_Bool exists; \ -} MSIHeader##header; +typedef struct { \ + val_type value; \ + bool exists; \ +} MSIHeader##header -GENERIC_HEADER ( Request, MSIRequest ) -GENERIC_HEADER ( Response, MSIResponse ) -GENERIC_HEADER ( CallId, MSICallIDType ) -GENERIC_HEADER ( Reason, MSIReasonStrType ) -GENERIC_HEADER ( CSettings, MSIRawCSettingsType ) +GENERIC_HEADER (Request, MSIRequest); +GENERIC_HEADER (Error, MSIError); +GENERIC_HEADER (Capabilities, uint8_t); -typedef struct _MSIMessage { - - MSIHeaderRequest request; - MSIHeaderResponse response; - MSIHeaderReason reason; - MSIHeaderCallId callid; - MSIHeaderCSettings csettings; - - int friend_id; - +typedef struct { + MSIHeaderRequest request; + MSIHeaderError error; + MSIHeaderCapabilities capabilities; } MSIMessage; -static void invoke_callback(MSISession *s, int32_t c, MSICallbackID i) -{ - if ( s->callbacks[i].first ) { - LOGGER_DEBUG("Invoking callback function: %d", i); +void msg_init (MSIMessage *dest, MSIRequest request); +int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length); +uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length); +static int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg); +int send_error (Messenger *m, uint32_t friend_number, MSIError error); +static int invoke_callback(MSICall *call, MSICallbackID cb); +static MSICall *get_call (MSISession *session, uint32_t friend_number); +MSICall *new_call (MSISession *session, uint32_t friend_number); +void kill_call (MSICall *call); +void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data); +void handle_init (MSICall *call, const MSIMessage *msg); +void handle_push (MSICall *call, const MSIMessage *msg); +void handle_pop (MSICall *call, const MSIMessage *msg); +void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object); - s->callbacks[i].first( s->agent_handler, c, s->callbacks[i].second ); - } -} /** - * Parse raw 'data' received from socket into MSIMessage struct. - * Every message has to have end value of 'end_byte' or _undefined_ behavior - * occures. The best practice is to check the end of the message at the handle_packet. + * Public functions */ -static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t length ) +void msi_register_callback (MSISession *session, msi_action_cb *callback, MSICallbackID id) { + if (!session) + return; + + pthread_mutex_lock(session->mutex); + session->callbacks[id] = callback; + pthread_mutex_unlock(session->mutex); +} +MSISession *msi_new (Messenger *m) +{ + if (m == NULL) { + LOGGER_ERROR("Could not init session on empty messenger!"); + return NULL; + } -#define FAIL_CONSTRAINT(constraint, wanted) if ((constraint -= wanted) < 1) { LOGGER_ERROR("Read over length!"); return -1; } -#define FAIL_SIZE(byte, valid) if ( byte != valid ) { LOGGER_ERROR("Invalid data size!"); return -1; } -#define FAIL_LIMITS(byte, high) if ( byte > high ) { LOGGER_ERROR("Failed limit!"); return -1; } + MSISession *retu = calloc (sizeof (MSISession), 1); - if ( msg == NULL ) { - LOGGER_ERROR("Could not parse message: no storage!"); + if (retu == NULL) { + LOGGER_ERROR("Allocation failed! Program might misbehave!"); + return NULL; + } + + if (create_recursive_mutex(retu->mutex) != 0) { + LOGGER_ERROR("Failed to init mutex! Program might misbehave"); + free(retu); + return NULL; + } + + retu->messenger = m; + + m_callback_msi_packet(m, handle_msi_packet, retu); + + /* This is called when remote terminates session */ + m_callback_connectionstatus_internal_av(m, on_peer_status, retu); + + LOGGER_DEBUG("New msi session: %p ", retu); + return retu; +} +int msi_kill (MSISession *session) +{ + if (session == NULL) { + LOGGER_ERROR("Tried to terminate non-existing session"); return -1; } - if ( data[length - 1] ) { /* End byte must have value 0 */ + m_callback_msi_packet((struct Messenger *) session->messenger, NULL, NULL); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR ("Failed to aquire lock on msi mutex"); + return -1; + } + + if (session->calls) { + MSIMessage msg; + msg_init(&msg, requ_pop); + + MSICall *it = get_call(session, session->calls_head); + + for (; it; it = it->next) { + send_message(session->messenger, it->friend_number, &msg); + kill_call(it); /* This will eventually free session->calls */ + } + } + + pthread_mutex_unlock(session->mutex); + pthread_mutex_destroy(session->mutex); + + LOGGER_DEBUG("Terminated session: %p", session); + free (session); + return 0; +} +int msi_invite (MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities) +{ + if (!session) + return -1; + + LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_number); + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR ("Failed to aquire lock on msi mutex"); + return -1; + } + + if (get_call(session, friend_number) != NULL) { + LOGGER_ERROR("Already in a call"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + (*call) = new_call (session, friend_number); + + if (*call == NULL) { + pthread_mutex_unlock(session->mutex); + return -1; + } + + (*call)->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, requ_init); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message ((*call)->session->messenger, (*call)->friend_number, &msg); + + (*call)->state = msi_CallRequesting; + + LOGGER_DEBUG("Invite sent"); + pthread_mutex_unlock(session->mutex); + return 0; +} +int msi_hangup (MSICall *call) +{ + if (!call || !call->session) + return -1; + + LOGGER_DEBUG("Session: %p Hanging up call with friend: %u", call->session, call->friend_number); + + MSISession *session = call->session; + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR ("Failed to aquire lock on msi mutex"); + return -1; + } + + if (call->state == msi_CallInactive) { + LOGGER_ERROR("Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + MSIMessage msg; + msg_init(&msg, requ_pop); + + send_message (session->messenger, call->friend_number, &msg); + + kill_call(call); + pthread_mutex_unlock(session->mutex); + return 0; +} +int msi_answer (MSICall *call, uint8_t capabilities) +{ + if (!call || !call->session) + return -1; + + LOGGER_DEBUG("Session: %p Answering call from: %u", call->session, call->friend_number); + + MSISession *session = call->session; + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR ("Failed to aquire lock on msi mutex"); + return -1; + } + + if (call->state != msi_CallRequested) { + /* Though sending in invalid state will not cause anything wierd + * Its better to not do it like a maniac */ + LOGGER_ERROR("Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + call->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, requ_push); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message (session->messenger, call->friend_number, &msg); + + call->state = msi_CallActive; + pthread_mutex_unlock(session->mutex); + + return 0; +} +int msi_change_capabilities(MSICall *call, uint8_t capabilities) +{ + if (!call || !call->session) + return -1; + + LOGGER_DEBUG("Session: %p Trying to change capabilities to friend %u", call->session, call->friend_number); + + MSISession *session = call->session; + + if (pthread_mutex_trylock(session->mutex) != 0) { + LOGGER_ERROR ("Failed to aquire lock on msi mutex"); + return -1; + } + + if (call->state != msi_CallActive) { + LOGGER_ERROR("Call is in invalid state!"); + pthread_mutex_unlock(session->mutex); + return -1; + } + + call->self_capabilities = capabilities; + + MSIMessage msg; + msg_init(&msg, requ_push); + + msg.capabilities.exists = true; + msg.capabilities.value = capabilities; + + send_message (call->session->messenger, call->friend_number, &msg); + + pthread_mutex_unlock(session->mutex); + return 0; +} + + +/** + * Private functions + */ +void msg_init(MSIMessage *dest, MSIRequest request) +{ + memset(dest, 0, sizeof(*dest)); + dest->request.exists = true; + dest->request.value = request; +} +int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length) +{ + /* Parse raw data received from socket into MSIMessage struct */ + +#define CHECK_SIZE(bytes, constraint, size) \ + if ((constraint -= (2 + size)) < 1) { LOGGER_ERROR("Read over length!"); return -1; } \ + if (bytes[1] != size) { LOGGER_ERROR("Invalid data size!"); return -1; } + +#define CHECK_ENUM_HIGH(bytes, enum_high) /* Assumes size == 1 */ \ + if (bytes[2] > enum_high) { LOGGER_ERROR("Failed enum high limit!"); return -1; } + +#define SET_UINT8(bytes, header) do { \ + header.value = bytes[2]; \ + header.exists = true; \ + bytes += 3; \ + } while(0) + +#define SET_UINT16(bytes, header) do { \ + memcpy(&header.value, bytes + 2, 2);\ + header.exists = true; \ + bytes += 4; \ + } while(0) + + + assert(dest); + + if (length == 0 || data[length - 1]) { /* End byte must have value 0 */ LOGGER_ERROR("Invalid end byte"); return -1; } + memset(dest, 0, sizeof(*dest)); + const uint8_t *it = data; int size_constraint = length; - while ( *it ) {/* until end byte is hit */ + while (*it) {/* until end byte is hit */ switch (*it) { case IDRequest: - FAIL_CONSTRAINT(size_constraint, 3); - FAIL_SIZE(it[1], 1); -// FAIL_LIMITS(it[2], invite, end); - FAIL_LIMITS(it[2], end); - msg->request.value = it[2]; - it += 3; - msg->request.exists = 1; + CHECK_SIZE(it, size_constraint, 1); + CHECK_ENUM_HIGH(it, requ_pop); + SET_UINT8(it, dest->request); break; - case IDResponse: - FAIL_CONSTRAINT(size_constraint, 3); - FAIL_SIZE(it[1], 1); -// FAIL_LIMITS(it[2], ringing, error); - FAIL_LIMITS(it[2], error); - msg->response.value = it[2]; - it += 3; - msg->response.exists = 1; + case IDError: + CHECK_SIZE(it, size_constraint, 1); + CHECK_ENUM_HIGH(it, msi_EUndisclosed); + SET_UINT8(it, dest->error); break; - case IDCallId: - FAIL_CONSTRAINT(size_constraint, sizeof(MSICallIDType) + 2); - FAIL_SIZE(it[1], sizeof(MSICallIDType)); - memcpy(msg->callid.value, it + 2, sizeof(MSICallIDType)); - it += sizeof(MSICallIDType) + 2; - msg->callid.exists = 1; - break; - - case IDReason: - FAIL_CONSTRAINT(size_constraint, sizeof(MSIReasonStrType) + 2); - FAIL_SIZE(it[1], sizeof(MSIReasonStrType)); - memcpy(msg->reason.value, it + 2, sizeof(MSIReasonStrType)); - it += sizeof(MSIReasonStrType) + 2; - msg->reason.exists = 1; - break; - - case IDCSettings: - FAIL_CONSTRAINT(size_constraint, sizeof(MSIRawCSettingsType) + 2); - FAIL_SIZE(it[1], sizeof(MSIRawCSettingsType)); - memcpy(msg->csettings.value, it + 2, sizeof(MSIRawCSettingsType)); - it += sizeof(MSIRawCSettingsType) + 2; - msg->csettings.exists = 1; + case IDCapabilities: + CHECK_SIZE(it, size_constraint, 1); + SET_UINT8(it, dest->capabilities); break; default: @@ -196,79 +380,24 @@ static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t lengt } } + if (dest->request.exists == false) { + LOGGER_ERROR("Invalid request field!"); + return -1; + } + return 0; + +#undef CHECK_SIZE +#undef CHECK_ENUM_HIGH +#undef SET_UINT8 +#undef SET_UINT16 } - -/** - * Create the message. - */ -MSIMessage *msi_new_message ( MSIMessageType type, const uint8_t type_value ) +uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length) { - MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); - - if ( retu == NULL ) { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); - return NULL; - } - - if ( type == TypeRequest ) { - retu->request.exists = 1; - retu->request.value = type_value; - - } else { - retu->response.exists = 1; - retu->response.value = type_value; - } - - return retu; -} - - -/** - * Parse data from handle_packet. - */ -MSIMessage *parse_recv ( const uint8_t *data, uint16_t length ) -{ - if ( data == NULL ) { - LOGGER_WARNING("Tried to parse empty message!"); - return NULL; - } - - MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); - - if ( retu == NULL ) { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); - return NULL; - } - - if ( parse_raw_data ( retu, data, length ) == -1 ) { - - free ( retu ); - return NULL; - } - - return retu; -} - - -/** - * Speaks for itself. - */ -uint8_t *format_output ( uint8_t *dest, - MSIHeaderID id, - const void *value, - uint8_t value_len, - uint16_t *length ) -{ - if ( dest == NULL ) { - LOGGER_ERROR("No destination space!"); - return NULL; - } - - if (value == NULL || value_len == 0) { - LOGGER_ERROR("Empty header value"); - return NULL; - } + /* Parse a single header for sending */ + assert(dest); + assert(value); + assert(value_len); *dest = id; dest ++; @@ -281,521 +410,208 @@ uint8_t *format_output ( uint8_t *dest, return dest + value_len; /* Set to next position ready to be written */ } - - -/** - * Parse MSIMessage to send. - */ -uint16_t parse_send ( MSIMessage *msg, uint8_t *dest ) +int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg) { - if (msg == NULL) { - LOGGER_ERROR("No message!"); - return 0; - } + /* Parse and send message */ + assert(m); - if (dest == NULL ) { - LOGGER_ERROR("No destination!"); - return 0; - } + uint8_t parsed [MSI_MAXMSG_SIZE]; - uint8_t *it = dest; + uint8_t *it = parsed; uint16_t size = 0; if (msg->request.exists) { uint8_t cast = msg->request.value; - it = format_output(it, IDRequest, &cast, 1, &size); + it = msg_parse_header_out(IDRequest, it, &cast, + sizeof(cast), &size); + } else { + LOGGER_DEBUG("Must have request field"); + return -1; } - if (msg->response.exists) { - uint8_t cast = msg->response.value; - it = format_output(it, IDResponse, &cast, 1, &size); + if (msg->error.exists) { + uint8_t cast = msg->error.value; + it = msg_parse_header_out(IDError, it, &cast, + sizeof(cast), &size); } - if (msg->callid.exists) { - it = format_output(it, IDCallId, &msg->callid.value, sizeof(msg->callid.value), &size); + if (msg->capabilities.exists) { + it = msg_parse_header_out(IDCapabilities, it, &msg->capabilities.value, + sizeof(msg->capabilities.value), &size); } - if (msg->reason.exists) { - it = format_output(it, IDReason, &msg->reason.value, sizeof(msg->reason.value), &size); - } - - if (msg->csettings.exists) { - it = format_output(it, IDCSettings, &msg->csettings.value, sizeof(msg->csettings.value), &size); + if (it == parsed) { + LOGGER_WARNING("Parsing message failed; empty message"); + return -1; } *it = 0; size ++; - return size; -} - -void msi_msg_set_reason ( MSIMessage *msg, const MSIReasonStrType value ) -{ - if ( !msg ) return; - - msg->reason.exists = 1; - memcpy(msg->reason.value, value, sizeof(MSIReasonStrType)); -} - -void msi_msg_set_callid ( MSIMessage *msg, const MSICallIDType value ) -{ - if ( !msg ) return; - - msg->callid.exists = 1; - memcpy(msg->callid.value, value, sizeof(MSICallIDType)); -} - -void msi_msg_set_csettings ( MSIMessage *msg, const MSICSettings *value ) -{ - if ( !msg ) return; - - msg->csettings.exists = 1; - - msg->csettings.value[0] = value->call_type; - uint8_t *iter = msg->csettings.value + 1; - - /* Video bitrate */ - uint32_t lval = htonl(value->video_bitrate); - memcpy(iter, &lval, 4); - iter += 4; - - /* Video max width */ - uint16_t sval = htons(value->max_video_width); - memcpy(iter, &sval, 2); - iter += 2; - - /* Video max height */ - sval = htons(value->max_video_height); - memcpy(iter, &sval, 2); - iter += 2; - - /* Audio bitrate */ - lval = htonl(value->audio_bitrate); - memcpy(iter, &lval, 4); - iter += 4; - - /* Audio frame duration */ - sval = htons(value->audio_frame_duration); - memcpy(iter, &sval, 2); - iter += 2; - - /* Audio sample rate */ - lval = htonl(value->audio_sample_rate); - memcpy(iter, &lval, 4); - iter += 4; - - /* Audio channels */ - lval = htonl(value->audio_channels); - memcpy(iter, &lval, 4); -} - -void msi_msg_get_csettings ( MSIMessage *msg, MSICSettings *dest ) -{ - if ( !msg || !dest || !msg->csettings.exists ) return; - - dest->call_type = msg->csettings.value[0]; - uint8_t *iter = msg->csettings.value + 1; - - memcpy(&dest->video_bitrate, iter, 4); - iter += 4; - dest->video_bitrate = ntohl(dest->video_bitrate); - - memcpy(&dest->max_video_width, iter, 2); - iter += 2; - dest->max_video_width = ntohs(dest->max_video_width); - - memcpy(&dest->max_video_height, iter, 2); - iter += 2; - dest->max_video_height = ntohs(dest->max_video_height); - - memcpy(&dest->audio_bitrate, iter, 4); - iter += 4; - dest->audio_bitrate = ntohl(dest->audio_bitrate); - - memcpy(&dest->audio_frame_duration, iter, 2); - iter += 2; - dest->audio_frame_duration = ntohs(dest->audio_frame_duration); - - memcpy(&dest->audio_sample_rate, iter, 4); - iter += 4; - dest->audio_sample_rate = ntohl(dest->audio_sample_rate); - - memcpy(&dest->audio_channels, iter, 4); - dest->audio_channels = ntohl(dest->audio_channels); -} - -typedef struct _Timer { - void (*func)(struct _Timer *); - uint64_t timeout; - MSISession *session; - int call_idx; - int id; - -} Timer; - -typedef struct _TimerHandler { - Timer **timers; - - uint32_t max_capacity; - uint32_t size; -} TimerHandler; - - -static int timer_alloc (MSISession *session , void (*func)(Timer *), int call_idx, uint32_t timeout) -{ - static int timer_id; - TimerHandler *timer_handler = session->timer_handler; - - uint32_t i = 0; - - for (; i < timer_handler->max_capacity && timer_handler->timers[i]; i ++); - - if (i == timer_handler->max_capacity) { - LOGGER_WARNING("Maximum capacity reached!"); - return -1; - } - - Timer *timer = timer_handler->timers[i] = calloc(sizeof(Timer), 1); - - if (timer == NULL) { - LOGGER_ERROR("Failed to allocate timer!"); - return -1; - } - - timer_handler->size ++; - - timer->func = func; - timer->session = session; - timer->call_idx = call_idx; - timer->timeout = timeout + current_time_monotonic(); /* In ms */ - ++timer_id; - timer->id = timer_id; - - /* reorder */ - if (i) { - int64_t j = i - 1; - - for (; j >= 0 && timeout < timer_handler->timers[j]->timeout; j--) { - Timer *tmp = timer_handler->timers[j]; - timer_handler->timers[j] = timer; - timer_handler->timers[j + 1] = tmp; - } - } - - LOGGER_DEBUG("Allocated timer index: %ull timeout: %ull, current size: %ull", i, timeout, timer_handler->size); - return timer->id; -} - -static int timer_release ( TimerHandler *timers_container, int id) -{ - Timer **timed_events = timers_container->timers; - - uint32_t i; - int rc = -1; - - for (i = 0; i < timers_container->max_capacity; ++i) { - if (timed_events[i] && timed_events[i]->id == id) { - rc = i; - break; - } - } - - if (rc == -1) { - LOGGER_WARNING("No event with id: %d", id); - return -1; - } - - free(timed_events[rc]); - - timed_events[rc] = NULL; - - i = rc + 1; - - for (; i < timers_container->max_capacity && timed_events[i]; i ++) { - timed_events[i - 1] = timed_events[i]; - timed_events[i] = NULL; - } - - timers_container->size--; - - LOGGER_DEBUG("Popped id: %d, current size: %ull ", id, timers_container->size); - return 0; -} - -/** - * Generate _random_ alphanumerical string. - */ -static void t_randomstr ( uint8_t *str, uint32_t size ) -{ - if (str == NULL) { - LOGGER_DEBUG("Empty destination!"); - return; - } - - static const uint8_t _bytes[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - uint32_t _it = 0; - - for ( ; _it < size; _it++ ) { - str[_it] = _bytes[ random_int() % 61 ]; - } -} - -/* TODO: it would be nice to actually have some sane error codes */ -typedef enum { - error_none, - error_deadcall, /* has call id but it's from old call */ - error_id_mismatch, /* non-existing call */ - - error_no_callid, /* not having call id */ - error_no_call, /* no call in session */ - error_no_crypto_key, /* no crypto key */ - - error_busy - -} MSICallError; /* Error codes */ - - -/** - * Stringify error code. - */ -static const uint8_t *stringify_error ( MSICallError error_code ) -{ - static const uint8_t *strings[] = { - ( uint8_t *) "", - ( uint8_t *) "Using dead call", - ( uint8_t *) "Call id not set to any call", - ( uint8_t *) "Call id not available", - ( uint8_t *) "No active call in session", - ( uint8_t *) "No Crypto-key set", - ( uint8_t *) "Callee busy" - }; - - return strings[error_code]; -} - -static int send_message ( MSISession *session, MSICall *call, MSIMessage *msg, uint32_t to ) -{ - msi_msg_set_callid ( msg, call->id ); - - uint8_t msg_string_final [MSI_MAXMSG_SIZE]; - uint16_t length = parse_send ( msg, msg_string_final ); - - if (!length) { - LOGGER_WARNING("Parsing message failed; nothing sent!"); - return -1; - } - - if ( m_msi_packet(session->messenger_handle, to, msg_string_final, length) ) { + if (m_msi_packet(m, friend_number, parsed, size)) { LOGGER_DEBUG("Sent message"); return 0; } return -1; } - -static int send_reponse ( MSISession *session, MSICall *call, MSIResponse response, uint32_t to ) +int send_error (Messenger *m, uint32_t friend_number, MSIError error) { - MSIMessage *msg = msi_new_message ( TypeResponse, response ); - int ret = send_message ( session, call, msg, to ); - free ( msg ); - return ret; -} + /* Send error message */ + assert(m); -static int send_error ( MSISession *session, MSICall *call, MSICallError errid, uint32_t to ) -{ - if (!call) { - LOGGER_WARNING("Cannot handle error on 'null' call"); - return -1; - } + LOGGER_DEBUG("Sending error: %d to friend: %d", error, friend_number); - LOGGER_DEBUG("Sending error: %d on call: %s", errid, call->id); + MSIMessage msg; + msg_init(&msg, requ_pop); - MSIMessage *msg_error = msi_new_message ( TypeResponse, error ); - - msi_msg_set_reason ( msg_error, stringify_error(errid) ); - send_message ( session, call, msg_error, to ); - free ( msg_error ); + msg.error.exists = true; + msg.error.value = error; + send_message (m, friend_number, &msg); return 0; } - -/** - * Determine 'bigger' call id - */ -static int call_id_bigger( const uint8_t *first, const uint8_t *second) +int invoke_callback(MSICall *call, MSICallbackID cb) { - return (memcmp(first, second, sizeof(MSICallIDType)) < 0); -} + assert(call); + if (call->session->callbacks[cb]) { + LOGGER_DEBUG("Invoking callback function: %d", cb); -/** - * Set/change peer csettings - */ -static int flush_peer_csettings ( MSICall *call, MSIMessage *msg, int peer_id ) -{ - if ( msg->csettings.exists ) { - msi_msg_get_csettings(msg, &call->csettings_peer[peer_id]); - - LOGGER_DEBUG("Peer: %d \n" - "Type: %u \n" - "Video bitrate: %u \n" - "Video height: %u \n" - "Video width: %u \n" - "Audio bitrate: %u \n" - "Audio framedur: %u \n" - "Audio sample rate: %u \n" - "Audio channels: %u \n", peer_id, - call->csettings_peer[peer_id].call_type, - call->csettings_peer[peer_id].video_bitrate, - call->csettings_peer[peer_id].max_video_height, - call->csettings_peer[peer_id].max_video_width, - call->csettings_peer[peer_id].audio_bitrate, - call->csettings_peer[peer_id].audio_frame_duration, - call->csettings_peer[peer_id].audio_sample_rate, - call->csettings_peer[peer_id].audio_channels ); + if (call->session->callbacks[cb] (call->session->av, call) != 0) { + LOGGER_WARNING("Callback state handling failed, sending error"); + goto FAILURE; + } return 0; } - LOGGER_WARNING("No csettings header!"); +FAILURE: + /* If no callback present or error happened while handling, + * an error message will be sent to friend + */ + + if (call->error == msi_ENone) + call->error = msi_EHandle; + return -1; } - - -/** - * Add peer to peer list. - */ -static void add_peer( MSICall *call, int peer_id ) +static MSICall *get_call (MSISession *session, uint32_t friend_number) { - uint32_t *peers = !call->peers ? peers = calloc(sizeof(uint32_t), 1) : - realloc( call->peers, sizeof(uint32_t) * call->peer_count); + assert(session); - if (!peers) { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); + if (session->calls == NULL || session->calls_tail < friend_number) + return NULL; + + return session->calls[friend_number]; +} +MSICall *new_call (MSISession *session, uint32_t friend_number) +{ + assert(session); + + MSICall *rc = calloc(sizeof(MSICall), 1); + + if (rc == NULL) + return NULL; + + rc->session = session; + rc->friend_number = friend_number; + + if (session->calls == NULL) { /* Creating */ + session->calls = calloc (sizeof(MSICall *), friend_number + 1); + + if (session->calls == NULL) { + free(rc); + return NULL; + } + + session->calls_tail = session->calls_head = friend_number; + + } else if (session->calls_tail < friend_number) { /* Appending */ + void *tmp = realloc(session->calls, sizeof(MSICall *) * (friend_number + 1)); + + if (tmp == NULL) { + free(rc); + return NULL; + } + + session->calls = tmp; + + /* Set fields in between to null */ + uint32_t i = session->calls_tail + 1; + + for (; i < friend_number; i ++) + session->calls[i] = NULL; + + rc->prev = session->calls[session->calls_tail]; + session->calls[session->calls_tail]->next = rc; + + session->calls_tail = friend_number; + + } else if (session->calls_head > friend_number) { /* Inserting at front */ + rc->next = session->calls[session->calls_head]; + session->calls[session->calls_head]->prev = rc; + session->calls_head = friend_number; + } + + session->calls[friend_number] = rc; + return rc; +} +void kill_call (MSICall *call) +{ + /* Assume that session mutex is locked */ + if (call == NULL) return; - } - call->peer_count ++; - call->peers = peers; - call->peers[call->peer_count - 1] = peer_id; + LOGGER_DEBUG("Killing call: %p", call); - LOGGER_DEBUG("Added peer: %d", peer_id); + MSISession *session = call->session; + + MSICall *prev = call->prev; + MSICall *next = call->next; + + if (prev) + prev->next = next; + else if (next) + session->calls_head = next->friend_number; + else goto CLEAR_CONTAINER; + + if (next) + next->prev = prev; + else if (prev) + session->calls_tail = prev->friend_number; + else goto CLEAR_CONTAINER; + + session->calls[call->friend_number] = NULL; + free(call); + return; + +CLEAR_CONTAINER: + session->calls_head = session->calls_tail = 0; + free(session->calls); + free(call); + session->calls = NULL; } - - -static MSICall *find_call ( MSISession *session, uint8_t *call_id ) +void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data) { - if ( call_id == NULL ) return NULL; + (void)m; + MSISession *session = data; - int32_t i = 0; + switch (status) { + case 0: { /* Friend is now offline */ + LOGGER_DEBUG("Friend %d is now offline", friend_number); - for (; i < session->max_calls; i ++ ) - if ( session->calls[i] && memcmp(session->calls[i]->id, call_id, sizeof(session->calls[i]->id)) == 0 ) { - return session->calls[i]; - } + pthread_mutex_lock(session->mutex); + MSICall *call = get_call(session, friend_number); - return NULL; -} - -static MSICall *init_call ( MSISession *session, int peers, int ringing_timeout ) -{ - - if (peers == 0) { - LOGGER_ERROR("No peers!"); - return NULL; - } - - int32_t call_idx = 0; - - for (; call_idx < session->max_calls; call_idx ++) { - if ( !session->calls[call_idx] ) { - - if (!(session->calls[call_idx] = calloc ( sizeof ( MSICall ), 1 ))) { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); - return NULL; + if (call == NULL) { + pthread_mutex_unlock(session->mutex); + return; } - break; - } - } - - if ( call_idx == session->max_calls ) { - LOGGER_WARNING("Reached maximum amount of calls!"); - return NULL; - } - - - MSICall *call = session->calls[call_idx]; - - call->call_idx = call_idx; - - if ( !(call->csettings_peer = calloc ( sizeof ( MSICSettings ), peers )) ) { - LOGGER_WARNING("Allocation failed! Program might misbehave!"); - free(call); - return NULL; - } - - call->session = session; - - call->request_timer_id = 0; - call->ringing_timer_id = 0; - - call->ringing_tout_ms = ringing_timeout; - - LOGGER_DEBUG("Started new call with index: %u", call_idx); - return call; -} - -static int terminate_call ( MSISession *session, MSICall *call ) -{ - if ( !call ) { - LOGGER_WARNING("Tried to terminate non-existing call!"); - return -1; - } - - /* Check event loop and cancel timed events if there are any - */ - timer_release ( session->timer_handler, call->request_timer_id); - timer_release ( session->timer_handler, call->ringing_timer_id); - - session->calls[call->call_idx] = NULL; - - LOGGER_DEBUG("Terminated call id: %d", call->call_idx); - - free ( call->csettings_peer ); - free ( call->peers ); - free ( call ); - - return 0; -} - -static void handle_remote_connection_change(Messenger *messenger, uint32_t friend_num, uint8_t status, void *session_p) -{ - (void)messenger; - MSISession *session = session_p; - - switch ( status ) { - case 0: { /* Went offline */ - int32_t j = 0; - - for ( ; j < session->max_calls; j ++ ) { - - if ( !session->calls[j] ) continue; - - uint16_t i = 0; - - for ( ; i < session->calls[j]->peer_count; i ++ ) - if ( session->calls[j]->peers[i] == (uint32_t)friend_num ) { - invoke_callback(session, j, msi_OnPeerTimeout); - terminate_call(session, session->calls[j]); - LOGGER_DEBUG("Remote: %d timed out!", friend_num); - return; /* TODO: On group calls change behaviour */ - } - } + invoke_callback(call, msi_OnPeerTimeout); /* Failure is ignored */ + kill_call(call); + pthread_mutex_unlock(session->mutex); } break; @@ -803,810 +619,207 @@ static void handle_remote_connection_change(Messenger *messenger, uint32_t frien break; } } - -/** - * Function called at request timeout - */ -static void handle_timeout ( Timer *timer ) +void handle_init (MSICall* call, const MSIMessage* msg) { - /* TODO: Cancel might not arrive there; set up - * timers on these cancels and terminate call on - * their timeout - */ - MSICall *call = timer->session->calls[timer->call_idx]; - - - if (call) { - LOGGER_DEBUG("[Call: %d] Request timed out!", call->call_idx); - - invoke_callback(timer->session, timer->call_idx, msi_OnRequestTimeout); - msi_cancel(timer->session, timer->call_idx, call->peers [0], "Request timed out"); + assert(call); + LOGGER_DEBUG("Session: %p Handling 'init' friend: %d", call->session, call->friend_number); + + if (!msg->capabilities.exists) { + LOGGER_WARNING("Session: %p Invalid capabilities on 'init'"); + call->error = msi_EInvalidMessage; + goto FAILURE; } + + switch (call->state) + { + case msi_CallInactive: { + /* Call requested */ + call->peer_capabilities = msg->capabilities.value; + call->state = msi_CallRequested; + + if (invoke_callback(call, msi_OnInvite) == -1) + goto FAILURE; + } + break; + + case msi_CallActive: { + /* If peer sent init while the call is already + * active it's probable that he is trying to + * re-call us while the call is not terminated + * on our side. We can assume that in this case + * we can automatically answer the re-call. + */ + + LOGGER_INFO("Friend is recalling us"); + + MSIMessage msg; + msg_init(&msg, requ_push); + + msg.capabilities.exists = true; + msg.capabilities.value = call->self_capabilities; + + send_message (call->session->messenger, call->friend_number, &msg); + + /* If peer changed capabilities during re-call they will + * be handled accordingly during the next step + */ + } + break; + + default: { + LOGGER_WARNING("Session: %p Invalid state on 'init'"); + call->error = msi_EInvalidState; + goto FAILURE; + } + break; + } + + return; +FAILURE: + send_error(call->session->messenger, call->friend_number, call->error); + kill_call(call); } - - -/********** Request handlers **********/ -static int handle_recv_invite ( MSISession *session, MSICall *call, MSIMessage *msg ) +void handle_push (MSICall *call, const MSIMessage *msg) { - LOGGER_DEBUG("Session: %p Handling 'invite' on call: %d", session, call ? call->call_idx : -1); + assert(call); + LOGGER_DEBUG("Session: %p Handling 'push' friend: %d", call->session, call->friend_number); - if (!msg->csettings.exists) {/**/ - LOGGER_WARNING("Peer sent invalid codec settings!"); - send_error ( session, call, error_no_callid, msg->friend_id ); - return 0; + if (!msg->capabilities.exists) { + LOGGER_WARNING("Session: %p Invalid capabilities on 'push'"); + call->error = msi_EInvalidMessage; + goto FAILURE; } - if ( call ) { - if ( call->peers[0] == (uint32_t)msg->friend_id ) { - if (call->state == msi_CallInviting) { - /* The glare case. A calls B when at the same time - * B calls A. Who has advantage is set bey calculating - * 'bigger' Call id and then that call id is being used in - * future. User with 'bigger' Call id has the advantage - * as in he will wait the response from the other. - */ - LOGGER_DEBUG("Glare case; Peer: %d", call->peers[0]); + switch (call->state) { + case msi_CallActive: { + /* Only act if capabilities changed */ + if (call->peer_capabilities != msg->capabilities.value) { + LOGGER_INFO("Friend is changing capabilities to: %u", msg->capabilities.value); - if ( call_id_bigger (call->id, msg->callid.value) == 1 ) { /* Peer has advantage */ + call->peer_capabilities = msg->capabilities.value; - /* Terminate call; peer will timeout(call) if call initialization fails */ - terminate_call(session, call); - - call = init_call ( session, 1, 0 ); - - if ( !call ) { - LOGGER_ERROR("Starting call"); - return 0; - } - - } else { - return 0; /* Wait for ringing from peer */ - } - } else if (call->state == msi_CallActive) { - /* Request for media change; call callback and send starting response */ - if (flush_peer_csettings(call, msg, 0) != 0) { /**/ - LOGGER_WARNING("Peer sent invalid csetting!"); - send_error ( session, call, error_no_callid, msg->friend_id ); - return 0; - } - - LOGGER_DEBUG("Set new call type: %s", call->csettings_peer[0].call_type == msi_TypeAudio ? "audio" : "video"); - send_reponse(session, call, starting, msg->friend_id); - invoke_callback(session, call->call_idx, msi_OnPeerCSChange); - return 1; + if (invoke_callback(call, msi_OnCapabilities) == -1) + goto FAILURE; } - } else { - send_error ( session, call, error_busy, msg->friend_id ); /* TODO: Ugh*/ - terminate_call(session, call); - return 0; } - } else { - call = init_call ( session, 1, 0 ); + break; + + case msi_CallRequesting: { + LOGGER_INFO("Friend answered our call"); + + /* Call started */ + call->peer_capabilities = msg->capabilities.value; + call->state = msi_CallActive; + + if (invoke_callback(call, msi_OnStart) == -1) + goto FAILURE; - if ( !call ) { - LOGGER_ERROR("Starting call"); - return 0; } + break; + + /* Pushes during initialization state are ignored */ + case msi_CallInactive: + case msi_CallRequested: { + LOGGER_WARNING("Ignoring invalid push"); + } + break; } - if ( !msg->callid.exists ) { - send_error ( session, call, error_no_callid, msg->friend_id ); - terminate_call(session, call); - return 0; - } + return; - memcpy ( call->id, msg->callid.value, sizeof(msg->callid.value) ); - call->state = msi_CallStarting; - - add_peer( call, msg->friend_id); - flush_peer_csettings ( call, msg, 0 ); - send_reponse(session, call, ringing, msg->friend_id); - invoke_callback(session, call->call_idx, msi_OnInvite); - - return 1; +FAILURE: + send_error(call->session->messenger, call->friend_number, call->error); + kill_call(call); } - -static int handle_recv_start ( MSISession *session, MSICall *call, MSIMessage *msg ) +void handle_pop (MSICall *call, const MSIMessage *msg) { - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; + assert(call); + + LOGGER_DEBUG("Session: %p Handling 'pop', friend id: %d", call->session, call->friend_number); + + /* callback errors are ignored */ + + if (msg->error.exists) { + LOGGER_WARNING("Friend detected an error: %d", msg->error.value); + call->error = msg->error.value; + invoke_callback(call, msi_OnError); + + } else switch (call->state) { + case msi_CallInactive: { + LOGGER_ERROR("Handling what should be impossible case"); + abort(); + } + break; + + case msi_CallActive: { + /* Hangup */ + LOGGER_INFO("Friend hung up on us"); + invoke_callback(call, msi_OnEnd); + } + break; + + case msi_CallRequesting: { + /* Reject */ + LOGGER_INFO("Friend rejected our call"); + invoke_callback(call, msi_OnEnd); + } + break; + + case msi_CallRequested: { + /* Cancel */ + LOGGER_INFO("Friend canceled call invite"); + invoke_callback(call, msi_OnEnd); + } + break; } - (void)msg; - - LOGGER_DEBUG("Session: %p Handling 'start' on call: %d, friend id: %d", session, call->call_idx, msg->friend_id ); - - call->state = msi_CallActive; - invoke_callback(session, call->call_idx, msi_OnStart); - return 1; + kill_call (call); } - -static int handle_recv_reject ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; - } - - LOGGER_DEBUG("Session: %p Handling 'reject' on call: %u", session, call->call_idx); - - invoke_callback(session, call->call_idx, msi_OnReject); - - send_reponse(session, call, ending, msg->friend_id); - terminate_call(session, call); - - return 1; -} - -static int handle_recv_cancel ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; - } - - (void)msg; - - LOGGER_DEBUG("Session: %p Handling 'cancel' on call: %u", session, call->call_idx); - - invoke_callback(session, call->call_idx, msi_OnCancel); - terminate_call ( session, call ); - - return 1; -} - -static int handle_recv_end ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; - } - - LOGGER_DEBUG("Session: %p Handling 'end' on call: %d", session, call->call_idx); - - invoke_callback(session, call->call_idx, msi_OnEnd); - send_reponse(session, call, ending, msg->friend_id); - terminate_call ( session, call ); - - return 1; -} - -/********** Response handlers **********/ -static int handle_recv_ringing ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; - } - - (void)msg; - - if ( call->ringing_timer_id ) { - LOGGER_WARNING("Call already ringing"); - return 0; - } - - LOGGER_DEBUG("Session: %p Handling 'ringing' on call: %d", session, call->call_idx ); - - call->ringing_timer_id = timer_alloc - ( session, handle_timeout, call->call_idx, call->ringing_tout_ms ); - invoke_callback(session, call->call_idx, msi_OnRinging); - return 1; -} -static int handle_recv_starting ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'starting' on non-existing call"); - return 0; - } - - if ( call->state == msi_CallActive ) { /* Change media */ - - LOGGER_DEBUG("Session: %p Changing media on call: %d", session, call->call_idx ); - - invoke_callback(session, call->call_idx, msi_OnSelfCSChange); - - } else if ( call->state == msi_CallInviting ) { - LOGGER_DEBUG("Session: %p Handling 'starting' on call: %d", session, call->call_idx ); - - call->state = msi_CallActive; - - MSIMessage *msg_start = msi_new_message ( TypeRequest, start ); - send_message ( session, call, msg_start, msg->friend_id ); - free ( msg_start ); - - - flush_peer_csettings ( call, msg, 0 ); - - /* This is here in case of glare */ - timer_release(session->timer_handler, call->ringing_timer_id); - invoke_callback(session, call->call_idx, msi_OnStart); - } else { - LOGGER_ERROR("Invalid call state"); - terminate_call(session, call ); - return 0; - } - - return 1; -} -static int handle_recv_ending ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Session: %p Handling 'start' on no call"); - return 0; - } - - (void)msg; - - LOGGER_DEBUG("Session: %p Handling 'ending' on call: %d", session, call->call_idx ); - - invoke_callback(session, call->call_idx, msi_OnEnd); - terminate_call ( session, call ); - - return 1; -} -static int handle_recv_error ( MSISession *session, MSICall *call, MSIMessage *msg ) -{ - if ( !call ) { - LOGGER_WARNING("Handling 'error' on non-existing call!"); - return -1; - } - - LOGGER_DEBUG("Session: %p Handling 'error' on call: %d", session, call->call_idx ); - - invoke_callback(session, call->call_idx, msi_OnEnd); - - /* Handle error accordingly */ - if ( msg->reason.exists ) { - /* TODO */ - } - - terminate_call ( session, call ); - - return 1; -} - -/** - * BASIC call flow: - * - * ALICE BOB - * | invite --> | - * | | - * | <-- ringing | - * | | - * | <-- starting | - * | | - * | start --> | - * | | - * | <-- MEDIA TRANS --> | - * | | - * | end --> | - * | | - * | <-- ending | - * - * Alice calls Bob by sending invite packet. - * Bob recvs the packet and sends an ringing packet; - * which notifies Alice that her invite is acknowledged. - * Ringing screen shown on both sides. - * Bob accepts the invite for a call by sending starting packet. - * Alice recvs the starting packet and sends the started packet to - * inform Bob that she recved the starting packet. - * Now the media transmission is established ( i.e. RTP transmission ). - * Alice hangs up and sends end packet. - * Bob recves the end packet and sends ending packet - * as the acknowledgement that the call is ending. - * - * - */ -static void msi_handle_packet ( Messenger *messenger, uint32_t source, const uint8_t *data, uint16_t length, - void *object ) +void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object) { LOGGER_DEBUG("Got msi message"); - /* Unused */ - (void)messenger; MSISession *session = object; - MSIMessage *msg; + MSIMessage msg; - if ( !length ) { - LOGGER_WARNING("Length param negative"); - return; - } - - msg = parse_recv ( data, length ); - - if ( !msg ) { + if (msg_parse_in (&msg, data, length) == -1) { LOGGER_WARNING("Error parsing message"); + send_error(m, friend_number, msi_EInvalidMessage); return; } else { LOGGER_DEBUG("Successfully parsed message"); } - msg->friend_id = source; - pthread_mutex_lock(session->mutex); + MSICall *call = get_call(session, friend_number); - /* Find what call */ - MSICall *call = msg->callid.exists ? find_call(session, msg->callid.value ) : NULL; - - /* Now handle message */ - - if ( msg->request.exists ) { /* Handle request */ - - switch (msg->request.value) { - case invite: - handle_recv_invite ( session, call, msg ); - break; - - case start: - handle_recv_start ( session, call, msg ); - break; - - case cancel: - handle_recv_cancel ( session, call, msg ); - break; - - case reject: - handle_recv_reject ( session, call, msg ); - break; - - case end: - handle_recv_end ( session, call, msg ); - break; - } - - } else if ( msg->response.exists ) { /* Handle response */ - - /* Got response so cancel timer */ - if ( call ) timer_release(session->timer_handler, call->request_timer_id); - - switch (msg->response.value) { - case ringing: - handle_recv_ringing ( session, call, msg ); - break; - - case starting: - handle_recv_starting ( session, call, msg ); - break; - - case ending: - handle_recv_ending ( session, call, msg ); - break; - - case error: - handle_recv_error ( session, call, msg ); - break; - } - - } else { - LOGGER_WARNING("Invalid message: no resp nor requ headers"); - } - - free ( msg ); - - pthread_mutex_unlock(session->mutex); -} - - - -/********** User functions **********/ -void msi_register_callback ( MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata ) -{ - session->callbacks[id].first = callback; - session->callbacks[id].second = userdata; -} - - -MSISession *msi_new ( Messenger *messenger, int32_t max_calls ) -{ - if (messenger == NULL) { - LOGGER_ERROR("Could not init session on empty messenger!"); - return NULL; - } - - if ( !max_calls ) { - LOGGER_WARNING("Invalid max call treshold!"); - return NULL; - } - - MSISession *retu = calloc ( sizeof ( MSISession ), 1 ); - - if (retu == NULL) { - LOGGER_ERROR("Allocation failed! Program might misbehave!"); - return NULL; - } - - if (!(retu->calls = calloc( sizeof (MSICall *), max_calls ))) { - LOGGER_ERROR("Allocation failed! Program might misbehave!"); - goto error; - } - - retu->timer_handler = calloc(1, sizeof(TimerHandler)); - - if (retu->timer_handler == NULL) { - LOGGER_ERROR("Allocation failed! Program might misbehave!"); - goto error; - } - - /* Allocate space for timers */ - ((TimerHandler *)retu->timer_handler)->max_capacity = max_calls * 10; - - if (!(((TimerHandler *)retu->timer_handler)->timers = calloc(max_calls * 10, sizeof(Timer *)))) { - LOGGER_ERROR("Allocation failed! Program might misbehave!"); - goto error; - } - - if (create_recursive_mutex(retu->mutex) != 0) { - LOGGER_ERROR("Failed to init mutex! Program might misbehave"); - goto error; - } - - retu->messenger_handle = messenger; - retu->agent_handler = NULL; - retu->max_calls = max_calls; - retu->frequ = 10000; /* default value? */ - retu->call_timeout = 30000; /* default value? */ - - m_callback_msi_packet(messenger, msi_handle_packet, retu ); - - /* This is called when remote terminates session */ - m_callback_connectionstatus_internal_av(messenger, handle_remote_connection_change, retu); - - LOGGER_DEBUG("New msi session: %p max calls: %u", retu, max_calls); - return retu; - -error: - - if (retu->timer_handler) { - free(((TimerHandler *)retu->timer_handler)->timers); - free(retu->timer_handler); - } - - free(retu->calls); - free(retu); - return NULL; -} - - -int msi_kill ( MSISession *session ) -{ - if (session == NULL) { - LOGGER_ERROR("Tried to terminate non-existing session"); - return -1; - } - - m_callback_msi_packet((struct Messenger *) session->messenger_handle, NULL, NULL); - pthread_mutex_lock(session->mutex); - - /* Cancel active calls */ - int32_t idx = 0; - - for (; idx < session->max_calls; idx ++) if ( session->calls[idx] ) { - /* Cancel all? */ - uint16_t _it = 0; - /*for ( ; _it < session->calls[idx]->peer_count; _it++ ) - * FIXME: will not work on multiple peers, must cancel call for all peers - */ - MSICallState state = session->calls[idx]->state; - - if (state == msi_CallInviting) { - msi_cancel( session, idx, session->calls[idx]->peers [_it], "MSI session terminated!" ); - } else { - msi_stopcall(session, idx); - } - } - - free(((TimerHandler *)session->timer_handler)->timers); - free(session->timer_handler); - - free ( session->calls ); - pthread_mutex_unlock(session->mutex); - pthread_mutex_destroy(session->mutex); - - LOGGER_DEBUG("Terminated session: %p", session); - free ( session ); - return 0; -} - -int msi_invite ( MSISession *session, - int32_t *call_index, - const MSICSettings *csettings, - uint32_t rngsec, - uint32_t friend_id ) -{ - pthread_mutex_lock(session->mutex); - - LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_id); - - - int i = 0; - - for (; i < session->max_calls; i ++) - if (session->calls[i] && session->calls[i]->peers[0] == friend_id) { - LOGGER_ERROR("Already in a call with friend %d", friend_id); + if (call == NULL) { + if (msg.request.value != requ_init) { + send_error(m, friend_number, msi_EStrayMessage); pthread_mutex_unlock(session->mutex); - return msi_ErrorAlreadyInCallWithPeer; + return; } + call = new_call(session, friend_number); - MSICall *call = init_call ( session, 1, rngsec ); /* Just one peer for now */ - - if ( !call ) { - pthread_mutex_unlock(session->mutex); - LOGGER_ERROR("Cannot handle more calls"); - return msi_ErrorReachedCallLimit; + if (call == NULL) { + send_error(m, friend_number, msi_ESystem); + pthread_mutex_unlock(session->mutex); + return; + } } - *call_index = call->call_idx; - - t_randomstr ( call->id, sizeof(call->id) ); - - add_peer ( call, friend_id ); - - call->csettings_local = *csettings; - - MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); - - msi_msg_set_csettings(msg_invite, csettings); - send_message ( session, call, msg_invite, friend_id ); - free( msg_invite ); - - call->state = msi_CallInviting; - - call->request_timer_id = timer_alloc ( session, handle_timeout, call->call_idx, m_deftout ); - - LOGGER_DEBUG("Invite sent"); - - pthread_mutex_unlock(session->mutex); - - return 0; -} - -int msi_hangup ( MSISession *session, int32_t call_index ) -{ - pthread_mutex_lock(session->mutex); - LOGGER_DEBUG("Session: %p Hanging up call: %u", session, call_index); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - LOGGER_ERROR("Invalid call index!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - if ( session->calls[call_index]->state != msi_CallActive ) { - LOGGER_ERROR("Call is not active!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorInvalidState; - } - - MSIMessage *msg_end = msi_new_message ( TypeRequest, end ); - - /* hangup for each peer */ - int it = 0; - - for ( ; it < session->calls[call_index]->peer_count; it ++ ) - send_message ( session, session->calls[call_index], msg_end, session->calls[call_index]->peers[it] ); - - session->calls[call_index]->state = msi_CallOver; - - free ( msg_end ); - - session->calls[call_index]->request_timer_id = - timer_alloc ( session, handle_timeout, call_index, m_deftout ); - - pthread_mutex_unlock(session->mutex); - return 0; -} - -int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ) -{ - pthread_mutex_lock(session->mutex); - LOGGER_DEBUG("Session: %p Answering call: %u", session, call_index); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - LOGGER_ERROR("Invalid call index!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - if ( session->calls[call_index]->state != msi_CallStarting ) { - LOGGER_ERROR("Call is in invalid state!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorInvalidState; - } - - MSIMessage *msg_starting = msi_new_message ( TypeResponse, starting ); - - session->calls[call_index]->csettings_local = *csettings; - - msi_msg_set_csettings(msg_starting, csettings); - - send_message ( session, session->calls[call_index], msg_starting, session->calls[call_index]->peers[0] ); - free ( msg_starting ); - - session->calls[call_index]->state = msi_CallActive; - - pthread_mutex_unlock(session->mutex); - return 0; -} - -int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ) -{ - pthread_mutex_lock(session->mutex); - LOGGER_DEBUG("Session: %p Canceling call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - LOGGER_ERROR("Invalid call index!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - if ( session->calls[call_index]->state != msi_CallInviting ) { - LOGGER_ERROR("Call is in invalid state: %u", session->calls[call_index]->state); - pthread_mutex_unlock(session->mutex); - return msi_ErrorInvalidState; - } - - MSIMessage *msg_cancel = msi_new_message ( TypeRequest, cancel ); - - /* FIXME */ -#if 0 - - if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { - MSIReasonStrType reason_cast; - memset(reason_cast, '\0', sizeof(MSIReasonStrType)); - memcpy(reason_cast, reason, strlen(reason)); - msi_msg_set_reason(msg_cancel, reason_cast); - } - -#else - (void)reason; - -#endif - - send_message ( session, session->calls[call_index], msg_cancel, peer ); - free ( msg_cancel ); - - terminate_call ( session, session->calls[call_index] ); - pthread_mutex_unlock(session->mutex); - - return 0; -} - -int msi_reject ( MSISession *session, int32_t call_index, const char *reason ) -{ - pthread_mutex_lock(session->mutex); - LOGGER_DEBUG("Session: %p Rejecting call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - LOGGER_ERROR("Invalid call index!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - if ( session->calls[call_index]->state != msi_CallStarting ) { - LOGGER_ERROR("Call is in invalid state!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorInvalidState; - } - - MSIMessage *msg_reject = msi_new_message ( TypeRequest, reject ); - - /* FIXME */ -#if 0 - - if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { - MSIReasonStrType reason_cast; - memset(reason_cast, '\0', sizeof(MSIReasonStrType)); - memcpy(reason_cast, reason, strlen(reason)); - msi_msg_set_reason(msg_reject, reason_cast); - } - -#else - (void)reason; - -#endif - - send_message ( session, session->calls[call_index], msg_reject, - session->calls[call_index]->peers[session->calls[call_index]->peer_count - 1] ); - free ( msg_reject ); - - session->calls[call_index]->state = msi_CallOver; - session->calls[call_index]->request_timer_id = - timer_alloc ( session, handle_timeout, call_index, m_deftout ); - - pthread_mutex_unlock(session->mutex); - return 0; -} - -int msi_stopcall ( MSISession *session, int32_t call_index ) -{ - pthread_mutex_lock(session->mutex); - LOGGER_DEBUG("Session: %p Stopping call index: %u", session, call_index); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - /* just terminate it */ - - terminate_call ( session, session->calls[call_index] ); - - pthread_mutex_unlock(session->mutex); - return 0; -} - -int msi_change_csettings(MSISession *session, int32_t call_index, const MSICSettings *csettings) -{ - pthread_mutex_lock(session->mutex); - - LOGGER_DEBUG("Changing media on call: %d", call_index); - - if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { - LOGGER_ERROR("Invalid call index!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorNoCall; - } - - MSICall *call = session->calls[call_index]; - - if ( call->state != msi_CallActive ) { - LOGGER_ERROR("Call is not active!"); - pthread_mutex_unlock(session->mutex); - return msi_ErrorInvalidState; - } - - MSICSettings *local = &call->csettings_local; - - if ( - local->call_type == csettings->call_type && - local->video_bitrate == csettings->video_bitrate && - local->max_video_width == csettings->max_video_width && - local->max_video_height == csettings->max_video_height && - local->audio_bitrate == csettings->audio_bitrate && - local->audio_frame_duration == csettings->audio_frame_duration && - local->audio_sample_rate == csettings->audio_sample_rate && - local->audio_channels == csettings->audio_channels ) { - LOGGER_ERROR("Call is already set accordingly!"); - pthread_mutex_unlock(session->mutex); - return -1; - } - - *local = *csettings; - - MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); - - msi_msg_set_csettings ( msg_invite, local ); - send_message ( session, call, msg_invite, call->peers[0] ); - free ( msg_invite ); - - LOGGER_DEBUG("Request for media change sent"); - - pthread_mutex_unlock(session->mutex); - - return 0; -} - -void msi_do(MSISession *session) -{ - pthread_mutex_lock(session->mutex); - - TimerHandler *timer = session->timer_handler; - - uint64_t time = current_time_monotonic(); - - while ( timer->timers[0] && timer->timers[0]->timeout < time ) { - LOGGER_DEBUG("Executing timer assigned at: %d", timer->timers[0]->timeout); - - int id = timer->timers[0]->id; - timer->timers[0]->func(timer->timers[0]); - - /* In case function has released timer */ - if (timer->timers[0] && timer->timers[0]->id == id) - timer_release(timer, id); + switch (msg.request.value) { + case requ_init: + handle_init(call, &msg); + break; + case requ_push: + handle_push(call, &msg); + break; + case requ_pop: + handle_pop(call, &msg); /* always kills the call */ + break; } pthread_mutex_unlock(session->mutex); diff --git a/toxav/msi.h b/toxav/msi.h index 6a00def68..e69581d15 100644 --- a/toxav/msi.h +++ b/toxav/msi.h @@ -1,6 +1,6 @@ /** msi.h * - * Copyright (C) 2013 Tox project All Rights Reserved. + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * * This file is part of Tox. * @@ -19,184 +19,133 @@ * */ -#ifndef __TOXMSI -#define __TOXMSI +#ifndef MSI_H +#define MSI_H -#include "codec.h" +#include +#include + +#include "audio.h" +#include "video.h" #include "../toxcore/Messenger.h" -typedef uint8_t MSICallIDType[12]; -typedef uint8_t MSIReasonStrType[255]; -typedef void ( *MSICallbackType ) ( void *agent, int32_t call_idx, void *arg ); - /** - * Call type identifier. Also used as rtp callback prefix. + * Error codes. */ typedef enum { - msi_TypeAudio = 192, - msi_TypeVideo -} MSICallType; + msi_ENone, + msi_EInvalidMessage, + msi_EInvalidParam, + msi_EInvalidState, + msi_EStrayMessage, + msi_ESystem, + msi_EHandle, + msi_EUndisclosed, /* NOTE: must be last enum otherwise parsing will not work */ +} MSIError; + +/** + * Supported capabilities + */ +typedef enum { + msi_CapSAudio = 4, /* sending audio */ + msi_CapSVideo = 8, /* sending video */ + msi_CapRAudio = 16, /* receiving audio */ + msi_CapRVideo = 32, /* receiving video */ +} MSICapabilities; /** * Call state identifiers. */ typedef enum { - msi_CallInviting, /* when sending call invite */ - msi_CallStarting, /* when getting call invite */ + msi_CallInactive, /* Default */ msi_CallActive, - msi_CallHold, - msi_CallOver - + msi_CallRequesting, /* when sending call invite */ + msi_CallRequested, /* when getting call invite */ } MSICallState; - -/** - * Encoding settings. - */ -typedef struct _MSICodecSettings { - MSICallType call_type; - - uint32_t video_bitrate; /* In kbits/s */ - uint16_t max_video_width; /* In px */ - uint16_t max_video_height; /* In px */ - - uint32_t audio_bitrate; /* In bits/s */ - uint16_t audio_frame_duration; /* In ms */ - uint32_t audio_sample_rate; /* In Hz */ - uint32_t audio_channels; -} MSICSettings; - - /** * Callbacks ids that handle the states */ typedef enum { msi_OnInvite, /* Incoming call */ - msi_OnRinging, /* When peer is ready to accept/reject the call */ msi_OnStart, /* Call (RTP transmission) started */ - msi_OnCancel, /* The side that initiated call canceled invite */ - msi_OnReject, /* The side that was invited rejected the call */ msi_OnEnd, /* Call that was active ended */ - msi_OnRequestTimeout, /* When the requested action didn't get response in specified time */ + msi_OnError, /* On protocol error */ msi_OnPeerTimeout, /* Peer timed out; stop the call */ - msi_OnPeerCSChange, /* Peer requested Csettings change */ - msi_OnSelfCSChange /* Csettings change confirmation */ + msi_OnCapabilities, /* Peer requested capabilities change */ } MSICallbackID; /** - * Errors + * The call struct. Please do not modify outside msi.c */ -typedef enum { - msi_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ - msi_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ - msi_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ - msi_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ -} MSIError; +typedef struct MSICall_s { + struct MSISession_s *session; /* Session pointer */ -/** - * The call struct. - */ -typedef struct _MSICall { /* Call info structure */ - struct _MSISession *session; /* Session pointer */ + MSICallState state; + uint8_t peer_capabilities; /* Peer capabilities */ + uint8_t self_capabilities; /* Self capabilities */ + uint16_t peer_vfpsz; /* Video frame piece size */ + uint32_t friend_number; /* Index of this call in MSISession */ + MSIError error; /* Last error */ - MSICallState state; + void *av_call; /* Pointer to av call handler */ - MSICSettings csettings_local; /* Local call settings */ - MSICSettings *csettings_peer; /* Peers call settings */ - - MSICallIDType id; /* Random value identifying the call */ - - int ringing_tout_ms; /* Ringing timeout in ms */ - - int request_timer_id; /* Timer id for outgoing request/action */ - int ringing_timer_id; /* Timer id for ringing timeout */ - - uint32_t *peers; - uint16_t peer_count; - - int32_t call_idx; /* Index of this call in MSISession */ + struct MSICall_s *next; + struct MSICall_s *prev; } MSICall; /** - * Control session struct + * Expected return on success is 0, if any other number is + * returned the call is considered errored and will be handled + * as such which means it will be terminated without any notice. */ -typedef struct _MSISession { +typedef int msi_action_cb (void *av, MSICall *call); +/** + * Control session struct. Please do not modify outside msi.c + */ +typedef struct MSISession_s { /* Call handlers */ MSICall **calls; - int32_t max_calls; + uint32_t calls_tail; + uint32_t calls_head; - void *agent_handler; - Messenger *messenger_handle; - - uint32_t frequ; - uint32_t call_timeout; /* Time of the timeout for some action to end; 0 if infinite */ + void *av; + Messenger *messenger; pthread_mutex_t mutex[1]; - - void *timer_handler; - PAIR(MSICallbackType, void *) callbacks[10]; + msi_action_cb *callbacks[7]; } MSISession; /** * Start the control session. */ -MSISession *msi_new ( Messenger *messenger, int32_t max_calls ); - +MSISession *msi_new(Messenger *m); /** - * Terminate control session. + * Terminate control session. NOTE: all calls will be freed */ -int msi_kill ( MSISession *session ); - +int msi_kill(MSISession *session); /** * Callback setter. */ -void msi_register_callback(MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata); - +void msi_register_callback(MSISession *session, msi_action_cb *callback, MSICallbackID id); /** - * Send invite request to friend_id. + * Send invite request to friend_number. */ -int msi_invite ( MSISession *session, - int32_t *call_index, - const MSICSettings *csettings, - uint32_t rngsec, - uint32_t friend_id ); - +int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities); /** - * Hangup active call. + * Hangup call. NOTE: 'call' will be freed */ -int msi_hangup ( MSISession *session, int32_t call_index ); - +int msi_hangup(MSICall *call); /** - * Answer active call request. + * Answer call request. */ -int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); - +int msi_answer(MSICall *call, uint8_t capabilities); /** - * Cancel request. + * Change capabilities of the call. */ -int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ); +int msi_change_capabilities(MSICall *call, uint8_t capabilities); -/** - * Reject incoming call. - */ -int msi_reject ( MSISession *session, int32_t call_index, const char *reason ); - -/** - * Terminate the call. - */ -int msi_stopcall ( MSISession *session, int32_t call_index ); - -/** - * Change codec settings of the current call. - */ -int msi_change_csettings ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); - -/** - * Main msi loop - */ -void msi_do( MSISession *session ); - -#endif /* __TOXMSI */ +#endif /* MSI_H */ diff --git a/toxav/rtp.c b/toxav/rtp.c index 95d3bf3d3..763166cd1 100644 --- a/toxav/rtp.c +++ b/toxav/rtp.c @@ -1,6 +1,6 @@ /** rtp.c * - * Copyright (C) 2013 Tox project All Rights Reserved. + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * * This file is part of Tox. * @@ -24,505 +24,369 @@ #endif /* HAVE_CONFIG_H */ #include "rtp.h" +#include "bwcontroler.h" #include "../toxcore/logger.h" #include "../toxcore/util.h" +#include "../toxcore/Messenger.h" #include -void queue_message(RTPSession *_session, RTPMessage *_msg); +#include -#define size_32 4 -#define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0) -#define ADD_FLAG_PADDING(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xDF; ( _h->flags ) |= ( ( ( _v ) << 5 ) & 0x20 ); } while(0) -#define ADD_FLAG_EXTENSION(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xEF;( _h->flags ) |= ( ( ( _v ) << 4 ) & 0x10 ); } while(0) -#define ADD_FLAG_CSRCC(_h, _v) do { ( _h->flags ) &= 0xF0; ( _h->flags ) |= ( ( _v ) & 0x0F ); } while(0) -#define ADD_SETTING_MARKER(_h, _v) do { if ( _v > 1 ) _v = 1; ( _h->marker_payloadt ) &= 0x7F; ( _h->marker_payloadt ) |= ( ( ( _v ) << 7 ) /*& 0x80 */ ); } while(0) -#define ADD_SETTING_PAYLOAD(_h, _v) do { if ( _v > 127 ) _v = 127; ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0) +int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); -#define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) -#define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) -#define GET_FLAG_EXTENSION(_h) (( _h->flags & 0x10 ) >> 4) -#define GET_FLAG_CSRCC(_h) ( _h->flags & 0x0f ) -#define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) -#define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) -/** - * Checks if message came in late. - */ -static int check_late_message (RTPSession *session, RTPMessage *msg) +RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friendnumber, + BWControler *bwc, void *cs, + int (*mcb) (void *, struct RTPMessage *)) { - /* - * Check Sequence number. If this new msg has lesser number then the session->rsequnum - * it shows that the message came in late. Also check timestamp to be 100% certain. - * - */ - return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1; -} + assert(mcb); + assert(cs); + assert(m); + RTPSession *retu = calloc(1, sizeof(RTPSession)); -/** - * Extracts header from payload. - */ -RTPHeader *extract_header ( const uint8_t *payload, int length ) -{ - if ( !payload || !length ) { - LOGGER_WARNING("No payload to extract!"); - return NULL; - } - - RTPHeader *retu = calloc(1, sizeof (RTPHeader)); - - if ( !retu ) { + if (!retu) { LOGGER_WARNING("Alloc failed! Program might misbehave!"); return NULL; } - memcpy(&retu->sequnum, payload, sizeof(retu->sequnum)); - retu->sequnum = ntohs(retu->sequnum); + retu->ssrc = random_int(); + retu->payload_type = payload_type; - const uint8_t *it = payload + 2; + retu->m = m; + retu->friend_number = friendnumber; - retu->flags = *it; - ++it; + /* Also set payload type as prefix */ - /* This indicates if the first 2 bits are valid. - * Now it may happen that this is out of order but - * it cuts down chances of parsing some invalid value - */ + retu->bwc = bwc; + retu->cs = cs; + retu->mcb = mcb; - if ( GET_FLAG_VERSION(retu) != RTP_VERSION ) { - /* Deallocate */ - LOGGER_WARNING("Invalid version!"); + if (-1 == rtp_allow_receiving(retu)) { + LOGGER_WARNING("Failed to start rtp receiving mode"); free(retu); return NULL; } - /* - * Added a check for the size of the header little sooner so - * I don't need to parse the other stuff if it's bad - */ - uint8_t cc = GET_FLAG_CSRCC ( retu ); - int total = 12 /* Minimum header len */ + ( cc * 4 ); - - if ( length < total ) { - /* Deallocate */ - LOGGER_WARNING("Length invalid!"); - free(retu); - return NULL; - } - - memset(retu->csrc, 0, 16 * sizeof (uint32_t)); - - retu->marker_payloadt = *it; - ++it; - retu->length = total; - - - memcpy(&retu->timestamp, it, sizeof(retu->timestamp)); - retu->timestamp = ntohl(retu->timestamp); - it += 4; - memcpy(&retu->ssrc, it, sizeof(retu->ssrc)); - retu->ssrc = ntohl(retu->ssrc); - - uint8_t x; - - for ( x = 0; x < cc; x++ ) { - it += 4; - memcpy(&retu->csrc[x], it, sizeof(retu->csrc[x])); - retu->csrc[x] = ntohl(retu->csrc[x]); - } - return retu; } - -/** - * Extracts external header from payload. Must be called AFTER extract_header()! - */ -RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length ) +void rtp_kill (RTPSession *session) { - const uint8_t *it = payload; + if (!session) + return; - RTPExtHeader *retu = calloc(1, sizeof (RTPExtHeader)); + LOGGER_DEBUG("Terminated RTP session: %p", session); - if ( !retu ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - return NULL; - } - - uint16_t ext_length; - memcpy(&ext_length, it, sizeof(ext_length)); - ext_length = ntohs(ext_length); - it += 2; - - - if ( length < ( ext_length * sizeof(uint32_t) ) ) { - LOGGER_WARNING("Length invalid!"); - free(retu); - return NULL; - } - - retu->length = ext_length; - memcpy(&retu->type, it, sizeof(retu->type)); - retu->type = ntohs(retu->type); - it += 2; - - if ( !(retu->table = calloc(ext_length, sizeof (uint32_t))) ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - free(retu); - return NULL; - } - - uint16_t x; - - for ( x = 0; x < ext_length; x++ ) { - it += 4; - memcpy(&(retu->table[x]), it, sizeof(retu->table[x])); - retu->table[x] = ntohl(retu->table[x]); - } - - return retu; + rtp_stop_receiving (session); + free (session); } - -/** - * Adds header to payload. Make sure _payload_ has enough space. - */ -uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) +int rtp_allow_receiving(RTPSession *session) { - uint8_t cc = GET_FLAG_CSRCC ( header ); - uint8_t *it = payload; - uint16_t sequnum; - uint32_t timestamp; - uint32_t ssrc; - uint32_t csrc; + if (session == NULL) + return -1; - - /* Add sequence number first */ - sequnum = htons(header->sequnum); - memcpy(it, &sequnum, sizeof(sequnum)); - it += 2; - - *it = header->flags; - ++it; - *it = header->marker_payloadt; - ++it; - - - timestamp = htonl(header->timestamp); - memcpy(it, ×tamp, sizeof(timestamp)); - it += 4; - ssrc = htonl(header->ssrc); - memcpy(it, &ssrc, sizeof(ssrc)); - - uint8_t x; - - for ( x = 0; x < cc; x++ ) { - it += 4; - csrc = htonl(header->csrc[x]); - memcpy(it, &csrc, sizeof(csrc)); + if (m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, + handle_rtp_packet, session) == -1) { + LOGGER_WARNING("Failed to register rtp receive handler"); + return -1; } - return it + 4; + LOGGER_DEBUG("Started receiving on session: %p", session); + return 0; } - -/** - * Adds extension header to payload. Make sure _payload_ has enough space. - */ -uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) +int rtp_stop_receiving(RTPSession *session) { - uint8_t *it = payload; - uint16_t length; - uint16_t type; - uint32_t entry; + if (session == NULL) + return -1; - length = htons(header->length); - memcpy(it, &length, sizeof(length)); - it += 2; - type = htons(header->type); - memcpy(it, &type, sizeof(type)); - it -= 2; /* Return to 0 position */ + m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, NULL, NULL); - if ( header->table ) { - - uint16_t x; - - for ( x = 0; x < header->length; x++ ) { - it += 4; - entry = htonl(header->table[x]); - memcpy(it, &entry, sizeof(entry)); - } - } - - return it + 4; + LOGGER_DEBUG("Stopped receiving on session: %p", session); + return 0; } - -/** - * Builds header from control session values. - */ -RTPHeader *build_header ( RTPSession *session ) +int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length) { - RTPHeader *retu = calloc ( 1, sizeof (RTPHeader) ); - - if ( !retu ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - return NULL; + if (!session) { + LOGGER_WARNING("No session!"); + return -1; } - ADD_FLAG_VERSION ( retu, session->version ); - ADD_FLAG_PADDING ( retu, session->padding ); - ADD_FLAG_EXTENSION ( retu, session->extension ); - ADD_FLAG_CSRCC ( retu, session->cc ); - ADD_SETTING_MARKER ( retu, session->marker ); - ADD_SETTING_PAYLOAD ( retu, session->payload_type ); + uint8_t rdata[length + sizeof(struct RTPHeader) + 1]; + memset(rdata, 0, sizeof(rdata)); - retu->sequnum = session->sequnum; - retu->timestamp = current_time_monotonic(); /* milliseconds */ - retu->ssrc = session->ssrc; + rdata[0] = session->payload_type; - int i; + struct RTPHeader *header = (struct RTPHeader *)(rdata + 1); - for ( i = 0; i < session->cc; i++ ) - retu->csrc[i] = session->csrc[i]; + header->ve = 2; + header->pe = 0; + header->xe = 0; + header->cc = 0; - retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); + header->ma = 0; + header->pt = session->payload_type % 128; - return retu; -} + header->sequnum = htons(session->sequnum); + header->timestamp = htonl(current_time_monotonic()); + header->ssrc = htonl(session->ssrc); + header->cpart = 0; + header->tlen = htons(length); -/** - * Parses data into RTPMessage struct. Stores headers separately from the payload data - * and so the length variable is set accordingly. - */ -RTPMessage *msg_parse ( const uint8_t *data, int length ) -{ - RTPMessage *retu = calloc(1, sizeof (RTPMessage)); + if (MAX_CRYPTO_DATA_SIZE > length + sizeof(struct RTPHeader) + 1) { - retu->header = extract_header ( data, length ); /* It allocates memory and all */ + /** + * The lenght is lesser than the maximum allowed lenght (including header) + * Send the packet in single piece. + */ - if ( !retu->header ) { - LOGGER_WARNING("Header failed to extract!"); - free(retu); - return NULL; - } + memcpy(rdata + 1 + sizeof(struct RTPHeader), data, length); - uint16_t from_pos = retu->header->length; - retu->length = length - from_pos; - - - - if ( GET_FLAG_EXTENSION ( retu->header ) ) { - retu->ext_header = extract_ext_header ( data + from_pos, length ); - - if ( retu->ext_header ) { - retu->length -= ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); - from_pos += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); - } else { /* Error */ - LOGGER_WARNING("Ext Header failed to extract!"); - rtp_free_msg(NULL, retu); - return NULL; - } + if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata, sizeof(rdata))) + LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", sizeof(rdata), strerror(errno)); } else { - retu->ext_header = NULL; + + /** + * The lenght is greater than the maximum allowed lenght (including header) + * Send the packet in multiple pieces. + */ + + uint16_t sent = 0; + uint16_t piece = MAX_CRYPTO_DATA_SIZE - (sizeof(struct RTPHeader) + 1); + + while ((length - sent) + sizeof(struct RTPHeader) + 1 > MAX_CRYPTO_DATA_SIZE) { + memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece); + + if (-1 == send_custom_lossy_packet(session->m, session->friend_number, + rdata, piece + sizeof(struct RTPHeader) + 1)) + LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", + piece + sizeof(struct RTPHeader) + 1, strerror(errno)); + + sent += piece; + header->cpart = htons(sent); + } + + /* Send remaining */ + piece = length - sent; + + if (piece) { + memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece); + + if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata, + piece + sizeof(struct RTPHeader) + 1)) + LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", + piece + sizeof(struct RTPHeader) + 1, strerror(errno)); + } } - if ( length - from_pos <= MAX_RTP_SIZE ) - memcpy ( retu->data, data + from_pos, length - from_pos ); - else { - LOGGER_WARNING("Invalid length!"); - rtp_free_msg(NULL, retu); - return NULL; - } - - retu->next = NULL; - - return retu; + session->sequnum ++; + return 0; } -/** - * Callback for networking core. - */ -int rtp_handle_packet ( Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object ) -{ - RTPSession *session = object; - RTPMessage *msg; - if ( !session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */ +bool chloss (const RTPSession *session, const struct RTPHeader *header) +{ + if (ntohl(header->timestamp) < session->rtimestamp) { + uint16_t hosq, lost = 0; + + hosq = ntohs(header->sequnum); + + lost = (hosq > session->rsequnum) ? + (session->rsequnum + 65535) - hosq : + session->rsequnum - hosq; + + puts ("Lost packet"); + while (lost --) + bwc_add_lost(session->bwc ,0); + + return true; + } + + return false; +} +struct RTPMessage *new_message (size_t allocate_len, const uint8_t *data, uint16_t data_length) +{ + assert(allocate_len >= data_length); + + struct RTPMessage *msg = calloc(sizeof(struct RTPMessage) + (allocate_len - sizeof(struct RTPHeader)), 1); + + msg->len = data_length - sizeof(struct RTPHeader); + memcpy(&msg->header, data, data_length); + + msg->header.sequnum = ntohs(msg->header.sequnum); + msg->header.timestamp = ntohl(msg->header.timestamp); + msg->header.ssrc = ntohl(msg->header.ssrc); + + msg->header.cpart = ntohs(msg->header.cpart); + msg->header.tlen = ntohs(msg->header.tlen); + + return msg; +} +int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) +{ + (void) m; + (void) friendnumber; + + RTPSession *session = object; + + data ++; + length--; + + if (!session || length < sizeof (struct RTPHeader)) { LOGGER_WARNING("No session or invalid length of received buffer!"); return -1; } - msg = msg_parse ( data + 1, length - 1 ); + const struct RTPHeader *header = (struct RTPHeader *) data; - if ( !msg ) { - LOGGER_WARNING("Could not parse message!"); + if (header->pt != session->payload_type % 128) { + LOGGER_WARNING("Invalid payload type with the session"); return -1; } - /* Check if message came in late */ - if ( check_late_message(session, msg) < 0 ) { /* Not late */ - session->rsequnum = msg->header->sequnum; - session->timestamp = msg->header->timestamp; + if (ntohs(header->cpart) >= ntohs(header->tlen)) { + /* Never allow this case to happen */ + return -1; } - queue_message(session, msg); + bwc_feed_avg(session->bwc, length); + + if (ntohs(header->tlen) == length - sizeof (struct RTPHeader)) { + /* The message is sent in single part */ - return 0; -} - -/** - * Allocate message and store data there - */ -RTPMessage *rtp_new_message ( RTPSession *session, const uint8_t *data, uint32_t length ) -{ - if ( !session ) { - LOGGER_WARNING("No session!"); - return NULL; - } - - uint8_t *from_pos; - RTPMessage *retu = calloc(1, sizeof (RTPMessage)); - - if ( !retu ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - return NULL; - } - - /* Sets header values and copies the extension header in retu */ - retu->header = build_header ( session ); /* It allocates memory and all */ - retu->ext_header = session->ext_header; - - - uint32_t total_length = length + retu->header->length + 1; - - retu->data[0] = session->prefix; - - if ( retu->ext_header ) { - total_length += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); - - from_pos = add_header ( retu->header, retu->data + 1 ); - from_pos = add_ext_header ( retu->ext_header, from_pos + 1 ); - } else { - from_pos = add_header ( retu->header, retu->data + 1 ); - } - - /* - * Parses the extension header into the message - * Of course if any - */ - - /* Appends data on to retu->data */ - memcpy ( from_pos, data, length ); - - retu->length = total_length; - - retu->next = NULL; - - return retu; -} - - - -int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ) -{ - RTPMessage *msg = rtp_new_message (session, data, length); - - if ( !msg ) return -1; - - int ret = send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length); - - if ( 0 != ret) { - LOGGER_WARNING("Failed to send full packet (len: %d)! error: %i", length, ret); - rtp_free_msg ( session, msg ); - return rtp_ErrorSending; - } - - /* Set sequ number */ - session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1; - rtp_free_msg ( session, msg ); - - return 0; -} - -void rtp_free_msg ( RTPSession *session, RTPMessage *msg ) -{ - if ( !session ) { - if ( msg->ext_header ) { - free ( msg->ext_header->table ); - free ( msg->ext_header ); + /* Only allow messages which have arrived in order; + * drop late messages + */ + if (chloss(session, header)) { + return 0; + } else { + /* Message is not late; pick up the latest parameters */ + session->rsequnum = ntohs(header->sequnum); + session->rtimestamp = ntohl(header->timestamp); } + + bwc_add_recv(session->bwc, length); + + /* Invoke processing of active multiparted message */ + if (session->mp) { + if (session->mcb) + session->mcb (session->cs, session->mp); + else + free(session->mp); + + session->mp = NULL; + } + + /* The message came in the allowed time; + * process it only if handler for the session is present. + */ + + if (!session->mcb) + return 0; + + return session->mcb (session->cs, new_message(length, data, length)); } else { - if ( msg->ext_header && session->ext_header != msg->ext_header ) { - free ( msg->ext_header->table ); - free ( msg->ext_header ); + /* The message is sent in multiple parts */ + + if (session->mp) { + /* There are 2 possible situations in this case: + * 1) being that we got the part of already processing message. + * 2) being that we got the part of a new/old message. + * + * We handle them differently as we only allow a single multiparted + * processing message + */ + + if (session->mp->header.sequnum == ntohs(header->sequnum) && + session->mp->header.timestamp == ntohl(header->timestamp)) { + /* First case */ + + /* Make sure we have enough allocated memory */ + if (session->mp->header.tlen - session->mp->len < length - sizeof(struct RTPHeader) || + session->mp->header.tlen <= ntohs(header->cpart)) { + /* There happened to be some corruption on the stream; + * continue wihtout this part + */ + return 0; + } + + memcpy(session->mp->data + ntohs(header->cpart), data + sizeof(struct RTPHeader), + length - sizeof(struct RTPHeader)); + + session->mp->len += length - sizeof(struct RTPHeader); + + bwc_add_recv(session->bwc, length); + + if (session->mp->len == session->mp->header.tlen) { + /* Received a full message; now push it for the further + * processing. + */ + if (session->mcb) + session->mcb (session->cs, session->mp); + else + free(session->mp); + + session->mp = NULL; + } + } else { + /* Second case */ + + if (session->mp->header.timestamp > ntohl(header->timestamp)) + /* The received message part is from the old message; + * discard it. + */ + return 0; + + /* Measure missing parts of the old message */ + bwc_add_lost(session->bwc, + (session->mp->header.tlen - session->mp->len) + + + /* Must account sizes of rtp headers too */ + ((session->mp->header.tlen - session->mp->len) / + MAX_CRYPTO_DATA_SIZE) * sizeof(struct RTPHeader) ); + + /* Push the previous message for processing */ + if (session->mcb) + session->mcb (session->cs, session->mp); + else + free(session->mp); + + session->mp = NULL; + goto NEW_MULTIPARTED; + } + } else { + /* In this case threat the message as if it was received in order + */ + + /* This is also a point for new multiparted messages */ +NEW_MULTIPARTED: + + /* Only allow messages which have arrived in order; + * drop late messages + */ + if (chloss(session, header)) { + return 0; + } else { + /* Message is not late; pick up the latest parameters */ + session->rsequnum = ntohs(header->sequnum); + session->rtimestamp = ntohl(header->timestamp); + } + + bwc_add_recv(session->bwc, length); + + /* Again, only store message if handler is present + */ + if (session->mcb) { + session->mp = new_message(ntohs(header->tlen) + sizeof(struct RTPHeader), data, length); + + /* Reposition data if necessary */ + if (ntohs(header->cpart)); + + memmove(session->mp->data + ntohs(header->cpart), session->mp->data, session->mp->len); + } } } - free ( msg->header ); - free ( msg ); -} - -RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ) -{ - RTPSession *retu = calloc(1, sizeof(RTPSession)); - - if ( !retu ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - return NULL; - } - - if ( -1 == m_callback_rtp_packet(messenger, friend_num, payload_type, rtp_handle_packet, retu)) { - LOGGER_ERROR("Error setting custom register handler for rtp session"); - free(retu); - return NULL; - } - - LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num); - - retu->version = RTP_VERSION; /* It's always 2 */ - retu->padding = 0; /* If some additional data is needed about the packet */ - retu->extension = 0; /* If extension to header is needed */ - retu->cc = 1; /* Amount of contributors */ - retu->csrc = NULL; /* Container */ - retu->ssrc = random_int(); - retu->marker = 0; - retu->payload_type = payload_type % 128; - - retu->dest = friend_num; - - retu->rsequnum = retu->sequnum = 0; - - retu->ext_header = NULL; /* When needed allocate */ - - - if ( !(retu->csrc = calloc(1, sizeof (uint32_t))) ) { - LOGGER_WARNING("Alloc failed! Program might misbehave!"); - free(retu); - return NULL; - } - - retu->csrc[0] = retu->ssrc; /* Set my ssrc to the list receive */ - - /* Also set payload type as prefix */ - retu->prefix = payload_type; - - /* - * - */ - return retu; -} - -void rtp_kill ( RTPSession *session, Messenger *messenger ) -{ - if ( !session ) return; - - m_callback_rtp_packet(messenger, session->dest, session->prefix, NULL, NULL); - - free ( session->ext_header ); - free ( session->csrc ); - - LOGGER_DEBUG("Terminated RTP session: %p", session); - - /* And finally free session */ - free ( session ); - + return 0; } diff --git a/toxav/rtp.h b/toxav/rtp.h index c98840acc..0393ac273 100644 --- a/toxav/rtp.h +++ b/toxav/rtp.h @@ -1,6 +1,6 @@ /** rtp.h * - * Copyright (C) 2013 Tox project All Rights Reserved. + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * * This file is part of Tox. * @@ -19,109 +19,91 @@ * */ -#ifndef __TOXRTP -#define __TOXRTP - -#define RTP_VERSION 2 -#include -// #include +#ifndef RTP_H +#define RTP_H +#include "bwcontroler.h" #include "../toxcore/Messenger.h" - -#define MAX_SEQU_NUM 65535 -#define MAX_RTP_SIZE 65535 - -typedef enum { - rtp_ErrorSending = -40 -} RTPError; -/** - * Standard rtp header - */ -typedef struct _RTPHeader { - uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */ - uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */ - uint16_t sequnum; /* Sequence Number */ - uint32_t timestamp; /* Timestamp */ - uint32_t ssrc; /* SSRC */ - uint32_t csrc[16]; /* CSRC's table */ - uint32_t length; /* Length of the header in payload string. */ - -} RTPHeader; +#include "stdbool.h" /** - * Standard rtp extension header. + * Payload type identifier. Also used as rtp callback prefix. */ -typedef struct _RTPExtHeader { - uint16_t type; /* Extension profile */ - uint16_t length; /* Number of extensions */ - uint32_t *table; /* Extension's table */ +enum { + rtp_TypeAudio = 192, + rtp_TypeVideo, +}; -} RTPExtHeader; +struct RTPHeader { + /* Standard RTP header */ +#ifndef WORDS_BIGENDIAN + uint16_t cc: 4; /* Contributing sources count */ + uint16_t xe: 1; /* Extra header */ + uint16_t pe: 1; /* Padding */ + uint16_t ve: 2; /* Version */ -/** - * Standard rtp message. - */ -typedef struct _RTPMessage { - RTPHeader *header; - RTPExtHeader *ext_header; + uint16_t pt: 7; /* Payload type */ + uint16_t ma: 1; /* Marker */ +#else + uint16_t ve: 2; /* Version */ + uint16_t pe: 1; /* Padding */ + uint16_t xe: 1; /* Extra header */ + uint16_t cc: 4; /* Contributing sources count */ - uint8_t data[MAX_RTP_SIZE]; - uint32_t length; + uint16_t ma: 1; /* Marker */ + uint16_t pt: 7; /* Payload type */ +#endif - struct _RTPMessage *next; -} RTPMessage; + uint16_t sequnum; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[16]; + + /* Non-standard TOX-specific fields */ + uint16_t cpart;/* Data offset of the current part */ + uint16_t tlen; /* Total message lenght */ +} __attribute__ ((packed)); + +/* Check alignment */ +typedef char __fail_if_misaligned [ sizeof(struct RTPHeader) == 80 ? 1 : -1 ]; + +struct RTPMessage { + uint16_t len; + + struct RTPHeader header; + uint8_t data[]; +} __attribute__ ((packed)); + +/* Check alignment */ +typedef char __fail_if_misaligned [ sizeof(struct RTPMessage) == 82 ? 1 : -1 ]; /** * RTP control session. */ -typedef struct _RTPSession { - uint8_t version; - uint8_t padding; - uint8_t extension; - uint8_t cc; - uint8_t marker; - uint8_t payload_type; - uint16_t sequnum; /* Set when sending */ - uint16_t rsequnum; /* Check when recving msg */ - uint32_t timestamp; - uint32_t ssrc; - uint32_t *csrc; +typedef struct { + uint8_t payload_type; + uint16_t sequnum; /* Sending sequence number */ + uint16_t rsequnum; /* Receiving sequence number */ + uint32_t rtimestamp; + uint32_t ssrc; - /* If some additional data must be sent via message - * apply it here. Only by allocating this member you will be - * automatically placing it within a message. - */ - RTPExtHeader *ext_header; + struct RTPMessage *mp; /* Expected parted message */ - /* Msg prefix for core to know when recving */ - uint8_t prefix; - - int dest; - - struct _CSSession *cs; + Messenger *m; + uint32_t friend_number; + BWControler *bwc; + void *cs; + int (*mcb) (void *, struct RTPMessage *msg); } RTPSession; -/** - * Must be called before calling any other rtp function. - */ -RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ); -/** - * Terminate the session. - */ -void rtp_kill ( RTPSession *session, Messenger *messenger ); +RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friend_num, + BWControler *bwc, void *cs, + int (*mcb) (void *, struct RTPMessage *)); +void rtp_kill (RTPSession *session); +int rtp_allow_receiving (RTPSession *session); +int rtp_stop_receiving (RTPSession *session); +int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length); -/** - * Sends msg to _RTPSession::dest - */ -int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ); - -/** - * Dealloc msg. - */ -void rtp_free_msg ( RTPSession *session, RTPMessage *msg ); - - - -#endif /* __TOXRTP */ +#endif /* RTP_H */ diff --git a/toxav/toxav.c b/toxav/toxav.c index a51ec5e33..6a17f55dd 100644 --- a/toxav/toxav.c +++ b/toxav/toxav.c @@ -1,6 +1,6 @@ /** toxav.c * - * Copyright (C) 2013 Tox project All Rights Reserved. + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * * This file is part of Tox. * @@ -23,15 +23,10 @@ #include "config.h" #endif /* HAVE_CONFIG_H */ -#define TOX_DEFINED -typedef struct Messenger Tox; - -#define _GNU_SOURCE /* implicit declaration warning */ - -#include "codec.h" #include "msi.h" -#include "group.h" +#include "rtp.h" +#include "../toxcore/Messenger.h" #include "../toxcore/logger.h" #include "../toxcore/util.h" @@ -39,659 +34,1220 @@ typedef struct Messenger Tox; #include #include -/* Assume 24 fps*/ #define MAX_ENCODE_TIME_US ((1000 / 24) * 1000) -/* true if invalid call index */ -#define CALL_INVALID_INDEX(idx, max) (idx < 0 || idx >= max) +typedef struct ToxAVCall_s { + ToxAV *av; -const ToxAvCSettings av_DefaultSettings = { - av_TypeAudio, + pthread_mutex_t mutex_audio[1]; + PAIR(RTPSession *, ACSession *) audio; - 500, - 1280, - 720, + pthread_mutex_t mutex_video[1]; + PAIR(RTPSession *, VCSession *) video; - 32000, - 20, - 48000, - 1 -}; + BWControler *bwc; -static const uint32_t jbuf_capacity = 6; -static const uint8_t audio_index = 0, video_index = 1; + bool active; + MSICall *msi_call; + uint32_t friend_number; + + uint32_t audio_bit_rate; /* Sending audio bit rate */ + uint32_t video_bit_rate; /* Sending video bit rate */ + + /** Required for monitoring changes in states */ + uint8_t previous_self_capabilities; -typedef struct _ToxAvCall { pthread_mutex_t mutex[1]; - pthread_mutex_t mutex_encoding_audio[1]; - pthread_mutex_t mutex_encoding_video[1]; - pthread_mutex_t mutex_do[1]; - RTPSession *crtps[2]; /** Audio is first and video is second */ - CSSession *cs; - _Bool active; -} ToxAvCall; -struct _ToxAv { - Messenger *messenger; - MSISession *msi_session; /** Main msi session */ - ToxAvCall *calls; /** Per-call params */ - uint32_t max_calls; + struct ToxAVCall_s *prev; + struct ToxAVCall_s *next; +} ToxAVCall; - PAIR(ToxAvAudioCallback, void *) acb; - PAIR(ToxAvVideoCallback, void *) vcb; +struct ToxAV { + Messenger *m; + MSISession *msi; - /* Decode time measure */ - int32_t dectmsscount; /** Measure count */ - int32_t dectmsstotal; /** Last cycle total */ - int32_t avgdectms; /** Average decoding time in ms */ + /* Two-way storage: first is array of calls and second is list of calls with head and tail */ + ToxAVCall **calls; + uint32_t calls_tail; + uint32_t calls_head; + pthread_mutex_t mutex[1]; + + PAIR(toxav_call_cb *, void *) ccb; /* Call callback */ + PAIR(toxav_call_state_cb *, void *) scb; /* Call state callback */ + PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ + PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ + PAIR(toxav_bit_rate_status_cb *, void *) bcb; /* Bit rate control callback */ + + /** Decode time measures */ + int32_t dmssc; /** Measure count */ + int32_t dmsst; /** Last cycle total */ + int32_t dmssa; /** Average decoding time in ms */ + + uint32_t interval; /** Calculated interval */ }; -static const MSICSettings *msicsettings_cast (const ToxAvCSettings *from) +void callback_bwc (BWControler *bwc, uint32_t friend_number, float loss, void *user_data); + +int callback_invite(void *toxav_inst, MSICall *call); +int callback_start(void *toxav_inst, MSICall *call); +int callback_end(void *toxav_inst, MSICall *call); +int callback_error(void *toxav_inst, MSICall *call); +int callback_capabilites(void *toxav_inst, MSICall *call); + +bool audio_bit_rate_invalid(uint32_t bit_rate); +bool video_bit_rate_invalid(uint32_t bit_rate); +bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state); +ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error); +ToxAVCall *call_get(ToxAV *av, uint32_t friend_number); +ToxAVCall *call_remove(ToxAVCall *call); +bool call_prepare_transmission(ToxAVCall *call); +void call_kill_transmission(ToxAVCall *call); + +uint32_t toxav_version_major(void) { - assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); - return (const MSICSettings *) from; + return 0; } - -static const ToxAvCSettings *toxavcsettings_cast (const MSICSettings *from) +uint32_t toxav_version_minor(void) { - assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); - return (const ToxAvCSettings *) from; - + return 0; } - -ToxAv *toxav_new( Tox *messenger, int32_t max_calls) +uint32_t toxav_version_patch(void) { - ToxAv *av = calloc ( sizeof(ToxAv), 1); + return 0; +} +bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) +{ + (void)major; + (void)minor; + (void)patch; + + return 1; +} +ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error) +{ + TOXAV_ERR_NEW rc = TOXAV_ERR_NEW_OK; + ToxAV *av = NULL; + + if (tox == NULL) { + rc = TOXAV_ERR_NEW_NULL; + goto END; + } + + if (((Messenger *)tox)->msi_packet) { + rc = TOXAV_ERR_NEW_MULTIPLE; + goto END; + } + + av = calloc (sizeof(ToxAV), 1); if (av == NULL) { LOGGER_WARNING("Allocation failed!"); - return NULL; + rc = TOXAV_ERR_NEW_MALLOC; + goto END; } - av->messenger = (Messenger *)messenger; - av->msi_session = msi_new(av->messenger, max_calls); - av->msi_session->agent_handler = av; - av->calls = calloc(sizeof(ToxAvCall), max_calls); - av->max_calls = max_calls; + if (create_recursive_mutex(av->mutex) != 0) { + LOGGER_WARNING("Mutex creation failed!"); + rc = TOXAV_ERR_NEW_MALLOC; + goto END; + } - unsigned int i; + av->m = (Messenger *)tox; + av->msi = msi_new(av->m); - for (i = 0; i < max_calls; ++i) { - if (create_recursive_mutex(av->calls[i].mutex) != 0 ) { - LOGGER_WARNING("Failed to init call(%u) mutex!", i); - msi_kill(av->msi_session); + if (av->msi == NULL) { + pthread_mutex_destroy(av->mutex); + rc = TOXAV_ERR_NEW_MALLOC; + goto END; + } - free(av->calls); - free(av); - return NULL; - } + av->interval = 200; + av->msi->av = av; + + msi_register_callback(av->msi, callback_invite, msi_OnInvite); + msi_register_callback(av->msi, callback_start, msi_OnStart); + msi_register_callback(av->msi, callback_end, msi_OnEnd); + msi_register_callback(av->msi, callback_error, msi_OnError); + msi_register_callback(av->msi, callback_error, msi_OnPeerTimeout); + msi_register_callback(av->msi, callback_capabilites, msi_OnCapabilities); + +END: + + if (error) + *error = rc; + + if (rc != TOXAV_ERR_NEW_OK) { + free(av); + av = NULL; } return av; } - -void toxav_kill ( ToxAv *av ) +void toxav_kill(ToxAV *av) { - uint32_t i; + if (av == NULL) + return; - for (i = 0; i < av->max_calls; i ++) { - if ( av->calls[i].crtps[audio_index] ) - rtp_kill(av->calls[i].crtps[audio_index], av->msi_session->messenger_handle); - - - if ( av->calls[i].crtps[video_index] ) - rtp_kill(av->calls[i].crtps[video_index], av->msi_session->messenger_handle); - - if ( av->calls[i].cs ) - cs_kill(av->calls[i].cs); - - pthread_mutex_destroy(av->calls[i].mutex); + pthread_mutex_lock(av->mutex); + + /* To avoid possible deadlocks */ + while (av->msi && msi_kill(av->msi) != 0) { + pthread_mutex_unlock(av->mutex); + pthread_mutex_lock(av->mutex); } - msi_kill(av->msi_session); + /* Msi kill will hang up all calls so just clean these calls */ + if (av->calls) { + ToxAVCall *it = call_get(av, av->calls_head); - free(av->calls); + while (it) { + call_kill_transmission(it); + it = call_remove(it); /* This will eventually free av->calls */ + } + } + + pthread_mutex_unlock(av->mutex); + pthread_mutex_destroy(av->mutex); + free(av); } - -uint32_t toxav_do_interval(ToxAv *av) +Tox *toxav_get_tox(const ToxAV *av) { - int i = 0; - uint32_t rc = 200 + av->avgdectms; /* Return 200 if no call is active */ - - for (; i < av->max_calls; i ++) { - pthread_mutex_lock(av->calls[i].mutex); - - if (av->calls[i].active) { - /* This should work. Video payload will always come in greater intervals */ - rc = MIN(av->calls[i].cs->audio_decoder_frame_duration, rc); - } - - pthread_mutex_unlock(av->calls[i].mutex); - } - - return rc < av->avgdectms ? 0 : rc - av->avgdectms; + return (Tox *) av->m; } - -void toxav_do(ToxAv *av) +uint32_t toxav_iteration_interval(const ToxAV *av) { - msi_do(av->msi_session); + /* If no call is active interval is 200 */ + return av->calls ? av->interval : 200; +} +void toxav_iterate(ToxAV *av) +{ + pthread_mutex_lock(av->mutex); + + if (av->calls == NULL) { + pthread_mutex_unlock(av->mutex); + return; + } uint64_t start = current_time_monotonic(); + int32_t rc = 500; - uint32_t i = 0; + ToxAVCall *i = av->calls[av->calls_head]; - for (; i < av->max_calls; i ++) { - pthread_mutex_lock(av->calls[i].mutex); + for (; i; i = i->next) { + if (i->active) { + pthread_mutex_lock(i->mutex); + pthread_mutex_unlock(av->mutex); - if (av->calls[i].active) { - pthread_mutex_lock(av->calls[i].mutex_do); - pthread_mutex_unlock(av->calls[i].mutex); - cs_do(av->calls[i].cs); - pthread_mutex_unlock(av->calls[i].mutex_do); - } else { - pthread_mutex_unlock(av->calls[i].mutex); + ac_iterate(i->audio.second); + vc_iterate(i->video.second); + + if (i->msi_call->self_capabilities & msi_CapRAudio && + i->msi_call->peer_capabilities & msi_CapSAudio) + rc = MIN(i->audio.second->lp_frame_duration, rc); + + if (i->msi_call->self_capabilities & msi_CapRVideo && + i->msi_call->peer_capabilities & msi_CapSVideo) + rc = MIN(i->video.second->lcfd, (uint32_t) rc); + + uint32_t fid = i->friend_number; + + pthread_mutex_unlock(i->mutex); + pthread_mutex_lock(av->mutex); + + /* In case this call is popped from container stop iteration */ + if (call_get(av, fid) != i) + break; } } - uint64_t end = current_time_monotonic(); + pthread_mutex_unlock(av->mutex); - /* TODO maybe use variable for sizes */ - av->dectmsstotal += end - start; + av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa); + av->dmsst += current_time_monotonic() - start; - if (++av->dectmsscount == 3) { - av->avgdectms = av->dectmsstotal / 3 + 2 /* NOTE Magic Offset */; - av->dectmsscount = 0; - av->dectmsstotal = 0; + if (++av->dmssc == 3) { + av->dmssa = av->dmsst / 3 + 5 /* NOTE Magic Offset for precission */; + av->dmssc = 0; + av->dmsst = 0; } } - -void toxav_register_callstate_callback ( ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata ) +bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + TOXAV_ERR_CALL *error) { - msi_register_callback(av->msi_session, (MSICallbackType)cb, (MSICallbackID) id, userdata); -} - -void toxav_register_audio_callback(ToxAv *av, ToxAvAudioCallback cb, void *userdata) -{ - av->acb.first = cb; - av->acb.second = userdata; -} - -void toxav_register_video_callback(ToxAv *av, ToxAvVideoCallback cb, void *userdata) -{ - av->vcb.first = cb; - av->vcb.second = userdata; -} - -int toxav_call (ToxAv *av, - int32_t *call_index, - int user, - const ToxAvCSettings *csettings, - int ringing_seconds ) -{ - return msi_invite(av->msi_session, call_index, msicsettings_cast(csettings), ringing_seconds * 1000, user); -} - -int toxav_hangup ( ToxAv *av, int32_t call_index ) -{ - return msi_hangup(av->msi_session, call_index); -} - -int toxav_answer ( ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ) -{ - return msi_answer(av->msi_session, call_index, msicsettings_cast(csettings)); -} - -int toxav_reject ( ToxAv *av, int32_t call_index, const char *reason ) -{ - return msi_reject(av->msi_session, call_index, reason); -} - -int toxav_cancel ( ToxAv *av, int32_t call_index, int peer_id, const char *reason ) -{ - return msi_cancel(av->msi_session, call_index, peer_id, reason); -} - -int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings) -{ - return msi_change_csettings(av->msi_session, call_index, msicsettings_cast(csettings)); -} - -int toxav_stop_call ( ToxAv *av, int32_t call_index ) -{ - return msi_stopcall(av->msi_session, call_index); -} - -int toxav_prepare_transmission ( ToxAv *av, int32_t call_index, int support_video ) -{ - if ( !av->msi_session || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || - !av->msi_session->calls[call_index] || !av->msi_session->calls[call_index]->csettings_peer) { - LOGGER_ERROR("Error while starting RTP session: invalid call!\n"); - return av_ErrorNoCall; + TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK; + + pthread_mutex_lock(av->mutex); + + if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) + || (video_bit_rate && video_bit_rate_invalid(video_bit_rate))) { + rc = TOXAV_ERR_CALL_INVALID_BIT_RATE; + goto END; } - ToxAvCall *call = &av->calls[call_index]; + ToxAVCall *call = call_new(av, friend_number, error); - pthread_mutex_lock(call->mutex); + if (call == NULL) { + rc = TOXAV_ERR_CALL_MALLOC; + goto END; + } + + call->audio_bit_rate = audio_bit_rate; + call->video_bit_rate = video_bit_rate; + + call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; + + call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; + call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; + + if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) { + call_remove(call); + rc = TOXAV_ERR_CALL_SYNC; + goto END; + } + + call->msi_call->av_call = call; + +END: + pthread_mutex_unlock(av->mutex); + + if (error) + *error = rc; + + return rc == TOXAV_ERR_CALL_OK; +} +void toxav_callback_call(ToxAV *av, toxav_call_cb *function, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->ccb.first = function; + av->ccb.second = user_data; + pthread_mutex_unlock(av->mutex); +} +bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, + TOXAV_ERR_ANSWER *error) +{ + pthread_mutex_lock(av->mutex); + + TOXAV_ERR_ANSWER rc = TOXAV_ERR_ANSWER_OK; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND; + goto END; + } + + if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) + || (video_bit_rate && video_bit_rate_invalid(video_bit_rate)) + ) { + rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE; + goto END; + } + + ToxAVCall *call = call_get(av, friend_number); + + if (call == NULL) { + rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; + goto END; + } + + if (!call_prepare_transmission(call)) { + rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION; + goto END; + } + + call->audio_bit_rate = audio_bit_rate; + call->video_bit_rate = video_bit_rate; + + call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; + + call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; + call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; + + if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0) + rc = TOXAV_ERR_ANSWER_SYNC; + +END: + pthread_mutex_unlock(av->mutex); + + if (error) + *error = rc; + + return rc == TOXAV_ERR_ANSWER_OK; +} +void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *function, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->scb.first = function; + av->scb.second = user_data; + pthread_mutex_unlock(av->mutex); +} +bool toxav_call_control(ToxAV *av, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error) +{ + pthread_mutex_lock(av->mutex); + TOXAV_ERR_CALL_CONTROL rc = TOXAV_ERR_CALL_CONTROL_OK; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; + goto END; + } + + ToxAVCall *call = call_get(av, friend_number); + + if (call == NULL || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) { + rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; + goto END; + } + + switch (control) { + case TOXAV_CALL_CONTROL_RESUME: { + /* Only act if paused and had media transfer active before */ + if (call->msi_call->self_capabilities == 0 && + call->previous_self_capabilities) { + + if (msi_change_capabilities(call->msi_call, + call->previous_self_capabilities) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_allow_receiving(call->audio.first); + rtp_allow_receiving(call->video.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + + case TOXAV_CALL_CONTROL_PAUSE: { + /* Only act if not already paused */ + if (call->msi_call->self_capabilities) { + call->previous_self_capabilities = call->msi_call->self_capabilities; + + if (msi_change_capabilities(call->msi_call, 0) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_stop_receiving(call->audio.first); + rtp_stop_receiving(call->video.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + + case TOXAV_CALL_CONTROL_CANCEL: { + /* Hang up */ + pthread_mutex_lock(call->mutex); + if (msi_hangup(call->msi_call) != 0) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + pthread_mutex_unlock(call->mutex); + goto END; + } + + call->msi_call = NULL; + pthread_mutex_unlock(call->mutex); + + /* No mather the case, terminate the call */ + call_kill_transmission(call); + call_remove(call); + } + break; + + case TOXAV_CALL_CONTROL_MUTE_AUDIO: { + if (call->msi_call->self_capabilities & msi_CapRAudio) { + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities ^ msi_CapRAudio) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_stop_receiving(call->audio.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + + case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: { + if (call->msi_call->self_capabilities ^ msi_CapRAudio) { + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | msi_CapRAudio) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_allow_receiving(call->audio.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + + case TOXAV_CALL_CONTROL_HIDE_VIDEO: { + if (call->msi_call->self_capabilities & msi_CapRVideo) { + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities ^ msi_CapRVideo) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_stop_receiving(call->video.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + + case TOXAV_CALL_CONTROL_SHOW_VIDEO: { + if (call->msi_call->self_capabilities ^ msi_CapRVideo) { + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | msi_CapRVideo) == -1) { + rc = TOXAV_ERR_CALL_CONTROL_SYNC; + goto END; + } + + rtp_allow_receiving(call->audio.first); + } else { + rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; + goto END; + } + } + break; + } + +END: + pthread_mutex_unlock(av->mutex); + + if (error) + *error = rc; + + return rc == TOXAV_ERR_CALL_CONTROL_OK; +} +bool toxav_bit_rate_set(ToxAV *av, uint32_t friend_number, int32_t audio_bit_rate, + int32_t video_bit_rate, TOXAV_ERR_BIT_RATE_SET *error) +{ + TOXAV_ERR_BIT_RATE_SET rc = TOXAV_ERR_BIT_RATE_SET_OK; + ToxAVCall *call; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; + goto END; + } + + if (audio_bit_rate > 0 && audio_bit_rate_invalid(audio_bit_rate)) { + rc = TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE; + goto END; + } + + if (video_bit_rate > 0 && video_bit_rate_invalid(video_bit_rate)) { + rc = TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE; + goto END; + } + + pthread_mutex_lock(av->mutex); + call = call_get(av, friend_number); + + if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; + goto END; + } + + if (audio_bit_rate >= 0) { + LOGGER_DEBUG("Setting new audio bitrate to: %d", audio_bit_rate); + + if (call->audio_bit_rate == audio_bit_rate) { + LOGGER_DEBUG("Audio bitrate already set to: %d", audio_bit_rate); + } else if (audio_bit_rate == 0) { + LOGGER_DEBUG("Turned off audio sending"); + if (msi_change_capabilities(call->msi_call, call->msi_call-> + self_capabilities ^ msi_CapSAudio) != 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto END; + } + /* Audio sending is turned off; notify peer */ + call->audio_bit_rate = 0; + } else { + pthread_mutex_lock(call->mutex); + if (call->audio_bit_rate == 0) { + LOGGER_DEBUG("Turned on audio sending"); + /* The audio has been turned off before this */ + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | msi_CapSAudio) != 0) { + pthread_mutex_unlock(call->mutex); + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto END; + } + } else + LOGGER_DEBUG("Set new audio bit rate %d", audio_bit_rate); + call->audio_bit_rate = audio_bit_rate; + pthread_mutex_unlock(call->mutex); + } + } + + if (video_bit_rate >= 0) { + LOGGER_DEBUG("Setting new video bitrate to: %d", video_bit_rate); + + if (call->video_bit_rate == video_bit_rate) { + LOGGER_DEBUG("Video bitrate already set to: %d", video_bit_rate); + } else if (video_bit_rate == 0) { + LOGGER_DEBUG("Turned off video sending"); + /* Video sending is turned off; notify peer */ + if (msi_change_capabilities(call->msi_call, call->msi_call-> + self_capabilities ^ msi_CapSVideo) != 0) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto END; + } + call->video_bit_rate = 0; + } else { + pthread_mutex_lock(call->mutex); + if (call->video_bit_rate == 0) { + LOGGER_DEBUG("Turned on video sending"); + /* The video has been turned off before this */ + if (msi_change_capabilities(call->msi_call, call-> + msi_call->self_capabilities | msi_CapSVideo) != 0) { + pthread_mutex_unlock(call->mutex); + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_BIT_RATE_SET_SYNC; + goto END; + } + } else + LOGGER_DEBUG("Set new video bit rate %d", video_bit_rate); + call->video_bit_rate = video_bit_rate; + pthread_mutex_unlock(call->mutex); + } + } + + pthread_mutex_unlock(av->mutex); +END: + if (error) + *error = rc; + + return rc == TOXAV_ERR_BIT_RATE_SET_OK; +} +void toxav_callback_bit_rate_status(ToxAV *av, toxav_bit_rate_status_cb *function, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->bcb.first = function; + av->bcb.second = user_data; + pthread_mutex_unlock(av->mutex); +} +bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error) +{ + TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; + ToxAVCall *call; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; + goto END; + } + + if (pthread_mutex_trylock(av->mutex) != 0) { + rc = TOXAV_ERR_SEND_FRAME_SYNC; + goto END; + } + + call = call_get(av, friend_number); + + if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; + goto END; + } + + if (call->audio_bit_rate == 0 || + !(call->msi_call->self_capabilities & msi_CapSAudio) || + !(call->msi_call->peer_capabilities & msi_CapRAudio)) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; + goto END; + } + + pthread_mutex_lock(call->mutex_audio); + pthread_mutex_unlock(av->mutex); + + if (pcm == NULL) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_NULL; + goto END; + } + + if (channels > 2) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto END; + } + + { /* Encode and send */ + if (ac_reconfigure_encoder(call->audio.second, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto END; + } + + uint8_t dest[sample_count + sizeof(sampling_rate)]; /* This is more than enough always */ + + sampling_rate = htonl(sampling_rate); + memcpy(dest, &sampling_rate, sizeof(sampling_rate)); + int vrc = opus_encode(call->audio.second->encoder, pcm, sample_count, + dest + sizeof(sampling_rate), sizeof(dest) - sizeof(sampling_rate)); + + if (vrc < 0) { + LOGGER_WARNING("Failed to encode frame %s", opus_strerror(vrc)); + pthread_mutex_unlock(call->mutex_audio); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto END; + } + + if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate)) != 0) { + LOGGER_WARNING("Failed to send audio packet"); + rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; + } + } + + + pthread_mutex_unlock(call->mutex_audio); + +END: + if (error) + *error = rc; + + return rc == TOXAV_ERR_SEND_FRAME_OK; +} +bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, + const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error) +{ + TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; + ToxAVCall *call; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; + goto END; + } + + if (pthread_mutex_trylock(av->mutex) != 0) { + rc = TOXAV_ERR_SEND_FRAME_SYNC; + goto END; + } + + call = call_get(av, friend_number); + + if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; + goto END; + } + + if (call->video_bit_rate == 0 || + !(call->msi_call->self_capabilities & msi_CapSVideo) || + !(call->msi_call->peer_capabilities & msi_CapRVideo)) { + pthread_mutex_unlock(av->mutex); + rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; + goto END; + } + + pthread_mutex_lock(call->mutex_video); + pthread_mutex_unlock(av->mutex); + + if (y == NULL || u == NULL || v == NULL) { + pthread_mutex_unlock(call->mutex_video); + rc = TOXAV_ERR_SEND_FRAME_NULL; + goto END; + } + + if (vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height) != 0) { + pthread_mutex_unlock(call->mutex_video); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto END; + } + + { /* Encode */ + vpx_image_t img; + img.w = img.h = img.d_w = img.d_h = 0; + vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); + + /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." + * http://fourcc.org/yuv.php#IYUV + */ + memcpy(img.planes[VPX_PLANE_Y], y, width * height); + memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); + memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); + + int vrc = vpx_codec_encode(call->video.second->encoder, &img, + call->video.second->frame_counter, 1, 0, MAX_ENCODE_TIME_US); + + vpx_img_free(&img); + + if (vrc != VPX_CODEC_OK) { + pthread_mutex_unlock(call->mutex_video); + LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(vrc)); + rc = TOXAV_ERR_SEND_FRAME_INVALID; + goto END; + } + } + + ++call->video.second->frame_counter; + + { /* Send frames */ + vpx_codec_iter_t iter = NULL; + const vpx_codec_cx_pkt_t *pkt; + + while ((pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter))) { + if (pkt->kind == VPX_CODEC_CX_FRAME_PKT && + rtp_send_data(call->video.first, pkt->data.frame.buf, pkt->data.frame.sz) < 0) { + + pthread_mutex_unlock(call->mutex_video); + LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno)); + rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; + goto END; + } + } + } + + pthread_mutex_unlock(call->mutex_video); + +END: + if (error) + *error = rc; + + return rc == TOXAV_ERR_SEND_FRAME_OK; +} +void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *function, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->acb.first = function; + av->acb.second = user_data; + pthread_mutex_unlock(av->mutex); +} +void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *function, void *user_data) +{ + pthread_mutex_lock(av->mutex); + av->vcb.first = function; + av->vcb.second = user_data; + pthread_mutex_unlock(av->mutex); +} + + +/******************************************************************************* + * + * :: Internal + * + ******************************************************************************/ +void callback_bwc(BWControler* bwc, uint32_t friend_number, float loss, void* user_data) +{ + /* Callback which is called when the internal measure mechanism reported packet loss. + * We report suggested lowered bitrate to an app. If app is sending both audio and video, + * we will report lowered bitrate for video only because in that case video probably + * takes more than 90% bandwidth. Otherwise, we report lowered bitrate on audio. + * The application may choose to disable video totally if the stream is too bad. + */ + + ToxAVCall* call = user_data; + assert(call); + + LOGGER_DEBUG("Reported loss of %f%%", loss*100); + + if (loss < .01f) + return; + + pthread_mutex_lock(call->av->mutex); + if (!call->av->bcb.first) { + pthread_mutex_unlock(call->av->mutex); + LOGGER_WARNING("No callback to report loss on"); + return; + } + + if (call->video_bit_rate) + (*call->av->bcb.first) (call->av, friend_number, call->audio_bit_rate, + call->video_bit_rate - (call->video_bit_rate * loss), + call->av->bcb.second); + else if (call->audio_bit_rate) + (*call->av->bcb.first) (call->av, friend_number, + call->audio_bit_rate - (call->audio_bit_rate * loss), + 0, call->av->bcb.second); + + pthread_mutex_unlock(call->av->mutex); +} +int callback_invite(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = toxav_inst; + pthread_mutex_lock(toxav->mutex); + + ToxAVCall *av_call = call_new(toxav, call->friend_number, NULL); + + if (av_call == NULL) { + LOGGER_WARNING("Failed to initialize call..."); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + call->av_call = av_call; + av_call->msi_call = call; + + if (toxav->ccb.first) + toxav->ccb.first(toxav, call->friend_number, call->peer_capabilities & msi_CapSAudio, + call->peer_capabilities & msi_CapSVideo, toxav->ccb.second); + else { + /* No handler to capture the call request, send failure */ + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +int callback_start(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = toxav_inst; + pthread_mutex_lock(toxav->mutex); + + ToxAVCall *av_call = call_get(toxav, call->friend_number); + + if (av_call == NULL) { + /* Should this ever happen? */ + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + if (!call_prepare_transmission(av_call)) { + callback_error(toxav_inst, call); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) { + callback_error(toxav_inst, call); + pthread_mutex_unlock(toxav->mutex); + return -1; + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +int callback_end(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = toxav_inst; + pthread_mutex_lock(toxav->mutex); + + invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_FINISHED); + + if (call->av_call) { + call_kill_transmission(call->av_call); + call_remove(call->av_call); + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +int callback_error(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = toxav_inst; + pthread_mutex_lock(toxav->mutex); + + invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR); + + if (call->av_call) { + call_kill_transmission(call->av_call); + call_remove(call->av_call); + } + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +int callback_capabilites(void *toxav_inst, MSICall *call) +{ + ToxAV *toxav = toxav_inst; + pthread_mutex_lock(toxav->mutex); + + if (call->peer_capabilities & msi_CapSAudio) + rtp_allow_receiving(((ToxAVCall *)call->av_call)->audio.first); + else + rtp_stop_receiving(((ToxAVCall *)call->av_call)->audio.first); + + if (call->peer_capabilities & msi_CapSVideo) + rtp_allow_receiving(((ToxAVCall *)call->av_call)->video.first); + else + rtp_stop_receiving(((ToxAVCall *)call->av_call)->video.first); + + invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities); + + pthread_mutex_unlock(toxav->mutex); + return 0; +} +bool audio_bit_rate_invalid(uint32_t bit_rate) +{ + /* Opus RFC 6716 section-2.1.1 dictates the following: + * Opus supports all bit rates from 6 kbit/s to 510 kbit/s. + */ + return bit_rate < 6 || bit_rate > 510; +} +bool video_bit_rate_invalid(uint32_t bit_rate) +{ + (void) bit_rate; + /* TODO: If anyone knows the answer to this one please fill it up */ + return false; +} +bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state) +{ + if (av->scb.first) + av->scb.first(av, friend_number, state, av->scb.second); + else + return false; + + return true; +} +ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error) +{ + /* Assumes mutex locked */ + TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK; + ToxAVCall *call = NULL; + + if (m_friend_exists(av->m, friend_number) == 0) { + rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND; + goto END; + } + + if (m_get_friend_connectionstatus(av->m, friend_number) < 1) { + rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED; + goto END; + } + + if (call_get(av, friend_number) != NULL) { + rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL; + goto END; + } + + + call = calloc(sizeof(ToxAVCall), 1); + + if (call == NULL) { + rc = TOXAV_ERR_CALL_MALLOC; + goto END; + } + + call->av = av; + call->friend_number = friend_number; + + if (av->calls == NULL) { /* Creating */ + av->calls = calloc (sizeof(ToxAVCall *), friend_number + 1); + + if (av->calls == NULL) { + free(call); + call = NULL; + rc = TOXAV_ERR_CALL_MALLOC; + goto END; + } + + av->calls_tail = av->calls_head = friend_number; + + } else if (av->calls_tail < friend_number) { /* Appending */ + void *tmp = realloc(av->calls, sizeof(ToxAVCall *) * (friend_number + 1)); + + if (tmp == NULL) { + free(call); + call = NULL; + rc = TOXAV_ERR_CALL_MALLOC; + goto END; + } + + av->calls = tmp; + + /* Set fields in between to null */ + uint32_t i = av->calls_tail + 1; + + for (; i < friend_number; i ++) + av->calls[i] = NULL; + + call->prev = av->calls[av->calls_tail]; + av->calls[av->calls_tail]->next = call; + + av->calls_tail = friend_number; + + } else if (av->calls_head > friend_number) { /* Inserting at front */ + call->next = av->calls[av->calls_head]; + av->calls[av->calls_head]->prev = call; + av->calls_head = friend_number; + } + + av->calls[friend_number] = call; + +END: + + if (error) + *error = rc; + + return call; +} +ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) +{ + /* Assumes mutex locked */ + if (av->calls == NULL || av->calls_tail < friend_number) + return NULL; + + return av->calls[friend_number]; +} +ToxAVCall *call_remove(ToxAVCall *call) +{ + if (call == NULL) + return NULL; + + uint32_t friend_number = call->friend_number; + ToxAV *av = call->av; + + ToxAVCall *prev = call->prev; + ToxAVCall *next = call->next; + + /* Set av call in msi to NULL in order to know if call if ToxAVCall is + * removed from the msi call. + */ + if (call->msi_call) { + call->msi_call->av_call = NULL; + } + + free(call); + + if (prev) + prev->next = next; + else if (next) + av->calls_head = next->friend_number; + else goto CLEAR; + + if (next) + next->prev = prev; + else if (prev) + av->calls_tail = prev->friend_number; + else goto CLEAR; + + av->calls[friend_number] = NULL; + return next; + +CLEAR: + av->calls_head = av->calls_tail = 0; + free(av->calls); + av->calls = NULL; + + return NULL; +} +bool call_prepare_transmission(ToxAVCall *call) +{ + /* Assumes mutex locked */ + + if (call == NULL) + return false; + + ToxAV *av = call->av; + + if (!av->acb.first && !av->vcb.first) + /* It makes no sense to have CSession without callbacks */ + return false; if (call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_ERROR("Error while starting RTP session: call already active!\n"); - return av_ErrorAlreadyInCallWithPeer; + LOGGER_WARNING("Call already active!\n"); + return true; } - if (pthread_mutex_init(call->mutex_encoding_audio, NULL) != 0 - || pthread_mutex_init(call->mutex_encoding_video, NULL) != 0 || pthread_mutex_init(call->mutex_do, NULL) != 0) { - pthread_mutex_unlock(call->mutex); - LOGGER_ERROR("Error while starting RTP session: mutex initializing failed!\n"); - return av_ErrorUnknown; - } + if (create_recursive_mutex(call->mutex_audio) != 0) + return false; - const ToxAvCSettings *c_peer = toxavcsettings_cast - (&av->msi_session->calls[call_index]->csettings_peer[0]); - const ToxAvCSettings *c_self = toxavcsettings_cast - (&av->msi_session->calls[call_index]->csettings_local); + if (create_recursive_mutex(call->mutex_video) != 0) + goto FAILURE_3; - LOGGER_DEBUG( - "Type: %u(s) %u(p)\n" - "Video bitrate: %u(s) %u(p)\n" - "Video height: %u(s) %u(p)\n" - "Video width: %u(s) %u(p)\n" - "Audio bitrate: %u(s) %u(p)\n" - "Audio framedur: %u(s) %u(p)\n" - "Audio sample rate: %u(s) %u(p)\n" - "Audio channels: %u(s) %u(p)\n", - c_self->call_type, c_peer->call_type, - c_self->video_bitrate, c_peer->video_bitrate, - c_self->max_video_height, c_peer->max_video_height, - c_self->max_video_width, c_peer->max_video_width, - c_self->audio_bitrate, c_peer->audio_bitrate, - c_self->audio_frame_duration, c_peer->audio_frame_duration, - c_self->audio_sample_rate, c_peer->audio_sample_rate, - c_self->audio_channels, c_peer->audio_channels ); + if (create_recursive_mutex(call->mutex) != 0) + goto FAILURE_2; - if ( !(call->cs = cs_new(c_self, c_peer, jbuf_capacity, support_video)) ) { - LOGGER_ERROR("Error while starting Codec State!\n"); - pthread_mutex_unlock(call->mutex); - return av_ErrorInitializingCodecs; - } + /* Prepare bwc */ + call->bwc = bwc_new(av->m, call->friend_number, callback_bwc, call); + + { /* Prepare audio */ + call->audio.second = ac_new(av, call->friend_number, av->acb.first, av->acb.second); - call->cs->agent = av; - call->cs->call_idx = call_index; - - call->cs->acb.first = av->acb.first; - call->cs->acb.second = av->acb.second; - - call->cs->vcb.first = av->vcb.first; - call->cs->vcb.second = av->vcb.second; - - - call->crtps[audio_index] = - rtp_new(msi_TypeAudio, av->messenger, av->msi_session->calls[call_index]->peers[0]); - - if ( !call->crtps[audio_index] ) { - LOGGER_ERROR("Error while starting audio RTP session!\n"); - goto error; - } - - call->crtps[audio_index]->cs = call->cs; - - if ( support_video ) { - call->crtps[video_index] = - rtp_new(msi_TypeVideo, av->messenger, av->msi_session->calls[call_index]->peers[0]); - - if ( !call->crtps[video_index] ) { - LOGGER_ERROR("Error while starting video RTP session!\n"); - goto error; + if (!call->audio.second) { + LOGGER_ERROR("Failed to create audio codec session"); + goto FAILURE; } - call->crtps[video_index]->cs = call->cs; + call->audio.first = rtp_new(rtp_TypeAudio, av->m, call->friend_number, call->bwc, + call->audio.second, ac_queue_message); + + if (!call->audio.first) { + LOGGER_ERROR("Failed to create audio rtp session");; + goto FAILURE; + } + } + { /* Prepare video */ + call->video.second = vc_new(av, call->friend_number, av->vcb.first, av->vcb.second); + + if (!call->video.second) { + LOGGER_ERROR("Failed to create video codec session"); + goto FAILURE; + } + + call->video.first = rtp_new(rtp_TypeVideo, av->m, call->friend_number, call->bwc, + call->video.second, vc_queue_message); + + if (!call->video.first) { + LOGGER_ERROR("Failed to create video rtp session"); + goto FAILURE; + } } call->active = 1; - pthread_mutex_unlock(call->mutex); - return av_ErrorNone; -error: - rtp_kill(call->crtps[audio_index], av->messenger); - call->crtps[audio_index] = NULL; - rtp_kill(call->crtps[video_index], av->messenger); - call->crtps[video_index] = NULL; - cs_kill(call->cs); - call->cs = NULL; - call->active = 0; - pthread_mutex_destroy(call->mutex_encoding_audio); - pthread_mutex_destroy(call->mutex_encoding_video); - pthread_mutex_destroy(call->mutex_do); + return true; - pthread_mutex_unlock(call->mutex); - return av_ErrorCreatingRtpSessions; +FAILURE: + bwc_kill(call->bwc); + rtp_kill(call->audio.first); + ac_kill(call->audio.second); + call->audio.first = NULL; + call->audio.second = NULL; + rtp_kill(call->video.first); + vc_kill(call->video.second); + call->video.first = NULL; + call->video.second = NULL; + pthread_mutex_destroy(call->mutex); +FAILURE_2: + pthread_mutex_destroy(call->mutex_video); +FAILURE_3: + pthread_mutex_destroy(call->mutex_audio); + return false; } - -int toxav_kill_transmission ( ToxAv *av, int32_t call_index ) +void call_kill_transmission(ToxAVCall *call) { - if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { - LOGGER_WARNING("Invalid call index: %d", call_index); - return av_ErrorNoCall; - } - - ToxAvCall *call = &av->calls[call_index]; - - pthread_mutex_lock(call->mutex); - - if (!call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Action on inactive call: %d", call_index); - return av_ErrorInvalidState; - } + if (call == NULL || call->active == 0) + return; call->active = 0; - pthread_mutex_lock(call->mutex_encoding_audio); - pthread_mutex_unlock(call->mutex_encoding_audio); - pthread_mutex_lock(call->mutex_encoding_video); - pthread_mutex_unlock(call->mutex_encoding_video); - pthread_mutex_lock(call->mutex_do); - pthread_mutex_unlock(call->mutex_do); - - rtp_kill(call->crtps[audio_index], av->messenger); - call->crtps[audio_index] = NULL; - rtp_kill(call->crtps[video_index], av->messenger); - call->crtps[video_index] = NULL; - cs_kill(call->cs); - call->cs = NULL; - - pthread_mutex_destroy(call->mutex_encoding_audio); - pthread_mutex_destroy(call->mutex_encoding_video); - pthread_mutex_destroy(call->mutex_do); - - pthread_mutex_unlock(call->mutex); - - return av_ErrorNone; -} - -static int toxav_send_rtp_payload(ToxAv *av, - ToxAvCall *call, - ToxAvCallType type, - const uint8_t *payload, - unsigned int length) -{ - if (call->crtps[type - av_TypeAudio]) { - - /* Audio */ - if (type == av_TypeAudio) - return rtp_send_msg(call->crtps[audio_index], av->messenger, payload, length); - - /* Video */ - int parts = cs_split_video_payload(call->cs, payload, length); - - if (parts < 0) return parts; - - uint16_t part_size; - const uint8_t *iter; - - int i; - - for (i = 0; i < parts; i++) { - iter = cs_get_split_video_frame(call->cs, &part_size); - - if (rtp_send_msg(call->crtps[video_index], av->messenger, iter, part_size) < 0) - return av_ErrorSendingPayload; - } - - return av_ErrorNone; - - } else return av_ErrorNoRtpSession; -} - -int toxav_prepare_video_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input) -{ - if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { - LOGGER_WARNING("Invalid call index: %d", call_index); - return av_ErrorNoCall; - } - - - ToxAvCall *call = &av->calls[call_index]; + pthread_mutex_lock(call->mutex_audio); + pthread_mutex_unlock(call->mutex_audio); + pthread_mutex_lock(call->mutex_video); + pthread_mutex_unlock(call->mutex_video); pthread_mutex_lock(call->mutex); - - if (!call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Action on inactive call: %d", call_index); - return av_ErrorInvalidState; - } - - if (!(call->cs->capabilities & cs_VideoEncoding)) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Call doesn't support encoding video: %d", call_index); - return av_ErrorInvalidState; - } - - if (cs_set_video_encoder_resolution(call->cs, input->w, input->h) < 0) { - pthread_mutex_unlock(call->mutex); - return av_ErrorSettingVideoResolution; - } - - pthread_mutex_lock(call->mutex_encoding_video); pthread_mutex_unlock(call->mutex); - int rc = vpx_codec_encode(&call->cs->v_encoder, input, call->cs->frame_counter, 1, 0, MAX_ENCODE_TIME_US); + bwc_kill(call->bwc); + + rtp_kill(call->audio.first); + ac_kill(call->audio.second); + call->audio.first = NULL; + call->audio.second = NULL; - if ( rc != VPX_CODEC_OK) { - LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(rc)); - pthread_mutex_unlock(call->mutex_encoding_video); - return av_ErrorEncodingVideo; - } + rtp_kill(call->video.first); + vc_kill(call->video.second); + call->video.first = NULL; + call->video.second = NULL; - ++call->cs->frame_counter; - - vpx_codec_iter_t iter = NULL; - const vpx_codec_cx_pkt_t *pkt; - int copied = 0; - - while ( (pkt = vpx_codec_get_cx_data(&call->cs->v_encoder, &iter)) ) { - if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { - if ( copied + pkt->data.frame.sz > dest_max ) { - pthread_mutex_unlock(call->mutex_encoding_video); - return av_ErrorPacketTooLarge; - } - - memcpy(dest + copied, pkt->data.frame.buf, pkt->data.frame.sz); - copied += pkt->data.frame.sz; - } - } - - pthread_mutex_unlock(call->mutex_encoding_video); - return copied; + pthread_mutex_destroy(call->mutex_audio); + pthread_mutex_destroy(call->mutex_video); + pthread_mutex_destroy(call->mutex); } - -int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size) -{ - - if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { - LOGGER_WARNING("Invalid call index: %d", call_index); - return av_ErrorNoCall; - } - - ToxAvCall *call = &av->calls[call_index]; - pthread_mutex_lock(call->mutex); - - - if (!call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Action on inactive call: %d", call_index); - return av_ErrorInvalidState; - } - - int rc = toxav_send_rtp_payload(av, call, av_TypeVideo, frame, frame_size); - pthread_mutex_unlock(call->mutex); - - return rc; -} - -int toxav_prepare_audio_frame ( ToxAv *av, - int32_t call_index, - uint8_t *dest, - int dest_max, - const int16_t *frame, - int frame_size) -{ - if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { - LOGGER_WARNING("Action on nonexisting call: %d", call_index); - return av_ErrorNoCall; - } - - ToxAvCall *call = &av->calls[call_index]; - pthread_mutex_lock(call->mutex); - - if (!call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Action on inactive call: %d", call_index); - return av_ErrorInvalidState; - } - - pthread_mutex_lock(call->mutex_encoding_audio); - pthread_mutex_unlock(call->mutex); - int32_t rc = opus_encode(call->cs->audio_encoder, frame, frame_size, dest, dest_max); - pthread_mutex_unlock(call->mutex_encoding_audio); - - if (rc < 0) { - LOGGER_ERROR("Failed to encode payload: %s\n", opus_strerror(rc)); - return av_ErrorEncodingAudio; - } - - return rc; -} - -int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *data, unsigned int size) -{ - if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { - LOGGER_WARNING("Action on nonexisting call: %d", call_index); - return av_ErrorNoCall; - } - - ToxAvCall *call = &av->calls[call_index]; - pthread_mutex_lock(call->mutex); - - - if (!call->active) { - pthread_mutex_unlock(call->mutex); - LOGGER_WARNING("Action on inactive call: %d", call_index); - return av_ErrorInvalidState; - } - - int rc = toxav_send_rtp_payload(av, call, av_TypeAudio, data, size); - pthread_mutex_unlock(call->mutex); - return rc; -} - -int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ) -{ - if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || - !av->msi_session->calls[call_index] || av->msi_session->calls[call_index]->peer_count <= peer ) - return av_ErrorNoCall; - - *dest = *toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[peer]); - return av_ErrorNone; -} - -int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ) -{ - if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] - || av->msi_session->calls[call_index]->peer_count <= peer ) - return av_ErrorNoCall; - - return av->msi_session->calls[call_index]->peers[peer]; -} - -ToxAvCallState toxav_get_call_state(ToxAv *av, int32_t call_index) -{ - if ( CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] ) - return av_CallNonExistent; - - return av->msi_session->calls[call_index]->state; - -} - -int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ) -{ - return av->calls[call_index].cs ? av->calls[call_index].cs->capabilities & (CSCapabilities) capability : 0; - /* 0 is error here */ -} - -Tox *toxav_get_tox(ToxAv *av) -{ - return (Tox *)av->messenger; -} - -int toxav_get_active_count(ToxAv *av) -{ - if (!av) return -1; - - int rc = 0, i = 0; - - for (; i < av->max_calls; i++) { - pthread_mutex_lock(av->calls[i].mutex); - - if (av->calls[i].active) rc++; - - pthread_mutex_unlock(av->calls[i].mutex); - } - - return rc; -} - -/* Create a new toxav group. - * - * return group number on success. - * return -1 on failure. - * - * Audio data callback format: - * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) - * - * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). - */ -int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, - uint8_t, unsigned int, void *), void *userdata) -{ - Messenger *m = tox; - return add_av_groupchat(m->group_chat_object, audio_callback, userdata); -} - -/* Join a AV group (you need to have been invited first.) - * - * returns group number on success - * returns -1 on failure. - * - * Audio data callback format (same as the one for toxav_add_av_groupchat()): - * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) - * - * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). - */ -int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, - void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), - void *userdata) -{ - Messenger *m = tox; - return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata); -} - -/* Send audio to the group chat. - * - * return 0 on success. - * return -1 on failure. - * - * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). - * - * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) - * Valid number of channels are 1 or 2. - * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. - * - * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 - */ -int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, - unsigned int sample_rate) -{ - Messenger *m = tox; - return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate); -} - diff --git a/toxav/toxav.h b/toxav/toxav.h index 7285f45cc..5c5195b33 100644 --- a/toxav/toxav.h +++ b/toxav/toxav.h @@ -1,285 +1,695 @@ -/** toxav.h +/* toxav.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. * - * Copyright (C) 2013 Tox project All Rights Reserved. + * This file is part of Tox. * - * This file is part of Tox. + * Tox 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. * - * Tox 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. - * - * Tox 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 Tox. If not, see . + * Tox 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 Tox. If not, see . + * */ +#ifndef TOXAV_H +#define TOXAV_H -#ifndef __TOXAV -#define __TOXAV -#include +#include +#include +#include #ifdef __cplusplus extern "C" { #endif -typedef struct _ToxAv ToxAv; - -/* vpx_image_t */ -#include - -typedef void ( *ToxAVCallback ) ( void *agent, int32_t call_idx, void *arg ); -typedef void ( *ToxAvAudioCallback ) (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data); -typedef void ( *ToxAvVideoCallback ) (void *agent, int32_t call_idx, const vpx_image_t *img, void *data); - +/** \page av Public audio/video API for Tox clients. + * + * This API can handle multiple calls. Each call has its state, in very rare + * occasions the library can change the state of the call without apps knowledge. + * + */ +/** \subsection events Events and callbacks + * + * As in Core API, events are handled by callbacks. One callback can be + * registered per event. All events have a callback function type named + * `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`. + * Passing a NULL callback will result in no callback being registered for that + * event. Only one callback per event can be registered, so if a client needs + * multiple event listeners, it needs to implement the dispatch functionality + * itself. Unlike Core API, lack of some event handlers will cause the the + * library to drop calls before they are started. Hanging up call from a + * callback causes undefined behaviour. + * + */ +/** \subsection threading Threading implications + * + * Unlike the Core API, this API is fully thread-safe. The library will ensure + * the proper synchronization of parallel calls. + * + * A common way to run ToxAV (multiple or single instance) is to have a thread, + * separate from tox instance thread, running a simple toxav_iterate loop, + * sleeping for toxav_iteration_interval * milliseconds on each iteration. + * + * An important thing to note is that events are triggered from both tox and + * toxav thread (see above). Audio and video receive frame events are triggered + * from toxav thread while all the other events are triggered from tox thread. + * + * Tox thread has priority with mutex mechanisms. Any api function can + * fail if mutexes are held by tox thread in which case they will set SYNC + * error code. + */ +/** + * External Tox type. + */ #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; -#endif - -#define RTP_PAYLOAD_SIZE 65535 - +#endif /* TOX_DEFINED */ /** - * Callbacks ids that handle the call states. + * ToxAV. */ -typedef enum { - av_OnInvite, /* Incoming call */ - av_OnRinging, /* When peer is ready to accept/reject the call */ - av_OnStart, /* Call (RTP transmission) started */ - av_OnCancel, /* The side that initiated call canceled invite */ - av_OnReject, /* The side that was invited rejected the call */ - av_OnEnd, /* Call that was active ended */ - av_OnRequestTimeout, /* When the requested action didn't get response in specified time */ - av_OnPeerTimeout, /* Peer timed out; stop the call */ - av_OnPeerCSChange, /* Peer changing Csettings. Prepare for changed AV */ - av_OnSelfCSChange /* Csettings change confirmation. Once triggered peer is ready to recv changed AV */ -} ToxAvCallbackID; +/** + * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox + * instance, and Tox instance can have only one ToxAV instance. One must make + * sure to close ToxAV instance prior closing Tox instance otherwise undefined + * behaviour occurs. Upon closing of ToxAV instance, all active calls will be + * forcibly terminated without notifying peers. + * + */ +#ifndef TOXAV_DEFINED +#define TOXAV_DEFINED +typedef struct ToxAV ToxAV; +#endif /* TOXAV_DEFINED */ +/******************************************************************************* + * + * :: API version + * + ******************************************************************************/ +/** + * The major version number. Incremented when the API or ABI changes in an + * incompatible way. + */ +#define TOXAV_VERSION_MAJOR 0u + /** - * Call type identifier. + * The minor version number. Incremented when functionality is added without + * breaking the API or ABI. Set to 0 when the major version number is + * incremented. */ -typedef enum { - av_TypeAudio = 192, - av_TypeVideo -} ToxAvCallType; - - -typedef enum { - av_CallNonExistent = -1, - av_CallInviting, /* when sending call invite */ - av_CallStarting, /* when getting call invite */ - av_CallActive, - av_CallHold, - av_CallHungUp -} ToxAvCallState; +#define TOXAV_VERSION_MINOR 0u /** - * Error indicators. Values under -20 are reserved for toxcore. + * The patch or revision number. Incremented when bugfixes are applied without + * changing any functionality or API or ABI. */ -typedef enum { - av_ErrorNone = 0, - av_ErrorUnknown = -1, /* Unknown error */ - av_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ - av_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ - av_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ - av_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ - av_ErrorInitializingCodecs = -30, /* Failed creating CSSession */ - av_ErrorSettingVideoResolution = -31, /* Error setting resolution */ - av_ErrorSettingVideoBitrate = -32, /* Error setting bitrate */ - av_ErrorSplittingVideoPayload = -33, /* Error splitting video payload */ - av_ErrorEncodingVideo = -34, /* vpx_codec_encode failed */ - av_ErrorEncodingAudio = -35, /* opus_encode failed */ - av_ErrorSendingPayload = -40, /* Sending lossy packet failed */ - av_ErrorCreatingRtpSessions = -41, /* One of the rtp sessions failed to initialize */ - av_ErrorNoRtpSession = -50, /* Trying to perform rtp action on invalid session */ - av_ErrorInvalidCodecState = -51, /* Codec state not initialized */ - av_ErrorPacketTooLarge = -52, /* Split packet exceeds it's limit */ -} ToxAvError; - +#define TOXAV_VERSION_PATCH 0u /** - * Locally supported capabilities. + * A macro to check at preprocessing time whether the client code is compatible + * with the installed version of ToxAV. */ -typedef enum { - av_AudioEncoding = 1 << 0, - av_AudioDecoding = 1 << 1, - av_VideoEncoding = 1 << 2, - av_VideoDecoding = 1 << 3 -} ToxAvCapabilities; - +#define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ + (TOXAV_VERSION_MAJOR == MAJOR && \ + (TOXAV_VERSION_MINOR > MINOR || \ + (TOXAV_VERSION_MINOR == MINOR && \ + TOXAV_VERSION_PATCH >= PATCH))) /** - * Encoding settings. + * A macro to make compilation fail if the client code is not compatible with + * the installed version of ToxAV. */ -typedef struct _ToxAvCSettings { - ToxAvCallType call_type; - - uint32_t video_bitrate; /* In kbits/s */ - uint16_t max_video_width; /* In px */ - uint16_t max_video_height; /* In px */ - - uint32_t audio_bitrate; /* In bits/s */ - uint16_t audio_frame_duration; /* In ms */ - uint32_t audio_sample_rate; /* In Hz */ - uint32_t audio_channels; -} ToxAvCSettings; - -extern const ToxAvCSettings av_DefaultSettings; +#define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ + typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] /** - * Start new A/V session. There can only be one session at the time. + * A convenience macro to call toxav_version_is_compatible with the currently + * compiling API version. */ -ToxAv *toxav_new(Tox *messenger, int32_t max_calls); +#define TOXAV_VERSION_IS_ABI_COMPATIBLE() \ + toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH) /** - * Remove A/V session. + * Return the major version number of the library. Can be used to display the + * ToxAV library version or to check whether the client is compatible with the + * dynamically linked version of ToxAV. */ -void toxav_kill(ToxAv *av); +uint32_t toxav_version_major(void); /** - * Returns the interval in milliseconds when the next toxav_do() should be called. - * If no call is active at the moment returns 200. + * Return the minor version number of the library. */ -uint32_t toxav_do_interval(ToxAv *av); +uint32_t toxav_version_minor(void); /** - * Main loop for the session. Best called right after tox_do(); + * Return the patch number of the library. */ -void toxav_do(ToxAv *av); +uint32_t toxav_version_patch(void); /** - * Register callback for call state. + * Return whether the compiled library version is compatible with the passed + * version numbers. */ -void toxav_register_callstate_callback (ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata); +bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); + + +/******************************************************************************* + * + * :: Creation and destruction + * + ******************************************************************************/ +typedef enum TOXAV_ERR_NEW { + /** + * The function returned successfully. + */ + TOXAV_ERR_NEW_OK, + /** + * One of the arguments to the function was NULL when it was not expected. + */ + TOXAV_ERR_NEW_NULL, + /** + * Memory allocation failure while trying to allocate structures required for + * the A/V session. + */ + TOXAV_ERR_NEW_MALLOC, + /** + * Attempted to create a second session for the same Tox instance. + */ + TOXAV_ERR_NEW_MULTIPLE, +} TOXAV_ERR_NEW; /** - * Register callback for audio data. + * Start new A/V session. There can only be only one session per Tox instance. */ -void toxav_register_audio_callback (ToxAv *av, ToxAvAudioCallback cb, void *userdata); +ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error); /** - * Register callback for video data. + * Releases all resources associated with the A/V session. + * + * If any calls were ongoing, these will be forcibly terminated without + * notifying peers. After calling this function, no other functions may be + * called and the av pointer becomes invalid. */ -void toxav_register_video_callback (ToxAv *av, ToxAvVideoCallback cb, void *userdata); +void toxav_kill(ToxAV *toxAV); /** - * Call user. Use its friend_id. + * Returns the Tox instance the A/V object was created for. */ -int toxav_call(ToxAv *av, - int32_t *call_index, - int friend_id, - const ToxAvCSettings *csettings, - int ringing_seconds); +Tox *toxav_get_tox(const ToxAV *toxAV); + + +/******************************************************************************* + * + * :: A/V event loop + * + ******************************************************************************/ +/** + * Returns the interval in milliseconds when the next toxav_iterate call should + * be. If no call is active at the moment, this function returns 200. + */ +uint32_t toxav_iteration_interval(const ToxAV *toxAV); /** - * Hangup active call. + * Main loop for the session. This function needs to be called in intervals of + * toxav_iteration_interval() milliseconds. It is best called in the separate + * thread from tox_iterate. */ -int toxav_hangup(ToxAv *av, int32_t call_index); +void toxav_iterate(ToxAV *toxAV); + + +/******************************************************************************* + * + * :: Call setup + * + ******************************************************************************/ +typedef enum TOXAV_ERR_CALL { + /** + * The function returned successfully. + */ + TOXAV_ERR_CALL_OK, + /** + * A resource allocation error occurred while trying to create the structures + * required for the call. + */ + TOXAV_ERR_CALL_MALLOC, + /** + * Synchronization error occurred. + */ + TOXAV_ERR_CALL_SYNC, + /** + * The friend number did not designate a valid friend. + */ + TOXAV_ERR_CALL_FRIEND_NOT_FOUND, + /** + * The friend was valid, but not currently connected. + */ + TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED, + /** + * Attempted to call a friend while already in an audio or video call with + * them. + */ + TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL, + /** + * Audio or video bit rate is invalid. + */ + TOXAV_ERR_CALL_INVALID_BIT_RATE, +} TOXAV_ERR_CALL; /** - * Answer incoming call. Pass the csettings that you will use. + * Call a friend. This will start ringing the friend. + * + * It is the client's responsibility to stop ringing after a certain timeout, + * if such behaviour is desired. If the client does not stop ringing, the + * library will not stop until the friend is disconnected. Audio and video + * receiving are both enabled by default. + * + * @param friend_number The friend number of the friend that should be called. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. */ -int toxav_answer(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ); +bool toxav_call(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, + uint32_t video_bit_rate, TOXAV_ERR_CALL *error); /** - * Reject incoming call. + * The function type for the call callback. + * + * @param friend_number The friend number from which the call is incoming. + * @param audio_enabled True if friend is sending audio. + * @param video_enabled True if friend is sending video. */ -int toxav_reject(ToxAv *av, int32_t call_index, const char *reason); +typedef void toxav_call_cb(ToxAV *toxAV, uint32_t friend_number, bool audio_enabled, + bool video_enabled, void *user_data); /** - * Cancel outgoing request. + * Set the callback for the `call` event. Pass NULL to unset. + * */ -int toxav_cancel(ToxAv *av, int32_t call_index, int peer_id, const char *reason); +void toxav_callback_call(ToxAV *toxAV, toxav_call_cb *callback, void *user_data); + +typedef enum TOXAV_ERR_ANSWER { + /** + * The function returned successfully. + */ + TOXAV_ERR_ANSWER_OK, + /** + * Synchronization error occurred. + */ + TOXAV_ERR_ANSWER_SYNC, + /** + * Failed to initialize codecs for call session. Note that codec initiation + * will fail if there is no receive callback registered for either audio or + * video. + */ + TOXAV_ERR_ANSWER_CODEC_INITIALIZATION, + /** + * The friend number did not designate a valid friend. + */ + TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND, + /** + * The friend was valid, but they are not currently trying to initiate a call. + * This is also returned if this client is already in a call with the friend. + */ + TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING, + /** + * Audio or video bit rate is invalid. + */ + TOXAV_ERR_ANSWER_INVALID_BIT_RATE, +} TOXAV_ERR_ANSWER; /** - * Notify peer that we are changing codec settings. + * Accept an incoming call. + * + * If answering fails for any reason, the call will still be pending and it is + * possible to try and answer it later. Audio and video receiving are both + * enabled by default. + * + * @param friend_number The friend number of the friend that is calling. + * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable + * audio sending. + * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable + * video sending. */ -int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings); +bool toxav_answer(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); + + +/******************************************************************************* + * + * :: Call state graph + * + ******************************************************************************/ +enum TOXAV_FRIEND_CALL_STATE { + /** + * Set by the AV core if an error occurred on the remote end or if friend + * timed out. This is the final state after which no more state + * transitions can occur for the call. This call state will never be triggered + * in combination with other call states. + */ + TOXAV_FRIEND_CALL_STATE_ERROR = 1, + /** + * The call has finished. This is the final state after which no more state + * transitions can occur for the call. This call state will never be + * triggered in combination with other call states. + */ + TOXAV_FRIEND_CALL_STATE_FINISHED = 2, + /** + * The flag that marks that friend is sending audio. + */ + TOXAV_FRIEND_CALL_STATE_SENDING_A = 4, + /** + * The flag that marks that friend is sending video. + */ + TOXAV_FRIEND_CALL_STATE_SENDING_V = 8, + /** + * The flag that marks that friend is receiving audio. + */ + TOXAV_FRIEND_CALL_STATE_ACCEPTING_A = 16, + /** + * The flag that marks that friend is receiving video. + */ + TOXAV_FRIEND_CALL_STATE_ACCEPTING_V = 32, +}; /** - * Terminate transmission. Note that transmission will be - * terminated without informing remote peer. Usually called when we can't inform peer. + * The function type for the call_state callback. + * + * @param friend_number The friend number for which the call state changed. + * @param state The bitmask of the new call state which is guaranteed to be + * different than the previous state. The state is set to 0 when the call is + * paused. The bitmask represents all the activities currently performed by the + * friend. */ -int toxav_stop_call(ToxAv *av, int32_t call_index); +typedef void toxav_call_state_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t state, void *user_data); /** - * Allocates transmission data. Must be call before calling toxav_prepare_* and toxav_send_*. - * Also, it must be called when call is started + * Set the callback for the `call_state` event. Pass NULL to unset. + * */ -int toxav_prepare_transmission(ToxAv *av, int32_t call_index, int support_video); +void toxav_callback_call_state(ToxAV *toxAV, toxav_call_state_cb *callback, void *user_data); + +/******************************************************************************* + * + * :: Call control + * + ******************************************************************************/ +typedef enum TOXAV_CALL_CONTROL { + /** + * Resume a previously paused call. Only valid if the pause was caused by this + * client, if not, this control is ignored. Not valid before the call is accepted. + */ + TOXAV_CALL_CONTROL_RESUME, + /** + * Put a call on hold. Not valid before the call is accepted. + */ + TOXAV_CALL_CONTROL_PAUSE, + /** + * Reject a call if it was not answered, yet. Cancel a call after it was + * answered. + */ + TOXAV_CALL_CONTROL_CANCEL, + /** + * Request that the friend stops sending audio. Regardless of the friend's + * compliance, this will cause the audio_receive_frame event to stop being + * triggered on receiving an audio frame from the friend. + */ + TOXAV_CALL_CONTROL_MUTE_AUDIO, + /** + * Calling this control will notify client to start sending audio again. + */ + TOXAV_CALL_CONTROL_UNMUTE_AUDIO, + /** + * Request that the friend stops sending video. Regardless of the friend's + * compliance, this will cause the video_receive_frame event to stop being + * triggered on receiving a video frame from the friend. + */ + TOXAV_CALL_CONTROL_HIDE_VIDEO, + /** + * Calling this control will notify client to start sending video again. + */ + TOXAV_CALL_CONTROL_SHOW_VIDEO, +} TOXAV_CALL_CONTROL; + +typedef enum TOXAV_ERR_CALL_CONTROL { + /** + * The function returned successfully. + */ + TOXAV_ERR_CALL_CONTROL_OK, + /** + * Synchronization error occurred. + */ + TOXAV_ERR_CALL_CONTROL_SYNC, + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. Before the call is + * answered, only CANCEL is a valid control. + */ + TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL, + /** + * Happens if user tried to pause an already paused call or if trying to + * resume a call that is not paused. + */ + TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION, +} TOXAV_ERR_CALL_CONTROL; /** - * Clears transmission data. Call this at the end of the transmission. + * Sends a call control command to a friend. + * + * @param friend_number The friend number of the friend this client is in a call + * with. + * @param control The control command to send. + * + * @return true on success. */ -int toxav_kill_transmission(ToxAv *av, int32_t call_index); +bool toxav_call_control(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); + + +/******************************************************************************* + * + * :: Controlling bit rates + * + ******************************************************************************/ +typedef enum TOXAV_ERR_BIT_RATE_SET { + /** + * The function returned successfully. + */ + TOXAV_ERR_BIT_RATE_SET_OK, + /** + * Synchronization error occurred. + */ + TOXAV_ERR_BIT_RATE_SET_SYNC, + /** + * The audio bit rate passed was not one of the supported values. + */ + TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE, + /** + * The video bit rate passed was not one of the supported values. + */ + TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE, + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. + */ + TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL, +} TOXAV_ERR_BIT_RATE_SET; /** - * Encode video frame. + * Set the bit rate to be used in subsequent audio/video frames. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable + * audio sending. Set to -1 to leave unchanged. + * @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable + * video sending. Set to -1 to leave unchanged. + * */ -int toxav_prepare_video_frame ( ToxAv *av, - int32_t call_index, - uint8_t *dest, - int dest_max, - vpx_image_t *input); +bool toxav_bit_rate_set(ToxAV *toxAV, uint32_t friend_number, int32_t audio_bit_rate, + int32_t video_bit_rate, TOXAV_ERR_BIT_RATE_SET *error); /** - * Send encoded video packet. + * The function type for the bit_rate_status callback. The event is triggered + * when the network becomes too saturated for current bit rates at which + * point core suggests new bit rates. + * + * @param friend_number The friend number of the friend for which to set the + * bit rate. + * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. + * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. */ -int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, uint32_t frame_size); +typedef void toxav_bit_rate_status_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, void *user_data); /** - * Encode audio frame. + * Set the callback for the `bit_rate_status` event. Pass NULL to unset. + * */ -int toxav_prepare_audio_frame ( ToxAv *av, - int32_t call_index, - uint8_t *dest, - int dest_max, - const int16_t *frame, - int frame_size); +void toxav_callback_bit_rate_status(ToxAV *toxAV, toxav_bit_rate_status_cb *callback, void *user_data); + + +/******************************************************************************* + * + * :: A/V sending + * + ******************************************************************************/ +typedef enum TOXAV_ERR_SEND_FRAME { + /** + * The function returned successfully. + */ + TOXAV_ERR_SEND_FRAME_OK, + /** + * In case of video, one of Y, U, or V was NULL. In case of audio, the samples + * data pointer was NULL. + */ + TOXAV_ERR_SEND_FRAME_NULL, + /** + * The friend_number passed did not designate a valid friend. + */ + TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND, + /** + * This client is currently not in a call with the friend. + */ + TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL, + /** + * Synchronization error occurred. + */ + TOXAV_ERR_SEND_FRAME_SYNC, + /** + * One of the frame parameters was invalid. E.g. the resolution may be too + * small or too large, or the audio sampling rate may be unsupported. + */ + TOXAV_ERR_SEND_FRAME_INVALID, + /** + * Either friend turned off audio or video receiving or we turned off sending + * for the said payload. + */ + TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED, + /** + * Failed to push frame through rtp interface. + */ + TOXAV_ERR_SEND_FRAME_RTP_FAILED, +} TOXAV_ERR_SEND_FRAME; /** - * Send encoded audio frame. + * Send an audio frame to a friend. + * + * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... + * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... + * For mono audio, this has no meaning, every sample is subsequent. For stereo, + * this means the expected format is LRLRLR... with samples for left and right + * alternating. + * + * @param friend_number The friend number of the friend to which to send an + * audio frame. + * @param pcm An array of audio samples. The size of this array must be + * sample_count * channels. + * @param sample_count Number of samples in this frame. Valid numbers here are + * ((sample rate) * (audio length) / 1000), where audio length can be + * 2.5, 5, 10, 20, 40 or 60 millseconds. + * @param channels Number of audio channels. Supported values are 1 and 2. + * @param sampling_rate Audio sampling rate used in this frame. Valid sampling + * rates are 8000, 12000, 16000, 24000, or 48000. */ -int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int size); +bool toxav_audio_send_frame(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, + size_t sample_count, uint8_t channels, uint32_t sampling_rate, + TOXAV_ERR_SEND_FRAME *error); /** - * Get codec settings from the peer. These were exchanged during call initialization - * or when peer send us new csettings. + * Send a video frame to a friend. + * + * Y - plane should be of size: height * width + * U - plane should be of size: (height/2) * (width/2) + * V - plane should be of size: (height/2) * (width/2) + * + * @param friend_number The friend number of the friend to which to send a video + * frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y Y (Luminance) plane data. + * @param u U (Chroma) plane data. + * @param v V (Chroma) plane data. */ -int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ); +bool toxav_video_send_frame(ToxAV *toxAV, uint32_t friend_number, uint16_t width, + uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, + TOXAV_ERR_SEND_FRAME *error); + + +/******************************************************************************* + * + * :: A/V receiving + * + ******************************************************************************/ +/** + * The function type for the audio_receive_frame callback. The callback can be + * called multiple times per single iteration depending on the amount of queued + * frames in the buffer. The received format is the same as in send function. + * + * @param friend_number The friend number of the friend who sent an audio frame. + * @param pcm An array of audio samples (sample_count * channels elements). + * @param sample_count The number of audio samples per channel in the PCM array. + * @param channels Number of audio channels. + * @param sampling_rate Sampling rate used in this frame. + * + */ +typedef void toxav_audio_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, + size_t sample_count, uint8_t channels, uint32_t sampling_rate, + void *user_data); /** - * Get friend id of peer participating in conversation. + * Set the callback for the `audio_receive_frame` event. Pass NULL to unset. + * */ -int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ); +void toxav_callback_audio_receive_frame(ToxAV *toxAV, toxav_audio_receive_frame_cb *callback, void *user_data); /** - * Get current call state. + * The function type for the video_receive_frame callback. + * + * @param friend_number The friend number of the friend who sent a video frame. + * @param width Width of the frame in pixels. + * @param height Height of the frame in pixels. + * @param y + * @param u + * @param v Plane data. + * The size of plane data is derived from width and height where + * Y = MAX(width, abs(ystride)) * height, + * U = MAX(width/2, abs(ustride)) * (height/2) and + * V = MAX(width/2, abs(vstride)) * (height/2). + * @param ystride + * @param ustride + * @param vstride Strides data. Strides represent padding for each plane + * that may or may not be present. You must handle strides in + * your image processing code. Strides are negative if the + * image is bottom-up hence why you MUST abs() it when + * calculating plane buffer size. */ -ToxAvCallState toxav_get_call_state ( ToxAv *av, int32_t call_index ); +typedef void toxav_video_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, uint16_t width, + uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, + int32_t ystride, int32_t ustride, int32_t vstride, void *user_data); /** - * Is certain capability supported. Used to determine if encoding/decoding is ready. + * Set the callback for the `video_receive_frame` event. Pass NULL to unset. + * */ -int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ); +void toxav_callback_video_receive_frame(ToxAV *toxAV, toxav_video_receive_frame_cb *callback, void *user_data); /** - * Returns tox reference. + * NOTE Compatibility with old toxav group calls TODO remove */ -Tox *toxav_get_tox (ToxAv *av); - -/** - * Returns number of active calls or -1 on error. - */ -int toxav_get_active_count (ToxAv *av); - /* Create a new toxav group. * * return group number on success. @@ -290,7 +700,7 @@ int toxav_get_active_count (ToxAv *av); * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ -int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, +int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void*, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); /* Join a AV group (you need to have been invited first.) @@ -304,7 +714,7 @@ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Tox *, int, int, con * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, - void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); + void (*audio_callback)(void*, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); /* Send audio to the group chat. * @@ -325,5 +735,4 @@ int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsign #ifdef __cplusplus } #endif - -#endif /* __TOXAV */ +#endif /* TOXAV_H */ diff --git a/toxav/toxav_old.c b/toxav/toxav_old.c new file mode 100644 index 000000000..7d7e5e7bd --- /dev/null +++ b/toxav/toxav_old.c @@ -0,0 +1,81 @@ +/* toxav_old.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ +/** + * This file contains the group chats code for the backwards compatibility. + */ + +#include "toxav.h" +#include "group.h" + +/* Create a new toxav group. + * + * return group number on success. + * return -1 on failure. + * + * Audio data callback format: + * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + */ +int toxav_add_av_groupchat(struct Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, + uint8_t, unsigned int, void *), void *userdata) +{ + Messenger *m = (Messenger *)tox; + return add_av_groupchat(m->group_chat_object, audio_callback, userdata); +} + +/* Join a AV group (you need to have been invited first.) + * + * returns group number on success + * returns -1 on failure. + * + * Audio data callback format (same as the one for toxav_add_av_groupchat()): + * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + */ +int toxav_join_av_groupchat(struct Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, + void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), + void *userdata) +{ + Messenger *m = (Messenger *)tox; + return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata); +} + +/* Send audio to the group chat. + * + * return 0 on success. + * return -1 on failure. + * + * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). + * + * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) + * Valid number of channels are 1 or 2. + * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. + * + * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 + */ +int toxav_group_send_audio(struct Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, + unsigned int sample_rate) +{ + Messenger *m = (Messenger *)tox; + return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate); +} \ No newline at end of file diff --git a/toxav/video.c b/toxav/video.c new file mode 100644 index 000000000..acc1852bc --- /dev/null +++ b/toxav/video.c @@ -0,0 +1,267 @@ +/** video.c + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#include "video.h" +#include "msi.h" +#include "rtp.h" + +#include "../toxcore/logger.h" +#include "../toxcore/network.h" + +#define MAX_DECODE_TIME_US 0 /* Good quality encode. */ +#define VIDEO_DECODE_BUFFER_SIZE 20 + +VCSession *vc_new(ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data) +{ + VCSession *vc = calloc(sizeof(VCSession), 1); + + if (!vc) { + LOGGER_WARNING("Allocation failed! Application might misbehave!"); + return NULL; + } + + if (create_recursive_mutex(vc->queue_mutex) != 0) { + LOGGER_WARNING("Failed to create recursive mutex!"); + free(vc); + return NULL; + } + + if (!(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE))) + goto BASE_CLEANUP; + + int rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); + goto BASE_CLEANUP; + } + + /* Set encoder to some initial values + */ + vpx_codec_enc_cfg_t cfg; + rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc)); + goto BASE_CLEANUP_1; + } + + cfg.rc_target_bitrate = 500000; + cfg.g_w = 800; + cfg.g_h = 600; + cfg.g_pass = VPX_RC_ONE_PASS; + /* FIXME If we set error resilience the app will crash due to bug in vp8. + Perhaps vp9 has solved it?*/ +// cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; + cfg.g_lag_in_frames = 0; + cfg.kf_min_dist = 0; + cfg.kf_max_dist = 48; + cfg.kf_mode = VPX_KF_AUTO; + + rc = vpx_codec_enc_init(vc->encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); + goto BASE_CLEANUP_1; + } + + rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, 8); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + vpx_codec_destroy(vc->encoder); + goto BASE_CLEANUP_1; + } + + vc->linfts = current_time_monotonic(); + vc->lcfd = 60; + vc->vcb.first = cb; + vc->vcb.second = cb_data; + vc->friend_number = friend_number; + vc->av = av; + + return vc; + +BASE_CLEANUP_1: + vpx_codec_destroy(vc->decoder); +BASE_CLEANUP: + pthread_mutex_destroy(vc->queue_mutex); + rb_kill(vc->vbuf_raw); + free(vc); + return NULL; +} +void vc_kill(VCSession *vc) +{ + if (!vc) + return; + + vpx_codec_destroy(vc->encoder); + vpx_codec_destroy(vc->decoder); + + void *p; + + while (rb_read(vc->vbuf_raw, (void **)&p)) + free(p); + + rb_kill(vc->vbuf_raw); + + pthread_mutex_destroy(vc->queue_mutex); + + LOGGER_DEBUG("Terminated video handler: %p", vc); + free(vc); +} +void vc_iterate(VCSession *vc) +{ + if (!vc) + return; + + struct RTPMessage *p; + int rc; + + pthread_mutex_lock(vc->queue_mutex); + + if (rb_read(vc->vbuf_raw, (void **)&p)) { + pthread_mutex_unlock(vc->queue_mutex); + + rc = vpx_codec_decode(vc->decoder, p->data, p->len, NULL, MAX_DECODE_TIME_US); + free(p); + + if (rc != VPX_CODEC_OK) + LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); + else { + vpx_codec_iter_t iter = NULL; + vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); + + /* Play decoded images */ + for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) { + if (vc->vcb.first) + vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h, + (const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2], + dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second); + + vpx_img_free(dest); + } + } + + return; + } + + pthread_mutex_unlock(vc->queue_mutex); +} +int vc_queue_message(void *vcp, struct RTPMessage *msg) +{ + /* This function does the reconstruction of video packets. + * See more info about video splitting in docs + */ + if (!vcp || !msg) + return -1; + + if (msg->header.pt == (rtp_TypeVideo + 2) % 128) { + LOGGER_WARNING("Got dummy!"); + free(msg); + return 0; + } + + if (msg->header.pt != rtp_TypeVideo % 128) { + LOGGER_WARNING("Invalid payload type!"); + free(msg); + return -1; + } + + VCSession *vc = vcp; + + pthread_mutex_lock(vc->queue_mutex); + free(rb_write(vc->vbuf_raw, msg)); + { + /* Calculate time took for peer to send us this frame */ + uint32_t t_lcfd = current_time_monotonic() - vc->linfts; + vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; + vc->linfts = current_time_monotonic(); + } + pthread_mutex_unlock(vc->queue_mutex); + + return 0; +} +int vc_reconfigure_encoder(VCSession* vc, uint32_t bit_rate, uint16_t width, uint16_t height) +{ + if (!vc) + return -1; + + vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc; + int rc; + + if (cfg.rc_target_bitrate == bit_rate && cfg.g_w == width && cfg.g_h == height) + return 0; /* Nothing changed */ + + if (cfg.g_w == width && cfg.g_h == height) + { + /* Only bit rate changed */ + cfg.rc_target_bitrate = bit_rate; + + rc = vpx_codec_enc_config_set(vc->encoder, &cfg); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + return -1; + } + } + else + { + /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support + * reconfiguring encoder to use resolutions greater than initially set. + */ + + LOGGER_DEBUG("Have to reinitialize vpx encoder on session %p", vc); + + cfg.rc_target_bitrate = bit_rate; + cfg.g_w = width; + cfg.g_h = height; + + vpx_codec_ctx_t new_c; + + rc = vpx_codec_enc_init(&new_c, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, 8); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + vpx_codec_destroy(&new_c); + return -1; + } + + vpx_codec_destroy(vc->encoder); + memcpy(vc->encoder, &new_c, sizeof(new_c)); + } + + return 0; +} diff --git a/toxav/video.h b/toxav/video.h new file mode 100644 index 000000000..51f343188 --- /dev/null +++ b/toxav/video.h @@ -0,0 +1,67 @@ +/** video.h + * + * Copyright (C) 2013-2015 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifndef VIDEO_H +#define VIDEO_H + +#include +#include +#include +#include +#include +#define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) +#define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) + +#include + +#include "toxav.h" + +#include "../toxcore/util.h" + +struct RTPMessage; + +typedef struct VCSession_s { + /* encoding */ + vpx_codec_ctx_t encoder[1]; + uint32_t frame_counter; + + /* decoding */ + vpx_codec_ctx_t decoder[1]; + void *vbuf_raw; /* Un-decoded data */ + + uint64_t linfts; /* Last received frame time stamp */ + uint32_t lcfd; /* Last calculated frame duration for incoming video payload */ + + ToxAV *av; + uint32_t friend_number; + + PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ + + pthread_mutex_t queue_mutex[1]; +} VCSession; + +VCSession *vc_new(ToxAV* av, uint32_t friend_number, toxav_video_receive_frame_cb* cb, void* cb_data); +void vc_kill(VCSession *vc); +void vc_iterate(VCSession *vc); +int vc_queue_message(void *vcp, struct RTPMessage *msg); +int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height); + +#endif /* VIDEO_H */ diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 887c3702e..6d45077ab 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -2245,7 +2245,7 @@ static void connection_status_cb(Messenger *m) } -#ifdef LOGGING +#ifdef TOX_LOGGER #define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL static time_t lastdump = 0; static char IDString[crypto_box_PUBLICKEYBYTES * 2 + 1]; @@ -2321,7 +2321,7 @@ void do_messenger(Messenger *m) do_friends(m); connection_status_cb(m); -#ifdef LOGGING +#ifdef TOX_LOGGER if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { @@ -2420,7 +2420,7 @@ void do_messenger(Messenger *m) } } -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ } /* new messenger format for load/save, more robust and forward compatible */ diff --git a/toxcore/assoc.c b/toxcore/assoc.c index 44c4cc30c..932adc76c 100644 --- a/toxcore/assoc.c +++ b/toxcore/assoc.c @@ -878,9 +878,9 @@ void Assoc_self_client_id_changed(Assoc *assoc, const uint8_t *id) } } -#ifdef LOGGING +#ifdef TOX_LOGGER static char *idpart2str(uint8_t *id, size_t len); -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ /* refresh buckets */ void do_Assoc(Assoc *assoc, DHT *dht) @@ -974,7 +974,7 @@ void kill_Assoc(Assoc *assoc) } } -#ifdef LOGGING +#ifdef TOX_LOGGER static char buffer[crypto_box_PUBLICKEYBYTES * 2 + 1]; static char *idpart2str(uint8_t *id, size_t len) @@ -1028,4 +1028,4 @@ void Assoc_status(const Assoc *assoc) } } -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ diff --git a/toxcore/assoc.h b/toxcore/assoc.h index 1b4e1ff9e..65a2745d8 100644 --- a/toxcore/assoc.h +++ b/toxcore/assoc.h @@ -97,8 +97,8 @@ void do_Assoc(Assoc *assoc, DHT *dht); /* destroy */ void kill_Assoc(Assoc *assoc); -#ifdef LOGGING +#ifdef TOX_LOGGER void Assoc_status(const Assoc *assoc); -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ #endif /* !__ASSOC_H__ */ diff --git a/toxcore/logger.c b/toxcore/logger.c index e8aef7e0b..f19f76b10 100644 --- a/toxcore/logger.c +++ b/toxcore/logger.c @@ -44,7 +44,7 @@ #endif -struct logger { +struct Logger { FILE *log_file; LOG_LEVEL level; uint64_t start_time; /* Time when lib loaded */ @@ -87,7 +87,7 @@ char *strtime(char *dest, size_t max_len) */ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) { -#ifndef LOGGING /* Disabled */ +#ifndef TOX_LOGGER /* Disabled */ return NULL; #endif @@ -96,7 +96,7 @@ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) if (!retu) return NULL; - if ( pthread_mutex_init(retu->mutex, NULL) != 0 ) { + if (pthread_mutex_init(retu->mutex, NULL) != 0) { free(retu); return NULL; } @@ -110,7 +110,7 @@ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) if (!(retu->tstr = calloc(16, sizeof (char))) || !(retu->posstr = calloc(300, sizeof (char))) || - !(retu->msg = calloc(4096, sizeof (char))) ) + !(retu->msg = calloc(4096, sizeof (char)))) goto FAILURE; if (id) { @@ -147,7 +147,7 @@ FAILURE: void logger_kill(Logger *log) { -#ifndef LOGGING /* Disabled */ +#ifndef TOX_LOGGER /* Disabled */ return; #endif @@ -160,7 +160,7 @@ void logger_kill(Logger *log) free(log->posstr); free(log->msg); - if (fclose(log->log_file) != 0 ) + if (fclose(log->log_file) != 0) perror("Could not close log file"); pthread_mutex_unlock(log->mutex); @@ -177,7 +177,7 @@ void logger_kill_global(void) void logger_set_global(Logger *log) { -#ifndef LOGGING /* Disabled */ +#ifndef TOX_LOGGER /* Disabled */ return; #endif @@ -186,7 +186,7 @@ void logger_set_global(Logger *log) Logger *logger_get_global(void) { -#ifndef LOGGING /* Disabled */ +#ifndef TOX_LOGGER /* Disabled */ return NULL; #endif @@ -195,17 +195,17 @@ Logger *logger_get_global(void) void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, const char *format, ...) { -#ifndef LOGGING /* Disabled */ +#ifndef TOX_LOGGER /* Disabled */ return; #endif static const char *logger_format = - "%s " /* Logger id string */ - "%-16s" /* Time string of format: %m:%d %H:%M:%S */ - "%u " /* Thread id */ - "%-5s " /* Logger lever string */ - "%-20s " /* File:line string */ - "- %s" /* Output message */ + "%s " /* Logger id string */ + "%-16s" /* Time string of format: %m:%d %H:%M:%S */ + "%-12u " /* Thread id */ + "%-5s " /* Logger lever string */ + "%-20s " /* File:line string */ + "- %s" /* Output message */ WIN_CR "\n"; /* Every new print new line */ diff --git a/toxcore/logger.h b/toxcore/logger.h index 0513b32cc..4d3e3b542 100644 --- a/toxcore/logger.h +++ b/toxcore/logger.h @@ -43,7 +43,7 @@ typedef enum { LOG_ERROR } LOG_LEVEL; -typedef struct logger Logger; +typedef struct Logger Logger; /** * Set 'level' as the lowest printable level. If id == NULL, random number is used. @@ -66,21 +66,22 @@ void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, con /* To do some checks or similar only when logging, use this */ -#ifdef LOGGING +#ifdef TOX_LOGGER # define LOGGER_SCOPE(__SCOPE_DO__) do { __SCOPE_DO__ } while(0) # define LOGGER_WRITE(log, level, format, ...) \ - logger_write(log, level, __FILE__, __LINE__, format, ##__VA_ARGS__ ) + logger_write(log, level, __FILE__, __LINE__, format, ##__VA_ARGS__) #else +/* # warning "Logging disabled" */ # define LOGGER_SCOPE(__SCOPE_DO__) do {} while(0) # define LOGGER_WRITE(log, level, format, ...) do {} while(0) -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ /* To log with an logger */ -#define LOGGER_TRACE_(log, format, ...) LOGGER_WRITE(log, LOG_TRACE, format, ##__VA_ARGS__ ) -#define LOGGER_DEBUG_(log, format, ...) LOGGER_WRITE(log, LOG_DEBUG, format, ##__VA_ARGS__ ) -#define LOGGER_INFO_(log, format, ...) LOGGER_WRITE(log, LOG_INFO, format, ##__VA_ARGS__ ) -#define LOGGER_WARNING_(log, format, ...) LOGGER_WRITE(log, LOG_WARNING, format, ##__VA_ARGS__ ) -#define LOGGER_ERROR_(log, format, ...) LOGGER_WRITE(log, LOG_ERROR, format, ##__VA_ARGS__ ) +#define LOGGER_TRACE_(log, format, ...) LOGGER_WRITE(log, LOG_TRACE, format, ##__VA_ARGS__) +#define LOGGER_DEBUG_(log, format, ...) LOGGER_WRITE(log, LOG_DEBUG, format, ##__VA_ARGS__) +#define LOGGER_INFO_(log, format, ...) LOGGER_WRITE(log, LOG_INFO, format, ##__VA_ARGS__) +#define LOGGER_WARNING_(log, format, ...) LOGGER_WRITE(log, LOG_WARNING, format, ##__VA_ARGS__) +#define LOGGER_ERROR_(log, format, ...) LOGGER_WRITE(log, LOG_ERROR, format, ##__VA_ARGS__) /* To log with the global logger */ #define LOGGER_TRACE(format, ...) LOGGER_TRACE_(NULL, format, ##__VA_ARGS__) diff --git a/toxcore/network.c b/toxcore/network.c index 22ee42028..965e65f91 100644 --- a/toxcore/network.c +++ b/toxcore/network.c @@ -266,7 +266,7 @@ uint64_t current_time_monotonic(void) } /* In case no logging */ -#ifndef LOGGING +#ifndef TOX_LOGGER #define loglogdata(__message__, __buffer__, __buflen__, __ip_port__, __res__) #else #define data_0(__buflen__, __buffer__) __buflen__ > 4 ? ntohl(*(uint32_t *)&__buffer__[1]) : 0 @@ -287,7 +287,7 @@ uint64_t current_time_monotonic(void) __buffer__[0], __message__, (size_t)__res__, (!__res__ ? '!' : '>'), __buflen__, \ ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), 0, "OK", data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ /* Basic network functions: * Function to send packet(data) of length length to ip_port. @@ -615,9 +615,9 @@ Networking_Core *new_networking_ex(IP ip, uint16_t port_from, uint16_t port_to, } if (ip.family == AF_INET6) { -#ifdef LOGGING +#ifdef TOX_LOGGER int is_dualstack = -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ set_socket_dualstack(temp->sock); LOGGER_DEBUG( "Dual-stack socket: %s", is_dualstack ? "enabled" : "Failed to enable, won't be able to receive from/send to IPv4 addresses" ); @@ -628,9 +628,9 @@ Networking_Core *new_networking_ex(IP ip, uint16_t port_from, uint16_t port_to, mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; mreq.ipv6mr_interface = 0; -#ifdef LOGGING +#ifdef TOX_LOGGER int res = -#endif /* LOGGING */ +#endif /* TOX_LOGGER */ setsockopt(temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)); LOGGER_DEBUG(res < 0 ? "Failed to activate local multicast membership. (%u, %s)" : diff --git a/toxcore/util.c b/toxcore/util.c index c320cdc48..81fa84c6e 100644 --- a/toxcore/util.c +++ b/toxcore/util.c @@ -192,3 +192,85 @@ int create_recursive_mutex(pthread_mutex_t *mutex) return 0; } + + +struct RingBuffer { + uint16_t size; /* Max size */ + uint16_t start; + uint16_t end; + void **data; +}; + +bool rb_full(const RingBuffer *b) +{ + return (b->end + 1) % b->size == b->start; +} +bool rb_empty(const RingBuffer *b) +{ + return b->end == b->start; +} +void* rb_write(RingBuffer *b, void *p) +{ + void* rc = NULL; + if ((b->end + 1) % b->size == b->start) /* full */ + rc = b->data[b->start]; + + b->data[b->end] = p; + b->end = (b->end + 1) % b->size; + + if (b->end == b->start) + b->start = (b->start + 1) % b->size; + + return rc; +} +bool rb_read(RingBuffer *b, void **p) +{ + if (b->end == b->start) { /* Empty */ + *p = NULL; + return false; + } + + *p = b->data[b->start]; + b->start = (b->start + 1) % b->size; + return true; +} +RingBuffer *rb_new(int size) +{ + RingBuffer *buf = calloc(sizeof(RingBuffer), 1); + + if (!buf) return NULL; + + buf->size = size + 1; /* include empty elem */ + + if (!(buf->data = calloc(buf->size, sizeof(void *)))) { + free(buf); + return NULL; + } + + return buf; +} +void rb_kill(RingBuffer *b) +{ + if (b) { + free(b->data); + free(b); + } +} +uint16_t rb_size(const RingBuffer* b) +{ + if (rb_empty(b)) + return 0; + + return + b->end > b->start ? + b->end - b->start : + (b->size - b->start) + b->end; +} +uint16_t rb_data(const RingBuffer* b, void** dest) +{ + uint16_t i = 0; + for (; i < rb_size(b); i++) + dest[i] = b->data[(b->start + i) % b->size]; + + return i; +} diff --git a/toxcore/util.h b/toxcore/util.h index fde9f03cb..7cf631789 100644 --- a/toxcore/util.h +++ b/toxcore/util.h @@ -30,6 +30,7 @@ #include #define MIN(a,b) (((a)<(b))?(a):(b)) +#define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } void unix_time_update(); uint64_t unix_time(); @@ -54,6 +55,18 @@ typedef int (*load_state_callback_func)(void *outer, const uint8_t *data, uint32 int load_state(load_state_callback_func load_state_callback, void *outer, const uint8_t *data, uint32_t length, uint16_t cookie_inner); +/* Returns -1 if failed or 0 if success */ int create_recursive_mutex(pthread_mutex_t *mutex); +/* Ring buffer */ +typedef struct RingBuffer RingBuffer; +bool rb_full(const RingBuffer *b); +bool rb_empty(const RingBuffer *b); +void* rb_write(RingBuffer* b, void* p); +bool rb_read(RingBuffer* b, void** p); +RingBuffer *rb_new(int size); +void rb_kill(RingBuffer *b); +uint16_t rb_size(const RingBuffer *b); +uint16_t rb_data(const RingBuffer* b, void** dest); + #endif /* __UTIL_H__ */