diff options
36 files changed, 5971 insertions, 4279 deletions
@@ -78,7 +78,7 @@ Note, if you install from ports select NaCl for performance, and sodium if you w | |||
78 | 78 | ||
79 | You should get and install [libsodium](https://github.com/jedisct1/libsodium). If you have installed `libsodium` from repo, ommit this step, and jump directly to [compiling toxcore](#compile-toxcore): | 79 | You should get and install [libsodium](https://github.com/jedisct1/libsodium). If you have installed `libsodium` from repo, ommit this step, and jump directly to [compiling toxcore](#compile-toxcore): |
80 | ```bash | 80 | ```bash |
81 | git clone git://github.com/jedisct1/libsodium.git | 81 | git clone https://github.com/jedisct1/libsodium.git |
82 | cd libsodium | 82 | cd libsodium |
83 | git checkout tags/1.0.3 | 83 | git checkout tags/1.0.3 |
84 | ./autogen.sh | 84 | ./autogen.sh |
@@ -93,7 +93,7 @@ Or if checkinstall is not easily available for your distribution (e.g., Fedora), | |||
93 | this will install the libs to /usr/local/lib and the headers to /usr/local/include: | 93 | this will install the libs to /usr/local/lib and the headers to /usr/local/include: |
94 | 94 | ||
95 | ```bash | 95 | ```bash |
96 | git clone git://github.com/jedisct1/libsodium.git | 96 | git clone https://github.com/jedisct1/libsodium.git |
97 | cd libsodium | 97 | cd libsodium |
98 | git checkout tags/1.0.3 | 98 | git checkout tags/1.0.3 |
99 | ./autogen.sh | 99 | ./autogen.sh |
@@ -121,7 +121,7 @@ sudo ldconfig | |||
121 | 121 | ||
122 | Then clone this repo, generate makefile, and install `toxcore` system-wide: | 122 | Then clone this repo, generate makefile, and install `toxcore` system-wide: |
123 | ```bash | 123 | ```bash |
124 | git clone git://github.com/irungentoo/toxcore.git | 124 | git clone https://github.com/irungentoo/toxcore.git |
125 | cd toxcore | 125 | cd toxcore |
126 | autoreconf -i | 126 | autoreconf -i |
127 | ./configure | 127 | ./configure |
@@ -153,7 +153,7 @@ brew install libtool automake autoconf libsodium check | |||
153 | ``` | 153 | ``` |
154 | Then clone this repo and generate makefile: | 154 | Then clone this repo and generate makefile: |
155 | ```bash | 155 | ```bash |
156 | git clone git://github.com/irungentoo/toxcore.git | 156 | git clone https://github.com/irungentoo/toxcore.git |
157 | cd toxcore | 157 | cd toxcore |
158 | autoreconf -i | 158 | autoreconf -i |
159 | ./configure | 159 | ./configure |
@@ -1,9 +1,11 @@ | |||
1 | ![Project Tox](https://raw.github.com/irungentoo/toxcore/master/other/tox.png "Project Tox") | 1 | ![Project Tox](https://raw.github.com/irungentoo/toxcore/master/other/tox.png "Project Tox") |
2 | *** | 2 | *** |
3 | 3 | ||
4 | With the rise of governmental monitoring programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures their users full privacy and secure message delivery.<br /> <br /> | 4 | With the rise of government surveillance programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures full privacy and secure message delivery.<br /> <br /> |
5 | 5 | ||
6 | [**Website**](https://tox.chat) **|** [**Wiki**](https://wiki.tox.chat/) **|** [**Blog**](https://blog.tox.chat/) **|** [**FAQ**](https://wiki.tox.chat/doku.php?id=users:faq) **|** [**Binaries/Downloads**](https://wiki.tox.chat/Binaries) **|** [**Clients**](https://wiki.tox.chat/doku.php?id=clients) **|** [**Compiling**](/INSTALL.md) **|** **IRC Channel:** [#tox@freenode](https://webchat.freenode.net/?channels=tox) | 6 | [**Website**](https://tox.chat) **|** [**Wiki**](https://wiki.tox.chat/) **|** [**Blog**](https://blog.tox.chat/) **|** [**FAQ**](https://wiki.tox.chat/doku.php?id=users:faq) **|** [**Binaries/Downloads**](https://wiki.tox.chat/Binaries) **|** [**Clients**](https://wiki.tox.chat/doku.php?id=clients) **|** [**Compiling**](/INSTALL.md) |
7 | |||
8 | **IRC Channels:** [#tox@freenode](https://webchat.freenode.net/?channels=tox), [#tox-dev@freenode](https://webchat.freenode.net/?channels=tox-dev) | ||
7 | 9 | ||
8 | 10 | ||
9 | ## The Complex Stuff: | 11 | ## The Complex Stuff: |
@@ -36,4 +38,3 @@ The goal of this project is to create a configuration-free P2P Skype replacement | |||
36 | - [Compiling](/INSTALL.md) | 38 | - [Compiling](/INSTALL.md) |
37 | - [DHT Protocol](/docs/updates/DHT.md)<br /> | 39 | - [DHT Protocol](/docs/updates/DHT.md)<br /> |
38 | - [Crypto](/docs/updates/Crypto.md)<br /> | 40 | - [Crypto](/docs/updates/Crypto.md)<br /> |
39 | |||
diff --git a/auto_tests/dht_test.c b/auto_tests/dht_test.c index 32985bca..8304a046 100644 --- a/auto_tests/dht_test.c +++ b/auto_tests/dht_test.c | |||
@@ -153,7 +153,8 @@ void test_addto_lists_bad(DHT *dht, | |||
153 | { | 153 | { |
154 | // check "bad" clients replacement | 154 | // check "bad" clients replacement |
155 | int used, test1, test2, test3; | 155 | int used, test1, test2, test3; |
156 | uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES], test_id3[crypto_box_PUBLICKEYBYTES]; | 156 | uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES], |
157 | test_id3[crypto_box_PUBLICKEYBYTES]; | ||
157 | uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0; | 158 | uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0; |
158 | 159 | ||
159 | randombytes(public_key, sizeof(public_key)); | 160 | randombytes(public_key, sizeof(public_key)); |
@@ -196,7 +197,8 @@ void test_addto_lists_possible_bad(DHT *dht, | |||
196 | { | 197 | { |
197 | // check "possibly bad" clients replacement | 198 | // check "possibly bad" clients replacement |
198 | int used, test1, test2, test3; | 199 | int used, test1, test2, test3; |
199 | uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES], test_id3[crypto_box_PUBLICKEYBYTES]; | 200 | uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES], |
201 | test_id3[crypto_box_PUBLICKEYBYTES]; | ||
200 | uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0; | 202 | uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0; |
201 | 203 | ||
202 | randombytes(public_key, sizeof(public_key)); | 204 | randombytes(public_key, sizeof(public_key)); |
diff --git a/auto_tests/messenger_test.c b/auto_tests/messenger_test.c index 2a813c9b..2b0b2519 100644 --- a/auto_tests/messenger_test.c +++ b/auto_tests/messenger_test.c | |||
@@ -135,7 +135,7 @@ START_TEST(test_m_addfriend) | |||
135 | if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, really_bad_len) != FAERR_TOOLONG) | 135 | if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, really_bad_len) != FAERR_TOOLONG) |
136 | ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", really_bad_len); | 136 | ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", really_bad_len); |
137 | */ | 137 | */ |
138 | /* this will error if the original m_addfriend_norequest() failed */ | 138 | /* this will return an error if the original m_addfriend_norequest() failed */ |
139 | /* if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, good_len) != FAERR_ALREADYSENT) | 139 | /* if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, good_len) != FAERR_ALREADYSENT) |
140 | ck_abort_msg("m_addfriend did NOT catch adding a friend we already have.\n" | 140 | ck_abort_msg("m_addfriend did NOT catch adding a friend we already have.\n" |
141 | "(this can be caused by the error of m_addfriend_norequest in" | 141 | "(this can be caused by the error of m_addfriend_norequest in" |
@@ -144,7 +144,7 @@ START_TEST(test_m_addfriend) | |||
144 | if(m_addfriend(m, (uint8_t *)good_id_b, (uint8_t *)bad_data, bad_len) != FAERR_NOMESSAGE) | 144 | if(m_addfriend(m, (uint8_t *)good_id_b, (uint8_t *)bad_data, bad_len) != FAERR_NOMESSAGE) |
145 | ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", bad_len); | 145 | ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", bad_len); |
146 | */ | 146 | */ |
147 | /* this should REALLY error */ | 147 | /* this should REALLY return an error */ |
148 | /* | 148 | /* |
149 | * TODO: validate client_id in m_addfriend? | 149 | * TODO: validate client_id in m_addfriend? |
150 | if(m_addfriend((uint8_t *)bad_id, (uint8_t *)good_data, good_len) >= 0) | 150 | if(m_addfriend((uint8_t *)bad_id, (uint8_t *)good_data, good_len) >= 0) |
diff --git a/auto_tests/toxav_basic_test.c b/auto_tests/toxav_basic_test.c index af8d91e9..20432dca 100644 --- a/auto_tests/toxav_basic_test.c +++ b/auto_tests/toxav_basic_test.c | |||
@@ -2,23 +2,31 @@ | |||
2 | #include "config.h" | 2 | #include "config.h" |
3 | #endif | 3 | #endif |
4 | 4 | ||
5 | #ifndef HAVE_LIBCHECK | ||
6 | # include <assert.h> | ||
7 | |||
8 | # define ck_assert(X) assert(X); | ||
9 | # define START_TEST(NAME) void NAME () | ||
10 | # define END_TEST | ||
11 | #else | ||
12 | # include "helpers.h" | ||
13 | #endif | ||
14 | |||
5 | #include <sys/types.h> | 15 | #include <sys/types.h> |
6 | #include <stdint.h> | 16 | #include <stdint.h> |
7 | #include <string.h> | 17 | #include <string.h> |
8 | #include <stdio.h> | 18 | #include <stdio.h> |
9 | #include <check.h> | ||
10 | #include <stdlib.h> | 19 | #include <stdlib.h> |
11 | #include <time.h> | 20 | #include <time.h> |
12 | #include <assert.h> | ||
13 | 21 | ||
14 | #include <vpx/vpx_image.h> | 22 | #include <vpx/vpx_image.h> |
15 | 23 | ||
16 | #include "../toxcore/tox.h" | 24 | #include "../toxcore/tox.h" |
25 | #include "../toxcore/util.h" | ||
17 | #include "../toxcore/logger.h" | 26 | #include "../toxcore/logger.h" |
18 | #include "../toxcore/crypto_core.h" | 27 | #include "../toxcore/crypto_core.h" |
19 | #include "../toxav/toxav.h" | 28 | #include "../toxav/toxav.h" |
20 | 29 | ||
21 | #include "helpers.h" | ||
22 | 30 | ||
23 | #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) | 31 | #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) |
24 | #define c_sleep(x) Sleep(1*x) | 32 | #define c_sleep(x) Sleep(1*x) |
@@ -28,604 +36,564 @@ | |||
28 | #endif | 36 | #endif |
29 | 37 | ||
30 | 38 | ||
39 | #define TEST_REGULAR_AV 1 | ||
40 | #define TEST_REGULAR_A 1 | ||
41 | #define TEST_REGULAR_V 1 | ||
42 | #define TEST_REJECT 1 | ||
43 | #define TEST_CANCEL 1 | ||
44 | #define TEST_MUTE_UNMUTE 1 | ||
45 | #define TEST_STOP_RESUME_PAYLOAD 1 | ||
46 | #define TEST_PAUSE_RESUME_SEND 1 | ||
31 | 47 | ||
32 | typedef enum _CallStatus { | ||
33 | none, | ||
34 | InCall, | ||
35 | Ringing, | ||
36 | Ended, | ||
37 | Rejected, | ||
38 | Canceled, | ||
39 | TimedOut | ||
40 | 48 | ||
41 | } CallStatus; | 49 | typedef struct { |
50 | bool incoming; | ||
51 | uint32_t state; | ||
42 | 52 | ||
43 | typedef struct _Party { | 53 | } CallControl; |
44 | CallStatus status; | ||
45 | ToxAv *av; | ||
46 | time_t *CallStarted; | ||
47 | int call_index; | ||
48 | } Party; | ||
49 | 54 | ||
50 | typedef struct _Status { | ||
51 | Party Alice; | ||
52 | Party Bob; | ||
53 | } Status; | ||
54 | 55 | ||
55 | /* My default settings */ | 56 | /** |
56 | static ToxAvCSettings muhcaps; | 57 | * Callbacks |
57 | 58 | */ | |
58 | void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) | 59 | void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) |
59 | { | 60 | { |
60 | if (length == 7 && memcmp("gentoo", data, 7) == 0) { | 61 | (void) av; |
61 | tox_friend_add_norequest(m, public_key, 0); | 62 | (void) friend_number; |
62 | } | 63 | (void) audio_enabled; |
63 | } | 64 | (void) video_enabled; |
64 | 65 | ||
65 | 66 | printf("Handling CALL callback\n"); | |
66 | /******************************************************************************/ | 67 | ((CallControl *)user_data)->incoming = true; |
67 | void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) | ||
68 | { | ||
69 | Status *cast = _arg; | ||
70 | |||
71 | if (cast->Alice.av == av) { | ||
72 | // ... | ||
73 | } else if (cast->Bob.av == av) { | ||
74 | /* Bob always receives invite */ | ||
75 | cast->Bob.status = Ringing; | ||
76 | cast->Bob.call_index = call_index; | ||
77 | } | ||
78 | } | 68 | } |
79 | void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) | 69 | void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) |
80 | { | 70 | { |
81 | Status *cast = _arg; | 71 | (void) av; |
72 | (void) friend_number; | ||
82 | 73 | ||
83 | if (cast->Alice.av == av) { | 74 | printf("Handling CALL STATE callback: %d\n", state); |
84 | /* Alice always sends invite */ | 75 | ((CallControl *)user_data)->state = state; |
85 | cast->Alice.status = Ringing; | ||
86 | } else if (cast->Bob.av == av) { | ||
87 | // ... | ||
88 | } | ||
89 | } | 76 | } |
90 | 77 | void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, | |
91 | 78 | uint16_t width, uint16_t height, | |
92 | void callback_call_started ( void *av, int32_t call_index, void *_arg ) | 79 | uint8_t const *y, uint8_t const *u, uint8_t const *v, |
80 | int32_t ystride, int32_t ustride, int32_t vstride, | ||
81 | void *user_data) | ||
93 | { | 82 | { |
94 | Status *cast = _arg; | 83 | (void) av; |
95 | 84 | (void) friend_number; | |
96 | if (cast->Alice.av == av) { | 85 | (void) width; |
97 | printf("Call started on Alices side...\n"); | 86 | (void) height; |
98 | cast->Alice.status = InCall; | 87 | (void) y; |
99 | toxav_prepare_transmission(av, call_index, 1); | 88 | (void) u; |
100 | } else if (cast->Bob.av == av) { | 89 | (void) v; |
101 | printf("Call started on Bob side...\n"); | 90 | (void) ystride; |
102 | cast->Bob.status = InCall; | 91 | (void) ustride; |
103 | toxav_prepare_transmission(av, call_index, 1); | 92 | (void) vstride; |
104 | } | 93 | (void) user_data; |
94 | printf("Received video payload\n"); | ||
105 | } | 95 | } |
106 | void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) | 96 | void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, |
97 | int16_t const *pcm, | ||
98 | size_t sample_count, | ||
99 | uint8_t channels, | ||
100 | uint32_t sampling_rate, | ||
101 | void *user_data) | ||
107 | { | 102 | { |
108 | Status *cast = _arg; | 103 | (void) av; |
109 | 104 | (void) friend_number; | |
110 | if (cast->Alice.av == av) { | 105 | (void) pcm; |
111 | // ... | 106 | (void) sample_count; |
112 | } else if (cast->Bob.av == av) { | 107 | (void) channels; |
113 | printf ( "Call Canceled for Bob!\n" ); | 108 | (void) sampling_rate; |
114 | cast->Bob.status = Canceled; | 109 | (void) user_data; |
115 | } | 110 | printf("Received audio payload\n"); |
116 | } | 111 | } |
117 | void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) | 112 | void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) |
118 | { | 113 | { |
119 | Status *cast = _arg; | 114 | (void) userdata; |
120 | 115 | ||
121 | printf ( "Call rejected by Bob!\n" | 116 | if (length == 7 && memcmp("gentoo", data, 7) == 0) { |
122 | "Call ended for Alice!\n" ); | 117 | ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); |
123 | |||
124 | /* If Bob rejects, call is ended for alice and she sends ending */ | ||
125 | if (cast->Alice.av == av) { | ||
126 | cast->Alice.status = Rejected; | ||
127 | } else if (cast->Bob.av == av) { | ||
128 | //... ignor | ||
129 | } | ||
130 | } | ||
131 | void callback_call_ended ( void *av, int32_t call_index, void *_arg ) | ||
132 | { | ||
133 | Status *cast = _arg; | ||
134 | |||
135 | if (cast->Alice.av == av) { | ||
136 | printf ( "Call ended for Alice!\n" ); | ||
137 | cast->Alice.status = Ended; | ||
138 | } else if (cast->Bob.av == av) { | ||
139 | printf ( "Call ended for Bob!\n" ); | ||
140 | cast->Bob.status = Ended; | ||
141 | } | 118 | } |
142 | } | 119 | } |
143 | 120 | ||
144 | void callback_peer_cs_change ( void *av, int32_t call_index, void *_arg ) | ||
145 | { | ||
146 | ToxAvCSettings csettings; | ||
147 | toxav_get_peer_csettings(av, call_index, 0, &csettings); | ||
148 | |||
149 | printf("Peer changing settings to: \n" | ||
150 | "Type: %u \n" | ||
151 | "Video bitrate: %u \n" | ||
152 | "Video height: %u \n" | ||
153 | "Video width: %u \n" | ||
154 | "Audio bitrate: %u \n" | ||
155 | "Audio framedur: %u \n" | ||
156 | "Audio sample rate: %u \n" | ||
157 | "Audio channels: %u \n", | ||
158 | csettings.call_type, | ||
159 | csettings.video_bitrate, | ||
160 | csettings.max_video_height, | ||
161 | csettings.max_video_width, | ||
162 | csettings.audio_bitrate, | ||
163 | csettings.audio_frame_duration, | ||
164 | csettings.audio_sample_rate, | ||
165 | csettings.audio_channels | ||
166 | ); | ||
167 | } | ||
168 | |||
169 | void callback_self_cs_change ( void *av, int32_t call_index, void *_arg ) | ||
170 | { | ||
171 | ToxAvCSettings csettings; | ||
172 | toxav_get_peer_csettings(av, call_index, 0, &csettings); | ||
173 | |||
174 | printf("Changed settings to: \n" | ||
175 | "Type: %u \n" | ||
176 | "Video bitrate: %u \n" | ||
177 | "Video height: %u \n" | ||
178 | "Video width: %u \n" | ||
179 | "Audio bitrate: %u \n" | ||
180 | "Audio framedur: %u \n" | ||
181 | "Audio sample rate: %u \n" | ||
182 | "Audio channels: %u \n", | ||
183 | csettings.call_type, | ||
184 | csettings.video_bitrate, | ||
185 | csettings.max_video_height, | ||
186 | csettings.max_video_width, | ||
187 | csettings.audio_bitrate, | ||
188 | csettings.audio_frame_duration, | ||
189 | csettings.audio_sample_rate, | ||
190 | csettings.audio_channels | ||
191 | ); | ||
192 | } | ||
193 | 121 | ||
194 | void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) | 122 | /** |
123 | * Iterate helper | ||
124 | */ | ||
125 | int iterate_tox(Tox *bootstrap, Tox *Alice, Tox *Bob) | ||
195 | { | 126 | { |
196 | Status *cast = _arg; | 127 | tox_iterate(bootstrap); |
197 | printf("Call timed-out!\n"); | 128 | tox_iterate(Alice); |
129 | tox_iterate(Bob); | ||
198 | 130 | ||
199 | if (cast->Alice.av == av) { | 131 | return MIN(tox_iteration_interval(Alice), tox_iteration_interval(Bob)); |
200 | cast->Alice.status = TimedOut; | ||
201 | } else if (cast->Bob.av == av) { | ||
202 | cast->Bob.status = TimedOut; | ||
203 | } | ||
204 | } | 132 | } |
205 | 133 | ||
206 | void callback_audio (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data) | ||
207 | {} | ||
208 | 134 | ||
209 | void callback_video (void *agent, int32_t call_idx, const vpx_image_t *img, void *data) | ||
210 | {} | ||
211 | 135 | ||
212 | void register_callbacks(ToxAv *av, void *data) | 136 | START_TEST(test_AV_flows) |
213 | { | 137 | { |
214 | toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); | 138 | Tox *Alice, *Bob, *bootstrap; |
215 | toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); | 139 | ToxAV *AliceAV, *BobAV; |
216 | toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); | ||
217 | toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); | ||
218 | toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); | ||
219 | toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); | ||
220 | toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); | ||
221 | toxav_register_callstate_callback(av, callback_peer_cs_change, av_OnPeerCSChange, data); | ||
222 | toxav_register_callstate_callback(av, callback_self_cs_change, av_OnSelfCSChange, data); | ||
223 | toxav_register_audio_callback(av, callback_audio, NULL); | ||
224 | toxav_register_video_callback(av, callback_video, NULL); | ||
225 | } | ||
226 | 140 | ||
141 | CallControl AliceCC, BobCC; | ||
227 | 142 | ||
228 | /*************************************************************************************************/ | 143 | { |
144 | TOX_ERR_NEW error; | ||
229 | 145 | ||
230 | /* Alice calls bob and the call starts. | 146 | bootstrap = tox_new(NULL, &error); |
231 | * What happens during the call is defined after. To quit the loop use: step++; | 147 | ck_assert(error == TOX_ERR_NEW_OK); |
232 | */ | ||
233 | #define CALL_AND_START_LOOP(AliceCallType, BobCallType) \ | ||
234 | { int step = 0, running = 1; while (running) {\ | ||
235 | tox_iterate(bootstrap_node); tox_iterate(Alice); tox_iterate(Bob); \ | ||
236 | toxav_do(status_control.Bob.av); toxav_do(status_control.Alice.av); \ | ||
237 | switch ( step ) {\ | ||
238 | case 0: /* Alice */ printf("Alice is calling...\n");\ | ||
239 | toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); step++; break;\ | ||
240 | case 1: /* Bob */ if (status_control.Bob.status == Ringing) { printf("Bob answers...\n");\ | ||
241 | cur_time = time(NULL); toxav_answer(status_control.Bob.av, status_control.Bob.call_index, &muhcaps); step++; } break; \ | ||
242 | case 2: /* Rtp transmission */ \ | ||
243 | if (status_control.Bob.status == InCall && status_control.Alice.status == InCall) | ||
244 | |||
245 | |||
246 | #define TERMINATE_SCOPE() break;\ | ||
247 | case 3: /* Wait for Both to have status ended */\ | ||
248 | if (status_control.Alice.status == Ended && status_control.Bob.status == Ended) running = 0; break; } c_sleep(20); } } printf("\n"); | ||
249 | 148 | ||
250 | START_TEST(test_AV_flows) | 149 | Alice = tox_new(NULL, &error); |
251 | { | 150 | ck_assert(error == TOX_ERR_NEW_OK); |
252 | long long unsigned int cur_time = time(NULL); | ||
253 | Tox *bootstrap_node = tox_new(0, 0); | ||
254 | Tox *Alice = tox_new(0, 0); | ||
255 | Tox *Bob = tox_new(0, 0); | ||
256 | 151 | ||
257 | ck_assert_msg(bootstrap_node || Alice || Bob, "Failed to create 3 tox instances"); | 152 | Bob = tox_new(NULL, &error); |
153 | ck_assert(error == TOX_ERR_NEW_OK); | ||
154 | } | ||
155 | |||
156 | printf("Created 3 instances of Tox\n"); | ||
157 | printf("Preparing network...\n"); | ||
158 | long long unsigned int cur_time = time(NULL); | ||
258 | 159 | ||
259 | uint32_t to_compare = 974536; | 160 | uint32_t to_compare = 974536; |
260 | tox_callback_friend_request(Alice, accept_friend_request, &to_compare); | ||
261 | uint8_t address[TOX_ADDRESS_SIZE]; | 161 | uint8_t address[TOX_ADDRESS_SIZE]; |
162 | |||
163 | tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); | ||
262 | tox_self_get_address(Alice, address); | 164 | tox_self_get_address(Alice, address); |
263 | uint32_t test = tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, 0); | ||
264 | 165 | ||
265 | ck_assert_msg(test == 0, "Failed to add friend error code: %i", test); | 166 | |
167 | ck_assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); | ||
266 | 168 | ||
267 | uint8_t off = 1; | 169 | uint8_t off = 1; |
268 | 170 | ||
269 | while (1) { | 171 | while (1) { |
270 | tox_iterate(bootstrap_node); | 172 | iterate_tox(bootstrap, Alice, Bob); |
271 | tox_iterate(Alice); | ||
272 | tox_iterate(Bob); | ||
273 | 173 | ||
274 | if (tox_self_get_connection_status(bootstrap_node) && tox_self_get_connection_status(Alice) | 174 | if (tox_self_get_connection_status(bootstrap) && |
275 | && tox_self_get_connection_status(Bob) | 175 | tox_self_get_connection_status(Alice) && |
276 | && off) { | 176 | tox_self_get_connection_status(Bob) && off) { |
277 | printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); | 177 | printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); |
278 | off = 0; | 178 | off = 0; |
279 | } | 179 | } |
280 | 180 | ||
281 | if (tox_friend_get_connection_status(Alice, 0, 0) && tox_friend_get_connection_status(Bob, 0, 0)) | 181 | if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && |
182 | tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP) | ||
282 | break; | 183 | break; |
283 | 184 | ||
284 | c_sleep(20); | 185 | c_sleep(20); |
285 | } | 186 | } |
286 | 187 | ||
287 | printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); | ||
288 | 188 | ||
289 | muhcaps = av_DefaultSettings; | 189 | { |
290 | muhcaps.max_video_height = muhcaps.max_video_width = 128; | 190 | TOXAV_ERR_NEW error; |
291 | 191 | AliceAV = toxav_new(Alice, &error); | |
292 | Status status_control = { | 192 | ck_assert(error == TOXAV_ERR_NEW_OK); |
293 | {none, toxav_new(Alice, 1), NULL, -1}, | ||
294 | {none, toxav_new(Bob, 1), NULL, -1}, | ||
295 | }; | ||
296 | |||
297 | ck_assert_msg(status_control.Alice.av || status_control.Bob.av, "Failed to create 2 toxav instances"); | ||
298 | |||
299 | |||
300 | register_callbacks(status_control.Alice.av, &status_control); | ||
301 | register_callbacks(status_control.Bob.av, &status_control); | ||
302 | |||
303 | const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); | ||
304 | int16_t sample_payload[frame_size]; | ||
305 | randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); | ||
306 | 193 | ||
307 | uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; | 194 | BobAV = toxav_new(Bob, &error); |
308 | int payload_size; | 195 | ck_assert(error == TOXAV_ERR_NEW_OK); |
196 | } | ||
309 | 197 | ||
310 | vpx_image_t *sample_image = vpx_img_alloc(NULL, VPX_IMG_FMT_I420, 128, 128, 1); | 198 | toxav_callback_call(AliceAV, t_toxav_call_cb, &AliceCC); |
199 | toxav_callback_call_state(AliceAV, t_toxav_call_state_cb, &AliceCC); | ||
200 | toxav_callback_video_receive_frame(AliceAV, t_toxav_receive_video_frame_cb, &AliceCC); | ||
201 | toxav_callback_audio_receive_frame(AliceAV, t_toxav_receive_audio_frame_cb, &AliceCC); | ||
202 | |||
203 | toxav_callback_call(BobAV, t_toxav_call_cb, &BobCC); | ||
204 | toxav_callback_call_state(BobAV, t_toxav_call_state_cb, &BobCC); | ||
205 | toxav_callback_video_receive_frame(BobAV, t_toxav_receive_video_frame_cb, &BobCC); | ||
206 | toxav_callback_audio_receive_frame(BobAV, t_toxav_receive_audio_frame_cb, &BobCC); | ||
207 | |||
208 | printf("Created 2 instances of ToxAV\n"); | ||
209 | printf("All set after %llu seconds!\n", time(NULL) - cur_time); | ||
210 | |||
211 | |||
212 | #define REGULAR_CALL_FLOW(A_BR, V_BR) \ | ||
213 | do { \ | ||
214 | memset(&AliceCC, 0, sizeof(CallControl)); \ | ||
215 | memset(&BobCC, 0, sizeof(CallControl)); \ | ||
216 | \ | ||
217 | TOXAV_ERR_CALL rc; \ | ||
218 | toxav_call(AliceAV, 0, A_BR, V_BR, &rc); \ | ||
219 | \ | ||
220 | if (rc != TOXAV_ERR_CALL_OK) { \ | ||
221 | printf("toxav_call failed: %d\n", rc); \ | ||
222 | ck_assert(0); \ | ||
223 | } \ | ||
224 | \ | ||
225 | \ | ||
226 | long long unsigned int start_time = time(NULL); \ | ||
227 | \ | ||
228 | \ | ||
229 | while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) { \ | ||
230 | \ | ||
231 | if (BobCC.incoming) { \ | ||
232 | TOXAV_ERR_ANSWER rc; \ | ||
233 | toxav_answer(BobAV, 0, A_BR, V_BR, &rc); \ | ||
234 | \ | ||
235 | if (rc != TOXAV_ERR_ANSWER_OK) { \ | ||
236 | printf("toxav_answer failed: %d\n", rc); \ | ||
237 | ck_assert(0); \ | ||
238 | } \ | ||
239 | BobCC.incoming = false; \ | ||
240 | } else { \ | ||
241 | /* TODO rtp */ \ | ||
242 | \ | ||
243 | if (time(NULL) - start_time >= 1) { \ | ||
244 | \ | ||
245 | TOXAV_ERR_CALL_CONTROL rc; \ | ||
246 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); \ | ||
247 | \ | ||
248 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { \ | ||
249 | printf("toxav_call_control failed: %d\n", rc); \ | ||
250 | ck_assert(0); \ | ||
251 | } \ | ||
252 | } \ | ||
253 | } \ | ||
254 | \ | ||
255 | iterate_tox(bootstrap, Alice, Bob); \ | ||
256 | } \ | ||
257 | printf("Success!\n");\ | ||
258 | } while(0) | ||
259 | |||
260 | if (TEST_REGULAR_AV) { | ||
261 | printf("\nTrying regular call (Audio and Video)...\n"); | ||
262 | REGULAR_CALL_FLOW(48, 4000); | ||
263 | } | ||
311 | 264 | ||
312 | memcpy(sample_image->planes[VPX_PLANE_Y], sample_payload, 10); | 265 | if (TEST_REGULAR_A) { |
313 | memcpy(sample_image->planes[VPX_PLANE_U], sample_payload, 10); | 266 | printf("\nTrying regular call (Audio only)...\n"); |
314 | memcpy(sample_image->planes[VPX_PLANE_V], sample_payload, 10); | 267 | REGULAR_CALL_FLOW(48, 0); |
268 | } | ||
315 | 269 | ||
270 | if (TEST_REGULAR_V) { | ||
271 | printf("\nTrying regular call (Video only)...\n"); | ||
272 | REGULAR_CALL_FLOW(0, 4000); | ||
273 | } | ||
316 | 274 | ||
317 | /************************************************************************************************* | 275 | #undef REGULAR_CALL_FLOW |
318 | * Successful flows (when call starts) | ||
319 | */ | ||
320 | 276 | ||
321 | /* | 277 | if (TEST_REJECT) { /* Alice calls; Bob rejects */ |
322 | * Call with audio only on both sides. Alice calls Bob. | 278 | printf("\nTrying reject flow...\n"); |
323 | */ | ||
324 | 279 | ||
280 | memset(&AliceCC, 0, sizeof(CallControl)); | ||
281 | memset(&BobCC, 0, sizeof(CallControl)); | ||
325 | 282 | ||
326 | CALL_AND_START_LOOP(TypeAudio, TypeAudio) { | 283 | { |
327 | /* Both send */ | 284 | TOXAV_ERR_CALL rc; |
328 | payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, | 285 | toxav_call(AliceAV, 0, 48, 0, &rc); |
329 | 1000, sample_payload, frame_size); | ||
330 | 286 | ||
331 | if ( payload_size < 0 ) { | 287 | if (rc != TOXAV_ERR_CALL_OK) { |
332 | ck_assert_msg ( 0, "Failed to encode payload" ); | 288 | printf("toxav_call failed: %d\n", rc); |
289 | ck_assert(0); | ||
290 | } | ||
333 | } | 291 | } |
334 | 292 | ||
335 | toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); | 293 | while (!BobCC.incoming) |
294 | iterate_tox(bootstrap, Alice, Bob); | ||
336 | 295 | ||
337 | payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, | 296 | /* Reject */ |
338 | sample_payload, frame_size); | 297 | { |
298 | TOXAV_ERR_CALL_CONTROL rc; | ||
299 | toxav_call_control(BobAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
339 | 300 | ||
340 | if ( payload_size < 0 ) { | 301 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { |
341 | ck_assert_msg ( 0, "Failed to encode payload" ); | 302 | printf("toxav_call_control failed: %d\n", rc); |
303 | ck_assert(0); | ||
304 | } | ||
342 | } | 305 | } |
343 | 306 | ||
344 | toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); | 307 | while (AliceCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) |
345 | 308 | iterate_tox(bootstrap, Alice, Bob); | |
346 | if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ | ||
347 | step++; /* This terminates the loop */ | ||
348 | toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); | ||
349 | toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); | ||
350 | 309 | ||
351 | /* Call over Alice hangs up */ | 310 | printf("Success!\n"); |
352 | toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); | ||
353 | } | ||
354 | } | 311 | } |
355 | TERMINATE_SCOPE() | ||
356 | |||
357 | 312 | ||
358 | /* | 313 | if (TEST_CANCEL) { /* Alice calls; Alice cancels while ringing */ |
359 | * Call with audio on both sides and video on one side. Alice calls Bob. | 314 | printf("\nTrying cancel (while ringing) flow...\n"); |
360 | */ | ||
361 | CALL_AND_START_LOOP(TypeAudio, TypeVideo) { | ||
362 | /* Both send */ | ||
363 | payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, | ||
364 | 1000, sample_payload, frame_size); | ||
365 | |||
366 | if ( payload_size < 0 ) { | ||
367 | ck_assert_msg ( 0, "Failed to encode payload" ); | ||
368 | } | ||
369 | 315 | ||
370 | toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); | 316 | memset(&AliceCC, 0, sizeof(CallControl)); |
317 | memset(&BobCC, 0, sizeof(CallControl)); | ||
371 | 318 | ||
372 | payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, | 319 | { |
373 | sample_payload, frame_size); | 320 | TOXAV_ERR_CALL rc; |
321 | toxav_call(AliceAV, 0, 48, 0, &rc); | ||
374 | 322 | ||
375 | if ( payload_size < 0 ) { | 323 | if (rc != TOXAV_ERR_CALL_OK) { |
376 | ck_assert_msg ( 0, "Failed to encode payload" ); | 324 | printf("toxav_call failed: %d\n", rc); |
325 | ck_assert(0); | ||
326 | } | ||
377 | } | 327 | } |
378 | 328 | ||
379 | toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); | 329 | while (!BobCC.incoming) |
380 | 330 | iterate_tox(bootstrap, Alice, Bob); | |
381 | // toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); | ||
382 | 331 | ||
383 | if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ | 332 | /* Cancel */ |
384 | step++; /* This terminates the loop */ | 333 | { |
385 | toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); | 334 | TOXAV_ERR_CALL_CONTROL rc; |
386 | toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); | 335 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); |
387 | 336 | ||
388 | /* Call over Alice hangs up */ | 337 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { |
389 | toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); | 338 | printf("toxav_call_control failed: %d\n", rc); |
339 | ck_assert(0); | ||
340 | } | ||
390 | } | 341 | } |
391 | } | ||
392 | TERMINATE_SCOPE() | ||
393 | |||
394 | 342 | ||
395 | /* | 343 | /* Alice will not receive end state */ |
396 | * Call with audio and video on both sides. Alice calls Bob. | 344 | while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) |
397 | */ | 345 | iterate_tox(bootstrap, Alice, Bob); |
398 | CALL_AND_START_LOOP(TypeVideo, TypeVideo) { | ||
399 | /* Both send */ | ||
400 | 346 | ||
401 | payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, | 347 | printf("Success!\n"); |
402 | 1000, sample_payload, frame_size); | 348 | } |
403 | 349 | ||
404 | if ( payload_size < 0 ) { | 350 | if (TEST_MUTE_UNMUTE) { /* Check Mute-Unmute etc */ |
405 | ck_assert_msg ( 0, "Failed to encode payload" ); | 351 | printf("\nTrying mute functionality...\n"); |
406 | } | ||
407 | 352 | ||
408 | toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); | 353 | memset(&AliceCC, 0, sizeof(CallControl)); |
354 | memset(&BobCC, 0, sizeof(CallControl)); | ||
409 | 355 | ||
410 | payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, | 356 | /* Assume sending audio and video */ |
411 | sample_payload, frame_size); | 357 | { |
358 | TOXAV_ERR_CALL rc; | ||
359 | toxav_call(AliceAV, 0, 48, 1000, &rc); | ||
412 | 360 | ||
413 | if ( payload_size < 0 ) { | 361 | if (rc != TOXAV_ERR_CALL_OK) { |
414 | ck_assert_msg ( 0, "Failed to encode payload" ); | 362 | printf("toxav_call failed: %d\n", rc); |
363 | ck_assert(0); | ||
364 | } | ||
415 | } | 365 | } |
416 | 366 | ||
417 | toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); | 367 | while (!BobCC.incoming) |
418 | 368 | iterate_tox(bootstrap, Alice, Bob); | |
419 | // toxav_send_video(status_control.Alice.av, status_control.Alice.call_index, sample_image); | ||
420 | // toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); | ||
421 | 369 | ||
370 | /* At first try all stuff while in invalid state */ | ||
371 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); | ||
372 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); | ||
373 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); | ||
374 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); | ||
375 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL)); | ||
376 | ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL)); | ||
422 | 377 | ||
423 | if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ | 378 | { |
424 | step++; /* This terminates the loop */ | 379 | TOXAV_ERR_ANSWER rc; |
425 | toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); | 380 | toxav_answer(BobAV, 0, 48, 4000, &rc); |
426 | toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); | ||
427 | 381 | ||
428 | /* Call over Alice hangs up */ | 382 | if (rc != TOXAV_ERR_ANSWER_OK) { |
429 | toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); | 383 | printf("toxav_answer failed: %d\n", rc); |
384 | ck_assert(0); | ||
385 | } | ||
430 | } | 386 | } |
431 | } | ||
432 | TERMINATE_SCOPE() | ||
433 | |||
434 | 387 | ||
435 | uint64_t times_they_are_a_changin = time(NULL); | 388 | iterate_tox(bootstrap, Alice, Bob); |
436 | /* Media change */ | 389 | |
437 | CALL_AND_START_LOOP(TypeAudio, TypeAudio) { | 390 | /* Pause and Resume */ |
438 | /* Both send */ | 391 | printf("Pause and Resume\n"); |
439 | payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, | 392 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); |
440 | 1000, sample_payload, frame_size); | 393 | iterate_tox(bootstrap, Alice, Bob); |
441 | 394 | ck_assert(BobCC.state == 0); | |
442 | if ( payload_size < 0 ) { | 395 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); |
443 | ck_assert_msg ( 0, "Failed to encode payload" ); | 396 | iterate_tox(bootstrap, Alice, Bob); |
397 | ck_assert(BobCC.state & (TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V)); | ||
398 | |||
399 | /* Mute/Unmute single */ | ||
400 | printf("Mute/Unmute single\n"); | ||
401 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); | ||
402 | iterate_tox(bootstrap, Alice, Bob); | ||
403 | ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); | ||
404 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); | ||
405 | iterate_tox(bootstrap, Alice, Bob); | ||
406 | ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); | ||
407 | |||
408 | /* Mute/Unmute both */ | ||
409 | printf("Mute/Unmute both\n"); | ||
410 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL)); | ||
411 | iterate_tox(bootstrap, Alice, Bob); | ||
412 | ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); | ||
413 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL)); | ||
414 | iterate_tox(bootstrap, Alice, Bob); | ||
415 | ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); | ||
416 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL)); | ||
417 | iterate_tox(bootstrap, Alice, Bob); | ||
418 | ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); | ||
419 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL)); | ||
420 | iterate_tox(bootstrap, Alice, Bob); | ||
421 | ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); | ||
422 | |||
423 | { | ||
424 | TOXAV_ERR_CALL_CONTROL rc; | ||
425 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
426 | |||
427 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { | ||
428 | printf("toxav_call_control failed: %d\n", rc); | ||
429 | ck_assert(0); | ||
430 | } | ||
444 | } | 431 | } |
445 | 432 | ||
446 | toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); | 433 | iterate_tox(bootstrap, Alice, Bob); |
434 | ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); | ||
447 | 435 | ||
448 | payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, | 436 | printf("Success!\n"); |
449 | sample_payload, frame_size); | 437 | } |
450 | 438 | ||
451 | if ( payload_size < 0 ) { | 439 | if (TEST_STOP_RESUME_PAYLOAD) { /* Stop and resume audio/video payload */ |
452 | ck_assert_msg ( 0, "Failed to encode payload" ); | 440 | printf("\nTrying stop/resume functionality...\n"); |
453 | } | ||
454 | 441 | ||
455 | toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); | 442 | memset(&AliceCC, 0, sizeof(CallControl)); |
443 | memset(&BobCC, 0, sizeof(CallControl)); | ||
456 | 444 | ||
457 | /* Wait 2 seconds and change transmission type */ | 445 | /* Assume sending audio and video */ |
458 | if (time(NULL) - times_they_are_a_changin > 2) { | 446 | { |
459 | times_they_are_a_changin = time(NULL); | 447 | TOXAV_ERR_CALL rc; |
460 | muhcaps.audio_bitrate ++; | 448 | toxav_call(AliceAV, 0, 48, 0, &rc); |
461 | toxav_change_settings(status_control.Alice.av, status_control.Alice.call_index, &muhcaps); | ||
462 | } | ||
463 | 449 | ||
464 | if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ | 450 | if (rc != TOXAV_ERR_CALL_OK) { |
465 | step++; /* This terminates the loop */ | 451 | printf("toxav_call failed: %d\n", rc); |
466 | toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); | 452 | ck_assert(0); |
467 | toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); | 453 | } |
468 | |||
469 | /* Call over Alice hangs up */ | ||
470 | toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); | ||
471 | } | 454 | } |
472 | } | ||
473 | TERMINATE_SCOPE() | ||
474 | 455 | ||
456 | while (!BobCC.incoming) | ||
457 | iterate_tox(bootstrap, Alice, Bob); | ||
475 | 458 | ||
476 | /************************************************************************************************* | 459 | { |
477 | * Other flows | 460 | TOXAV_ERR_ANSWER rc; |
478 | */ | 461 | toxav_answer(BobAV, 0, 48, 0, &rc); |
479 | 462 | ||
480 | /* | 463 | if (rc != TOXAV_ERR_ANSWER_OK) { |
481 | * Call and reject | 464 | printf("toxav_answer failed: %d\n", rc); |
482 | */ | 465 | ck_assert(0); |
483 | { | ||
484 | int step = 0; | ||
485 | int running = 1; | ||
486 | |||
487 | while (running) { | ||
488 | tox_iterate(bootstrap_node); | ||
489 | tox_iterate(Alice); | ||
490 | tox_iterate(Bob); | ||
491 | |||
492 | switch ( step ) { | ||
493 | case 0: /* Alice */ | ||
494 | printf("Alice is calling...\n"); | ||
495 | toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); | ||
496 | step++; | ||
497 | break; | ||
498 | |||
499 | case 1: /* Bob */ | ||
500 | if (status_control.Bob.status == Ringing) { | ||
501 | printf("Bob rejects...\n"); | ||
502 | toxav_reject(status_control.Bob.av, status_control.Bob.call_index, "Who likes D's anyway?"); | ||
503 | step++; | ||
504 | } | ||
505 | |||
506 | break; | ||
507 | |||
508 | case 2: /* Wait for Both to have status ended */ | ||
509 | if (status_control.Alice.status == Rejected && status_control.Bob.status == Ended) running = 0; | ||
510 | |||
511 | break; | ||
512 | } | 466 | } |
513 | |||
514 | c_sleep(20); | ||
515 | } | 467 | } |
516 | 468 | ||
517 | printf("\n"); | 469 | iterate_tox(bootstrap, Alice, Bob); |
518 | } | ||
519 | |||
520 | |||
521 | /* | ||
522 | * Call and cancel | ||
523 | */ | ||
524 | { | ||
525 | int step = 0; | ||
526 | int running = 1; | ||
527 | |||
528 | while (running) { | ||
529 | tox_iterate(bootstrap_node); | ||
530 | tox_iterate(Alice); | ||
531 | tox_iterate(Bob); | ||
532 | 470 | ||
533 | toxav_do(status_control.Alice.av); | 471 | printf("Call started as audio only\n"); |
534 | toxav_do(status_control.Bob.av); | 472 | printf("Turning on video for Alice...\n"); |
473 | ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 1000, NULL)); | ||
535 | 474 | ||
475 | iterate_tox(bootstrap, Alice, Bob); | ||
476 | ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V); | ||
536 | 477 | ||
537 | switch ( step ) { | 478 | printf("Turning off video for Alice...\n"); |
538 | case 0: /* Alice */ | 479 | ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 0, NULL)); |
539 | printf("Alice is calling...\n"); | ||
540 | toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); | ||
541 | step++; | ||
542 | break; | ||
543 | 480 | ||
481 | iterate_tox(bootstrap, Alice, Bob); | ||
482 | ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)); | ||
544 | 483 | ||
545 | case 1: /* Alice again */ | 484 | printf("Turning off audio for Alice...\n"); |
546 | if (status_control.Bob.status == Ringing) { | 485 | ck_assert(toxav_bit_rate_set(AliceAV, 0, 0, -1, NULL)); |
547 | printf("Alice cancels...\n"); | ||
548 | toxav_cancel(status_control.Alice.av, status_control.Alice.call_index, 0, "Who likes D's anyway?"); | ||
549 | step++; | ||
550 | } | ||
551 | 486 | ||
552 | break; | 487 | iterate_tox(bootstrap, Alice, Bob); |
488 | ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_A)); | ||
553 | 489 | ||
554 | case 2: /* Wait for Both to have status ended */ | 490 | { |
555 | if (status_control.Bob.status == Canceled) running = 0; | 491 | TOXAV_ERR_CALL_CONTROL rc; |
492 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
556 | 493 | ||
557 | break; | 494 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { |
495 | printf("toxav_call_control failed: %d\n", rc); | ||
496 | ck_assert(0); | ||
558 | } | 497 | } |
559 | |||
560 | c_sleep(20); | ||
561 | } | 498 | } |
562 | 499 | ||
563 | printf("\n"); | 500 | iterate_tox(bootstrap, Alice, Bob); |
501 | ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); | ||
502 | |||
503 | printf("Success!\n"); | ||
564 | } | 504 | } |
565 | 505 | ||
566 | /* | 506 | if (TEST_PAUSE_RESUME_SEND) { /* Stop and resume audio/video payload and test send options */ |
567 | * Timeout | 507 | printf("\nTrying stop/resume functionality...\n"); |
568 | */ | ||
569 | { | ||
570 | int step = 0; | ||
571 | int running = 1; | ||
572 | 508 | ||
573 | while (running) { | 509 | memset(&AliceCC, 0, sizeof(CallControl)); |
574 | tox_iterate(bootstrap_node); | 510 | memset(&BobCC, 0, sizeof(CallControl)); |
575 | tox_iterate(Alice); | ||
576 | tox_iterate(Bob); | ||
577 | 511 | ||
578 | toxav_do(status_control.Alice.av); | 512 | /* Assume sending audio and video */ |
579 | toxav_do(status_control.Bob.av); | 513 | { |
514 | TOXAV_ERR_CALL rc; | ||
515 | toxav_call(AliceAV, 0, 48, 0, &rc); | ||
580 | 516 | ||
581 | switch ( step ) { | 517 | if (rc != TOXAV_ERR_CALL_OK) { |
582 | case 0: | 518 | printf("toxav_call failed: %d\n", rc); |
583 | printf("Alice is calling...\n"); | 519 | ck_assert(0); |
584 | toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); | 520 | } |
585 | step++; | 521 | } |
586 | break; | ||
587 | 522 | ||
588 | case 1: | 523 | while (!BobCC.incoming) |
589 | if (status_control.Alice.status == TimedOut) running = 0; | 524 | iterate_tox(bootstrap, Alice, Bob); |
590 | 525 | ||
591 | break; | 526 | { |
527 | TOXAV_ERR_ANSWER rc; | ||
528 | toxav_answer(BobAV, 0, 48, 0, &rc); | ||
529 | |||
530 | if (rc != TOXAV_ERR_ANSWER_OK) { | ||
531 | printf("toxav_answer failed: %d\n", rc); | ||
532 | ck_assert(0); | ||
592 | } | 533 | } |
534 | } | ||
593 | 535 | ||
594 | c_sleep(20); | 536 | int16_t PCM[5670]; |
537 | |||
538 | iterate_tox(bootstrap, Alice, Bob); | ||
539 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL)); | ||
540 | iterate_tox(bootstrap, Alice, Bob); | ||
541 | ck_assert(!toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL)); | ||
542 | ck_assert(!toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL)); | ||
543 | ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL)); | ||
544 | iterate_tox(bootstrap, Alice, Bob); | ||
545 | ck_assert(toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL)); | ||
546 | ck_assert(toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL)); | ||
547 | iterate_tox(bootstrap, Alice, Bob); | ||
548 | |||
549 | { | ||
550 | TOXAV_ERR_CALL_CONTROL rc; | ||
551 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
552 | |||
553 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { | ||
554 | printf("toxav_call_control failed: %d\n", rc); | ||
555 | ck_assert(0); | ||
556 | } | ||
595 | } | 557 | } |
596 | 558 | ||
597 | printf("\n"); | 559 | iterate_tox(bootstrap, Alice, Bob); |
560 | ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); | ||
561 | |||
562 | printf("Success!\n"); | ||
598 | } | 563 | } |
599 | 564 | ||
600 | vpx_img_free(sample_image); | 565 | toxav_kill(BobAV); |
601 | toxav_kill(status_control.Alice.av); | 566 | toxav_kill(AliceAV); |
602 | toxav_kill(status_control.Bob.av); | ||
603 | tox_kill(bootstrap_node); | ||
604 | tox_kill(Alice); | ||
605 | tox_kill(Bob); | 567 | tox_kill(Bob); |
568 | tox_kill(Alice); | ||
569 | tox_kill(bootstrap); | ||
606 | 570 | ||
607 | printf("Calls ended!\n"); | 571 | printf("\nTest successful!\n"); |
608 | } | 572 | } |
609 | END_TEST | 573 | END_TEST |
610 | 574 | ||
611 | /*************************************************************************************************/ | 575 | #ifndef HAVE_LIBCHECK |
612 | 576 | int main(int argc, char *argv[]) | |
613 | 577 | { | |
614 | /*************************************************************************************************/ | 578 | (void) argc; |
615 | 579 | (void) argv; | |
616 | /*************************************************************************************************/ | ||
617 | |||
618 | 580 | ||
581 | test_AV_flows(); | ||
582 | return 0; | ||
583 | } | ||
584 | #else | ||
619 | Suite *tox_suite(void) | 585 | Suite *tox_suite(void) |
620 | { | 586 | { |
621 | Suite *s = suite_create("ToxAV"); | 587 | Suite *s = suite_create("ToxAV"); |
622 | 588 | ||
623 | DEFTESTCASE_SLOW(AV_flows, 200); | 589 | DEFTESTCASE_SLOW(AV_flows, 200); |
624 | |||
625 | return s; | 590 | return s; |
626 | } | 591 | } |
627 | int main(int argc, char *argv[]) | 592 | int main(int argc, char *argv[]) |
628 | { | 593 | { |
594 | (void) argc; | ||
595 | (void) argv; | ||
596 | |||
629 | Suite *tox = tox_suite(); | 597 | Suite *tox = tox_suite(); |
630 | SRunner *test_runner = srunner_create(tox); | 598 | SRunner *test_runner = srunner_create(tox); |
631 | 599 | ||
@@ -637,6 +605,5 @@ int main(int argc, char *argv[]) | |||
637 | srunner_free(test_runner); | 605 | srunner_free(test_runner); |
638 | 606 | ||
639 | return number_failed; | 607 | return number_failed; |
640 | |||
641 | // return test_AV_flows(); | ||
642 | } | 608 | } |
609 | #endif | ||
diff --git a/auto_tests/toxav_many_test.c b/auto_tests/toxav_many_test.c index 6017e526..ab496024 100644 --- a/auto_tests/toxav_many_test.c +++ b/auto_tests/toxav_many_test.c | |||
@@ -2,18 +2,27 @@ | |||
2 | #include "config.h" | 2 | #include "config.h" |
3 | #endif | 3 | #endif |
4 | 4 | ||
5 | #ifndef HAVE_LIBCHECK | ||
6 | # include <assert.h> | ||
7 | |||
8 | # define ck_assert(X) assert(X); | ||
9 | # define START_TEST(NAME) void NAME () | ||
10 | # define END_TEST | ||
11 | #else | ||
12 | # include "helpers.h" | ||
13 | #endif | ||
14 | |||
5 | #include <sys/types.h> | 15 | #include <sys/types.h> |
6 | #include <stdint.h> | 16 | #include <stdint.h> |
7 | #include <string.h> | 17 | #include <string.h> |
8 | #include <stdio.h> | 18 | #include <stdio.h> |
9 | #include <check.h> | ||
10 | #include <stdlib.h> | 19 | #include <stdlib.h> |
11 | #include <time.h> | 20 | #include <time.h> |
12 | #include <assert.h> | ||
13 | 21 | ||
14 | #include <vpx/vpx_image.h> | 22 | #include <vpx/vpx_image.h> |
15 | 23 | ||
16 | #include "../toxcore/tox.h" | 24 | #include "../toxcore/tox.h" |
25 | #include "../toxcore/util.h" | ||
17 | #include "../toxcore/logger.h" | 26 | #include "../toxcore/logger.h" |
18 | #include "../toxcore/crypto_core.h" | 27 | #include "../toxcore/crypto_core.h" |
19 | #include "../toxav/toxav.h" | 28 | #include "../toxav/toxav.h" |
@@ -26,365 +35,321 @@ | |||
26 | #define c_sleep(x) usleep(1000*x) | 35 | #define c_sleep(x) usleep(1000*x) |
27 | #endif | 36 | #endif |
28 | 37 | ||
29 | pthread_mutex_t muhmutex; | ||
30 | |||
31 | typedef enum _CallStatus { | ||
32 | none, | ||
33 | InCall, | ||
34 | Ringing, | ||
35 | Ended, | ||
36 | Rejected, | ||
37 | Canceled | ||
38 | |||
39 | } CallStatus; | ||
40 | 38 | ||
41 | typedef struct _Party { | 39 | typedef struct { |
42 | CallStatus status; | 40 | bool incoming; |
43 | ToxAv *av; | 41 | uint32_t state; |
44 | int id; | 42 | } CallControl; |
45 | } Party; | ||
46 | 43 | ||
47 | typedef struct _ACall { | 44 | typedef struct { |
48 | pthread_t tid; | 45 | ToxAV *AliceAV; |
49 | int idx; | 46 | ToxAV *BobAV; |
47 | CallControl *AliceCC; | ||
48 | CallControl *BobCC; | ||
49 | uint32_t friend_number; | ||
50 | } thread_data; | ||
50 | 51 | ||
51 | Party Caller; | 52 | /** |
52 | Party Callee; | 53 | * Callbacks |
53 | } ACall; | 54 | */ |
54 | 55 | void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) | |
55 | typedef struct _Status { | ||
56 | ACall calls[3]; /* Make 3 calls for this test */ | ||
57 | } Status; | ||
58 | |||
59 | Status status_control; | ||
60 | |||
61 | void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) | ||
62 | { | 56 | { |
63 | if (length == 7 && memcmp("gentoo", data, 7) == 0) { | 57 | (void) av; |
64 | tox_friend_add_norequest(m, public_key, 0); | 58 | (void) audio_enabled; |
65 | } | 59 | (void) video_enabled; |
66 | } | ||
67 | 60 | ||
68 | 61 | printf("Handling CALL callback\n"); | |
69 | /******************************************************************************/ | 62 | ((CallControl *)user_data)[friend_number].incoming = true; |
70 | void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) | ||
71 | { | ||
72 | Status *cast = _arg; | ||
73 | cast->calls[call_index].Callee.status = Ringing; | ||
74 | } | ||
75 | void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) | ||
76 | { | ||
77 | Status *cast = _arg; | ||
78 | cast->calls[call_index].Caller.status = Ringing; | ||
79 | } | 63 | } |
80 | void callback_call_ended ( void *av, int32_t call_index, void *_arg ) | 64 | void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) |
81 | { | 65 | { |
82 | Status *cast = _arg; | 66 | printf("Handling CALL STATE callback: %d %p\n", state, av); |
83 | 67 | ((CallControl *)user_data)[friend_number].state = state; | |
84 | if (av == cast->calls[call_index].Caller.av) | ||
85 | cast->calls[call_index].Caller.status = Ended; | ||
86 | else | ||
87 | cast->calls[call_index].Callee.status = Ended; | ||
88 | } | 68 | } |
89 | void callback_call_started ( void *av, int32_t call_index, void *_arg ) | 69 | void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, |
70 | uint16_t width, uint16_t height, | ||
71 | uint8_t const *y, uint8_t const *u, uint8_t const *v, | ||
72 | int32_t ystride, int32_t ustride, int32_t vstride, | ||
73 | void *user_data) | ||
90 | { | 74 | { |
91 | Status *cast = _arg; | 75 | (void) av; |
92 | 76 | (void) friend_number; | |
93 | if (av == cast->calls[call_index].Caller.av) | 77 | (void) width; |
94 | cast->calls[call_index].Caller.status = InCall; | 78 | (void) height; |
95 | else | 79 | (void) y; |
96 | cast->calls[call_index].Callee.status = InCall; | 80 | (void) u; |
81 | (void) v; | ||
82 | (void) ystride; | ||
83 | (void) ustride; | ||
84 | (void) vstride; | ||
85 | (void) user_data; | ||
97 | } | 86 | } |
98 | void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) | 87 | void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, |
88 | int16_t const *pcm, | ||
89 | size_t sample_count, | ||
90 | uint8_t channels, | ||
91 | uint32_t sampling_rate, | ||
92 | void *user_data) | ||
99 | { | 93 | { |
94 | (void) av; | ||
95 | (void) friend_number; | ||
96 | (void) pcm; | ||
97 | (void) sample_count; | ||
98 | (void) channels; | ||
99 | (void) sampling_rate; | ||
100 | (void) user_data; | ||
100 | } | 101 | } |
101 | void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) | 102 | void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) |
102 | { | 103 | { |
103 | Status *cast = _arg; | 104 | (void) userdata; |
104 | cast->calls[call_index].Caller.status = Rejected; | ||
105 | } | ||
106 | 105 | ||
107 | void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) | 106 | if (length == 7 && memcmp("gentoo", data, 7) == 0) { |
108 | { | 107 | ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); |
109 | ck_assert_msg(0, "No answer!"); | 108 | } |
110 | } | 109 | } |
111 | 110 | ||
112 | void callback_audio (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data) | ||
113 | {} | ||
114 | |||
115 | void callback_video (void *agent, int32_t call_idx, const vpx_image_t *img, void *data) | ||
116 | {} | ||
117 | 111 | ||
118 | void register_callbacks(ToxAv *av, void *data) | 112 | /** |
113 | * Iterate helper | ||
114 | */ | ||
115 | ToxAV *setup_av_instance(Tox *tox, CallControl *CC) | ||
119 | { | 116 | { |
120 | toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); | 117 | TOXAV_ERR_NEW error; |
121 | toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); | ||
122 | toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); | ||
123 | toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); | ||
124 | toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); | ||
125 | toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); | ||
126 | toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); | ||
127 | 118 | ||
119 | ToxAV *av = toxav_new(tox, &error); | ||
120 | ck_assert(error == TOXAV_ERR_NEW_OK); | ||
128 | 121 | ||
129 | toxav_register_audio_callback(av, callback_audio, NULL); | 122 | toxav_callback_call(av, t_toxav_call_cb, CC); |
130 | toxav_register_video_callback(av, callback_video, NULL); | 123 | toxav_callback_call_state(av, t_toxav_call_state_cb, CC); |
131 | } | 124 | toxav_callback_video_receive_frame(av, t_toxav_receive_video_frame_cb, CC); |
132 | /*************************************************************************************************/ | 125 | toxav_callback_audio_receive_frame(av, t_toxav_receive_audio_frame_cb, CC); |
133 | |||
134 | int call_running[3]; | ||
135 | 126 | ||
136 | void *in_thread_call (void *arg) | 127 | return av; |
128 | } | ||
129 | void *call_thread(void *pd) | ||
137 | { | 130 | { |
138 | #define call_print(call, what, args...) printf("[%d] " what "\n", call, ##args) | 131 | ToxAV *AliceAV = ((thread_data *) pd)->AliceAV; |
139 | 132 | ToxAV *BobAV = ((thread_data *) pd)->BobAV; | |
140 | ACall *this_call = arg; | 133 | CallControl *AliceCC = ((thread_data *) pd)->AliceCC; |
141 | uint64_t start = 0; | 134 | CallControl *BobCC = ((thread_data *) pd)->BobCC; |
142 | int step = 0; | 135 | uint32_t friend_number = ((thread_data *) pd)->friend_number; |
143 | int call_idx; | ||
144 | |||
145 | const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); | ||
146 | int16_t sample_payload[frame_size]; | ||
147 | randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); | ||
148 | |||
149 | uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; | ||
150 | |||
151 | register_callbacks(this_call->Caller.av, &status_control); | ||
152 | register_callbacks(this_call->Callee.av, arg); | ||
153 | |||
154 | /* NOTE: CALLEE WILL ALWAHYS NEED CALL_IDX == 0 */ | ||
155 | pthread_mutex_lock(&muhmutex); | ||
156 | |||
157 | while (call_running[this_call->idx]) { | ||
158 | |||
159 | pthread_mutex_unlock(&muhmutex); | ||
160 | |||
161 | switch ( step ) { | ||
162 | case 0: /* CALLER */ | ||
163 | toxav_call(this_call->Caller.av, &call_idx, this_call->Callee.id, &av_DefaultSettings, 10); | ||
164 | call_print(call_idx, "Calling ..."); | ||
165 | step++; | ||
166 | break; | ||
167 | 136 | ||
168 | case 1: /* CALLEE */ | ||
169 | pthread_mutex_lock(&muhmutex); | ||
170 | 137 | ||
171 | if (this_call->Caller.status == Ringing) { | 138 | memset(AliceCC, 0, sizeof(CallControl)); |
172 | call_print(call_idx, "Callee answers ..."); | 139 | memset(BobCC, 0, sizeof(CallControl)); |
173 | pthread_mutex_unlock(&muhmutex); | ||
174 | toxav_answer(this_call->Callee.av, 0, &av_DefaultSettings); | ||
175 | step++; | ||
176 | start = time(NULL); | ||
177 | pthread_mutex_lock(&muhmutex); | ||
178 | } | ||
179 | 140 | ||
180 | pthread_mutex_unlock(&muhmutex); | 141 | { /* Call */ |
181 | break; | 142 | TOXAV_ERR_CALL rc; |
143 | toxav_call(AliceAV, friend_number, 48, 3000, &rc); | ||
182 | 144 | ||
183 | case 2: /* Rtp transmission */ | 145 | if (rc != TOXAV_ERR_CALL_OK) { |
184 | pthread_mutex_lock(&muhmutex); | 146 | printf("toxav_call failed: %d\n", rc); |
185 | 147 | ck_assert(0); | |
186 | if (this_call->Caller.status == InCall) { /* I think this is okay */ | 148 | } |
187 | call_print(call_idx, "Sending rtp ..."); | 149 | } |
188 | pthread_mutex_unlock(&muhmutex); | ||
189 | |||
190 | c_sleep(1000); /* We have race condition here */ | ||
191 | toxav_prepare_transmission(this_call->Callee.av, 0, 1); | ||
192 | toxav_prepare_transmission(this_call->Caller.av, call_idx, 1); | ||
193 | |||
194 | int payload_size = toxav_prepare_audio_frame(this_call->Caller.av, call_idx, prepared_payload, RTP_PAYLOAD_SIZE, | ||
195 | sample_payload, frame_size); | ||
196 | |||
197 | if ( payload_size < 0 ) { | ||
198 | ck_assert_msg ( 0, "Failed to encode payload" ); | ||
199 | } | ||
200 | |||
201 | |||
202 | while (time(NULL) - start < 10) { /* 10 seconds */ | ||
203 | /* Both send */ | ||
204 | toxav_send_audio(this_call->Caller.av, call_idx, prepared_payload, payload_size); | ||
205 | 150 | ||
206 | toxav_send_audio(this_call->Callee.av, 0, prepared_payload, payload_size); | 151 | while (!BobCC->incoming) |
152 | c_sleep(10); | ||
207 | 153 | ||
208 | /* Both receive */ | 154 | { /* Answer */ |
209 | int16_t storage[RTP_PAYLOAD_SIZE]; | 155 | TOXAV_ERR_ANSWER rc; |
210 | int recved; | 156 | toxav_answer(BobAV, 0, 8, 500, &rc); |
211 | 157 | ||
212 | c_sleep(20); | 158 | if (rc != TOXAV_ERR_ANSWER_OK) { |
213 | } | 159 | printf("toxav_answer failed: %d\n", rc); |
160 | ck_assert(0); | ||
161 | } | ||
162 | } | ||
214 | 163 | ||
215 | step++; /* This terminates the loop */ | 164 | c_sleep(30); |
216 | 165 | ||
217 | pthread_mutex_lock(&muhmutex); | 166 | int16_t PCM[960]; |
218 | toxav_kill_transmission(this_call->Callee.av, 0); | 167 | uint8_t video_y[800 * 600]; |
219 | toxav_kill_transmission(this_call->Caller.av, call_idx); | 168 | uint8_t video_u[800 * 600 / 2]; |
220 | pthread_mutex_unlock(&muhmutex); | 169 | uint8_t video_v[800 * 600 / 2]; |
221 | 170 | ||
222 | /* Call over CALLER hangs up */ | 171 | memset(PCM, 0, sizeof(PCM)); |
223 | toxav_hangup(this_call->Caller.av, call_idx); | 172 | memset(video_y, 0, sizeof(video_y)); |
224 | call_print(call_idx, "Hanging up ..."); | 173 | memset(video_u, 0, sizeof(video_u)); |
174 | memset(video_v, 0, sizeof(video_v)); | ||
225 | 175 | ||
226 | pthread_mutex_lock(&muhmutex); | 176 | time_t start_time = time(NULL); |
227 | } | ||
228 | 177 | ||
229 | pthread_mutex_unlock(&muhmutex); | 178 | while (time(NULL) - start_time < 4) { |
230 | break; | 179 | toxav_iterate(AliceAV); |
180 | toxav_iterate(BobAV); | ||
231 | 181 | ||
232 | case 3: /* Wait for Both to have status ended */ | 182 | toxav_audio_send_frame(AliceAV, friend_number, PCM, 960, 1, 48000, NULL); |
233 | pthread_mutex_lock(&muhmutex); | 183 | toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL); |
234 | 184 | ||
235 | if (this_call->Caller.status == Ended) { | 185 | toxav_video_send_frame(AliceAV, friend_number, 800, 600, video_y, video_u, video_v, NULL); |
236 | pthread_mutex_unlock(&muhmutex); | 186 | toxav_video_send_frame(BobAV, 0, 800, 600, video_y, video_u, video_v, NULL); |
237 | c_sleep(1000); /* race condition */ | ||
238 | pthread_mutex_lock(&muhmutex); | ||
239 | this_call->Callee.status = Ended; | ||
240 | call_running[this_call->idx] = 0; | ||
241 | } | ||
242 | 187 | ||
243 | pthread_mutex_unlock(&muhmutex); | 188 | c_sleep(10); |
189 | } | ||
244 | 190 | ||
245 | break; | 191 | { /* Hangup */ |
192 | TOXAV_ERR_CALL_CONTROL rc; | ||
193 | toxav_call_control(AliceAV, friend_number, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
246 | 194 | ||
195 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { | ||
196 | printf("toxav_call_control failed: %d %p %p\n", rc, AliceAV, BobAV); | ||
247 | } | 197 | } |
248 | |||
249 | c_sleep(20); | ||
250 | |||
251 | pthread_mutex_lock(&muhmutex); | ||
252 | } | 198 | } |
253 | 199 | ||
254 | pthread_mutex_unlock(&muhmutex); | 200 | c_sleep(30); |
255 | call_print(call_idx, "Call ended successfully!"); | 201 | |
202 | printf ("Closing thread\n"); | ||
256 | pthread_exit(NULL); | 203 | pthread_exit(NULL); |
257 | } | 204 | } |
258 | 205 | ||
259 | 206 | ||
207 | START_TEST(test_AV_three_calls) | ||
208 | { | ||
209 | Tox *Alice, *bootstrap, *Bobs[3]; | ||
210 | ToxAV *AliceAV, *BobsAV[3]; | ||
260 | 211 | ||
212 | CallControl AliceCC[3], BobsCC[3]; | ||
261 | 213 | ||
214 | { | ||
215 | TOX_ERR_NEW error; | ||
262 | 216 | ||
263 | START_TEST(test_AV_three_calls) | 217 | bootstrap = tox_new(NULL, &error); |
264 | // void test_AV_three_calls() | 218 | ck_assert(error == TOX_ERR_NEW_OK); |
265 | { | ||
266 | long long unsigned int cur_time = time(NULL); | ||
267 | Tox *bootstrap_node = tox_new(0, 0); | ||
268 | Tox *caller = tox_new(0, 0); | ||
269 | Tox *callees[3] = { | ||
270 | tox_new(0, 0), | ||
271 | tox_new(0, 0), | ||
272 | tox_new(0, 0), | ||
273 | }; | ||
274 | 219 | ||
220 | Alice = tox_new(NULL, &error); | ||
221 | ck_assert(error == TOX_ERR_NEW_OK); | ||
275 | 222 | ||
276 | ck_assert_msg(bootstrap_node != NULL, "Failed to create bootstrap node"); | 223 | Bobs[0] = tox_new(NULL, &error); |
224 | ck_assert(error == TOX_ERR_NEW_OK); | ||
277 | 225 | ||
278 | int i = 0; | 226 | Bobs[1] = tox_new(NULL, &error); |
227 | ck_assert(error == TOX_ERR_NEW_OK); | ||
279 | 228 | ||
280 | for (; i < 3; i ++) { | 229 | Bobs[2] = tox_new(NULL, &error); |
281 | ck_assert_msg(callees[i] != NULL, "Failed to create 3 tox instances"); | 230 | ck_assert(error == TOX_ERR_NEW_OK); |
282 | } | 231 | } |
283 | 232 | ||
284 | for ( i = 0; i < 3; i ++ ) { | 233 | printf("Created 5 instances of Tox\n"); |
285 | uint32_t to_compare = 974536; | 234 | printf("Preparing network...\n"); |
286 | tox_callback_friend_request(callees[i], accept_friend_request, &to_compare); | 235 | long long unsigned int cur_time = time(NULL); |
287 | uint8_t address[TOX_ADDRESS_SIZE]; | ||
288 | tox_self_get_address(callees[i], address); | ||
289 | 236 | ||
290 | uint32_t test = tox_friend_add(caller, address, (uint8_t *)"gentoo", 7, 0); | 237 | uint32_t to_compare = 974536; |
291 | ck_assert_msg( test == i, "Failed to add friend error code: %i", test); | 238 | uint8_t address[TOX_ADDRESS_SIZE]; |
292 | } | ||
293 | 239 | ||
294 | uint8_t off = 1; | 240 | tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); |
241 | tox_self_get_address(Alice, address); | ||
295 | 242 | ||
296 | while (1) { | ||
297 | tox_iterate(bootstrap_node); | ||
298 | tox_iterate(caller); | ||
299 | 243 | ||
300 | for (i = 0; i < 3; i ++) { | 244 | ck_assert(tox_friend_add(Bobs[0], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); |
301 | tox_iterate(callees[i]); | 245 | ck_assert(tox_friend_add(Bobs[1], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); |
302 | } | 246 | ck_assert(tox_friend_add(Bobs[2], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); |
303 | 247 | ||
248 | uint8_t off = 1; | ||
304 | 249 | ||
305 | if (tox_self_get_connection_status(bootstrap_node) && | 250 | while (1) { |
306 | tox_self_get_connection_status(caller) && | 251 | tox_iterate(bootstrap); |
307 | tox_self_get_connection_status(callees[0]) && | 252 | tox_iterate(Alice); |
308 | tox_self_get_connection_status(callees[1]) && | 253 | tox_iterate(Bobs[0]); |
309 | tox_self_get_connection_status(callees[2]) && off) { | 254 | tox_iterate(Bobs[1]); |
255 | tox_iterate(Bobs[2]); | ||
256 | |||
257 | if (tox_self_get_connection_status(bootstrap) && | ||
258 | tox_self_get_connection_status(Alice) && | ||
259 | tox_self_get_connection_status(Bobs[0]) && | ||
260 | tox_self_get_connection_status(Bobs[1]) && | ||
261 | tox_self_get_connection_status(Bobs[2]) && off) { | ||
310 | printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); | 262 | printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); |
311 | off = 0; | 263 | off = 0; |
312 | } | 264 | } |
313 | 265 | ||
314 | 266 | if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && | |
315 | if (tox_friend_get_connection_status(caller, 0, 0) && | 267 | tox_friend_get_connection_status(Alice, 1, NULL) == TOX_CONNECTION_UDP && |
316 | tox_friend_get_connection_status(caller, 1, 0) && | 268 | tox_friend_get_connection_status(Alice, 2, NULL) == TOX_CONNECTION_UDP && |
317 | tox_friend_get_connection_status(caller, 2, 0) ) | 269 | tox_friend_get_connection_status(Bobs[0], 0, NULL) == TOX_CONNECTION_UDP && |
270 | tox_friend_get_connection_status(Bobs[1], 0, NULL) == TOX_CONNECTION_UDP && | ||
271 | tox_friend_get_connection_status(Bobs[2], 0, NULL) == TOX_CONNECTION_UDP) | ||
318 | break; | 272 | break; |
319 | 273 | ||
320 | c_sleep(20); | 274 | c_sleep(20); |
321 | } | 275 | } |
322 | 276 | ||
323 | printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); | 277 | AliceAV = setup_av_instance(Alice, AliceCC); |
324 | 278 | BobsAV[0] = setup_av_instance(Bobs[0], BobsCC + 0); | |
325 | ToxAv *uniqcallerav = toxav_new(caller, 3); | 279 | BobsAV[1] = setup_av_instance(Bobs[1], BobsCC + 1); |
326 | 280 | BobsAV[2] = setup_av_instance(Bobs[2], BobsCC + 2); | |
327 | for (i = 0; i < 3; i ++) { | 281 | |
328 | status_control.calls[i].idx = i; | 282 | printf("Created 4 instances of ToxAV\n"); |
329 | 283 | printf("All set after %llu seconds!\n", time(NULL) - cur_time); | |
330 | status_control.calls[i].Caller.av = uniqcallerav; | 284 | |
331 | status_control.calls[i].Caller.id = 0; | 285 | thread_data tds[3]; |
332 | status_control.calls[i].Caller.status = none; | 286 | tds[0].AliceAV = AliceAV; |
333 | 287 | tds[0].BobAV = BobsAV[0]; | |
334 | status_control.calls[i].Callee.av = toxav_new(callees[i], 1); | 288 | tds[0].AliceCC = AliceCC + 0; |
335 | status_control.calls[i].Callee.id = i; | 289 | tds[0].BobCC = BobsCC + 0; |
336 | status_control.calls[i].Callee.status = none; | 290 | tds[0].friend_number = 0; |
337 | } | 291 | |
338 | 292 | tds[1].AliceAV = AliceAV; | |
339 | pthread_mutex_init(&muhmutex, NULL); | 293 | tds[1].BobAV = BobsAV[1]; |
340 | 294 | tds[1].AliceCC = AliceCC + 1; | |
341 | for ( i = 0; i < 3; i++ ) { | 295 | tds[1].BobCC = BobsCC + 1; |
342 | call_running[i] = 1; | 296 | tds[1].friend_number = 1; |
343 | pthread_create(&status_control.calls[i].tid, NULL, in_thread_call, &status_control.calls[i]); | 297 | |
344 | } | 298 | tds[2].AliceAV = AliceAV; |
345 | 299 | tds[2].BobAV = BobsAV[2]; | |
346 | /* Now start 3 calls and they'll run for 10 s */ | 300 | tds[2].AliceCC = AliceCC + 2; |
347 | 301 | tds[2].BobCC = BobsCC + 2; | |
348 | for ( i = 0; i < 3; i++ ) | 302 | tds[2].friend_number = 2; |
349 | pthread_detach(status_control.calls[i].tid); | 303 | |
350 | 304 | pthread_t tids[3]; | |
351 | while (call_running[0] || call_running[1] || call_running[2]) { | 305 | (void) pthread_create(tids + 0, NULL, call_thread, tds + 0); |
352 | pthread_mutex_lock(&muhmutex); | 306 | (void) pthread_create(tids + 1, NULL, call_thread, tds + 1); |
353 | 307 | (void) pthread_create(tids + 2, NULL, call_thread, tds + 2); | |
354 | tox_iterate(bootstrap_node); | 308 | |
355 | tox_iterate(caller); | 309 | (void) pthread_detach(tids[0]); |
356 | tox_iterate(callees[0]); | 310 | (void) pthread_detach(tids[1]); |
357 | tox_iterate(callees[1]); | 311 | (void) pthread_detach(tids[2]); |
358 | tox_iterate(callees[2]); | 312 | |
359 | 313 | time_t start_time = time(NULL); | |
360 | for ( i = 0; i < 3; i++ ) | 314 | |
361 | toxav_do(status_control.calls[0].Caller.av); | 315 | while (time(NULL) - start_time < 5) { |
362 | 316 | tox_iterate(Alice); | |
363 | toxav_do(status_control.calls[0].Callee.av); | 317 | tox_iterate(Bobs[0]); |
364 | toxav_do(status_control.calls[1].Callee.av); | 318 | tox_iterate(Bobs[1]); |
365 | toxav_do(status_control.calls[2].Callee.av); | 319 | tox_iterate(Bobs[2]); |
366 | |||
367 | pthread_mutex_unlock(&muhmutex); | ||
368 | c_sleep(20); | 320 | c_sleep(20); |
369 | } | 321 | } |
370 | 322 | ||
371 | toxav_kill(status_control.calls[0].Caller.av); | 323 | (void) pthread_join(tids[0], NULL); |
372 | toxav_kill(status_control.calls[0].Callee.av); | 324 | (void) pthread_join(tids[1], NULL); |
373 | toxav_kill(status_control.calls[1].Callee.av); | 325 | (void) pthread_join(tids[2], NULL); |
374 | toxav_kill(status_control.calls[2].Callee.av); | 326 | |
375 | 327 | printf ("Killing all instances\n"); | |
376 | tox_kill(bootstrap_node); | 328 | toxav_kill(BobsAV[0]); |
377 | tox_kill(caller); | 329 | toxav_kill(BobsAV[1]); |
378 | 330 | toxav_kill(BobsAV[2]); | |
379 | for ( i = 0; i < 3; i ++) | 331 | toxav_kill(AliceAV); |
380 | tox_kill(callees[i]); | 332 | tox_kill(Bobs[0]); |
381 | 333 | tox_kill(Bobs[1]); | |
334 | tox_kill(Bobs[2]); | ||
335 | tox_kill(Alice); | ||
336 | tox_kill(bootstrap); | ||
337 | |||
338 | printf("\nTest successful!\n"); | ||
382 | } | 339 | } |
383 | END_TEST | 340 | END_TEST |
384 | 341 | ||
385 | 342 | ||
343 | #ifndef HAVE_LIBCHECK | ||
344 | int main(int argc, char *argv[]) | ||
345 | { | ||
346 | (void) argc; | ||
347 | (void) argv; | ||
386 | 348 | ||
387 | 349 | test_AV_three_calls(); | |
350 | return 0; | ||
351 | } | ||
352 | #else | ||
388 | Suite *tox_suite(void) | 353 | Suite *tox_suite(void) |
389 | { | 354 | { |
390 | Suite *s = suite_create("ToxAV"); | 355 | Suite *s = suite_create("ToxAV"); |
@@ -399,6 +364,9 @@ Suite *tox_suite(void) | |||
399 | 364 | ||
400 | int main(int argc, char *argv[]) | 365 | int main(int argc, char *argv[]) |
401 | { | 366 | { |
367 | (void) argc; | ||
368 | (void) argv; | ||
369 | |||
402 | Suite *tox = tox_suite(); | 370 | Suite *tox = tox_suite(); |
403 | SRunner *test_runner = srunner_create(tox); | 371 | SRunner *test_runner = srunner_create(tox); |
404 | 372 | ||
@@ -410,8 +378,5 @@ int main(int argc, char *argv[]) | |||
410 | srunner_free(test_runner); | 378 | srunner_free(test_runner); |
411 | 379 | ||
412 | return number_failed; | 380 | return number_failed; |
413 | |||
414 | // test_AV_three_calls(); | ||
415 | |||
416 | // return 0; | ||
417 | } | 381 | } |
382 | #endif | ||
diff --git a/configure.ac b/configure.ac index 30f71968..ecbb5e46 100644 --- a/configure.ac +++ b/configure.ac | |||
@@ -33,7 +33,7 @@ BUILD_TESTS="yes" | |||
33 | BUILD_AV="yes" | 33 | BUILD_AV="yes" |
34 | BUILD_TESTING="yes" | 34 | BUILD_TESTING="yes" |
35 | 35 | ||
36 | LOGGING="no" | 36 | TOX_LOGGER="no" |
37 | LOGGING_OUTNAM="libtoxcore.log" | 37 | LOGGING_OUTNAM="libtoxcore.log" |
38 | 38 | ||
39 | NCURSES_FOUND="no" | 39 | NCURSES_FOUND="no" |
@@ -82,13 +82,13 @@ AC_ARG_ENABLE([randombytes-stir], | |||
82 | ] | 82 | ] |
83 | ) | 83 | ) |
84 | 84 | ||
85 | AC_ARG_ENABLE([log], | 85 | AC_ARG_ENABLE([logging], |
86 | [AC_HELP_STRING([--enable-log], [enable logging (default: auto)]) ], | 86 | [AC_HELP_STRING([--enable-logging], [enable logging (default: auto)]) ], |
87 | [ | 87 | [ |
88 | if test "x$enableval" = "xyes"; then | 88 | if test "x$enableval" = "xyes"; then |
89 | LOGGING="yes" | 89 | TOX_LOGGER="yes" |
90 | 90 | ||
91 | AC_DEFINE([LOGGING], [], [If logging enabled]) | 91 | AC_DEFINE([TOX_LOGGER], [], [If logging enabled]) |
92 | AC_DEFINE([LOGGER_LEVEL], [LOG_DEBUG], [LOG_LEVEL value]) | 92 | AC_DEFINE([LOGGER_LEVEL], [LOG_DEBUG], [LOG_LEVEL value]) |
93 | AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$LOGGING_OUTNAM"], [Output of logger]) | 93 | AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$LOGGING_OUTNAM"], [Output of logger]) |
94 | fi | 94 | fi |
@@ -99,7 +99,7 @@ AC_ARG_WITH(log-level, | |||
99 | AC_HELP_STRING([--with-log-level=LEVEL], | 99 | AC_HELP_STRING([--with-log-level=LEVEL], |
100 | [Logger levels: TRACE; DEBUG; INFO; WARNING; ERROR ]), | 100 | [Logger levels: TRACE; DEBUG; INFO; WARNING; ERROR ]), |
101 | [ | 101 | [ |
102 | if test "x$LOGGING" = "xno"; then | 102 | if test "x$TOX_LOGGER" = "xno"; then |
103 | AC_MSG_WARN([Logging disabled!]) | 103 | AC_MSG_WARN([Logging disabled!]) |
104 | else | 104 | else |
105 | if test "x$withval" = "xTRACE"; then | 105 | if test "x$withval" = "xTRACE"; then |
@@ -127,7 +127,7 @@ AC_ARG_WITH(log-path, | |||
127 | AC_HELP_STRING([--with-log-path=DIR], | 127 | AC_HELP_STRING([--with-log-path=DIR], |
128 | [Path of logger output]), | 128 | [Path of logger output]), |
129 | [ | 129 | [ |
130 | if test "x$LOGGING" = "xno"; then | 130 | if test "x$TOX_LOGGER" = "xno"; then |
131 | AC_MSG_WARN([Logging disabled!]) | 131 | AC_MSG_WARN([Logging disabled!]) |
132 | else | 132 | else |
133 | AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$withval""/""$LOGGING_OUTNAM"], [Output of logger]) | 133 | 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 00000000..ab89b0ea --- /dev/null +++ b/other/apidsl/toxav.in.h | |||
@@ -0,0 +1,615 @@ | |||
1 | %{ | ||
2 | /* toxav.h | ||
3 | * | ||
4 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
5 | * | ||
6 | * This file is part of Tox. | ||
7 | * | ||
8 | * Tox is free software: you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation, either version 3 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * Tox is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #ifndef TOXAV_H | ||
24 | #define TOXAV_H | ||
25 | |||
26 | #include <stdbool.h> | ||
27 | #include <stddef.h> | ||
28 | #include <stdint.h> | ||
29 | |||
30 | #ifdef __cplusplus | ||
31 | extern "C" { | ||
32 | #endif | ||
33 | %} | ||
34 | |||
35 | /** \page av Public audio/video API for Tox clients. | ||
36 | * | ||
37 | * This API can handle multiple calls. Each call has its state, in very rare | ||
38 | * occasions the library can change the state of the call without apps knowledge. | ||
39 | * | ||
40 | */ | ||
41 | |||
42 | /** \subsection events Events and callbacks | ||
43 | * | ||
44 | * As in Core API, events are handled by callbacks. One callback can be | ||
45 | * registered per event. All events have a callback function type named | ||
46 | * `toxav_{event}_cb` and a function to register it named `tox_callback_{event}`. | ||
47 | * Passing a NULL callback will result in no callback being registered for that | ||
48 | * event. Only one callback per event can be registered, so if a client needs | ||
49 | * multiple event listeners, it needs to implement the dispatch functionality | ||
50 | * itself. Unlike Core API, lack of some event handlers will cause the the | ||
51 | * library to drop calls before they are started. Hanging up call from a | ||
52 | * callback causes undefined behaviour. | ||
53 | * | ||
54 | */ | ||
55 | |||
56 | /** \subsection threading Threading implications | ||
57 | * | ||
58 | * Unlike the Core API, this API is fully thread-safe. The library will ensure | ||
59 | * the proper synchronization of parallel calls. | ||
60 | * | ||
61 | * A common way to run ToxAV (multiple or single instance) is to have a thread, | ||
62 | * separate from tox instance thread, running a simple ${toxAV.iterate} loop, | ||
63 | * sleeping for ${toxAV.iteration_interval} * milliseconds on each iteration. | ||
64 | * | ||
65 | * An important thing to note is that events are triggered from both tox and | ||
66 | * toxav thread (see above). audio and video receive frame events are triggered | ||
67 | * from toxav thread while all the other events are triggered from tox thread. | ||
68 | * | ||
69 | * Tox thread has priority with mutex mechanisms. Any api function can | ||
70 | * fail if mutexes are held by tox thread in which case they will set SYNC | ||
71 | * error code. | ||
72 | */ | ||
73 | |||
74 | /** | ||
75 | * External Tox type. | ||
76 | */ | ||
77 | class tox { | ||
78 | struct this; | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * ToxAV. | ||
83 | */ | ||
84 | class toxAV { | ||
85 | |||
86 | /** | ||
87 | * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox | ||
88 | * instance, and Tox instance can have only one ToxAV instance. One must make | ||
89 | * sure to close ToxAV instance prior closing Tox instance otherwise undefined | ||
90 | * behaviour occurs. Upon closing of ToxAV instance, all active calls will be | ||
91 | * forcibly terminated without notifying peers. | ||
92 | * | ||
93 | */ | ||
94 | struct this; | ||
95 | /******************************************************************************* | ||
96 | * | ||
97 | * :: API version | ||
98 | * | ||
99 | ******************************************************************************/ | ||
100 | /** | ||
101 | * The major version number. Incremented when the API or ABI changes in an | ||
102 | * incompatible way. | ||
103 | */ | ||
104 | #define TOXAV_VERSION_MAJOR 0u | ||
105 | /** | ||
106 | * The minor version number. Incremented when functionality is added without | ||
107 | * breaking the API or ABI. Set to 0 when the major version number is | ||
108 | * incremented. | ||
109 | */ | ||
110 | #define TOXAV_VERSION_MINOR 0u | ||
111 | /** | ||
112 | * The patch or revision number. Incremented when bugfixes are applied without | ||
113 | * changing any functionality or API or ABI. | ||
114 | */ | ||
115 | #define TOXAV_VERSION_PATCH 0u | ||
116 | |||
117 | /** | ||
118 | * A macro to check at preprocessing time whether the client code is compatible | ||
119 | * with the installed version of ToxAV. | ||
120 | */ | ||
121 | #define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ | ||
122 | (TOXAV_VERSION_MAJOR == MAJOR && \ | ||
123 | (TOXAV_VERSION_MINOR > MINOR || \ | ||
124 | (TOXAV_VERSION_MINOR == MINOR && \ | ||
125 | TOXAV_VERSION_PATCH >= PATCH))) | ||
126 | |||
127 | /** | ||
128 | * A macro to make compilation fail if the client code is not compatible with | ||
129 | * the installed version of ToxAV. | ||
130 | */ | ||
131 | #define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ | ||
132 | typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] | ||
133 | |||
134 | /** | ||
135 | * A convenience macro to call ${version.is_compatible} with the currently | ||
136 | * compiling API version. | ||
137 | */ | ||
138 | #define TOXAV_VERSION_IS_ABI_COMPATIBLE() \ | ||
139 | toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH) | ||
140 | |||
141 | |||
142 | static namespace version { | ||
143 | |||
144 | /** | ||
145 | * Return the major version number of the library. Can be used to display the | ||
146 | * ToxAV library version or to check whether the client is compatible with the | ||
147 | * dynamically linked version of ToxAV. | ||
148 | */ | ||
149 | uint32_t major(); | ||
150 | |||
151 | /** | ||
152 | * Return the minor version number of the library. | ||
153 | */ | ||
154 | uint32_t minor(); | ||
155 | |||
156 | /** | ||
157 | * Return the patch number of the library. | ||
158 | */ | ||
159 | uint32_t patch(); | ||
160 | |||
161 | /** | ||
162 | * Return whether the compiled library version is compatible with the passed | ||
163 | * version numbers. | ||
164 | */ | ||
165 | bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch); | ||
166 | |||
167 | } | ||
168 | /******************************************************************************* | ||
169 | * | ||
170 | * :: Creation and destruction | ||
171 | * | ||
172 | ******************************************************************************/ | ||
173 | /** | ||
174 | * Start new A/V session. There can only be only one session per Tox instance. | ||
175 | */ | ||
176 | static this new (tox::this *tox) { | ||
177 | NULL, | ||
178 | /** | ||
179 | * Memory allocation failure while trying to allocate structures required for | ||
180 | * the A/V session. | ||
181 | */ | ||
182 | MALLOC, | ||
183 | /** | ||
184 | * Attempted to create a second session for the same Tox instance. | ||
185 | */ | ||
186 | MULTIPLE, | ||
187 | } | ||
188 | /** | ||
189 | * Releases all resources associated with the A/V session. | ||
190 | * | ||
191 | * If any calls were ongoing, these will be forcibly terminated without | ||
192 | * notifying peers. After calling this function, no other functions may be | ||
193 | * called and the av pointer becomes invalid. | ||
194 | */ | ||
195 | void kill(); | ||
196 | /** | ||
197 | * Returns the Tox instance the A/V object was created for. | ||
198 | */ | ||
199 | tox::this *tox { get(); } | ||
200 | /******************************************************************************* | ||
201 | * | ||
202 | * :: A/V event loop | ||
203 | * | ||
204 | ******************************************************************************/ | ||
205 | /** | ||
206 | * Returns the interval in milliseconds when the next toxav_iterate call should | ||
207 | * be. If no call is active at the moment, this function returns 200. | ||
208 | */ | ||
209 | const uint32_t iteration_interval(); | ||
210 | /** | ||
211 | * Main loop for the session. This function needs to be called in intervals of | ||
212 | * toxav_iteration_interval() milliseconds. It is best called in the separate | ||
213 | * thread from tox_iterate. | ||
214 | */ | ||
215 | void iterate(); | ||
216 | /******************************************************************************* | ||
217 | * | ||
218 | * :: Call setup | ||
219 | * | ||
220 | ******************************************************************************/ | ||
221 | /** | ||
222 | * Call a friend. This will start ringing the friend. | ||
223 | * | ||
224 | * It is the client's responsibility to stop ringing after a certain timeout, | ||
225 | * if such behaviour is desired. If the client does not stop ringing, the | ||
226 | * library will not stop until the friend is disconnected. Audio and video | ||
227 | * receiving are both enabled by default. | ||
228 | * | ||
229 | * @param friend_number The friend number of the friend that should be called. | ||
230 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable | ||
231 | * audio sending. | ||
232 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
233 | * video sending. | ||
234 | */ | ||
235 | bool call(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { | ||
236 | /** | ||
237 | * A resource allocation error occurred while trying to create the structures | ||
238 | * required for the call. | ||
239 | */ | ||
240 | MALLOC, | ||
241 | /** | ||
242 | * Synchronization error occurred. | ||
243 | */ | ||
244 | SYNC, | ||
245 | /** | ||
246 | * The friend number did not designate a valid friend. | ||
247 | */ | ||
248 | FRIEND_NOT_FOUND, | ||
249 | /** | ||
250 | * The friend was valid, but not currently connected. | ||
251 | */ | ||
252 | FRIEND_NOT_CONNECTED, | ||
253 | /** | ||
254 | * Attempted to call a friend while already in an audio or video call with | ||
255 | * them. | ||
256 | */ | ||
257 | FRIEND_ALREADY_IN_CALL, | ||
258 | /** | ||
259 | * Audio or video bit rate is invalid. | ||
260 | */ | ||
261 | INVALID_BIT_RATE, | ||
262 | } | ||
263 | event call { | ||
264 | /** | ||
265 | * The function type for the ${event call} callback. | ||
266 | * | ||
267 | * @param friend_number The friend number from which the call is incoming. | ||
268 | * @param audio_enabled True if friend is sending audio. | ||
269 | * @param video_enabled True if friend is sending video. | ||
270 | */ | ||
271 | typedef void(uint32_t friend_number, bool audio_enabled, bool video_enabled); | ||
272 | } | ||
273 | /** | ||
274 | * Accept an incoming call. | ||
275 | * | ||
276 | * If answering fails for any reason, the call will still be pending and it is | ||
277 | * possible to try and answer it later. Audio and video receiving are both | ||
278 | * enabled by default. | ||
279 | * | ||
280 | * @param friend_number The friend number of the friend that is calling. | ||
281 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable | ||
282 | * audio sending. | ||
283 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
284 | * video sending. | ||
285 | */ | ||
286 | bool answer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { | ||
287 | /** | ||
288 | * Synchronization error occurred. | ||
289 | */ | ||
290 | SYNC, | ||
291 | /** | ||
292 | * Failed to initialize codecs for call session. Note that codec initiation | ||
293 | * will fail if there is no receive callback registered for either audio or | ||
294 | * video. | ||
295 | */ | ||
296 | CODEC_INITIALIZATION, | ||
297 | /** | ||
298 | * The friend number did not designate a valid friend. | ||
299 | */ | ||
300 | FRIEND_NOT_FOUND, | ||
301 | /** | ||
302 | * The friend was valid, but they are not currently trying to initiate a call. | ||
303 | * This is also returned if this client is already in a call with the friend. | ||
304 | */ | ||
305 | FRIEND_NOT_CALLING, | ||
306 | /** | ||
307 | * Audio or video bit rate is invalid. | ||
308 | */ | ||
309 | INVALID_BIT_RATE, | ||
310 | } | ||
311 | /******************************************************************************* | ||
312 | * | ||
313 | * :: Call state graph | ||
314 | * | ||
315 | ******************************************************************************/ | ||
316 | bitmask FRIEND_CALL_STATE { | ||
317 | /** | ||
318 | * Set by the AV core if an error occurred on the remote end or if friend | ||
319 | * timed out. This is the final state after which no more state | ||
320 | * transitions can occur for the call. This call state will never be triggered | ||
321 | * in combination with other call states. | ||
322 | */ | ||
323 | ERROR, | ||
324 | /** | ||
325 | * The call has finished. This is the final state after which no more state | ||
326 | * transitions can occur for the call. This call state will never be | ||
327 | * triggered in combination with other call states. | ||
328 | */ | ||
329 | FINISHED, | ||
330 | /** | ||
331 | * The flag that marks that friend is sending audio. | ||
332 | */ | ||
333 | SENDING_A, | ||
334 | /** | ||
335 | * The flag that marks that friend is sending video. | ||
336 | */ | ||
337 | SENDING_V, | ||
338 | /** | ||
339 | * The flag that marks that friend is receiving audio. | ||
340 | */ | ||
341 | ACCEPTING_A, | ||
342 | /** | ||
343 | * The flag that marks that friend is receiving video. | ||
344 | */ | ||
345 | ACCEPTING_V, | ||
346 | } | ||
347 | event call_state { | ||
348 | /** | ||
349 | * The function type for the ${event call_state} callback. | ||
350 | * | ||
351 | * @param friend_number The friend number for which the call state changed. | ||
352 | * @param state The bitmask of the new call state which is guaranteed to be | ||
353 | * different than the previous state. The state is set to 0 when the call is | ||
354 | * paused. The bitmask represents all the activities currently performed by the | ||
355 | * friend. | ||
356 | */ | ||
357 | typedef void(uint32_t friend_number, uint32_t state); | ||
358 | } | ||
359 | /******************************************************************************* | ||
360 | * | ||
361 | * :: Call control | ||
362 | * | ||
363 | ******************************************************************************/ | ||
364 | enum class CALL_CONTROL { | ||
365 | /** | ||
366 | * Resume a previously paused call. Only valid if the pause was caused by this | ||
367 | * client, if not, this control is ignored. Not valid before the call is accepted. | ||
368 | */ | ||
369 | RESUME, | ||
370 | /** | ||
371 | * Put a call on hold. Not valid before the call is accepted. | ||
372 | */ | ||
373 | PAUSE, | ||
374 | /** | ||
375 | * Reject a call if it was not answered, yet. Cancel a call after it was | ||
376 | * answered. | ||
377 | */ | ||
378 | CANCEL, | ||
379 | /** | ||
380 | * Request that the friend stops sending audio. Regardless of the friend's | ||
381 | * compliance, this will cause the ${event audio.receive_frame} event to stop being | ||
382 | * triggered on receiving an audio frame from the friend. | ||
383 | */ | ||
384 | MUTE_AUDIO, | ||
385 | /** | ||
386 | * Calling this control will notify client to start sending audio again. | ||
387 | */ | ||
388 | UNMUTE_AUDIO, | ||
389 | /** | ||
390 | * Request that the friend stops sending video. Regardless of the friend's | ||
391 | * compliance, this will cause the ${event video.receive_frame} event to stop being | ||
392 | * triggered on receiving a video frame from the friend. | ||
393 | */ | ||
394 | HIDE_VIDEO, | ||
395 | /** | ||
396 | * Calling this control will notify client to start sending video again. | ||
397 | */ | ||
398 | SHOW_VIDEO, | ||
399 | } | ||
400 | /** | ||
401 | * Sends a call control command to a friend. | ||
402 | * | ||
403 | * @param friend_number The friend number of the friend this client is in a call | ||
404 | * with. | ||
405 | * @param control The control command to send. | ||
406 | * | ||
407 | * @return true on success. | ||
408 | */ | ||
409 | bool call_control (uint32_t friend_number, CALL_CONTROL control) { | ||
410 | /** | ||
411 | * Synchronization error occurred. | ||
412 | */ | ||
413 | SYNC, | ||
414 | /** | ||
415 | * The friend_number passed did not designate a valid friend. | ||
416 | */ | ||
417 | FRIEND_NOT_FOUND, | ||
418 | /** | ||
419 | * This client is currently not in a call with the friend. Before the call is | ||
420 | * answered, only CANCEL is a valid control. | ||
421 | */ | ||
422 | FRIEND_NOT_IN_CALL, | ||
423 | /** | ||
424 | * Happens if user tried to pause an already paused call or if trying to | ||
425 | * resume a call that is not paused. | ||
426 | */ | ||
427 | INVALID_TRANSITION, | ||
428 | } | ||
429 | /******************************************************************************* | ||
430 | * | ||
431 | * :: Controlling bit rates | ||
432 | * | ||
433 | ******************************************************************************/ | ||
434 | namespace bit_rate { | ||
435 | /** | ||
436 | * Set the audio bit rate to be used in subsequent audio/video frames. | ||
437 | * | ||
438 | * @param friend_number The friend number. | ||
439 | * @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable | ||
440 | * audio sending. Set to -1 to leave unchanged. | ||
441 | * @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable | ||
442 | * video sending. Set to -1 to leave unchanged. | ||
443 | * | ||
444 | */ | ||
445 | bool set(uint32_t friend_number, int32_t audio_bit_rate, int32_t video_bit_rate) { | ||
446 | /** | ||
447 | * Synchronization error occurred. | ||
448 | */ | ||
449 | SYNC, | ||
450 | /** | ||
451 | * The bit rate passed was not one of the supported values. | ||
452 | */ | ||
453 | INVALID, | ||
454 | /** | ||
455 | * The friend_number passed did not designate a valid friend. | ||
456 | */ | ||
457 | FRIEND_NOT_FOUND, | ||
458 | /** | ||
459 | * This client is currently not in a call with the friend. | ||
460 | */ | ||
461 | FRIEND_NOT_IN_CALL, | ||
462 | } | ||
463 | event status { | ||
464 | /** | ||
465 | * The function type for the ${event status} callback. The event is triggered | ||
466 | * when the network becomes too saturated for current bit rates at which | ||
467 | * point core suggests new bit rates. | ||
468 | * | ||
469 | * @param friend_number The friend number. | ||
470 | * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. | ||
471 | * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. | ||
472 | */ | ||
473 | typedef void(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate); | ||
474 | } | ||
475 | } | ||
476 | /******************************************************************************* | ||
477 | * | ||
478 | * :: A/V sending | ||
479 | * | ||
480 | ******************************************************************************/ | ||
481 | error for send_frame { | ||
482 | /** | ||
483 | * In case of video, one of Y, U, or V was NULL. In case of audio, the samples | ||
484 | * data pointer was NULL. | ||
485 | */ | ||
486 | NULL, | ||
487 | /** | ||
488 | * The friend_number passed did not designate a valid friend. | ||
489 | */ | ||
490 | FRIEND_NOT_FOUND, | ||
491 | /** | ||
492 | * This client is currently not in a call with the friend. | ||
493 | */ | ||
494 | FRIEND_NOT_IN_CALL, | ||
495 | /** | ||
496 | * Synchronization error occurred. | ||
497 | */ | ||
498 | SYNC, | ||
499 | /** | ||
500 | * One of the frame parameters was invalid. E.g. the resolution may be too | ||
501 | * small or too large, or the audio sampling rate may be unsupported. | ||
502 | */ | ||
503 | INVALID, | ||
504 | /** | ||
505 | * Either friend turned off audio or video receiving or we turned off sending | ||
506 | * for the said payload. | ||
507 | */ | ||
508 | PAYLOAD_TYPE_DISABLED, | ||
509 | /** | ||
510 | * Failed to push frame through rtp interface. | ||
511 | */ | ||
512 | RTP_FAILED, | ||
513 | } | ||
514 | namespace audio { | ||
515 | /** | ||
516 | * Send an audio frame to a friend. | ||
517 | * | ||
518 | * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... | ||
519 | * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... | ||
520 | * For mono audio, this has no meaning, every sample is subsequent. For stereo, | ||
521 | * this means the expected format is LRLRLR... with samples for left and right | ||
522 | * alternating. | ||
523 | * | ||
524 | * @param friend_number The friend number of the friend to which to send an | ||
525 | * audio frame. | ||
526 | * @param pcm An array of audio samples. The size of this array must be | ||
527 | * sample_count * channels. | ||
528 | * @param sample_count Number of samples in this frame. Valid numbers here are | ||
529 | * ((sample rate) * (audio length) / 1000), where audio length can be | ||
530 | * 2.5, 5, 10, 20, 40 or 60 millseconds. | ||
531 | * @param channels Number of audio channels. Supported values are 1 and 2. | ||
532 | * @param sampling_rate Audio sampling rate used in this frame. Valid sampling | ||
533 | * rates are 8000, 12000, 16000, 24000, or 48000. | ||
534 | */ | ||
535 | bool send_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, | ||
536 | uint8_t channels, uint32_t sampling_rate) with error for send_frame; | ||
537 | } | ||
538 | namespace video { | ||
539 | /** | ||
540 | * Send a video frame to a friend. | ||
541 | * | ||
542 | * Y - plane should be of size: height * width | ||
543 | * U - plane should be of size: (height/2) * (width/2) | ||
544 | * V - plane should be of size: (height/2) * (width/2) | ||
545 | * | ||
546 | * @param friend_number The friend number of the friend to which to send a video | ||
547 | * frame. | ||
548 | * @param width Width of the frame in pixels. | ||
549 | * @param height Height of the frame in pixels. | ||
550 | * @param y Y (Luminance) plane data. | ||
551 | * @param u U (Chroma) plane data. | ||
552 | * @param v V (Chroma) plane data. | ||
553 | */ | ||
554 | bool send_frame(uint32_t friend_number, uint16_t width, uint16_t height, | ||
555 | const uint8_t *y, const uint8_t *u, const uint8_t *v) with error for send_frame; | ||
556 | } | ||
557 | /******************************************************************************* | ||
558 | * | ||
559 | * :: A/V receiving | ||
560 | * | ||
561 | ******************************************************************************/ | ||
562 | namespace audio { | ||
563 | event receive_frame { | ||
564 | /** | ||
565 | * The function type for the ${event receive_frame} callback. The callback can be | ||
566 | * called multiple times per single iteration depending on the amount of queued | ||
567 | * frames in the buffer. The received format is the same as in send function. | ||
568 | * | ||
569 | * @param friend_number The friend number of the friend who sent an audio frame. | ||
570 | * @param pcm An array of audio samples (sample_count * channels elements). | ||
571 | * @param sample_count The number of audio samples per channel in the PCM array. | ||
572 | * @param channels Number of audio channels. | ||
573 | * @param sampling_rate Sampling rate used in this frame. | ||
574 | * | ||
575 | */ | ||
576 | typedef void(uint32_t friend_number, const int16_t *pcm, size_t sample_count, | ||
577 | uint8_t channels, uint32_t sampling_rate); | ||
578 | } | ||
579 | } | ||
580 | namespace video { | ||
581 | event receive_frame { | ||
582 | /** | ||
583 | * The function type for the ${event receive_frame} callback. | ||
584 | * | ||
585 | * @param friend_number The friend number of the friend who sent a video frame. | ||
586 | * @param width Width of the frame in pixels. | ||
587 | * @param height Height of the frame in pixels. | ||
588 | * @param y | ||
589 | * @param u | ||
590 | * @param v Plane data. | ||
591 | * The size of plane data is derived from width and height where | ||
592 | * Y = MAX(width, abs(ystride)) * height, | ||
593 | * U = MAX(width/2, abs(ustride)) * (height/2) and | ||
594 | * V = MAX(width/2, abs(vstride)) * (height/2). | ||
595 | * @param ystride | ||
596 | * @param ustride | ||
597 | * @param vstride Strides data. Strides represent padding for each plane | ||
598 | * that may or may not be present. You must handle strides in | ||
599 | * your image processing code. Strides are negative if the | ||
600 | * image is bottom-up hence why you MUST abs() it when | ||
601 | * calculating plane buffer size. | ||
602 | */ | ||
603 | typedef void(uint32_t friend_number, uint16_t width, uint16_t height, | ||
604 | const uint8_t *y, const uint8_t *u, const uint8_t *v, | ||
605 | int32_t ystride, int32_t ustride, int32_t vstride); | ||
606 | } | ||
607 | } | ||
608 | |||
609 | } | ||
610 | %{ | ||
611 | #ifdef __cplusplus | ||
612 | } | ||
613 | #endif | ||
614 | #endif /* TOXAV_H */ | ||
615 | %} | ||
diff --git a/testing/av_test.c b/testing/av_test.c new file mode 100644 index 00000000..4b2fe61d --- /dev/null +++ b/testing/av_test.c | |||
@@ -0,0 +1,766 @@ | |||
1 | /** av_test.c | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | * Compile with (Linux only; in newly created directory toxcore/dir_name): | ||
21 | * gcc -o av_test ../toxav/av_test.c ../build/.libs/libtox*.a -lopencv_core \ | ||
22 | * -lopencv_highgui -lopencv_imgproc -lsndfile -pthread -lvpx -lopus -lsodium -lportaudio | ||
23 | */ | ||
24 | |||
25 | |||
26 | #include "../toxav/toxav.h" | ||
27 | #include "../toxcore/tox.h" | ||
28 | #include "../toxcore/util.h" | ||
29 | #include "../toxcore/network.h" /* current_time_monotonic() */ | ||
30 | |||
31 | /* Playing audio data */ | ||
32 | #include <portaudio.h> | ||
33 | /* Reading audio */ | ||
34 | #include <sndfile.h> | ||
35 | |||
36 | /* Reading and Displaying video data */ | ||
37 | #include <opencv/cv.h> | ||
38 | #include <opencv/highgui.h> | ||
39 | #include <opencv/cvwimage.h> | ||
40 | |||
41 | #include <sys/stat.h> | ||
42 | #include <assert.h> | ||
43 | #include <stdio.h> | ||
44 | #include <stdlib.h> | ||
45 | #include <time.h> | ||
46 | #include <string.h> | ||
47 | #include <errno.h> | ||
48 | #include <unistd.h> | ||
49 | |||
50 | #define c_sleep(x) usleep(1000*x) | ||
51 | |||
52 | |||
53 | #define CLIP(X) ((X) > 255 ? 255 : (X) < 0 ? 0 : X) | ||
54 | |||
55 | // RGB -> YUV | ||
56 | #define RGB2Y(R, G, B) CLIP((( 66 * (R) + 129 * (G) + 25 * (B) + 128) >> 8) + 16) | ||
57 | #define RGB2U(R, G, B) CLIP(((-38 * (R) - 74 * (G) + 112 * (B) + 128) >> 8) + 128) | ||
58 | #define RGB2V(R, G, B) CLIP(((112 * (R) - 94 * (G) - 18 * (B) + 128) >> 8) + 128) | ||
59 | |||
60 | // YUV -> RGB | ||
61 | #define C(Y) ((Y) - 16 ) | ||
62 | #define D(U) ((U) - 128 ) | ||
63 | #define E(V) ((V) - 128 ) | ||
64 | |||
65 | #define YUV2R(Y, U, V) CLIP((298 * C(Y) + 409 * E(V) + 128) >> 8) | ||
66 | #define YUV2G(Y, U, V) CLIP((298 * C(Y) - 100 * D(U) - 208 * E(V) + 128) >> 8) | ||
67 | #define YUV2B(Y, U, V) CLIP((298 * C(Y) + 516 * D(U) + 128) >> 8) | ||
68 | |||
69 | |||
70 | #define TEST_TRANSFER_A 0 | ||
71 | #define TEST_TRANSFER_V 1 | ||
72 | |||
73 | |||
74 | typedef struct { | ||
75 | bool incoming; | ||
76 | uint32_t state; | ||
77 | pthread_mutex_t arb_mutex[1]; | ||
78 | RingBuffer *arb; /* Audio ring buffer */ | ||
79 | |||
80 | } CallControl; | ||
81 | |||
82 | struct toxav_thread_data { | ||
83 | ToxAV *AliceAV; | ||
84 | ToxAV *BobAV; | ||
85 | int32_t sig; | ||
86 | }; | ||
87 | |||
88 | const char *vdout = "AV Test"; /* Video output */ | ||
89 | PaStream *adout = NULL; /* Audio output */ | ||
90 | |||
91 | typedef struct { | ||
92 | uint16_t size; | ||
93 | int16_t data[]; | ||
94 | } frame; | ||
95 | |||
96 | void *pa_write_thread (void *d) | ||
97 | { | ||
98 | /* The purpose of this thread is to make sure Pa_WriteStream will not block | ||
99 | * toxav_iterate thread | ||
100 | */ | ||
101 | CallControl *cc = d; | ||
102 | |||
103 | while (Pa_IsStreamActive(adout)) { | ||
104 | frame *f; | ||
105 | pthread_mutex_lock(cc->arb_mutex); | ||
106 | |||
107 | if (rb_read(cc->arb, (void **)&f)) { | ||
108 | pthread_mutex_unlock(cc->arb_mutex); | ||
109 | Pa_WriteStream(adout, f->data, f->size); | ||
110 | free(f); | ||
111 | } else { | ||
112 | pthread_mutex_unlock(cc->arb_mutex); | ||
113 | c_sleep(10); | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Callbacks | ||
120 | */ | ||
121 | void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) | ||
122 | { | ||
123 | printf("Handling CALL callback\n"); | ||
124 | ((CallControl *)user_data)->incoming = true; | ||
125 | } | ||
126 | void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) | ||
127 | { | ||
128 | printf("Handling CALL STATE callback: %d\n", state); | ||
129 | ((CallControl *)user_data)->state = state; | ||
130 | } | ||
131 | void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, | ||
132 | uint16_t width, uint16_t height, | ||
133 | uint8_t const *y, uint8_t const *u, uint8_t const *v, | ||
134 | int32_t ystride, int32_t ustride, int32_t vstride, | ||
135 | void *user_data) | ||
136 | { | ||
137 | ystride = abs(ystride); | ||
138 | ustride = abs(ustride); | ||
139 | vstride = abs(vstride); | ||
140 | |||
141 | uint16_t *img_data = malloc(height * width * 6); | ||
142 | |||
143 | unsigned long int i, j; | ||
144 | |||
145 | for (i = 0; i < height; ++i) { | ||
146 | for (j = 0; j < width; ++j) { | ||
147 | uint8_t *point = (uint8_t *) img_data + 3 * ((i * width) + j); | ||
148 | int yx = y[(i * ystride) + j]; | ||
149 | int ux = u[((i / 2) * ustride) + (j / 2)]; | ||
150 | int vx = v[((i / 2) * vstride) + (j / 2)]; | ||
151 | |||
152 | point[0] = YUV2R(yx, ux, vx); | ||
153 | point[1] = YUV2G(yx, ux, vx); | ||
154 | point[2] = YUV2B(yx, ux, vx); | ||
155 | } | ||
156 | } | ||
157 | |||
158 | |||
159 | CvMat mat = cvMat(height, width, CV_8UC3, img_data); | ||
160 | |||
161 | CvSize sz = {.height = height, .width = width}; | ||
162 | |||
163 | IplImage *header = cvCreateImageHeader(sz, 1, 3); | ||
164 | IplImage *img = cvGetImage(&mat, header); | ||
165 | cvShowImage(vdout, img); | ||
166 | free(img_data); | ||
167 | } | ||
168 | void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, | ||
169 | int16_t const *pcm, | ||
170 | size_t sample_count, | ||
171 | uint8_t channels, | ||
172 | uint32_t sampling_rate, | ||
173 | void *user_data) | ||
174 | { | ||
175 | CallControl *cc = user_data; | ||
176 | frame *f = malloc(sizeof(uint16_t) + sample_count * sizeof(int16_t) * channels); | ||
177 | memcpy(f->data, pcm, sample_count * sizeof(int16_t) * channels); | ||
178 | f->size = sample_count; | ||
179 | |||
180 | pthread_mutex_lock(cc->arb_mutex); | ||
181 | free(rb_write(cc->arb, f)); | ||
182 | pthread_mutex_unlock(cc->arb_mutex); | ||
183 | } | ||
184 | void t_toxav_bit_rate_status_cb(ToxAV *av, uint32_t friend_number, | ||
185 | uint32_t audio_bit_rate, uint32_t video_bit_rate, | ||
186 | void *user_data) | ||
187 | { | ||
188 | printf ("Suggested bit rates: audio: %d video: %d\n", audio_bit_rate, video_bit_rate); | ||
189 | } | ||
190 | void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) | ||
191 | { | ||
192 | if (length == 7 && memcmp("gentoo", data, 7) == 0) { | ||
193 | assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | */ | ||
199 | void initialize_tox(Tox **bootstrap, ToxAV **AliceAV, CallControl *AliceCC, ToxAV **BobAV, CallControl *BobCC) | ||
200 | { | ||
201 | Tox *Alice; | ||
202 | Tox *Bob; | ||
203 | |||
204 | struct Tox_Options opts; | ||
205 | tox_options_default(&opts); | ||
206 | |||
207 | opts.end_port = 0; | ||
208 | opts.ipv6_enabled = false; | ||
209 | |||
210 | { | ||
211 | TOX_ERR_NEW error; | ||
212 | |||
213 | opts.start_port = 33445; | ||
214 | *bootstrap = tox_new(&opts, &error); | ||
215 | assert(error == TOX_ERR_NEW_OK); | ||
216 | |||
217 | opts.start_port = 33455; | ||
218 | Alice = tox_new(&opts, &error); | ||
219 | assert(error == TOX_ERR_NEW_OK); | ||
220 | |||
221 | opts.start_port = 33465; | ||
222 | Bob = tox_new(&opts, &error); | ||
223 | assert(error == TOX_ERR_NEW_OK); | ||
224 | } | ||
225 | |||
226 | printf("Created 3 instances of Tox\n"); | ||
227 | printf("Preparing network...\n"); | ||
228 | long long unsigned int cur_time = time(NULL); | ||
229 | |||
230 | uint32_t to_compare = 974536; | ||
231 | uint8_t address[TOX_ADDRESS_SIZE]; | ||
232 | |||
233 | tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare); | ||
234 | tox_self_get_address(Alice, address); | ||
235 | |||
236 | |||
237 | assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0); | ||
238 | |||
239 | uint8_t off = 1; | ||
240 | |||
241 | while (1) { | ||
242 | tox_iterate(*bootstrap); | ||
243 | tox_iterate(Alice); | ||
244 | tox_iterate(Bob); | ||
245 | |||
246 | if (tox_self_get_connection_status(*bootstrap) && | ||
247 | tox_self_get_connection_status(Alice) && | ||
248 | tox_self_get_connection_status(Bob) && off) { | ||
249 | printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); | ||
250 | off = 0; | ||
251 | } | ||
252 | |||
253 | if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP && | ||
254 | tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP) | ||
255 | break; | ||
256 | |||
257 | c_sleep(20); | ||
258 | } | ||
259 | |||
260 | |||
261 | TOXAV_ERR_NEW rc; | ||
262 | *AliceAV = toxav_new(Alice, &rc); | ||
263 | assert(rc == TOXAV_ERR_NEW_OK); | ||
264 | |||
265 | *BobAV = toxav_new(Bob, &rc); | ||
266 | assert(rc == TOXAV_ERR_NEW_OK); | ||
267 | |||
268 | |||
269 | /* Alice */ | ||
270 | toxav_callback_call(*AliceAV, t_toxav_call_cb, AliceCC); | ||
271 | toxav_callback_call_state(*AliceAV, t_toxav_call_state_cb, AliceCC); | ||
272 | toxav_callback_bit_rate_status(*AliceAV, t_toxav_bit_rate_status_cb, AliceCC); | ||
273 | toxav_callback_video_receive_frame(*AliceAV, t_toxav_receive_video_frame_cb, AliceCC); | ||
274 | toxav_callback_audio_receive_frame(*AliceAV, t_toxav_receive_audio_frame_cb, AliceCC); | ||
275 | |||
276 | /* Bob */ | ||
277 | toxav_callback_call(*BobAV, t_toxav_call_cb, BobCC); | ||
278 | toxav_callback_call_state(*BobAV, t_toxav_call_state_cb, BobCC); | ||
279 | toxav_callback_bit_rate_status(*BobAV, t_toxav_bit_rate_status_cb, BobCC); | ||
280 | toxav_callback_video_receive_frame(*BobAV, t_toxav_receive_video_frame_cb, BobCC); | ||
281 | toxav_callback_audio_receive_frame(*BobAV, t_toxav_receive_audio_frame_cb, BobCC); | ||
282 | |||
283 | |||
284 | printf("Created 2 instances of ToxAV\n"); | ||
285 | printf("All set after %llu seconds!\n", time(NULL) - cur_time); | ||
286 | } | ||
287 | int iterate_tox(Tox *bootstrap, ToxAV *AliceAV, ToxAV *BobAV) | ||
288 | { | ||
289 | tox_iterate(bootstrap); | ||
290 | tox_iterate(toxav_get_tox(AliceAV)); | ||
291 | tox_iterate(toxav_get_tox(BobAV)); | ||
292 | |||
293 | return MIN(tox_iteration_interval(toxav_get_tox(AliceAV)), tox_iteration_interval(toxav_get_tox(BobAV))); | ||
294 | } | ||
295 | void *iterate_toxav (void *data) | ||
296 | { | ||
297 | struct toxav_thread_data *data_cast = data; | ||
298 | #if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 | ||
299 | cvNamedWindow(vdout, CV_WINDOW_AUTOSIZE); | ||
300 | #endif | ||
301 | |||
302 | while (data_cast->sig == 0) { | ||
303 | toxav_iterate(data_cast->AliceAV); | ||
304 | toxav_iterate(data_cast->BobAV); | ||
305 | int rc = MIN(toxav_iteration_interval(data_cast->AliceAV), toxav_iteration_interval(data_cast->BobAV)); | ||
306 | |||
307 | printf("\rIteration interval: %d ", rc); | ||
308 | fflush(stdout); | ||
309 | |||
310 | #if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 | ||
311 | |||
312 | if (!rc) | ||
313 | rc = 1; | ||
314 | |||
315 | cvWaitKey(rc); | ||
316 | #else | ||
317 | c_sleep(rc); | ||
318 | #endif | ||
319 | } | ||
320 | |||
321 | data_cast->sig = 1; | ||
322 | |||
323 | #if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1 | ||
324 | cvDestroyWindow(vdout); | ||
325 | #endif | ||
326 | |||
327 | pthread_exit(NULL); | ||
328 | } | ||
329 | |||
330 | int send_opencv_img(ToxAV *av, uint32_t friend_number, const IplImage *img) | ||
331 | { | ||
332 | int32_t strides[3] = { 1280, 640, 640 }; | ||
333 | uint8_t *planes[3] = { | ||
334 | malloc(img->height * img->width), | ||
335 | malloc(img->height * img->width / 4), | ||
336 | malloc(img->height * img->width / 4), | ||
337 | }; | ||
338 | |||
339 | int x_chroma_shift = 1; | ||
340 | int y_chroma_shift = 1; | ||
341 | |||
342 | int x, y; | ||
343 | |||
344 | for (y = 0; y < img->height; ++y) { | ||
345 | for (x = 0; x < img->width; ++x) { | ||
346 | uint8_t r = img->imageData[(x + y * img->width) * 3 + 0]; | ||
347 | uint8_t g = img->imageData[(x + y * img->width) * 3 + 1]; | ||
348 | uint8_t b = img->imageData[(x + y * img->width) * 3 + 2]; | ||
349 | |||
350 | planes[0][x + y * strides[0]] = RGB2Y(r, g, b); | ||
351 | |||
352 | if (!(x % (1 << x_chroma_shift)) && !(y % (1 << y_chroma_shift))) { | ||
353 | const int i = x / (1 << x_chroma_shift); | ||
354 | const int j = y / (1 << y_chroma_shift); | ||
355 | planes[1][i + j * strides[1]] = RGB2U(r, g, b); | ||
356 | planes[2][i + j * strides[2]] = RGB2V(r, g, b); | ||
357 | } | ||
358 | } | ||
359 | } | ||
360 | |||
361 | int rc = toxav_video_send_frame(av, friend_number, img->width, img->height, | ||
362 | planes[0], planes[1], planes[2], NULL); | ||
363 | free(planes[0]); | ||
364 | free(planes[1]); | ||
365 | free(planes[2]); | ||
366 | return rc; | ||
367 | } | ||
368 | int print_audio_devices() | ||
369 | { | ||
370 | int i = 0; | ||
371 | |||
372 | for (i = 0; i < Pa_GetDeviceCount(); ++i) { | ||
373 | const PaDeviceInfo *info = Pa_GetDeviceInfo(i); | ||
374 | |||
375 | if (info) | ||
376 | printf("%d) %s\n", i, info->name); | ||
377 | } | ||
378 | |||
379 | return 0; | ||
380 | } | ||
381 | int print_help (const char *name) | ||
382 | { | ||
383 | printf("Usage: %s -[a:v:o:dh]\n" | ||
384 | "-a <path> audio input file\n" | ||
385 | "-b <ms> audio frame duration\n" | ||
386 | "-v <path> video input file\n" | ||
387 | "-x <ms> video frame duration\n" | ||
388 | "-o <idx> output audio device index\n" | ||
389 | "-d print output audio devices\n" | ||
390 | "-h print this help\n", name); | ||
391 | |||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | int main (int argc, char **argv) | ||
396 | { | ||
397 | freopen("/dev/zero", "w", stderr); | ||
398 | Pa_Initialize(); | ||
399 | |||
400 | struct stat st; | ||
401 | |||
402 | /* AV files for testing */ | ||
403 | const char *af_name = NULL; | ||
404 | const char *vf_name = NULL; | ||
405 | long audio_out_dev_idx = -1; | ||
406 | |||
407 | int32_t audio_frame_duration = 20; | ||
408 | int32_t video_frame_duration = 10; | ||
409 | |||
410 | /* Parse settings */ | ||
411 | CHECK_ARG: | ||
412 | |||
413 | switch (getopt(argc, argv, "a:b:v:x:o:dh")) { | ||
414 | case 'a': | ||
415 | af_name = optarg; | ||
416 | goto CHECK_ARG; | ||
417 | |||
418 | case 'b': { | ||
419 | char *d; | ||
420 | audio_frame_duration = strtol(optarg, &d, 10); | ||
421 | |||
422 | if (*d) { | ||
423 | printf("Invalid value for argument: 'b'"); | ||
424 | exit(1); | ||
425 | } | ||
426 | |||
427 | goto CHECK_ARG; | ||
428 | } | ||
429 | |||
430 | case 'v': | ||
431 | vf_name = optarg; | ||
432 | goto CHECK_ARG; | ||
433 | |||
434 | case 'x': { | ||
435 | char *d; | ||
436 | video_frame_duration = strtol(optarg, &d, 10); | ||
437 | |||
438 | if (*d) { | ||
439 | printf("Invalid value for argument: 'x'"); | ||
440 | exit(1); | ||
441 | } | ||
442 | |||
443 | goto CHECK_ARG; | ||
444 | } | ||
445 | |||
446 | case 'o': { | ||
447 | char *d; | ||
448 | audio_out_dev_idx = strtol(optarg, &d, 10); | ||
449 | |||
450 | if (*d) { | ||
451 | printf("Invalid value for argument: 'o'"); | ||
452 | exit(1); | ||
453 | } | ||
454 | |||
455 | goto CHECK_ARG; | ||
456 | } | ||
457 | |||
458 | case 'd': | ||
459 | return print_audio_devices(); | ||
460 | |||
461 | case 'h': | ||
462 | return print_help(argv[0]); | ||
463 | |||
464 | case '?': | ||
465 | exit(1); | ||
466 | |||
467 | case -1: | ||
468 | ; | ||
469 | } | ||
470 | |||
471 | { /* Check files */ | ||
472 | if (!af_name) { | ||
473 | printf("Required audio input file!\n"); | ||
474 | exit(1); | ||
475 | } | ||
476 | |||
477 | if (!vf_name) { | ||
478 | printf("Required video input file!\n"); | ||
479 | exit(1); | ||
480 | } | ||
481 | |||
482 | /* Check for files */ | ||
483 | if (stat(af_name, &st) != 0 || !S_ISREG(st.st_mode)) { | ||
484 | printf("%s doesn't seem to be a regular file!\n", af_name); | ||
485 | exit(1); | ||
486 | } | ||
487 | |||
488 | if (stat(vf_name, &st) != 0 || !S_ISREG(st.st_mode)) { | ||
489 | printf("%s doesn't seem to be a regular file!\n", vf_name); | ||
490 | exit(1); | ||
491 | } | ||
492 | } | ||
493 | |||
494 | if (audio_out_dev_idx < 0) | ||
495 | audio_out_dev_idx = Pa_GetDefaultOutputDevice(); | ||
496 | |||
497 | const PaDeviceInfo *audio_dev = Pa_GetDeviceInfo(audio_out_dev_idx); | ||
498 | |||
499 | if (!audio_dev) { | ||
500 | fprintf(stderr, "Device under index: %ld invalid", audio_out_dev_idx); | ||
501 | return 1; | ||
502 | } | ||
503 | |||
504 | printf("Using audio device: %s\n", audio_dev->name); | ||
505 | printf("Using audio file: %s\n", af_name); | ||
506 | printf("Using video file: %s\n", vf_name); | ||
507 | |||
508 | /* START TOX NETWORK */ | ||
509 | |||
510 | Tox *bootstrap; | ||
511 | ToxAV *AliceAV; | ||
512 | ToxAV *BobAV; | ||
513 | |||
514 | CallControl AliceCC; | ||
515 | CallControl BobCC; | ||
516 | |||
517 | initialize_tox(&bootstrap, &AliceAV, &AliceCC, &BobAV, &BobCC); | ||
518 | |||
519 | if (TEST_TRANSFER_A) { | ||
520 | SNDFILE *af_handle; | ||
521 | SF_INFO af_info; | ||
522 | |||
523 | printf("\nTrying audio enc/dec...\n"); | ||
524 | |||
525 | memset(&AliceCC, 0, sizeof(CallControl)); | ||
526 | memset(&BobCC, 0, sizeof(CallControl)); | ||
527 | |||
528 | pthread_mutex_init(AliceCC.arb_mutex, NULL); | ||
529 | pthread_mutex_init(BobCC.arb_mutex, NULL); | ||
530 | |||
531 | AliceCC.arb = rb_new(16); | ||
532 | BobCC.arb = rb_new(16); | ||
533 | |||
534 | { /* Call */ | ||
535 | TOXAV_ERR_CALL rc; | ||
536 | toxav_call(AliceAV, 0, 48, 0, &rc); | ||
537 | |||
538 | if (rc != TOXAV_ERR_CALL_OK) { | ||
539 | printf("toxav_call failed: %d\n", rc); | ||
540 | exit(1); | ||
541 | } | ||
542 | } | ||
543 | |||
544 | while (!BobCC.incoming) | ||
545 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
546 | |||
547 | { /* Answer */ | ||
548 | TOXAV_ERR_ANSWER rc; | ||
549 | toxav_answer(BobAV, 0, 48, 0, &rc); | ||
550 | |||
551 | if (rc != TOXAV_ERR_ANSWER_OK) { | ||
552 | printf("toxav_answer failed: %d\n", rc); | ||
553 | exit(1); | ||
554 | } | ||
555 | } | ||
556 | |||
557 | while (AliceCC.state == 0) | ||
558 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
559 | |||
560 | /* Open audio file */ | ||
561 | af_handle = sf_open(af_name, SFM_READ, &af_info); | ||
562 | |||
563 | if (af_handle == NULL) { | ||
564 | printf("Failed to open the file.\n"); | ||
565 | exit(1); | ||
566 | } | ||
567 | |||
568 | int16_t PCM[5760]; | ||
569 | |||
570 | time_t start_time = time(NULL); | ||
571 | time_t expected_time = af_info.frames / af_info.samplerate + 2; | ||
572 | |||
573 | |||
574 | /* Start decode thread */ | ||
575 | struct toxav_thread_data data = { | ||
576 | .AliceAV = AliceAV, | ||
577 | .BobAV = BobAV, | ||
578 | .sig = 0 | ||
579 | }; | ||
580 | |||
581 | pthread_t dect; | ||
582 | pthread_create(&dect, NULL, iterate_toxav, &data); | ||
583 | pthread_detach(dect); | ||
584 | |||
585 | int frame_size = (af_info.samplerate * audio_frame_duration / 1000) * af_info.channels; | ||
586 | |||
587 | struct PaStreamParameters output; | ||
588 | output.device = audio_out_dev_idx; | ||
589 | output.channelCount = af_info.channels; | ||
590 | output.sampleFormat = paInt16; | ||
591 | output.suggestedLatency = audio_dev->defaultHighOutputLatency; | ||
592 | output.hostApiSpecificStreamInfo = NULL; | ||
593 | |||
594 | PaError err = Pa_OpenStream(&adout, NULL, &output, af_info.samplerate, frame_size, paNoFlag, NULL, NULL); | ||
595 | assert(err == paNoError); | ||
596 | |||
597 | err = Pa_StartStream(adout); | ||
598 | assert(err == paNoError); | ||
599 | |||
600 | // toxav_audio_bit_rate_set(AliceAV, 0, 64, false, NULL); | ||
601 | |||
602 | /* Start write thread */ | ||
603 | pthread_t t; | ||
604 | pthread_create(&t, NULL, pa_write_thread, &BobCC); | ||
605 | pthread_detach(t); | ||
606 | |||
607 | printf("Sample rate %d\n", af_info.samplerate); | ||
608 | |||
609 | while (start_time + expected_time > time(NULL) ) { | ||
610 | uint64_t enc_start_time = current_time_monotonic(); | ||
611 | int64_t count = sf_read_short(af_handle, PCM, frame_size); | ||
612 | |||
613 | if (count > 0) { | ||
614 | TOXAV_ERR_SEND_FRAME rc; | ||
615 | |||
616 | if (toxav_audio_send_frame(AliceAV, 0, PCM, count / af_info.channels, af_info.channels, af_info.samplerate, | ||
617 | &rc) == false) { | ||
618 | printf("Error sending frame of size %ld: %d\n", count, rc); | ||
619 | } | ||
620 | } | ||
621 | |||
622 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
623 | c_sleep(abs(audio_frame_duration - (current_time_monotonic() - enc_start_time) - 1)); | ||
624 | } | ||
625 | |||
626 | printf("Played file in: %lu; stopping stream...\n", time(NULL) - start_time); | ||
627 | |||
628 | Pa_StopStream(adout); | ||
629 | sf_close(af_handle); | ||
630 | |||
631 | { /* Hangup */ | ||
632 | TOXAV_ERR_CALL_CONTROL rc; | ||
633 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
634 | |||
635 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { | ||
636 | printf("toxav_call_control failed: %d\n", rc); | ||
637 | exit(1); | ||
638 | } | ||
639 | } | ||
640 | |||
641 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
642 | assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); | ||
643 | |||
644 | /* Stop decode thread */ | ||
645 | data.sig = -1; | ||
646 | |||
647 | while (data.sig != 1) | ||
648 | pthread_yield(); | ||
649 | |||
650 | pthread_mutex_destroy(AliceCC.arb_mutex); | ||
651 | pthread_mutex_destroy(BobCC.arb_mutex); | ||
652 | |||
653 | void *f = NULL; | ||
654 | |||
655 | while (rb_read(AliceCC.arb, &f)) | ||
656 | free(f); | ||
657 | |||
658 | while (rb_read(BobCC.arb, &f)) | ||
659 | free(f); | ||
660 | |||
661 | printf("Success!"); | ||
662 | } | ||
663 | |||
664 | if (TEST_TRANSFER_V) { | ||
665 | printf("\nTrying video enc/dec...\n"); | ||
666 | |||
667 | memset(&AliceCC, 0, sizeof(CallControl)); | ||
668 | memset(&BobCC, 0, sizeof(CallControl)); | ||
669 | |||
670 | { /* Call */ | ||
671 | TOXAV_ERR_CALL rc; | ||
672 | toxav_call(AliceAV, 0, 0, 2000, &rc); | ||
673 | |||
674 | if (rc != TOXAV_ERR_CALL_OK) { | ||
675 | printf("toxav_call failed: %d\n", rc); | ||
676 | exit(1); | ||
677 | } | ||
678 | } | ||
679 | |||
680 | while (!BobCC.incoming) | ||
681 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
682 | |||
683 | { /* Answer */ | ||
684 | TOXAV_ERR_ANSWER rc; | ||
685 | toxav_answer(BobAV, 0, 0, 5000, &rc); | ||
686 | |||
687 | if (rc != TOXAV_ERR_ANSWER_OK) { | ||
688 | printf("toxav_answer failed: %d\n", rc); | ||
689 | exit(1); | ||
690 | } | ||
691 | } | ||
692 | |||
693 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
694 | |||
695 | /* Start decode thread */ | ||
696 | struct toxav_thread_data data = { | ||
697 | .AliceAV = AliceAV, | ||
698 | .BobAV = BobAV, | ||
699 | .sig = 0 | ||
700 | }; | ||
701 | |||
702 | pthread_t dect; | ||
703 | pthread_create(&dect, NULL, iterate_toxav, &data); | ||
704 | pthread_detach(dect); | ||
705 | |||
706 | CvCapture *capture = cvCreateFileCapture(vf_name); | ||
707 | |||
708 | if (!capture) { | ||
709 | printf("Failed to open video file: %s\n", vf_name); | ||
710 | exit(1); | ||
711 | } | ||
712 | |||
713 | // toxav_video_bit_rate_set(AliceAV, 0, 5000, false, NULL); | ||
714 | |||
715 | time_t start_time = time(NULL); | ||
716 | |||
717 | while (start_time + 90 > time(NULL)) { | ||
718 | IplImage *frame = cvQueryFrame(capture ); | ||
719 | |||
720 | if (!frame) | ||
721 | break; | ||
722 | |||
723 | send_opencv_img(AliceAV, 0, frame); | ||
724 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
725 | c_sleep(10); | ||
726 | } | ||
727 | |||
728 | cvReleaseCapture(&capture); | ||
729 | |||
730 | { /* Hangup */ | ||
731 | TOXAV_ERR_CALL_CONTROL rc; | ||
732 | toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); | ||
733 | |||
734 | if (rc != TOXAV_ERR_CALL_CONTROL_OK) { | ||
735 | printf("toxav_call_control failed: %d\n", rc); | ||
736 | exit(1); | ||
737 | } | ||
738 | } | ||
739 | |||
740 | iterate_tox(bootstrap, AliceAV, BobAV); | ||
741 | assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); | ||
742 | |||
743 | /* Stop decode thread */ | ||
744 | printf("Stopping decode thread\n"); | ||
745 | data.sig = -1; | ||
746 | |||
747 | while (data.sig != 1) | ||
748 | pthread_yield(); | ||
749 | |||
750 | printf("Success!"); | ||
751 | } | ||
752 | |||
753 | |||
754 | Tox *Alice = toxav_get_tox(AliceAV); | ||
755 | Tox *Bob = toxav_get_tox(BobAV); | ||
756 | toxav_kill(BobAV); | ||
757 | toxav_kill(AliceAV); | ||
758 | tox_kill(Bob); | ||
759 | tox_kill(Alice); | ||
760 | tox_kill(bootstrap); | ||
761 | |||
762 | printf("\nTest successful!\n"); | ||
763 | |||
764 | Pa_Terminate(); | ||
765 | return 0; | ||
766 | } | ||
diff --git a/toxav/Makefile.inc b/toxav/Makefile.inc index 0b4b869d..083f862f 100644 --- a/toxav/Makefile.inc +++ b/toxav/Makefile.inc | |||
@@ -1,38 +1,42 @@ | |||
1 | if BUILD_AV | 1 | if BUILD_AV |
2 | 2 | ||
3 | lib_LTLIBRARIES += libtoxav.la | 3 | lib_LTLIBRARIES += libtoxav.la |
4 | libtoxav_la_include_HEADERS = ../toxav/toxav.h | 4 | libtoxav_la_include_HEADERS = ../toxav/toxav.h |
5 | libtoxav_la_includedir = $(includedir)/tox | 5 | libtoxav_la_includedir = $(includedir)/tox |
6 | 6 | ||
7 | libtoxav_la_SOURCES = ../toxav/rtp.h \ | 7 | libtoxav_la_SOURCES = ../toxav/rtp.h \ |
8 | ../toxav/rtp.c \ | 8 | ../toxav/rtp.c \ |
9 | ../toxav/msi.h \ | 9 | ../toxav/msi.h \ |
10 | ../toxav/msi.c \ | 10 | ../toxav/msi.c \ |
11 | ../toxav/group.h \ | 11 | ../toxav/group.h \ |
12 | ../toxav/group.c \ | 12 | ../toxav/group.c \ |
13 | ../toxav/codec.h \ | 13 | ../toxav/audio.h \ |
14 | ../toxav/codec.c \ | 14 | ../toxav/audio.c \ |
15 | ../toxav/toxav.h \ | 15 | ../toxav/video.h \ |
16 | ../toxav/toxav.c | 16 | ../toxav/video.c \ |
17 | 17 | ../toxav/bwcontroler.h \ | |
18 | ../toxav/bwcontroler.c \ | ||
19 | ../toxav/toxav.h \ | ||
20 | ../toxav/toxav.c \ | ||
21 | ../toxav/toxav_old.c | ||
18 | 22 | ||
19 | libtoxav_la_CFLAGS = -I../toxcore \ | 23 | libtoxav_la_CFLAGS = -I../toxcore \ |
20 | -I../toxav \ | 24 | -I../toxav \ |
21 | $(LIBSODIUM_CFLAGS) \ | 25 | $(LIBSODIUM_CFLAGS) \ |
22 | $(NACL_CFLAGS) \ | 26 | $(NACL_CFLAGS) \ |
23 | $(AV_CFLAGS) \ | 27 | $(AV_CFLAGS) \ |
24 | $(PTHREAD_CFLAGS) | 28 | $(PTHREAD_CFLAGS) |
25 | 29 | ||
26 | libtoxav_la_LDFLAGS = $(TOXAV_LT_LDFLAGS) \ | 30 | libtoxav_la_LDFLAGS = $(TOXAV_LT_LDFLAGS) \ |
27 | $(LIBSODIUM_LDFLAGS) \ | 31 | $(LIBSODIUM_LDFLAGS) \ |
28 | $(NACL_LDFLAGS) \ | 32 | $(NACL_LDFLAGS) \ |
29 | $(EXTRA_LT_LDFLAGS) \ | 33 | $(EXTRA_LT_LDFLAGS) \ |
30 | $(WINSOCK2_LIBS) | 34 | $(WINSOCK2_LIBS) |
31 | 35 | ||
32 | libtoxav_la_LIBADD = libtoxcore.la \ | 36 | libtoxav_la_LIBADD = libtoxcore.la \ |
33 | $(LIBSODIUM_LIBS) \ | 37 | $(LIBSODIUM_LIBS) \ |
34 | $(NACL_LIBS) \ | 38 | $(NACL_LIBS) \ |
35 | $(PTHREAD_LIBS) \ | 39 | $(PTHREAD_LIBS) \ |
36 | $(AV_LIBS) | 40 | $(AV_LIBS) |
37 | 41 | ||
38 | endif \ No newline at end of file | 42 | endif \ No newline at end of file |
diff --git a/toxav/audio.c b/toxav/audio.c new file mode 100644 index 00000000..ad543502 --- /dev/null +++ b/toxav/audio.c | |||
@@ -0,0 +1,439 @@ | |||
1 | /** audio.c | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifdef HAVE_CONFIG_H | ||
23 | #include "config.h" | ||
24 | #endif /* HAVE_CONFIG_H */ | ||
25 | |||
26 | #include <stdlib.h> | ||
27 | |||
28 | #include "audio.h" | ||
29 | #include "rtp.h" | ||
30 | |||
31 | #include "../toxcore/logger.h" | ||
32 | |||
33 | static struct JitterBuffer *jbuf_new(uint32_t capacity); | ||
34 | static void jbuf_clear(struct JitterBuffer *q); | ||
35 | static void jbuf_free(struct JitterBuffer *q); | ||
36 | static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m); | ||
37 | static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); | ||
38 | OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count); | ||
39 | bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, | ||
40 | int32_t *old_br, int32_t *old_sr, int32_t *old_ch); | ||
41 | bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels); | ||
42 | |||
43 | |||
44 | |||
45 | ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data) | ||
46 | { | ||
47 | ACSession *ac = calloc(sizeof(ACSession), 1); | ||
48 | |||
49 | if (!ac) { | ||
50 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
51 | return NULL; | ||
52 | } | ||
53 | |||
54 | if (create_recursive_mutex(ac->queue_mutex) != 0) { | ||
55 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
56 | free(ac); | ||
57 | return NULL; | ||
58 | } | ||
59 | |||
60 | int status; | ||
61 | ac->decoder = opus_decoder_create(48000, 2, &status); | ||
62 | |||
63 | if (status != OPUS_OK) { | ||
64 | LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(status)); | ||
65 | goto BASE_CLEANUP; | ||
66 | } | ||
67 | |||
68 | if (!(ac->j_buf = jbuf_new(3))) { | ||
69 | LOGGER_WARNING("Jitter buffer creaton failed!"); | ||
70 | opus_decoder_destroy(ac->decoder); | ||
71 | goto BASE_CLEANUP; | ||
72 | } | ||
73 | |||
74 | /* Initialize encoders with default values */ | ||
75 | ac->encoder = create_audio_encoder(48000, 48000, 2); | ||
76 | |||
77 | if (ac->encoder == NULL) | ||
78 | goto DECODER_CLEANUP; | ||
79 | |||
80 | ac->le_bit_rate = 48000; | ||
81 | ac->le_sample_rate = 48000; | ||
82 | ac->le_channel_count = 2; | ||
83 | |||
84 | ac->ld_channel_count = 2; | ||
85 | ac->ld_sample_rate = 48000; | ||
86 | ac->ldrts = 0; /* Make it possible to reconfigure straight away */ | ||
87 | |||
88 | /* These need to be set in order to properly | ||
89 | * do error correction with opus */ | ||
90 | ac->lp_frame_duration = 120; | ||
91 | ac->lp_sampling_rate = 48000; | ||
92 | ac->lp_channel_count = 1; | ||
93 | |||
94 | ac->av = av; | ||
95 | ac->friend_number = friend_number; | ||
96 | ac->acb.first = cb; | ||
97 | ac->acb.second = cb_data; | ||
98 | |||
99 | return ac; | ||
100 | |||
101 | DECODER_CLEANUP: | ||
102 | opus_decoder_destroy(ac->decoder); | ||
103 | jbuf_free(ac->j_buf); | ||
104 | BASE_CLEANUP: | ||
105 | pthread_mutex_destroy(ac->queue_mutex); | ||
106 | free(ac); | ||
107 | return NULL; | ||
108 | } | ||
109 | void ac_kill(ACSession *ac) | ||
110 | { | ||
111 | if (!ac) | ||
112 | return; | ||
113 | |||
114 | opus_encoder_destroy(ac->encoder); | ||
115 | opus_decoder_destroy(ac->decoder); | ||
116 | jbuf_free(ac->j_buf); | ||
117 | |||
118 | pthread_mutex_destroy(ac->queue_mutex); | ||
119 | |||
120 | LOGGER_DEBUG("Terminated audio handler: %p", ac); | ||
121 | free(ac); | ||
122 | } | ||
123 | void ac_iterate(ACSession *ac) | ||
124 | { | ||
125 | if (!ac) | ||
126 | return; | ||
127 | |||
128 | /* TODO fix this and jitter buffering */ | ||
129 | |||
130 | /* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */ | ||
131 | int16_t tmp[5760 * 2]; | ||
132 | |||
133 | struct RTPMessage *msg; | ||
134 | int rc = 0; | ||
135 | |||
136 | pthread_mutex_lock(ac->queue_mutex); | ||
137 | |||
138 | while ((msg = jbuf_read(ac->j_buf, &rc)) || rc == 2) { | ||
139 | pthread_mutex_unlock(ac->queue_mutex); | ||
140 | |||
141 | if (rc == 2) { | ||
142 | LOGGER_DEBUG("OPUS correction"); | ||
143 | int fs = (ac->lp_sampling_rate * ac->lp_frame_duration) / 1000; | ||
144 | rc = opus_decode(ac->decoder, NULL, 0, tmp, fs, 1); | ||
145 | } else { | ||
146 | /* Get values from packet and decode. */ | ||
147 | /* NOTE: This didn't work very well | ||
148 | rc = convert_bw_to_sampling_rate(opus_packet_get_bandwidth(msg->data)); | ||
149 | if (rc != -1) { | ||
150 | cs->last_packet_sampling_rate = rc; | ||
151 | } else { | ||
152 | LOGGER_WARNING("Failed to load packet values!"); | ||
153 | rtp_free_msg(msg); | ||
154 | continue; | ||
155 | }*/ | ||
156 | |||
157 | |||
158 | /* Pick up sampling rate from packet */ | ||
159 | memcpy(&ac->lp_sampling_rate, msg->data, 4); | ||
160 | ac->lp_sampling_rate = ntohl(ac->lp_sampling_rate); | ||
161 | |||
162 | ac->lp_channel_count = opus_packet_get_nb_channels(msg->data + 4); | ||
163 | |||
164 | /** NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa, | ||
165 | * it didn't work quite well. | ||
166 | */ | ||
167 | if (!reconfigure_audio_decoder(ac, ac->lp_sampling_rate, ac->lp_channel_count)) { | ||
168 | LOGGER_WARNING("Failed to reconfigure decoder!"); | ||
169 | free(msg); | ||
170 | continue; | ||
171 | } | ||
172 | |||
173 | rc = opus_decode(ac->decoder, msg->data + 4, msg->len - 4, tmp, 5760, 0); | ||
174 | free(msg); | ||
175 | } | ||
176 | |||
177 | if (rc < 0) { | ||
178 | LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); | ||
179 | } else if (ac->acb.first) { | ||
180 | ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; | ||
181 | |||
182 | ac->acb.first(ac->av, ac->friend_number, tmp, rc, ac->lp_channel_count, | ||
183 | ac->lp_sampling_rate, ac->acb.second); | ||
184 | } | ||
185 | |||
186 | return; | ||
187 | } | ||
188 | |||
189 | pthread_mutex_unlock(ac->queue_mutex); | ||
190 | } | ||
191 | int ac_queue_message(void *acp, struct RTPMessage *msg) | ||
192 | { | ||
193 | if (!acp || !msg) | ||
194 | return -1; | ||
195 | |||
196 | if ((msg->header.pt & 0x7f) == (rtp_TypeAudio + 2) % 128) { | ||
197 | LOGGER_WARNING("Got dummy!"); | ||
198 | free(msg); | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | if ((msg->header.pt & 0x7f) != rtp_TypeAudio % 128) { | ||
203 | LOGGER_WARNING("Invalid payload type!"); | ||
204 | free(msg); | ||
205 | return -1; | ||
206 | } | ||
207 | |||
208 | ACSession *ac = acp; | ||
209 | |||
210 | pthread_mutex_lock(ac->queue_mutex); | ||
211 | int rc = jbuf_write(ac->j_buf, msg); | ||
212 | pthread_mutex_unlock(ac->queue_mutex); | ||
213 | |||
214 | if (rc == -1) { | ||
215 | LOGGER_WARNING("Could not queue the message!"); | ||
216 | free(msg); | ||
217 | return -1; | ||
218 | } | ||
219 | |||
220 | return 0; | ||
221 | } | ||
222 | int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels) | ||
223 | { | ||
224 | if (!ac || !reconfigure_audio_encoder(&ac->encoder, bit_rate, | ||
225 | sampling_rate, channels, | ||
226 | &ac->le_bit_rate, | ||
227 | &ac->le_sample_rate, | ||
228 | &ac->le_channel_count)) | ||
229 | return -1; | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | |||
235 | |||
236 | struct JitterBuffer { | ||
237 | struct RTPMessage **queue; | ||
238 | uint32_t size; | ||
239 | uint32_t capacity; | ||
240 | uint16_t bottom; | ||
241 | uint16_t top; | ||
242 | }; | ||
243 | |||
244 | static struct JitterBuffer *jbuf_new(uint32_t capacity) | ||
245 | { | ||
246 | unsigned int size = 1; | ||
247 | |||
248 | while (size <= (capacity * 4)) { | ||
249 | size *= 2; | ||
250 | } | ||
251 | |||
252 | struct JitterBuffer *q; | ||
253 | |||
254 | if (!(q = calloc(sizeof(struct JitterBuffer), 1))) return NULL; | ||
255 | |||
256 | if (!(q->queue = calloc(sizeof(struct RTPMessage *), size))) { | ||
257 | free(q); | ||
258 | return NULL; | ||
259 | } | ||
260 | |||
261 | q->size = size; | ||
262 | q->capacity = capacity; | ||
263 | return q; | ||
264 | } | ||
265 | static void jbuf_clear(struct JitterBuffer *q) | ||
266 | { | ||
267 | for (; q->bottom != q->top; ++q->bottom) { | ||
268 | if (q->queue[q->bottom % q->size]) { | ||
269 | free(q->queue[q->bottom % q->size]); | ||
270 | q->queue[q->bottom % q->size] = NULL; | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | static void jbuf_free(struct JitterBuffer *q) | ||
275 | { | ||
276 | if (!q) return; | ||
277 | |||
278 | jbuf_clear(q); | ||
279 | free(q->queue); | ||
280 | free(q); | ||
281 | } | ||
282 | static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m) | ||
283 | { | ||
284 | uint16_t sequnum = m->header.sequnum; | ||
285 | |||
286 | unsigned int num = sequnum % q->size; | ||
287 | |||
288 | if ((uint32_t)(sequnum - q->bottom) > q->size) { | ||
289 | LOGGER_DEBUG("Clearing filled jitter buffer: %p", q); | ||
290 | |||
291 | jbuf_clear(q); | ||
292 | q->bottom = sequnum - q->capacity; | ||
293 | q->queue[num] = m; | ||
294 | q->top = sequnum + 1; | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | if (q->queue[num]) | ||
299 | return -1; | ||
300 | |||
301 | q->queue[num] = m; | ||
302 | |||
303 | if ((sequnum - q->bottom) >= (q->top - q->bottom)) | ||
304 | q->top = sequnum + 1; | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success) | ||
309 | { | ||
310 | if (q->top == q->bottom) { | ||
311 | *success = 0; | ||
312 | return NULL; | ||
313 | } | ||
314 | |||
315 | unsigned int num = q->bottom % q->size; | ||
316 | |||
317 | if (q->queue[num]) { | ||
318 | struct RTPMessage *ret = q->queue[num]; | ||
319 | q->queue[num] = NULL; | ||
320 | ++q->bottom; | ||
321 | *success = 1; | ||
322 | return ret; | ||
323 | } | ||
324 | |||
325 | if ((uint32_t)(q->top - q->bottom) > q->capacity) { | ||
326 | ++q->bottom; | ||
327 | *success = 2; | ||
328 | return NULL; | ||
329 | } | ||
330 | |||
331 | *success = 0; | ||
332 | return NULL; | ||
333 | } | ||
334 | OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count) | ||
335 | { | ||
336 | int status = OPUS_OK; | ||
337 | OpusEncoder *rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_VOIP, &status); | ||
338 | |||
339 | if (status != OPUS_OK) { | ||
340 | LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(status)); | ||
341 | return NULL; | ||
342 | } | ||
343 | |||
344 | status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate)); | ||
345 | |||
346 | if (status != OPUS_OK) { | ||
347 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
348 | goto FAILURE; | ||
349 | } | ||
350 | |||
351 | /* Enable in-band forward error correction in codec */ | ||
352 | status = opus_encoder_ctl(rc, OPUS_SET_INBAND_FEC(1)); | ||
353 | |||
354 | if (status != OPUS_OK) { | ||
355 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
356 | goto FAILURE; | ||
357 | } | ||
358 | |||
359 | /* Make codec resistant to up to 10% packet loss | ||
360 | * NOTE This could also be adjusted on the fly, rather than hard-coded, | ||
361 | * with feedback from the receiving client. | ||
362 | */ | ||
363 | status = opus_encoder_ctl(rc, OPUS_SET_PACKET_LOSS_PERC(10)); | ||
364 | |||
365 | if (status != OPUS_OK) { | ||
366 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
367 | goto FAILURE; | ||
368 | } | ||
369 | |||
370 | /* Set algorithm to the highest complexity, maximizing compression */ | ||
371 | status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(10)); | ||
372 | |||
373 | if (status != OPUS_OK) { | ||
374 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
375 | goto FAILURE; | ||
376 | } | ||
377 | |||
378 | return rc; | ||
379 | |||
380 | FAILURE: | ||
381 | opus_encoder_destroy(rc); | ||
382 | return NULL; | ||
383 | } | ||
384 | bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, | ||
385 | int32_t *old_br, int32_t *old_sr, int32_t *old_ch) | ||
386 | { | ||
387 | /* Values are checked in toxav.c */ | ||
388 | if (*old_sr != new_sr || *old_ch != new_ch) { | ||
389 | OpusEncoder *new_encoder = create_audio_encoder(new_br, new_sr, new_ch); | ||
390 | |||
391 | if (new_encoder == NULL) | ||
392 | return false; | ||
393 | |||
394 | opus_encoder_destroy(*e); | ||
395 | *e = new_encoder; | ||
396 | } else if (*old_br == new_br) | ||
397 | return true; /* Nothing changed */ | ||
398 | else { | ||
399 | int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br)); | ||
400 | |||
401 | if (status != OPUS_OK) { | ||
402 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
403 | return false; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | *old_br = new_br; | ||
408 | *old_sr = new_sr; | ||
409 | *old_ch = new_ch; | ||
410 | |||
411 | LOGGER_DEBUG ("Reconfigured audio encoder br: %d sr: %d cc:%d", new_br, new_sr, new_ch); | ||
412 | return true; | ||
413 | } | ||
414 | bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels) | ||
415 | { | ||
416 | if (sampling_rate != ac->ld_sample_rate || channels != ac->ld_channel_count) { | ||
417 | if (current_time_monotonic() - ac->ldrts < 500) | ||
418 | return false; | ||
419 | |||
420 | int status; | ||
421 | OpusDecoder *new_dec = opus_decoder_create(sampling_rate, channels, &status); | ||
422 | |||
423 | if (status != OPUS_OK) { | ||
424 | LOGGER_ERROR("Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status)); | ||
425 | return false; | ||
426 | } | ||
427 | |||
428 | ac->ld_sample_rate = sampling_rate; | ||
429 | ac->ld_channel_count = channels; | ||
430 | ac->ldrts = current_time_monotonic(); | ||
431 | |||
432 | opus_decoder_destroy(ac->decoder); | ||
433 | ac->decoder = new_dec; | ||
434 | |||
435 | LOGGER_DEBUG("Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels); | ||
436 | } | ||
437 | |||
438 | return true; | ||
439 | } | ||
diff --git a/toxav/audio.h b/toxav/audio.h new file mode 100644 index 00000000..b1db7448 --- /dev/null +++ b/toxav/audio.h | |||
@@ -0,0 +1,64 @@ | |||
1 | /** audio.h | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifndef AUDIO_H | ||
23 | #define AUDIO_H | ||
24 | |||
25 | #include <opus.h> | ||
26 | #include <pthread.h> | ||
27 | |||
28 | #include "toxav.h" | ||
29 | |||
30 | #include "../toxcore/util.h" | ||
31 | |||
32 | struct RTPMessage; | ||
33 | |||
34 | typedef struct ACSession_s { | ||
35 | /* encoding */ | ||
36 | OpusEncoder *encoder; | ||
37 | int32_t le_sample_rate; /* Last encoder sample rate */ | ||
38 | int32_t le_channel_count; /* Last encoder channel count */ | ||
39 | int32_t le_bit_rate; /* Last encoder bit rate */ | ||
40 | |||
41 | /* decoding */ | ||
42 | OpusDecoder *decoder; | ||
43 | int32_t lp_channel_count; /* Last packet channel count */ | ||
44 | int32_t lp_sampling_rate; /* Last packet sample rate */ | ||
45 | int32_t lp_frame_duration; /* Last packet frame duration */ | ||
46 | int32_t ld_sample_rate; /* Last decoder sample rate */ | ||
47 | int32_t ld_channel_count; /* Last decoder channel count */ | ||
48 | uint64_t ldrts; /* Last decoder reconfiguration time stamp */ | ||
49 | void *j_buf; | ||
50 | |||
51 | pthread_mutex_t queue_mutex[1]; | ||
52 | |||
53 | ToxAV *av; | ||
54 | uint32_t friend_number; | ||
55 | PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ | ||
56 | } ACSession; | ||
57 | |||
58 | ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data); | ||
59 | void ac_kill(ACSession *ac); | ||
60 | void ac_iterate(ACSession *ac); | ||
61 | int ac_queue_message(void *acp, struct RTPMessage *msg); | ||
62 | int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels); | ||
63 | |||
64 | #endif /* AUDIO_H */ | ||
diff --git a/toxav/bwcontroler.c b/toxav/bwcontroler.c new file mode 100644 index 00000000..9dd15c93 --- /dev/null +++ b/toxav/bwcontroler.c | |||
@@ -0,0 +1,205 @@ | |||
1 | /** bwcontroler.c | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifdef HAVE_CONFIG_H | ||
23 | #include "config.h" | ||
24 | #endif /* HAVE_CONFIG_H */ | ||
25 | |||
26 | #include <assert.h> | ||
27 | #include "bwcontroler.h" | ||
28 | #include "../toxcore/logger.h" | ||
29 | #include "../toxcore/util.h" | ||
30 | |||
31 | #define BWC_PACKET_ID 196 | ||
32 | #define BWC_SEND_INTERVAL_MS 1000 | ||
33 | #define BWC_REFRESH_INTERVAL_MS 10000 | ||
34 | #define BWC_AVG_PKT_COUNT 20 | ||
35 | |||
36 | /** | ||
37 | * | ||
38 | */ | ||
39 | |||
40 | struct BWControler_s { | ||
41 | void (*mcb) (BWControler *, uint32_t, float, void *); | ||
42 | void *mcb_data; | ||
43 | |||
44 | Messenger *m; | ||
45 | uint32_t friend_number; | ||
46 | |||
47 | struct { | ||
48 | uint32_t lru; /* Last recv update time stamp */ | ||
49 | uint32_t lsu; /* Last sent update time stamp */ | ||
50 | uint32_t lfu; /* Last refresh time stamp */ | ||
51 | |||
52 | uint32_t lost; | ||
53 | uint32_t recv; | ||
54 | } cycle; | ||
55 | |||
56 | struct { | ||
57 | uint32_t rb_s[BWC_AVG_PKT_COUNT]; | ||
58 | RingBuffer *rb; | ||
59 | } rcvpkt; /* To calculate average received packet */ | ||
60 | }; | ||
61 | |||
62 | int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); | ||
63 | void send_update(BWControler *bwc); | ||
64 | |||
65 | BWControler *bwc_new(Messenger *m, uint32_t friendnumber, | ||
66 | void (*mcb) (BWControler *, uint32_t, float, void *), | ||
67 | void *udata) | ||
68 | { | ||
69 | BWControler *retu = calloc(sizeof(struct BWControler_s), 1); | ||
70 | |||
71 | retu->mcb = mcb; | ||
72 | retu->mcb_data = udata; | ||
73 | retu->m = m; | ||
74 | retu->friend_number = friendnumber; | ||
75 | retu->cycle.lsu = retu->cycle.lfu = current_time_monotonic(); | ||
76 | retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); | ||
77 | |||
78 | /* Fill with zeros */ | ||
79 | int i = 0; | ||
80 | |||
81 | for (; i < BWC_AVG_PKT_COUNT; i ++) | ||
82 | rb_write(retu->rcvpkt.rb, retu->rcvpkt.rb_s + i); | ||
83 | |||
84 | m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); | ||
85 | |||
86 | return retu; | ||
87 | } | ||
88 | void bwc_kill(BWControler *bwc) | ||
89 | { | ||
90 | if (!bwc) | ||
91 | return; | ||
92 | |||
93 | m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, NULL, NULL); | ||
94 | |||
95 | rb_kill(bwc->rcvpkt.rb); | ||
96 | free(bwc); | ||
97 | } | ||
98 | void bwc_feed_avg(BWControler *bwc, uint32_t bytes) | ||
99 | { | ||
100 | uint32_t *p; | ||
101 | |||
102 | rb_read(bwc->rcvpkt.rb, (void **) &p); | ||
103 | rb_write(bwc->rcvpkt.rb, p); | ||
104 | |||
105 | *p = bytes; | ||
106 | } | ||
107 | void bwc_add_lost(BWControler *bwc, uint32_t bytes) | ||
108 | { | ||
109 | if (!bwc) | ||
110 | return; | ||
111 | |||
112 | if (!bytes) { | ||
113 | uint32_t *t_avg[BWC_AVG_PKT_COUNT], c = 1; | ||
114 | |||
115 | rb_data(bwc->rcvpkt.rb, (void **) t_avg); | ||
116 | |||
117 | int i = 0; | ||
118 | |||
119 | for (; i < BWC_AVG_PKT_COUNT; i ++) { | ||
120 | bytes += *(t_avg[i]); | ||
121 | |||
122 | if (*(t_avg[i])) | ||
123 | c++; | ||
124 | } | ||
125 | |||
126 | bytes /= c; | ||
127 | } | ||
128 | |||
129 | bwc->cycle.lost += bytes; | ||
130 | send_update(bwc); | ||
131 | } | ||
132 | void bwc_add_recv(BWControler *bwc, uint32_t bytes) | ||
133 | { | ||
134 | if (!bwc || !bytes) | ||
135 | return; | ||
136 | |||
137 | bwc->cycle.recv += bytes; | ||
138 | send_update(bwc); | ||
139 | } | ||
140 | |||
141 | |||
142 | struct BWCMessage { | ||
143 | uint32_t lost; | ||
144 | uint32_t recv; | ||
145 | }; | ||
146 | |||
147 | void send_update(BWControler *bwc) | ||
148 | { | ||
149 | if (current_time_monotonic() - bwc->cycle.lfu > BWC_REFRESH_INTERVAL_MS) { | ||
150 | |||
151 | bwc->cycle.lost /= 10; | ||
152 | bwc->cycle.recv /= 10; | ||
153 | bwc->cycle.lfu = current_time_monotonic(); | ||
154 | } else if (current_time_monotonic() - bwc->cycle.lsu > BWC_SEND_INTERVAL_MS) { | ||
155 | |||
156 | if (bwc->cycle.lost) { | ||
157 | LOGGER_DEBUG ("%p Sent update rcv: %u lost: %u", | ||
158 | bwc, bwc->cycle.recv, bwc->cycle.lost); | ||
159 | |||
160 | uint8_t p_msg[sizeof(struct BWCMessage) + 1]; | ||
161 | struct BWCMessage *b_msg = (struct BWCMessage *)(p_msg + 1); | ||
162 | |||
163 | p_msg[0] = BWC_PACKET_ID; | ||
164 | b_msg->lost = htonl(bwc->cycle.lost); | ||
165 | b_msg->recv = htonl(bwc->cycle.recv); | ||
166 | |||
167 | if (-1 == send_custom_lossy_packet(bwc->m, bwc->friend_number, p_msg, sizeof(p_msg))) | ||
168 | LOGGER_WARNING("BWC send failed (len: %d)! std error: %s", sizeof(p_msg), strerror(errno)); | ||
169 | } | ||
170 | |||
171 | bwc->cycle.lsu = current_time_monotonic(); | ||
172 | } | ||
173 | } | ||
174 | int on_update (BWControler *bwc, struct BWCMessage *msg) | ||
175 | { | ||
176 | LOGGER_DEBUG ("%p Got update from peer", bwc); | ||
177 | |||
178 | /* Peer must respect time boundary */ | ||
179 | if (current_time_monotonic() < bwc->cycle.lru + BWC_SEND_INTERVAL_MS) { | ||
180 | LOGGER_DEBUG("%p Rejecting extra update", bwc); | ||
181 | return -1; | ||
182 | } | ||
183 | |||
184 | bwc->cycle.lru = current_time_monotonic(); | ||
185 | |||
186 | msg->recv = ntohl(msg->recv); | ||
187 | msg->lost = ntohl(msg->lost); | ||
188 | |||
189 | LOGGER_DEBUG ("recved: %u lost: %u", msg->recv, msg->lost); | ||
190 | |||
191 | if (msg->lost && bwc->mcb) | ||
192 | bwc->mcb(bwc, bwc->friend_number, | ||
193 | ((float) (msg->lost) / (msg->recv + msg->lost)), | ||
194 | bwc->mcb_data); | ||
195 | |||
196 | return 0; | ||
197 | } | ||
198 | int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) | ||
199 | { | ||
200 | if (length - 1 != sizeof(struct BWCMessage)) | ||
201 | return -1; | ||
202 | |||
203 | /* NOTE the data is mutable */ | ||
204 | return on_update(object, (struct BWCMessage *) (data + 1)); | ||
205 | } | ||
diff --git a/toxav/bwcontroler.h b/toxav/bwcontroler.h new file mode 100644 index 00000000..53b07d38 --- /dev/null +++ b/toxav/bwcontroler.h | |||
@@ -0,0 +1,37 @@ | |||
1 | /** bwcontroler.h | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifndef BWCONROLER_H | ||
23 | #define BWCONROLER_H | ||
24 | #include "../toxcore/Messenger.h" | ||
25 | |||
26 | typedef struct BWControler_s BWControler; | ||
27 | |||
28 | BWControler *bwc_new(Messenger *m, uint32_t friendnumber, | ||
29 | void (*mcb) (BWControler *, uint32_t, float, void *), | ||
30 | void *udata); | ||
31 | void bwc_kill(BWControler *bwc); | ||
32 | |||
33 | void bwc_feed_avg(BWControler *bwc, uint32_t bytes); | ||
34 | void bwc_add_lost(BWControler *bwc, uint32_t bytes); | ||
35 | void bwc_add_recv(BWControler *bwc, uint32_t bytes); | ||
36 | |||
37 | #endif /* BWCONROLER_H */ | ||
diff --git a/toxav/codec.c b/toxav/codec.c deleted file mode 100644 index 8940aa25..00000000 --- a/toxav/codec.c +++ /dev/null | |||
@@ -1,705 +0,0 @@ | |||
1 | /** codec.c | ||
2 | * | ||
3 | * Audio and video codec intitialization, encoding/decoding and playback | ||
4 | * | ||
5 | * Copyright (C) 2013 Tox project All Rights Reserved. | ||
6 | * | ||
7 | * This file is part of Tox. | ||
8 | * | ||
9 | * Tox is free software: you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation, either version 3 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * Tox is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
21 | * | ||
22 | */ | ||
23 | |||
24 | |||
25 | #ifdef HAVE_CONFIG_H | ||
26 | #include "config.h" | ||
27 | #endif /* HAVE_CONFIG_H */ | ||
28 | |||
29 | #include "codec.h" | ||
30 | #include "../toxcore/logger.h" | ||
31 | #include "../toxcore/util.h" | ||
32 | |||
33 | #include <stdio.h> | ||
34 | #include <stdlib.h> | ||
35 | #include <math.h> | ||
36 | #include <assert.h> | ||
37 | #include <time.h> | ||
38 | |||
39 | #include "msi.h" | ||
40 | #include "rtp.h" | ||
41 | |||
42 | /* Good quality encode. */ | ||
43 | #define MAX_DECODE_TIME_US 0 | ||
44 | |||
45 | // TODO this has to be exchanged in msi | ||
46 | #define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */ | ||
47 | #define VIDEOFRAME_PIECE_SIZE 0x500 /* 1.25 KiB*/ | ||
48 | #define VIDEOFRAME_HEADER_SIZE 0x2 | ||
49 | |||
50 | /* FIXME: Might not be enough */ | ||
51 | #define VIDEO_DECODE_BUFFER_SIZE 20 | ||
52 | |||
53 | #define ARRAY(TYPE__) struct { uint16_t size; TYPE__ data[]; } | ||
54 | |||
55 | typedef ARRAY(uint8_t) Payload; | ||
56 | |||
57 | typedef struct { | ||
58 | uint16_t size; /* Max size */ | ||
59 | uint16_t start; | ||
60 | uint16_t end; | ||
61 | Payload **packets; | ||
62 | } PayloadBuffer; | ||
63 | |||
64 | static _Bool buffer_full(const PayloadBuffer *b) | ||
65 | { | ||
66 | return (b->end + 1) % b->size == b->start; | ||
67 | } | ||
68 | |||
69 | static _Bool buffer_empty(const PayloadBuffer *b) | ||
70 | { | ||
71 | return b->end == b->start; | ||
72 | } | ||
73 | |||
74 | static void buffer_write(PayloadBuffer *b, Payload *p) | ||
75 | { | ||
76 | b->packets[b->end] = p; | ||
77 | b->end = (b->end + 1) % b->size; | ||
78 | |||
79 | if (b->end == b->start) b->start = (b->start + 1) % b->size; /* full, overwrite */ | ||
80 | } | ||
81 | |||
82 | static void buffer_read(PayloadBuffer *b, Payload **p) | ||
83 | { | ||
84 | *p = b->packets[b->start]; | ||
85 | b->start = (b->start + 1) % b->size; | ||
86 | } | ||
87 | |||
88 | static void buffer_clear(PayloadBuffer *b) | ||
89 | { | ||
90 | while (!buffer_empty(b)) { | ||
91 | Payload *p; | ||
92 | buffer_read(b, &p); | ||
93 | free(p); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | static PayloadBuffer *buffer_new(int size) | ||
98 | { | ||
99 | PayloadBuffer *buf = calloc(sizeof(PayloadBuffer), 1); | ||
100 | |||
101 | if (!buf) return NULL; | ||
102 | |||
103 | buf->size = size + 1; /* include empty elem */ | ||
104 | |||
105 | if (!(buf->packets = calloc(buf->size, sizeof(Payload *)))) { | ||
106 | free(buf); | ||
107 | return NULL; | ||
108 | } | ||
109 | |||
110 | return buf; | ||
111 | } | ||
112 | |||
113 | static void buffer_free(PayloadBuffer *b) | ||
114 | { | ||
115 | if (b) { | ||
116 | buffer_clear(b); | ||
117 | free(b->packets); | ||
118 | free(b); | ||
119 | } | ||
120 | } | ||
121 | |||
122 | /* JITTER BUFFER WORK */ | ||
123 | typedef struct _JitterBuffer { | ||
124 | RTPMessage **queue; | ||
125 | uint32_t size; | ||
126 | uint32_t capacity; | ||
127 | uint16_t bottom; | ||
128 | uint16_t top; | ||
129 | } JitterBuffer; | ||
130 | |||
131 | static JitterBuffer *jbuf_new(uint32_t capacity) | ||
132 | { | ||
133 | unsigned int size = 1; | ||
134 | |||
135 | while (size <= (capacity * 4)) { | ||
136 | size *= 2; | ||
137 | } | ||
138 | |||
139 | JitterBuffer *q; | ||
140 | |||
141 | if ( !(q = calloc(sizeof(JitterBuffer), 1)) ) return NULL; | ||
142 | |||
143 | if (!(q->queue = calloc(sizeof(RTPMessage *), size))) { | ||
144 | free(q); | ||
145 | return NULL; | ||
146 | } | ||
147 | |||
148 | q->size = size; | ||
149 | q->capacity = capacity; | ||
150 | return q; | ||
151 | } | ||
152 | |||
153 | static void jbuf_clear(JitterBuffer *q) | ||
154 | { | ||
155 | for (; q->bottom != q->top; ++q->bottom) { | ||
156 | if (q->queue[q->bottom % q->size]) { | ||
157 | rtp_free_msg(NULL, q->queue[q->bottom % q->size]); | ||
158 | q->queue[q->bottom % q->size] = NULL; | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | |||
163 | static void jbuf_free(JitterBuffer *q) | ||
164 | { | ||
165 | if (!q) return; | ||
166 | |||
167 | jbuf_clear(q); | ||
168 | free(q->queue); | ||
169 | free(q); | ||
170 | } | ||
171 | |||
172 | static int jbuf_write(JitterBuffer *q, RTPMessage *m) | ||
173 | { | ||
174 | uint16_t sequnum = m->header->sequnum; | ||
175 | |||
176 | unsigned int num = sequnum % q->size; | ||
177 | |||
178 | if ((uint32_t)(sequnum - q->bottom) > q->size) { | ||
179 | jbuf_clear(q); | ||
180 | q->bottom = sequnum - q->capacity; | ||
181 | q->queue[num] = m; | ||
182 | q->top = sequnum + 1; | ||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | if (q->queue[num]) | ||
187 | return -1; | ||
188 | |||
189 | q->queue[num] = m; | ||
190 | |||
191 | if ((sequnum - q->bottom) >= (q->top - q->bottom)) | ||
192 | q->top = sequnum + 1; | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | /* Success is 0 when there is nothing to dequeue, | ||
198 | * 1 when there's a good packet, | ||
199 | * 2 when there's a lost packet */ | ||
200 | static RTPMessage *jbuf_read(JitterBuffer *q, int32_t *success) | ||
201 | { | ||
202 | if (q->top == q->bottom) { | ||
203 | *success = 0; | ||
204 | return NULL; | ||
205 | } | ||
206 | |||
207 | unsigned int num = q->bottom % q->size; | ||
208 | |||
209 | if (q->queue[num]) { | ||
210 | RTPMessage *ret = q->queue[num]; | ||
211 | q->queue[num] = NULL; | ||
212 | ++q->bottom; | ||
213 | *success = 1; | ||
214 | return ret; | ||
215 | } | ||
216 | |||
217 | if ((uint32_t)(q->top - q->bottom) > q->capacity) { | ||
218 | ++q->bottom; | ||
219 | *success = 2; | ||
220 | return NULL; | ||
221 | } | ||
222 | |||
223 | *success = 0; | ||
224 | return NULL; | ||
225 | } | ||
226 | |||
227 | static int init_video_decoder(CSSession *cs) | ||
228 | { | ||
229 | int rc = vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0, VPX_DECODER_ABI_VERSION); | ||
230 | |||
231 | if ( rc != VPX_CODEC_OK) { | ||
232 | LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); | ||
233 | return -1; | ||
234 | } | ||
235 | |||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static int init_audio_decoder(CSSession *cs) | ||
240 | { | ||
241 | int rc; | ||
242 | cs->audio_decoder = opus_decoder_create(cs->audio_decoder_sample_rate, cs->audio_decoder_channels, &rc ); | ||
243 | |||
244 | if ( rc != OPUS_OK ) { | ||
245 | LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); | ||
246 | return -1; | ||
247 | } | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int init_video_encoder(CSSession *cs, uint16_t max_width, uint16_t max_height, uint32_t video_bitrate) | ||
253 | { | ||
254 | vpx_codec_enc_cfg_t cfg; | ||
255 | int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
256 | |||
257 | if (rc != VPX_CODEC_OK) { | ||
258 | LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc)); | ||
259 | return -1; | ||
260 | } | ||
261 | |||
262 | cfg.rc_target_bitrate = video_bitrate; | ||
263 | cfg.g_w = max_width; | ||
264 | cfg.g_h = max_height; | ||
265 | cfg.g_pass = VPX_RC_ONE_PASS; | ||
266 | cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; | ||
267 | cfg.g_lag_in_frames = 0; | ||
268 | cfg.kf_min_dist = 0; | ||
269 | cfg.kf_max_dist = 48; | ||
270 | cfg.kf_mode = VPX_KF_AUTO; | ||
271 | |||
272 | rc = vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, VPX_ENCODER_ABI_VERSION); | ||
273 | |||
274 | if ( rc != VPX_CODEC_OK) { | ||
275 | LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | ||
276 | return -1; | ||
277 | } | ||
278 | |||
279 | rc = vpx_codec_control(&cs->v_encoder, VP8E_SET_CPUUSED, 8); | ||
280 | |||
281 | if ( rc != VPX_CODEC_OK) { | ||
282 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
283 | return -1; | ||
284 | } | ||
285 | |||
286 | cs->max_width = max_width; | ||
287 | cs->max_height = max_height; | ||
288 | cs->video_bitrate = video_bitrate; | ||
289 | |||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | static int init_audio_encoder(CSSession *cs) | ||
294 | { | ||
295 | int rc = OPUS_OK; | ||
296 | cs->audio_encoder = opus_encoder_create(cs->audio_encoder_sample_rate, | ||
297 | cs->audio_encoder_channels, OPUS_APPLICATION_VOIP, &rc); | ||
298 | |||
299 | if ( rc != OPUS_OK ) { | ||
300 | LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); | ||
301 | return -1; | ||
302 | } | ||
303 | |||
304 | rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_encoder_bitrate)); | ||
305 | |||
306 | if ( rc != OPUS_OK ) { | ||
307 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | ||
308 | return -1; | ||
309 | } | ||
310 | |||
311 | /* Enable in-band forward error correction in codec */ | ||
312 | rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_INBAND_FEC(1)); | ||
313 | |||
314 | if ( rc != OPUS_OK ) { | ||
315 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | ||
316 | return -1; | ||
317 | } | ||
318 | |||
319 | /* Make codec resistant to up to 10% packet loss */ | ||
320 | rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_PACKET_LOSS_PERC(10)); | ||
321 | |||
322 | if ( rc != OPUS_OK ) { | ||
323 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | ||
324 | return -1; | ||
325 | } | ||
326 | |||
327 | /* Set algorithm to the highest complexity, maximizing compression */ | ||
328 | rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10)); | ||
329 | |||
330 | if ( rc != OPUS_OK ) { | ||
331 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | ||
332 | return -1; | ||
333 | } | ||
334 | |||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | /* PUBLIC */ | ||
339 | int cs_split_video_payload(CSSession *cs, const uint8_t *payload, uint16_t length) | ||
340 | { | ||
341 | if (!cs || !length || length > cs->max_video_frame_size) { | ||
342 | LOGGER_ERROR("Invalid CodecState or video frame size: %u", length); | ||
343 | return cs_ErrorSplittingVideoPayload; | ||
344 | } | ||
345 | |||
346 | cs->split_video_frame[0] = cs->frameid_out++; | ||
347 | cs->split_video_frame[1] = 0; | ||
348 | cs->processing_video_frame = payload; | ||
349 | cs->processing_video_frame_size = length; | ||
350 | |||
351 | return ((length - 1) / cs->video_frame_piece_size) + 1; | ||
352 | } | ||
353 | |||
354 | const uint8_t *cs_get_split_video_frame(CSSession *cs, uint16_t *size) | ||
355 | { | ||
356 | if (!cs || !size) return NULL; | ||
357 | |||
358 | if (cs->processing_video_frame_size > cs->video_frame_piece_size) { | ||
359 | memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
360 | cs->processing_video_frame, | ||
361 | cs->video_frame_piece_size); | ||
362 | |||
363 | cs->processing_video_frame += cs->video_frame_piece_size; | ||
364 | cs->processing_video_frame_size -= cs->video_frame_piece_size; | ||
365 | |||
366 | *size = cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE; | ||
367 | } else { | ||
368 | memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
369 | cs->processing_video_frame, | ||
370 | cs->processing_video_frame_size); | ||
371 | |||
372 | *size = cs->processing_video_frame_size + VIDEOFRAME_HEADER_SIZE; | ||
373 | } | ||
374 | |||
375 | cs->split_video_frame[1]++; | ||
376 | |||
377 | return cs->split_video_frame; | ||
378 | } | ||
379 | |||
380 | void cs_do(CSSession *cs) | ||
381 | { | ||
382 | /* Codec session should always be protected by call mutex so no need to check for cs validity | ||
383 | */ | ||
384 | |||
385 | if (!cs) return; | ||
386 | |||
387 | Payload *p; | ||
388 | int rc; | ||
389 | |||
390 | int success = 0; | ||
391 | |||
392 | pthread_mutex_lock(cs->queue_mutex); | ||
393 | RTPMessage *msg; | ||
394 | |||
395 | while ((msg = jbuf_read(cs->j_buf, &success)) || success == 2) { | ||
396 | pthread_mutex_unlock(cs->queue_mutex); | ||
397 | |||
398 | uint16_t fsize = ((cs->audio_decoder_sample_rate * cs->audio_decoder_frame_duration) / 1000); | ||
399 | int16_t tmp[fsize * cs->audio_decoder_channels]; | ||
400 | |||
401 | if (success == 2) { | ||
402 | rc = opus_decode(cs->audio_decoder, 0, 0, tmp, fsize, 1); | ||
403 | } else { | ||
404 | rc = opus_decode(cs->audio_decoder, msg->data, msg->length, tmp, fsize, 0); | ||
405 | rtp_free_msg(NULL, msg); | ||
406 | } | ||
407 | |||
408 | if (rc < 0) { | ||
409 | LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); | ||
410 | } else if (cs->acb.first) { | ||
411 | /* Play */ | ||
412 | cs->acb.first(cs->agent, cs->call_idx, tmp, rc, cs->acb.second); | ||
413 | } | ||
414 | |||
415 | pthread_mutex_lock(cs->queue_mutex); | ||
416 | } | ||
417 | |||
418 | if (cs->vbuf_raw && !buffer_empty(cs->vbuf_raw)) { | ||
419 | /* Decode video */ | ||
420 | buffer_read(cs->vbuf_raw, &p); | ||
421 | |||
422 | /* Leave space for (possibly) other thread to queue more data after we read it here */ | ||
423 | pthread_mutex_unlock(cs->queue_mutex); | ||
424 | |||
425 | rc = vpx_codec_decode(&cs->v_decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US); | ||
426 | free(p); | ||
427 | |||
428 | if (rc != VPX_CODEC_OK) { | ||
429 | LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); | ||
430 | } else { | ||
431 | vpx_codec_iter_t iter = NULL; | ||
432 | vpx_image_t *dest = vpx_codec_get_frame(&cs->v_decoder, &iter); | ||
433 | |||
434 | /* Play decoded images */ | ||
435 | for (; dest; dest = vpx_codec_get_frame(&cs->v_decoder, &iter)) { | ||
436 | if (cs->vcb.first) | ||
437 | cs->vcb.first(cs->agent, cs->call_idx, dest, cs->vcb.second); | ||
438 | |||
439 | vpx_img_free(dest); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | return; | ||
444 | } | ||
445 | |||
446 | pthread_mutex_unlock(cs->queue_mutex); | ||
447 | } | ||
448 | |||
449 | int cs_set_video_encoder_resolution(CSSession *cs, uint16_t width, uint16_t height) | ||
450 | { | ||
451 | vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; | ||
452 | |||
453 | if (cfg.g_w == width && cfg.g_h == height) | ||
454 | return 0; | ||
455 | |||
456 | if (width * height > cs->max_width * cs->max_height) { | ||
457 | vpx_codec_ctx_t v_encoder = cs->v_encoder; | ||
458 | |||
459 | if (init_video_encoder(cs, width, height, cs->video_bitrate) == -1) { | ||
460 | cs->v_encoder = v_encoder; | ||
461 | return cs_ErrorSettingVideoResolution; | ||
462 | } | ||
463 | |||
464 | vpx_codec_destroy(&v_encoder); | ||
465 | return 0; | ||
466 | } | ||
467 | |||
468 | LOGGER_DEBUG("New video resolution: %u %u", width, height); | ||
469 | cfg.g_w = width; | ||
470 | cfg.g_h = height; | ||
471 | int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); | ||
472 | |||
473 | if ( rc != VPX_CODEC_OK) { | ||
474 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
475 | return cs_ErrorSettingVideoResolution; | ||
476 | } | ||
477 | |||
478 | return 0; | ||
479 | } | ||
480 | |||
481 | int cs_set_video_encoder_bitrate(CSSession *cs, uint32_t video_bitrate) | ||
482 | { | ||
483 | vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; | ||
484 | |||
485 | if (cfg.rc_target_bitrate == video_bitrate) | ||
486 | return 0; | ||
487 | |||
488 | LOGGER_DEBUG("New video bitrate: %u", video_bitrate); | ||
489 | cfg.rc_target_bitrate = video_bitrate; | ||
490 | int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); | ||
491 | |||
492 | if ( rc != VPX_CODEC_OK) { | ||
493 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
494 | return cs_ErrorSettingVideoBitrate; | ||
495 | } | ||
496 | |||
497 | cs->video_bitrate = video_bitrate; | ||
498 | return 0; | ||
499 | } | ||
500 | |||
501 | CSSession *cs_new(const ToxAvCSettings *cs_self, const ToxAvCSettings *cs_peer, uint32_t jbuf_size, int has_video) | ||
502 | { | ||
503 | CSSession *cs = calloc(sizeof(CSSession), 1); | ||
504 | |||
505 | if (!cs) { | ||
506 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
507 | return NULL; | ||
508 | } | ||
509 | |||
510 | if (create_recursive_mutex(cs->queue_mutex) != 0) { | ||
511 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
512 | free(cs); | ||
513 | return NULL; | ||
514 | } | ||
515 | |||
516 | if ( !(cs->j_buf = jbuf_new(jbuf_size)) ) { | ||
517 | LOGGER_WARNING("Jitter buffer creaton failed!"); | ||
518 | goto error; | ||
519 | } | ||
520 | |||
521 | cs->audio_encoder_bitrate = cs_self->audio_bitrate; | ||
522 | cs->audio_encoder_sample_rate = cs_self->audio_sample_rate; | ||
523 | cs->audio_encoder_channels = cs_self->audio_channels; | ||
524 | cs->audio_encoder_frame_duration = cs_self->audio_frame_duration; | ||
525 | |||
526 | cs->audio_decoder_bitrate = cs_peer->audio_bitrate; | ||
527 | cs->audio_decoder_sample_rate = cs_peer->audio_sample_rate; | ||
528 | cs->audio_decoder_channels = cs_peer->audio_channels; | ||
529 | cs->audio_decoder_frame_duration = cs_peer->audio_frame_duration; | ||
530 | |||
531 | |||
532 | cs->capabilities |= ( 0 == init_audio_encoder(cs) ) ? cs_AudioEncoding : 0; | ||
533 | cs->capabilities |= ( 0 == init_audio_decoder(cs) ) ? cs_AudioDecoding : 0; | ||
534 | |||
535 | if ( !(cs->capabilities & cs_AudioEncoding) || !(cs->capabilities & cs_AudioDecoding) ) goto error; | ||
536 | |||
537 | if ((cs->support_video = has_video)) { | ||
538 | cs->max_video_frame_size = MAX_VIDEOFRAME_SIZE; | ||
539 | cs->video_frame_piece_size = VIDEOFRAME_PIECE_SIZE; | ||
540 | |||
541 | cs->capabilities |= ( 0 == init_video_encoder(cs, cs_self->max_video_width, | ||
542 | cs_self->max_video_height, cs_self->video_bitrate) ) ? cs_VideoEncoding : 0; | ||
543 | cs->capabilities |= ( 0 == init_video_decoder(cs) ) ? cs_VideoDecoding : 0; | ||
544 | |||
545 | if ( !(cs->capabilities & cs_VideoEncoding) || !(cs->capabilities & cs_VideoDecoding) ) goto error; | ||
546 | |||
547 | if ( !(cs->frame_buf = calloc(cs->max_video_frame_size, 1)) ) goto error; | ||
548 | |||
549 | if ( !(cs->split_video_frame = calloc(cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE, 1)) ) | ||
550 | goto error; | ||
551 | |||
552 | if ( !(cs->vbuf_raw = buffer_new(VIDEO_DECODE_BUFFER_SIZE)) ) goto error; | ||
553 | } | ||
554 | |||
555 | return cs; | ||
556 | |||
557 | error: | ||
558 | LOGGER_WARNING("Error initializing codec session! Application might misbehave!"); | ||
559 | |||
560 | pthread_mutex_destroy(cs->queue_mutex); | ||
561 | |||
562 | if ( cs->audio_encoder ) opus_encoder_destroy(cs->audio_encoder); | ||
563 | |||
564 | if ( cs->audio_decoder ) opus_decoder_destroy(cs->audio_decoder); | ||
565 | |||
566 | |||
567 | if (has_video) { | ||
568 | if ( cs->capabilities & cs_VideoDecoding ) vpx_codec_destroy(&cs->v_decoder); | ||
569 | |||
570 | if ( cs->capabilities & cs_VideoEncoding ) vpx_codec_destroy(&cs->v_encoder); | ||
571 | |||
572 | buffer_free(cs->vbuf_raw); | ||
573 | |||
574 | free(cs->frame_buf); | ||
575 | free(cs->split_video_frame); | ||
576 | } | ||
577 | |||
578 | jbuf_free(cs->j_buf); | ||
579 | free(cs); | ||
580 | |||
581 | return NULL; | ||
582 | } | ||
583 | |||
584 | void cs_kill(CSSession *cs) | ||
585 | { | ||
586 | if (!cs) return; | ||
587 | |||
588 | /* queue_message will not be called since it's unregistered before cs_kill is called */ | ||
589 | pthread_mutex_destroy(cs->queue_mutex); | ||
590 | |||
591 | |||
592 | if ( cs->audio_encoder ) | ||
593 | opus_encoder_destroy(cs->audio_encoder); | ||
594 | |||
595 | if ( cs->audio_decoder ) | ||
596 | opus_decoder_destroy(cs->audio_decoder); | ||
597 | |||
598 | if ( cs->capabilities & cs_VideoDecoding ) | ||
599 | vpx_codec_destroy(&cs->v_decoder); | ||
600 | |||
601 | if ( cs->capabilities & cs_VideoEncoding ) | ||
602 | vpx_codec_destroy(&cs->v_encoder); | ||
603 | |||
604 | jbuf_free(cs->j_buf); | ||
605 | buffer_free(cs->vbuf_raw); | ||
606 | free(cs->frame_buf); | ||
607 | free(cs->split_video_frame); | ||
608 | |||
609 | LOGGER_DEBUG("Terminated codec state: %p", cs); | ||
610 | free(cs); | ||
611 | } | ||
612 | |||
613 | |||
614 | |||
615 | |||
616 | /* Called from RTP */ | ||
617 | void queue_message(RTPSession *session, RTPMessage *msg) | ||
618 | { | ||
619 | /* This function is unregistered during call termination befor destroing | ||
620 | * Codec session so no need to check for validity of cs | ||
621 | */ | ||
622 | CSSession *cs = session->cs; | ||
623 | |||
624 | if (!cs) return; | ||
625 | |||
626 | /* Audio */ | ||
627 | if (session->payload_type == msi_TypeAudio % 128) { | ||
628 | pthread_mutex_lock(cs->queue_mutex); | ||
629 | int ret = jbuf_write(cs->j_buf, msg); | ||
630 | pthread_mutex_unlock(cs->queue_mutex); | ||
631 | |||
632 | if (ret == -1) { | ||
633 | rtp_free_msg(NULL, msg); | ||
634 | } | ||
635 | } | ||
636 | /* Video */ | ||
637 | else { | ||
638 | uint8_t *packet = msg->data; | ||
639 | uint32_t packet_size = msg->length; | ||
640 | |||
641 | if (packet_size < VIDEOFRAME_HEADER_SIZE) | ||
642 | goto end; | ||
643 | |||
644 | uint8_t diff = packet[0] - cs->frameid_in; | ||
645 | |||
646 | if (diff != 0) { | ||
647 | if (diff < 225) { /* New frame */ | ||
648 | /* Flush last frames' data and get ready for this frame */ | ||
649 | Payload *p = malloc(sizeof(Payload) + cs->frame_size); | ||
650 | |||
651 | if (p) { | ||
652 | pthread_mutex_lock(cs->queue_mutex); | ||
653 | |||
654 | if (buffer_full(cs->vbuf_raw)) { | ||
655 | LOGGER_DEBUG("Dropped video frame"); | ||
656 | Payload *tp; | ||
657 | buffer_read(cs->vbuf_raw, &tp); | ||
658 | free(tp); | ||
659 | } else { | ||
660 | p->size = cs->frame_size; | ||
661 | memcpy(p->data, cs->frame_buf, cs->frame_size); | ||
662 | } | ||
663 | |||
664 | buffer_write(cs->vbuf_raw, p); | ||
665 | pthread_mutex_unlock(cs->queue_mutex); | ||
666 | } else { | ||
667 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
668 | goto end; | ||
669 | } | ||
670 | |||
671 | cs->last_timestamp = msg->header->timestamp; | ||
672 | cs->frameid_in = packet[0]; | ||
673 | memset(cs->frame_buf, 0, cs->frame_size); | ||
674 | cs->frame_size = 0; | ||
675 | |||
676 | } else { /* Old frame; drop */ | ||
677 | LOGGER_DEBUG("Old packet: %u", packet[0]); | ||
678 | goto end; | ||
679 | } | ||
680 | } | ||
681 | |||
682 | uint8_t piece_number = packet[1]; | ||
683 | |||
684 | uint32_t length_before_piece = ((piece_number - 1) * cs->video_frame_piece_size); | ||
685 | uint32_t framebuf_new_length = length_before_piece + (packet_size - VIDEOFRAME_HEADER_SIZE); | ||
686 | |||
687 | if (framebuf_new_length > cs->max_video_frame_size) { | ||
688 | goto end; | ||
689 | } | ||
690 | |||
691 | /* Otherwise it's part of the frame so just process */ | ||
692 | /* LOGGER_DEBUG("Video Packet: %u %u", packet[0], packet[1]); */ | ||
693 | |||
694 | memcpy(cs->frame_buf + length_before_piece, | ||
695 | packet + VIDEOFRAME_HEADER_SIZE, | ||
696 | packet_size - VIDEOFRAME_HEADER_SIZE); | ||
697 | |||
698 | if (framebuf_new_length > cs->frame_size) { | ||
699 | cs->frame_size = framebuf_new_length; | ||
700 | } | ||
701 | |||
702 | end: | ||
703 | rtp_free_msg(NULL, msg); | ||
704 | } | ||
705 | } | ||
diff --git a/toxav/codec.h b/toxav/codec.h deleted file mode 100644 index 6018e5df..00000000 --- a/toxav/codec.h +++ /dev/null | |||
@@ -1,176 +0,0 @@ | |||
1 | /** codec.h | ||
2 | * | ||
3 | * Audio and video codec intitialization, encoding/decoding and playback | ||
4 | * | ||
5 | * Copyright (C) 2013 Tox project All Rights Reserved. | ||
6 | * | ||
7 | * This file is part of Tox. | ||
8 | * | ||
9 | * Tox is free software: you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation, either version 3 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * Tox is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
21 | * | ||
22 | */ | ||
23 | |||
24 | #ifndef _CODEC_H_ | ||
25 | #define _CODEC_H_ | ||
26 | |||
27 | #include "toxav.h" | ||
28 | #include "rtp.h" | ||
29 | |||
30 | #include <stdio.h> | ||
31 | #include <math.h> | ||
32 | #include <pthread.h> | ||
33 | |||
34 | #include <vpx/vpx_decoder.h> | ||
35 | #include <vpx/vpx_encoder.h> | ||
36 | #include <vpx/vp8dx.h> | ||
37 | #include <vpx/vp8cx.h> | ||
38 | #include <vpx/vpx_image.h> | ||
39 | #define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) | ||
40 | #define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) | ||
41 | |||
42 | /* Audio encoding/decoding */ | ||
43 | #include <opus.h> | ||
44 | |||
45 | #define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } | ||
46 | |||
47 | typedef void (*CSAudioCallback) (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data); | ||
48 | typedef void (*CSVideoCallback) (void *agent, int32_t call_idx, const vpx_image_t *img, void *data); | ||
49 | |||
50 | /** | ||
51 | * Codec capabilities | ||
52 | */ | ||
53 | typedef enum { | ||
54 | cs_AudioEncoding = 1 << 0, | ||
55 | cs_AudioDecoding = 1 << 1, | ||
56 | cs_VideoEncoding = 1 << 2, | ||
57 | cs_VideoDecoding = 1 << 3 | ||
58 | } CSCapabilities; | ||
59 | |||
60 | /** | ||
61 | * Codec errors. | ||
62 | */ | ||
63 | typedef enum { | ||
64 | cs_ErrorSettingVideoResolution = -30, | ||
65 | cs_ErrorSettingVideoBitrate = -31, | ||
66 | cs_ErrorSplittingVideoPayload = -32, | ||
67 | } CSError; | ||
68 | |||
69 | /** | ||
70 | * Codec session - controling codec | ||
71 | */ | ||
72 | typedef struct _CSSession { | ||
73 | |||
74 | /* VIDEO | ||
75 | * | ||
76 | * | ||
77 | */ | ||
78 | int support_video; | ||
79 | |||
80 | /* video encoding */ | ||
81 | vpx_codec_ctx_t v_encoder; | ||
82 | uint32_t frame_counter; | ||
83 | |||
84 | /* video decoding */ | ||
85 | vpx_codec_ctx_t v_decoder; | ||
86 | int max_width; | ||
87 | int max_height; | ||
88 | unsigned int video_bitrate; | ||
89 | |||
90 | |||
91 | /* Data handling */ | ||
92 | uint8_t *frame_buf; /* buffer for split video payloads */ | ||
93 | uint32_t frame_size; /* largest address written to in frame_buf for current input frame*/ | ||
94 | uint8_t frameid_in, frameid_out; /* id of input and output video frame */ | ||
95 | uint32_t last_timestamp; /* calculating cycles */ | ||
96 | |||
97 | /* Limits */ | ||
98 | uint32_t video_frame_piece_size; | ||
99 | uint32_t max_video_frame_size; | ||
100 | |||
101 | /* Reassembling */ | ||
102 | uint8_t *split_video_frame; | ||
103 | const uint8_t *processing_video_frame; | ||
104 | uint16_t processing_video_frame_size; | ||
105 | |||
106 | |||
107 | |||
108 | /* AUDIO | ||
109 | * | ||
110 | * | ||
111 | */ | ||
112 | |||
113 | /* audio encoding */ | ||
114 | OpusEncoder *audio_encoder; | ||
115 | int audio_encoder_bitrate; | ||
116 | int audio_encoder_sample_rate; | ||
117 | int audio_encoder_frame_duration; | ||
118 | int audio_encoder_channels; | ||
119 | |||
120 | /* audio decoding */ | ||
121 | OpusDecoder *audio_decoder; | ||
122 | int audio_decoder_bitrate; | ||
123 | int audio_decoder_sample_rate; | ||
124 | int audio_decoder_frame_duration; | ||
125 | int audio_decoder_channels; | ||
126 | |||
127 | struct _JitterBuffer *j_buf; | ||
128 | |||
129 | |||
130 | /* Voice activity detection */ | ||
131 | uint32_t EVAD_tolerance; /* In frames */ | ||
132 | uint32_t EVAD_tolerance_cr; | ||
133 | |||
134 | |||
135 | |||
136 | /* OTHER | ||
137 | * | ||
138 | * | ||
139 | */ | ||
140 | |||
141 | uint64_t capabilities; /* supports*/ | ||
142 | |||
143 | /* Callbacks */ | ||
144 | PAIR(CSAudioCallback, void *) acb; | ||
145 | PAIR(CSVideoCallback, void *) vcb; | ||
146 | |||
147 | /* Buffering */ | ||
148 | void *vbuf_raw; /* Un-decoded data */ | ||
149 | pthread_mutex_t queue_mutex[1]; | ||
150 | |||
151 | void *agent; /* Pointer to ToxAv */ | ||
152 | int32_t call_idx; | ||
153 | } CSSession; | ||
154 | |||
155 | /* Make sure to be called BEFORE corresponding rtp_new */ | ||
156 | CSSession *cs_new(const ToxAvCSettings *cs_self, const ToxAvCSettings *cs_peer, uint32_t jbuf_size, int has_video); | ||
157 | /* Make sure to be called AFTER corresponding rtp_kill */ | ||
158 | void cs_kill(CSSession *cs); | ||
159 | |||
160 | int cs_split_video_payload(CSSession *cs, const uint8_t *payload, uint16_t length); | ||
161 | const uint8_t *cs_get_split_video_frame(CSSession *cs, uint16_t *size); | ||
162 | |||
163 | /** | ||
164 | * Call playback callbacks | ||
165 | */ | ||
166 | void cs_do(CSSession *cs); | ||
167 | |||
168 | |||
169 | /* Reconfigure video encoder; return 0 on success or -1 on failure. */ | ||
170 | int cs_set_video_encoder_resolution(CSSession *cs, uint16_t width, uint16_t height); | ||
171 | int cs_set_video_encoder_bitrate(CSSession *cs, uint32_t video_bitrate); | ||
172 | |||
173 | |||
174 | /* Internal. Called from rtp_handle_message */ | ||
175 | void queue_message(RTPSession *session, RTPMessage *msg); | ||
176 | #endif /* _CODEC_H_ */ | ||
diff --git a/toxav/group.c b/toxav/group.c index 817ee6e6..190c2c3d 100644 --- a/toxav/group.c +++ b/toxav/group.c | |||
@@ -20,7 +20,7 @@ | |||
20 | 20 | ||
21 | #ifdef HAVE_CONFIG_H | 21 | #ifdef HAVE_CONFIG_H |
22 | #include "config.h" | 22 | #include "config.h" |
23 | #endif | 23 | #endif /* HAVE_CONFIG_H */ |
24 | 24 | ||
25 | #include "group.h" | 25 | #include "group.h" |
26 | #include "../toxcore/util.h" | 26 | #include "../toxcore/util.h" |
@@ -54,7 +54,7 @@ static Group_JitterBuffer *create_queue(unsigned int capacity) | |||
54 | 54 | ||
55 | Group_JitterBuffer *q; | 55 | Group_JitterBuffer *q; |
56 | 56 | ||
57 | if ( !(q = calloc(sizeof(Group_JitterBuffer), 1)) ) return NULL; | 57 | if (!(q = calloc(sizeof(Group_JitterBuffer), 1))) return NULL; |
58 | 58 | ||
59 | if (!(q->queue = calloc(sizeof(Group_Audio_Packet *), size))) { | 59 | if (!(q->queue = calloc(sizeof(Group_Audio_Packet *), size))) { |
60 | free(q); | 60 | free(q); |
@@ -190,7 +190,7 @@ static int recreate_encoder(Group_AV *group_av) | |||
190 | group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels, | 190 | group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels, |
191 | OPUS_APPLICATION_AUDIO, &rc); | 191 | OPUS_APPLICATION_AUDIO, &rc); |
192 | 192 | ||
193 | if ( rc != OPUS_OK ) { | 193 | if (rc != OPUS_OK) { |
194 | LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); | 194 | LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); |
195 | group_av->audio_encoder = NULL; | 195 | group_av->audio_encoder = NULL; |
196 | return -1; | 196 | return -1; |
@@ -198,7 +198,7 @@ static int recreate_encoder(Group_AV *group_av) | |||
198 | 198 | ||
199 | rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate)); | 199 | rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate)); |
200 | 200 | ||
201 | if ( rc != OPUS_OK ) { | 201 | if (rc != OPUS_OK) { |
202 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | 202 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); |
203 | opus_encoder_destroy(group_av->audio_encoder); | 203 | opus_encoder_destroy(group_av->audio_encoder); |
204 | group_av->audio_encoder = NULL; | 204 | group_av->audio_encoder = NULL; |
@@ -207,7 +207,7 @@ static int recreate_encoder(Group_AV *group_av) | |||
207 | 207 | ||
208 | rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10)); | 208 | rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10)); |
209 | 209 | ||
210 | if ( rc != OPUS_OK ) { | 210 | if (rc != OPUS_OK) { |
211 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | 211 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); |
212 | opus_encoder_destroy(group_av->audio_encoder); | 212 | opus_encoder_destroy(group_av->audio_encoder); |
213 | group_av->audio_encoder = NULL; | 213 | 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 | |||
306 | int rc; | 306 | int rc; |
307 | peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc); | 307 | peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc); |
308 | 308 | ||
309 | if ( rc != OPUS_OK ) { | 309 | if (rc != OPUS_OK) { |
310 | LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); | 310 | LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); |
311 | free(pk); | 311 | free(pk); |
312 | return -1; | 312 | return -1; |
diff --git a/toxav/msi.c b/toxav/msi.c index 97ba936c..c08c0135 100644 --- a/toxav/msi.c +++ b/toxav/msi.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /** msi.c | 1 | /** msi.c |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
@@ -32,1581 +32,795 @@ | |||
32 | #include <string.h> | 32 | #include <string.h> |
33 | #include <stdlib.h> | 33 | #include <stdlib.h> |
34 | #include <stdbool.h> | 34 | #include <stdbool.h> |
35 | #include <assert.h> | ||
35 | 36 | ||
36 | #define MSI_MAXMSG_SIZE 256 | 37 | #define MSI_MAXMSG_SIZE 256 |
37 | 38 | ||
38 | /* Define default timeout for a request. | ||
39 | * There is no behavior specified by the msi on what will | ||
40 | * client do on timeout, but to call timeout callback. | ||
41 | */ | ||
42 | #define m_deftout 10000 /* in milliseconds */ | ||
43 | |||
44 | /** | 39 | /** |
45 | * Protocol: | 40 | * Protocol: |
46 | * | 41 | * |
47 | * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| | 42 | * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| |
48 | */ | 43 | */ |
49 | 44 | ||
50 | typedef uint8_t MSIRawCSettingsType[23]; | ||
51 | |||
52 | typedef enum { | 45 | typedef enum { |
53 | IDRequest = 1, | 46 | IDRequest = 1, |
54 | IDResponse, | 47 | IDError, |
55 | IDReason, | 48 | IDCapabilities, |
56 | IDCallId, | ||
57 | IDCSettings, | ||
58 | 49 | ||
59 | } MSIHeaderID; | 50 | } MSIHeaderID; |
60 | 51 | ||
61 | typedef enum { | ||
62 | TypeRequest, | ||
63 | TypeResponse, | ||
64 | |||
65 | } MSIMessageType; | ||
66 | 52 | ||
67 | typedef enum { | 53 | typedef enum { |
68 | invite, | 54 | requ_init, |
69 | start, | 55 | requ_push, |
70 | cancel, | 56 | requ_pop, |
71 | reject, | ||
72 | end, | ||
73 | |||
74 | } MSIRequest; | 57 | } MSIRequest; |
75 | 58 | ||
76 | typedef enum { | ||
77 | ringing, | ||
78 | starting, | ||
79 | ending, | ||
80 | error | ||
81 | |||
82 | } MSIResponse; | ||
83 | |||
84 | 59 | ||
85 | #define GENERIC_HEADER(header, val_type) \ | 60 | #define GENERIC_HEADER(header, val_type) \ |
86 | typedef struct _MSIHeader##header { \ | 61 | typedef struct { \ |
87 | val_type value; \ | 62 | val_type value; \ |
88 | _Bool exists; \ | 63 | bool exists; \ |
89 | } MSIHeader##header; | 64 | } MSIHeader##header |
90 | |||
91 | 65 | ||
92 | GENERIC_HEADER ( Request, MSIRequest ) | ||
93 | GENERIC_HEADER ( Response, MSIResponse ) | ||
94 | GENERIC_HEADER ( CallId, MSICallIDType ) | ||
95 | GENERIC_HEADER ( Reason, MSIReasonStrType ) | ||
96 | GENERIC_HEADER ( CSettings, MSIRawCSettingsType ) | ||
97 | 66 | ||
67 | GENERIC_HEADER (Request, MSIRequest); | ||
68 | GENERIC_HEADER (Error, MSIError); | ||
69 | GENERIC_HEADER (Capabilities, uint8_t); | ||
98 | 70 | ||
99 | typedef struct _MSIMessage { | ||
100 | |||
101 | MSIHeaderRequest request; | ||
102 | MSIHeaderResponse response; | ||
103 | MSIHeaderReason reason; | ||
104 | MSIHeaderCallId callid; | ||
105 | MSIHeaderCSettings csettings; | ||
106 | |||
107 | int friend_id; | ||
108 | 71 | ||
72 | typedef struct { | ||
73 | MSIHeaderRequest request; | ||
74 | MSIHeaderError error; | ||
75 | MSIHeaderCapabilities capabilities; | ||
109 | } MSIMessage; | 76 | } MSIMessage; |
110 | 77 | ||
111 | 78 | ||
112 | static void invoke_callback(MSISession *s, int32_t c, MSICallbackID i) | 79 | void msg_init (MSIMessage *dest, MSIRequest request); |
113 | { | 80 | int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length); |
114 | if ( s->callbacks[i].first ) { | 81 | uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length); |
115 | LOGGER_DEBUG("Invoking callback function: %d", i); | 82 | static int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg); |
116 | 83 | int send_error (Messenger *m, uint32_t friend_number, MSIError error); | |
117 | s->callbacks[i].first( s->agent_handler, c, s->callbacks[i].second ); | 84 | static int invoke_callback(MSICall *call, MSICallbackID cb); |
118 | } | 85 | static MSICall *get_call (MSISession *session, uint32_t friend_number); |
119 | } | 86 | MSICall *new_call (MSISession *session, uint32_t friend_number); |
120 | 87 | void kill_call (MSICall *call); | |
121 | /** | 88 | void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data); |
122 | * Parse raw 'data' received from socket into MSIMessage struct. | 89 | void handle_init (MSICall *call, const MSIMessage *msg); |
123 | * Every message has to have end value of 'end_byte' or _undefined_ behavior | 90 | void handle_push (MSICall *call, const MSIMessage *msg); |
124 | * occures. The best practice is to check the end of the message at the handle_packet. | 91 | void handle_pop (MSICall *call, const MSIMessage *msg); |
125 | */ | 92 | void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object); |
126 | static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t length ) | ||
127 | { | ||
128 | |||
129 | #define FAIL_CONSTRAINT(constraint, wanted) if ((constraint -= wanted) < 1) { LOGGER_ERROR("Read over length!"); return -1; } | ||
130 | #define FAIL_SIZE(byte, valid) if ( byte != valid ) { LOGGER_ERROR("Invalid data size!"); return -1; } | ||
131 | #define FAIL_LIMITS(byte, high) if ( byte > high ) { LOGGER_ERROR("Failed limit!"); return -1; } | ||
132 | |||
133 | if ( msg == NULL ) { | ||
134 | LOGGER_ERROR("Could not parse message: no storage!"); | ||
135 | return -1; | ||
136 | } | ||
137 | |||
138 | if ( data[length - 1] ) { /* End byte must have value 0 */ | ||
139 | LOGGER_ERROR("Invalid end byte"); | ||
140 | return -1; | ||
141 | } | ||
142 | |||
143 | const uint8_t *it = data; | ||
144 | int size_constraint = length; | ||
145 | |||
146 | while ( *it ) {/* until end byte is hit */ | ||
147 | switch (*it) { | ||
148 | case IDRequest: | ||
149 | FAIL_CONSTRAINT(size_constraint, 3); | ||
150 | FAIL_SIZE(it[1], 1); | ||
151 | // FAIL_LIMITS(it[2], invite, end); | ||
152 | FAIL_LIMITS(it[2], end); | ||
153 | msg->request.value = it[2]; | ||
154 | it += 3; | ||
155 | msg->request.exists = 1; | ||
156 | break; | ||
157 | |||
158 | case IDResponse: | ||
159 | FAIL_CONSTRAINT(size_constraint, 3); | ||
160 | FAIL_SIZE(it[1], 1); | ||
161 | // FAIL_LIMITS(it[2], ringing, error); | ||
162 | FAIL_LIMITS(it[2], error); | ||
163 | msg->response.value = it[2]; | ||
164 | it += 3; | ||
165 | msg->response.exists = 1; | ||
166 | break; | ||
167 | |||
168 | case IDCallId: | ||
169 | FAIL_CONSTRAINT(size_constraint, sizeof(MSICallIDType) + 2); | ||
170 | FAIL_SIZE(it[1], sizeof(MSICallIDType)); | ||
171 | memcpy(msg->callid.value, it + 2, sizeof(MSICallIDType)); | ||
172 | it += sizeof(MSICallIDType) + 2; | ||
173 | msg->callid.exists = 1; | ||
174 | break; | ||
175 | |||
176 | case IDReason: | ||
177 | FAIL_CONSTRAINT(size_constraint, sizeof(MSIReasonStrType) + 2); | ||
178 | FAIL_SIZE(it[1], sizeof(MSIReasonStrType)); | ||
179 | memcpy(msg->reason.value, it + 2, sizeof(MSIReasonStrType)); | ||
180 | it += sizeof(MSIReasonStrType) + 2; | ||
181 | msg->reason.exists = 1; | ||
182 | break; | ||
183 | 93 | ||
184 | case IDCSettings: | ||
185 | FAIL_CONSTRAINT(size_constraint, sizeof(MSIRawCSettingsType) + 2); | ||
186 | FAIL_SIZE(it[1], sizeof(MSIRawCSettingsType)); | ||
187 | memcpy(msg->csettings.value, it + 2, sizeof(MSIRawCSettingsType)); | ||
188 | it += sizeof(MSIRawCSettingsType) + 2; | ||
189 | msg->csettings.exists = 1; | ||
190 | break; | ||
191 | |||
192 | default: | ||
193 | LOGGER_ERROR("Invalid id byte"); | ||
194 | return -1; | ||
195 | break; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | return 0; | ||
200 | } | ||
201 | 94 | ||
202 | /** | 95 | /** |
203 | * Create the message. | 96 | * Public functions |
204 | */ | 97 | */ |
205 | MSIMessage *msi_new_message ( MSIMessageType type, const uint8_t type_value ) | 98 | void msi_register_callback (MSISession *session, msi_action_cb *callback, MSICallbackID id) |
206 | { | 99 | { |
207 | MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); | 100 | if (!session) |
208 | 101 | return; | |
209 | if ( retu == NULL ) { | ||
210 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
211 | return NULL; | ||
212 | } | ||
213 | |||
214 | if ( type == TypeRequest ) { | ||
215 | retu->request.exists = 1; | ||
216 | retu->request.value = type_value; | ||
217 | |||
218 | } else { | ||
219 | retu->response.exists = 1; | ||
220 | retu->response.value = type_value; | ||
221 | } | ||
222 | 102 | ||
223 | return retu; | 103 | pthread_mutex_lock(session->mutex); |
104 | session->callbacks[id] = callback; | ||
105 | pthread_mutex_unlock(session->mutex); | ||
224 | } | 106 | } |
225 | 107 | MSISession *msi_new (Messenger *m) | |
226 | |||
227 | /** | ||
228 | * Parse data from handle_packet. | ||
229 | */ | ||
230 | MSIMessage *parse_recv ( const uint8_t *data, uint16_t length ) | ||
231 | { | 108 | { |
232 | if ( data == NULL ) { | 109 | if (m == NULL) { |
233 | LOGGER_WARNING("Tried to parse empty message!"); | 110 | LOGGER_ERROR("Could not init session on empty messenger!"); |
234 | return NULL; | ||
235 | } | ||
236 | |||
237 | MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); | ||
238 | |||
239 | if ( retu == NULL ) { | ||
240 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
241 | return NULL; | 111 | return NULL; |
242 | } | 112 | } |
243 | 113 | ||
244 | if ( parse_raw_data ( retu, data, length ) == -1 ) { | 114 | MSISession *retu = calloc (sizeof (MSISession), 1); |
245 | 115 | ||
246 | free ( retu ); | 116 | if (retu == NULL) { |
247 | return NULL; | 117 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); |
248 | } | ||
249 | |||
250 | return retu; | ||
251 | } | ||
252 | |||
253 | |||
254 | /** | ||
255 | * Speaks for itself. | ||
256 | */ | ||
257 | uint8_t *format_output ( uint8_t *dest, | ||
258 | MSIHeaderID id, | ||
259 | const void *value, | ||
260 | uint8_t value_len, | ||
261 | uint16_t *length ) | ||
262 | { | ||
263 | if ( dest == NULL ) { | ||
264 | LOGGER_ERROR("No destination space!"); | ||
265 | return NULL; | 118 | return NULL; |
266 | } | 119 | } |
267 | 120 | ||
268 | if (value == NULL || value_len == 0) { | 121 | if (create_recursive_mutex(retu->mutex) != 0) { |
269 | LOGGER_ERROR("Empty header value"); | 122 | LOGGER_ERROR("Failed to init mutex! Program might misbehave"); |
123 | free(retu); | ||
270 | return NULL; | 124 | return NULL; |
271 | } | 125 | } |
272 | 126 | ||
273 | *dest = id; | 127 | retu->messenger = m; |
274 | dest ++; | ||
275 | *dest = value_len; | ||
276 | dest ++; | ||
277 | 128 | ||
278 | memcpy(dest, value, value_len); | 129 | m_callback_msi_packet(m, handle_msi_packet, retu); |
279 | 130 | ||
280 | *length += (2 + value_len); | 131 | /* This is called when remote terminates session */ |
132 | m_callback_connectionstatus_internal_av(m, on_peer_status, retu); | ||
281 | 133 | ||
282 | return dest + value_len; /* Set to next position ready to be written */ | 134 | LOGGER_DEBUG("New msi session: %p ", retu); |
135 | return retu; | ||
283 | } | 136 | } |
284 | 137 | int msi_kill (MSISession *session) | |
285 | |||
286 | /** | ||
287 | * Parse MSIMessage to send. | ||
288 | */ | ||
289 | uint16_t parse_send ( MSIMessage *msg, uint8_t *dest ) | ||
290 | { | 138 | { |
291 | if (msg == NULL) { | 139 | if (session == NULL) { |
292 | LOGGER_ERROR("No message!"); | 140 | LOGGER_ERROR("Tried to terminate non-existing session"); |
293 | return 0; | 141 | return -1; |
294 | } | ||
295 | |||
296 | if (dest == NULL ) { | ||
297 | LOGGER_ERROR("No destination!"); | ||
298 | return 0; | ||
299 | } | 142 | } |
300 | 143 | ||
301 | uint8_t *it = dest; | 144 | m_callback_msi_packet((struct Messenger *) session->messenger, NULL, NULL); |
302 | uint16_t size = 0; | ||
303 | 145 | ||
304 | if (msg->request.exists) { | 146 | if (pthread_mutex_trylock(session->mutex) != 0) { |
305 | uint8_t cast = msg->request.value; | 147 | LOGGER_ERROR ("Failed to aquire lock on msi mutex"); |
306 | it = format_output(it, IDRequest, &cast, 1, &size); | 148 | return -1; |
307 | } | ||
308 | |||
309 | if (msg->response.exists) { | ||
310 | uint8_t cast = msg->response.value; | ||
311 | it = format_output(it, IDResponse, &cast, 1, &size); | ||
312 | } | 149 | } |
313 | 150 | ||
314 | if (msg->callid.exists) { | 151 | if (session->calls) { |
315 | it = format_output(it, IDCallId, &msg->callid.value, sizeof(msg->callid.value), &size); | 152 | MSIMessage msg; |
316 | } | 153 | msg_init(&msg, requ_pop); |
317 | 154 | ||
318 | if (msg->reason.exists) { | 155 | MSICall *it = get_call(session, session->calls_head); |
319 | it = format_output(it, IDReason, &msg->reason.value, sizeof(msg->reason.value), &size); | ||
320 | } | ||
321 | 156 | ||
322 | if (msg->csettings.exists) { | 157 | for (; it; it = it->next) { |
323 | it = format_output(it, IDCSettings, &msg->csettings.value, sizeof(msg->csettings.value), &size); | 158 | send_message(session->messenger, it->friend_number, &msg); |
159 | kill_call(it); /* This will eventually free session->calls */ | ||
160 | } | ||
324 | } | 161 | } |
325 | 162 | ||
326 | *it = 0; | 163 | pthread_mutex_unlock(session->mutex); |
327 | size ++; | 164 | pthread_mutex_destroy(session->mutex); |
328 | |||
329 | return size; | ||
330 | } | ||
331 | |||
332 | void msi_msg_set_reason ( MSIMessage *msg, const MSIReasonStrType value ) | ||
333 | { | ||
334 | if ( !msg ) return; | ||
335 | 165 | ||
336 | msg->reason.exists = 1; | 166 | LOGGER_DEBUG("Terminated session: %p", session); |
337 | memcpy(msg->reason.value, value, sizeof(MSIReasonStrType)); | 167 | free (session); |
168 | return 0; | ||
338 | } | 169 | } |
339 | 170 | int msi_invite (MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities) | |
340 | void msi_msg_set_callid ( MSIMessage *msg, const MSICallIDType value ) | ||
341 | { | 171 | { |
342 | if ( !msg ) return; | 172 | if (!session) |
173 | return -1; | ||
343 | 174 | ||
344 | msg->callid.exists = 1; | 175 | LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_number); |
345 | memcpy(msg->callid.value, value, sizeof(MSICallIDType)); | ||
346 | } | ||
347 | 176 | ||
348 | void msi_msg_set_csettings ( MSIMessage *msg, const MSICSettings *value ) | 177 | if (pthread_mutex_trylock(session->mutex) != 0) { |
349 | { | 178 | LOGGER_ERROR ("Failed to aquire lock on msi mutex"); |
350 | if ( !msg ) return; | 179 | return -1; |
351 | 180 | } | |
352 | msg->csettings.exists = 1; | ||
353 | |||
354 | msg->csettings.value[0] = value->call_type; | ||
355 | uint8_t *iter = msg->csettings.value + 1; | ||
356 | |||
357 | /* Video bitrate */ | ||
358 | uint32_t lval = htonl(value->video_bitrate); | ||
359 | memcpy(iter, &lval, 4); | ||
360 | iter += 4; | ||
361 | |||
362 | /* Video max width */ | ||
363 | uint16_t sval = htons(value->max_video_width); | ||
364 | memcpy(iter, &sval, 2); | ||
365 | iter += 2; | ||
366 | |||
367 | /* Video max height */ | ||
368 | sval = htons(value->max_video_height); | ||
369 | memcpy(iter, &sval, 2); | ||
370 | iter += 2; | ||
371 | |||
372 | /* Audio bitrate */ | ||
373 | lval = htonl(value->audio_bitrate); | ||
374 | memcpy(iter, &lval, 4); | ||
375 | iter += 4; | ||
376 | |||
377 | /* Audio frame duration */ | ||
378 | sval = htons(value->audio_frame_duration); | ||
379 | memcpy(iter, &sval, 2); | ||
380 | iter += 2; | ||
381 | |||
382 | /* Audio sample rate */ | ||
383 | lval = htonl(value->audio_sample_rate); | ||
384 | memcpy(iter, &lval, 4); | ||
385 | iter += 4; | ||
386 | |||
387 | /* Audio channels */ | ||
388 | lval = htonl(value->audio_channels); | ||
389 | memcpy(iter, &lval, 4); | ||
390 | } | ||
391 | 181 | ||
392 | void msi_msg_get_csettings ( MSIMessage *msg, MSICSettings *dest ) | 182 | if (get_call(session, friend_number) != NULL) { |
393 | { | 183 | LOGGER_ERROR("Already in a call"); |
394 | if ( !msg || !dest || !msg->csettings.exists ) return; | 184 | pthread_mutex_unlock(session->mutex); |
185 | return -1; | ||
186 | } | ||
395 | 187 | ||
396 | dest->call_type = msg->csettings.value[0]; | 188 | (*call) = new_call (session, friend_number); |
397 | uint8_t *iter = msg->csettings.value + 1; | ||
398 | 189 | ||
399 | memcpy(&dest->video_bitrate, iter, 4); | 190 | if (*call == NULL) { |
400 | iter += 4; | 191 | pthread_mutex_unlock(session->mutex); |
401 | dest->video_bitrate = ntohl(dest->video_bitrate); | 192 | return -1; |
193 | } | ||
402 | 194 | ||
403 | memcpy(&dest->max_video_width, iter, 2); | 195 | (*call)->self_capabilities = capabilities; |
404 | iter += 2; | ||
405 | dest->max_video_width = ntohs(dest->max_video_width); | ||
406 | 196 | ||
407 | memcpy(&dest->max_video_height, iter, 2); | 197 | MSIMessage msg; |
408 | iter += 2; | 198 | msg_init(&msg, requ_init); |
409 | dest->max_video_height = ntohs(dest->max_video_height); | ||
410 | 199 | ||
411 | memcpy(&dest->audio_bitrate, iter, 4); | 200 | msg.capabilities.exists = true; |
412 | iter += 4; | 201 | msg.capabilities.value = capabilities; |
413 | dest->audio_bitrate = ntohl(dest->audio_bitrate); | ||
414 | 202 | ||
415 | memcpy(&dest->audio_frame_duration, iter, 2); | 203 | send_message ((*call)->session->messenger, (*call)->friend_number, &msg); |
416 | iter += 2; | ||
417 | dest->audio_frame_duration = ntohs(dest->audio_frame_duration); | ||
418 | 204 | ||
419 | memcpy(&dest->audio_sample_rate, iter, 4); | 205 | (*call)->state = msi_CallRequesting; |
420 | iter += 4; | ||
421 | dest->audio_sample_rate = ntohl(dest->audio_sample_rate); | ||
422 | 206 | ||
423 | memcpy(&dest->audio_channels, iter, 4); | 207 | LOGGER_DEBUG("Invite sent"); |
424 | dest->audio_channels = ntohl(dest->audio_channels); | 208 | pthread_mutex_unlock(session->mutex); |
209 | return 0; | ||
425 | } | 210 | } |
426 | 211 | int msi_hangup (MSICall *call) | |
427 | typedef struct _Timer { | ||
428 | void (*func)(struct _Timer *); | ||
429 | uint64_t timeout; | ||
430 | MSISession *session; | ||
431 | int call_idx; | ||
432 | int id; | ||
433 | |||
434 | } Timer; | ||
435 | |||
436 | typedef struct _TimerHandler { | ||
437 | Timer **timers; | ||
438 | |||
439 | uint32_t max_capacity; | ||
440 | uint32_t size; | ||
441 | } TimerHandler; | ||
442 | |||
443 | |||
444 | static int timer_alloc (MSISession *session , void (*func)(Timer *), int call_idx, uint32_t timeout) | ||
445 | { | 212 | { |
446 | static int timer_id; | 213 | if (!call || !call->session) |
447 | TimerHandler *timer_handler = session->timer_handler; | 214 | return -1; |
448 | 215 | ||
449 | uint32_t i = 0; | 216 | LOGGER_DEBUG("Session: %p Hanging up call with friend: %u", call->session, call->friend_number); |
450 | 217 | ||
451 | for (; i < timer_handler->max_capacity && timer_handler->timers[i]; i ++); | 218 | MSISession *session = call->session; |
452 | 219 | ||
453 | if (i == timer_handler->max_capacity) { | 220 | if (pthread_mutex_trylock(session->mutex) != 0) { |
454 | LOGGER_WARNING("Maximum capacity reached!"); | 221 | LOGGER_ERROR ("Failed to aquire lock on msi mutex"); |
455 | return -1; | 222 | return -1; |
456 | } | 223 | } |
457 | 224 | ||
458 | Timer *timer = timer_handler->timers[i] = calloc(sizeof(Timer), 1); | 225 | if (call->state == msi_CallInactive) { |
459 | 226 | LOGGER_ERROR("Call is in invalid state!"); | |
460 | if (timer == NULL) { | 227 | pthread_mutex_unlock(session->mutex); |
461 | LOGGER_ERROR("Failed to allocate timer!"); | ||
462 | return -1; | 228 | return -1; |
463 | } | 229 | } |
464 | 230 | ||
465 | timer_handler->size ++; | 231 | MSIMessage msg; |
466 | 232 | msg_init(&msg, requ_pop); | |
467 | timer->func = func; | ||
468 | timer->session = session; | ||
469 | timer->call_idx = call_idx; | ||
470 | timer->timeout = timeout + current_time_monotonic(); /* In ms */ | ||
471 | ++timer_id; | ||
472 | timer->id = timer_id; | ||
473 | |||
474 | /* reorder */ | ||
475 | if (i) { | ||
476 | int64_t j = i - 1; | ||
477 | 233 | ||
478 | for (; j >= 0 && timeout < timer_handler->timers[j]->timeout; j--) { | 234 | send_message (session->messenger, call->friend_number, &msg); |
479 | Timer *tmp = timer_handler->timers[j]; | ||
480 | timer_handler->timers[j] = timer; | ||
481 | timer_handler->timers[j + 1] = tmp; | ||
482 | } | ||
483 | } | ||
484 | 235 | ||
485 | LOGGER_DEBUG("Allocated timer index: %ull timeout: %ull, current size: %ull", i, timeout, timer_handler->size); | 236 | kill_call(call); |
486 | return timer->id; | 237 | pthread_mutex_unlock(session->mutex); |
238 | return 0; | ||
487 | } | 239 | } |
488 | 240 | int msi_answer (MSICall *call, uint8_t capabilities) | |
489 | static int timer_release ( TimerHandler *timers_container, int id) | ||
490 | { | 241 | { |
491 | Timer **timed_events = timers_container->timers; | 242 | if (!call || !call->session) |
243 | return -1; | ||
492 | 244 | ||
493 | uint32_t i; | 245 | LOGGER_DEBUG("Session: %p Answering call from: %u", call->session, call->friend_number); |
494 | int rc = -1; | ||
495 | 246 | ||
496 | for (i = 0; i < timers_container->max_capacity; ++i) { | 247 | MSISession *session = call->session; |
497 | if (timed_events[i] && timed_events[i]->id == id) { | ||
498 | rc = i; | ||
499 | break; | ||
500 | } | ||
501 | } | ||
502 | 248 | ||
503 | if (rc == -1) { | 249 | if (pthread_mutex_trylock(session->mutex) != 0) { |
504 | LOGGER_WARNING("No event with id: %d", id); | 250 | LOGGER_ERROR ("Failed to aquire lock on msi mutex"); |
505 | return -1; | 251 | return -1; |
506 | } | 252 | } |
507 | 253 | ||
508 | free(timed_events[rc]); | 254 | if (call->state != msi_CallRequested) { |
509 | 255 | /* Though sending in invalid state will not cause anything wierd | |
510 | timed_events[rc] = NULL; | 256 | * Its better to not do it like a maniac */ |
511 | 257 | LOGGER_ERROR("Call is in invalid state!"); | |
512 | i = rc + 1; | 258 | pthread_mutex_unlock(session->mutex); |
513 | 259 | return -1; | |
514 | for (; i < timers_container->max_capacity && timed_events[i]; i ++) { | ||
515 | timed_events[i - 1] = timed_events[i]; | ||
516 | timed_events[i] = NULL; | ||
517 | } | 260 | } |
518 | 261 | ||
519 | timers_container->size--; | 262 | call->self_capabilities = capabilities; |
520 | 263 | ||
521 | LOGGER_DEBUG("Popped id: %d, current size: %ull ", id, timers_container->size); | 264 | MSIMessage msg; |
522 | return 0; | 265 | msg_init(&msg, requ_push); |
523 | } | ||
524 | 266 | ||
525 | /** | 267 | msg.capabilities.exists = true; |
526 | * Generate _random_ alphanumerical string. | 268 | msg.capabilities.value = capabilities; |
527 | */ | ||
528 | static void t_randomstr ( uint8_t *str, uint32_t size ) | ||
529 | { | ||
530 | if (str == NULL) { | ||
531 | LOGGER_DEBUG("Empty destination!"); | ||
532 | return; | ||
533 | } | ||
534 | 269 | ||
535 | static const uint8_t _bytes[] = | 270 | send_message (session->messenger, call->friend_number, &msg); |
536 | "0123456789" | ||
537 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
538 | "abcdefghijklmnopqrstuvwxyz"; | ||
539 | 271 | ||
540 | uint32_t _it = 0; | 272 | call->state = msi_CallActive; |
273 | pthread_mutex_unlock(session->mutex); | ||
541 | 274 | ||
542 | for ( ; _it < size; _it++ ) { | 275 | return 0; |
543 | str[_it] = _bytes[ random_int() % 61 ]; | ||
544 | } | ||
545 | } | 276 | } |
546 | 277 | int msi_change_capabilities(MSICall *call, uint8_t capabilities) | |
547 | /* TODO: it would be nice to actually have some sane error codes */ | ||
548 | typedef enum { | ||
549 | error_none, | ||
550 | error_deadcall, /* has call id but it's from old call */ | ||
551 | error_id_mismatch, /* non-existing call */ | ||
552 | |||
553 | error_no_callid, /* not having call id */ | ||
554 | error_no_call, /* no call in session */ | ||
555 | error_no_crypto_key, /* no crypto key */ | ||
556 | |||
557 | error_busy | ||
558 | |||
559 | } MSICallError; /* Error codes */ | ||
560 | |||
561 | |||
562 | /** | ||
563 | * Stringify error code. | ||
564 | */ | ||
565 | static const uint8_t *stringify_error ( MSICallError error_code ) | ||
566 | { | 278 | { |
567 | static const uint8_t *strings[] = { | 279 | if (!call || !call->session) |
568 | ( uint8_t *) "", | 280 | return -1; |
569 | ( uint8_t *) "Using dead call", | ||
570 | ( uint8_t *) "Call id not set to any call", | ||
571 | ( uint8_t *) "Call id not available", | ||
572 | ( uint8_t *) "No active call in session", | ||
573 | ( uint8_t *) "No Crypto-key set", | ||
574 | ( uint8_t *) "Callee busy" | ||
575 | }; | ||
576 | |||
577 | return strings[error_code]; | ||
578 | } | ||
579 | 281 | ||
580 | static int send_message ( MSISession *session, MSICall *call, MSIMessage *msg, uint32_t to ) | 282 | LOGGER_DEBUG("Session: %p Trying to change capabilities to friend %u", call->session, call->friend_number); |
581 | { | ||
582 | msi_msg_set_callid ( msg, call->id ); | ||
583 | 283 | ||
584 | uint8_t msg_string_final [MSI_MAXMSG_SIZE]; | 284 | MSISession *session = call->session; |
585 | uint16_t length = parse_send ( msg, msg_string_final ); | ||
586 | 285 | ||
587 | if (!length) { | 286 | if (pthread_mutex_trylock(session->mutex) != 0) { |
588 | LOGGER_WARNING("Parsing message failed; nothing sent!"); | 287 | LOGGER_ERROR ("Failed to aquire lock on msi mutex"); |
589 | return -1; | 288 | return -1; |
590 | } | 289 | } |
591 | 290 | ||
592 | if ( m_msi_packet(session->messenger_handle, to, msg_string_final, length) ) { | 291 | if (call->state != msi_CallActive) { |
593 | LOGGER_DEBUG("Sent message"); | 292 | LOGGER_ERROR("Call is in invalid state!"); |
594 | return 0; | 293 | pthread_mutex_unlock(session->mutex); |
595 | } | ||
596 | |||
597 | return -1; | ||
598 | } | ||
599 | |||
600 | static int send_reponse ( MSISession *session, MSICall *call, MSIResponse response, uint32_t to ) | ||
601 | { | ||
602 | MSIMessage *msg = msi_new_message ( TypeResponse, response ); | ||
603 | int ret = send_message ( session, call, msg, to ); | ||
604 | free ( msg ); | ||
605 | return ret; | ||
606 | } | ||
607 | |||
608 | static int send_error ( MSISession *session, MSICall *call, MSICallError errid, uint32_t to ) | ||
609 | { | ||
610 | if (!call) { | ||
611 | LOGGER_WARNING("Cannot handle error on 'null' call"); | ||
612 | return -1; | 294 | return -1; |
613 | } | 295 | } |
614 | 296 | ||
615 | LOGGER_DEBUG("Sending error: %d on call: %s", errid, call->id); | 297 | call->self_capabilities = capabilities; |
616 | 298 | ||
617 | MSIMessage *msg_error = msi_new_message ( TypeResponse, error ); | 299 | MSIMessage msg; |
300 | msg_init(&msg, requ_push); | ||
618 | 301 | ||
619 | msi_msg_set_reason ( msg_error, stringify_error(errid) ); | 302 | msg.capabilities.exists = true; |
620 | send_message ( session, call, msg_error, to ); | 303 | msg.capabilities.value = capabilities; |
621 | free ( msg_error ); | ||
622 | 304 | ||
623 | return 0; | 305 | send_message (call->session->messenger, call->friend_number, &msg); |
624 | } | ||
625 | 306 | ||
626 | /** | 307 | pthread_mutex_unlock(session->mutex); |
627 | * Determine 'bigger' call id | 308 | return 0; |
628 | */ | ||
629 | static int call_id_bigger( const uint8_t *first, const uint8_t *second) | ||
630 | { | ||
631 | return (memcmp(first, second, sizeof(MSICallIDType)) < 0); | ||
632 | } | 309 | } |
633 | 310 | ||
634 | 311 | ||
635 | /** | 312 | /** |
636 | * Set/change peer csettings | 313 | * Private functions |
637 | */ | 314 | */ |
638 | static int flush_peer_csettings ( MSICall *call, MSIMessage *msg, int peer_id ) | 315 | void msg_init(MSIMessage *dest, MSIRequest request) |
639 | { | 316 | { |
640 | if ( msg->csettings.exists ) { | 317 | memset(dest, 0, sizeof(*dest)); |
641 | msi_msg_get_csettings(msg, &call->csettings_peer[peer_id]); | 318 | dest->request.exists = true; |
642 | 319 | dest->request.value = request; | |
643 | LOGGER_DEBUG("Peer: %d \n" | ||
644 | "Type: %u \n" | ||
645 | "Video bitrate: %u \n" | ||
646 | "Video height: %u \n" | ||
647 | "Video width: %u \n" | ||
648 | "Audio bitrate: %u \n" | ||
649 | "Audio framedur: %u \n" | ||
650 | "Audio sample rate: %u \n" | ||
651 | "Audio channels: %u \n", peer_id, | ||
652 | call->csettings_peer[peer_id].call_type, | ||
653 | call->csettings_peer[peer_id].video_bitrate, | ||
654 | call->csettings_peer[peer_id].max_video_height, | ||
655 | call->csettings_peer[peer_id].max_video_width, | ||
656 | call->csettings_peer[peer_id].audio_bitrate, | ||
657 | call->csettings_peer[peer_id].audio_frame_duration, | ||
658 | call->csettings_peer[peer_id].audio_sample_rate, | ||
659 | call->csettings_peer[peer_id].audio_channels ); | ||
660 | |||
661 | return 0; | ||
662 | } | ||
663 | |||
664 | LOGGER_WARNING("No csettings header!"); | ||
665 | return -1; | ||
666 | } | 320 | } |
667 | 321 | int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length) | |
668 | |||
669 | /** | ||
670 | * Add peer to peer list. | ||
671 | */ | ||
672 | static void add_peer( MSICall *call, int peer_id ) | ||
673 | { | 322 | { |
674 | uint32_t *peers = !call->peers ? peers = calloc(sizeof(uint32_t), 1) : | 323 | /* Parse raw data received from socket into MSIMessage struct */ |
675 | realloc( call->peers, sizeof(uint32_t) * call->peer_count); | ||
676 | |||
677 | if (!peers) { | ||
678 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
679 | return; | ||
680 | } | ||
681 | |||
682 | call->peer_count ++; | ||
683 | call->peers = peers; | ||
684 | call->peers[call->peer_count - 1] = peer_id; | ||
685 | |||
686 | LOGGER_DEBUG("Added peer: %d", peer_id); | ||
687 | } | ||
688 | 324 | ||
325 | #define CHECK_SIZE(bytes, constraint, size) \ | ||
326 | if ((constraint -= (2 + size)) < 1) { LOGGER_ERROR("Read over length!"); return -1; } \ | ||
327 | if (bytes[1] != size) { LOGGER_ERROR("Invalid data size!"); return -1; } | ||
689 | 328 | ||
690 | static MSICall *find_call ( MSISession *session, uint8_t *call_id ) | 329 | #define CHECK_ENUM_HIGH(bytes, enum_high) /* Assumes size == 1 */ \ |
691 | { | 330 | if (bytes[2] > enum_high) { LOGGER_ERROR("Failed enum high limit!"); return -1; } |
692 | if ( call_id == NULL ) return NULL; | ||
693 | 331 | ||
694 | int32_t i = 0; | 332 | #define SET_UINT8(bytes, header) do { \ |
333 | header.value = bytes[2]; \ | ||
334 | header.exists = true; \ | ||
335 | bytes += 3; \ | ||
336 | } while(0) | ||
695 | 337 | ||
696 | for (; i < session->max_calls; i ++ ) | 338 | #define SET_UINT16(bytes, header) do { \ |
697 | if ( session->calls[i] && memcmp(session->calls[i]->id, call_id, sizeof(session->calls[i]->id)) == 0 ) { | 339 | memcpy(&header.value, bytes + 2, 2);\ |
698 | return session->calls[i]; | 340 | header.exists = true; \ |
699 | } | 341 | bytes += 4; \ |
342 | } while(0) | ||
700 | 343 | ||
701 | return NULL; | ||
702 | } | ||
703 | 344 | ||
704 | static MSICall *init_call ( MSISession *session, int peers, int ringing_timeout ) | 345 | assert(dest); |
705 | { | ||
706 | 346 | ||
707 | if (peers == 0) { | 347 | if (length == 0 || data[length - 1]) { /* End byte must have value 0 */ |
708 | LOGGER_ERROR("No peers!"); | 348 | LOGGER_ERROR("Invalid end byte"); |
709 | return NULL; | 349 | return -1; |
710 | } | 350 | } |
711 | 351 | ||
712 | int32_t call_idx = 0; | 352 | memset(dest, 0, sizeof(*dest)); |
713 | 353 | ||
714 | for (; call_idx < session->max_calls; call_idx ++) { | 354 | const uint8_t *it = data; |
715 | if ( !session->calls[call_idx] ) { | 355 | int size_constraint = length; |
716 | |||
717 | if (!(session->calls[call_idx] = calloc ( sizeof ( MSICall ), 1 ))) { | ||
718 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
719 | return NULL; | ||
720 | } | ||
721 | |||
722 | break; | ||
723 | } | ||
724 | } | ||
725 | |||
726 | if ( call_idx == session->max_calls ) { | ||
727 | LOGGER_WARNING("Reached maximum amount of calls!"); | ||
728 | return NULL; | ||
729 | } | ||
730 | 356 | ||
357 | while (*it) {/* until end byte is hit */ | ||
358 | switch (*it) { | ||
359 | case IDRequest: | ||
360 | CHECK_SIZE(it, size_constraint, 1); | ||
361 | CHECK_ENUM_HIGH(it, requ_pop); | ||
362 | SET_UINT8(it, dest->request); | ||
363 | break; | ||
731 | 364 | ||
732 | MSICall *call = session->calls[call_idx]; | 365 | case IDError: |
366 | CHECK_SIZE(it, size_constraint, 1); | ||
367 | CHECK_ENUM_HIGH(it, msi_EUndisclosed); | ||
368 | SET_UINT8(it, dest->error); | ||
369 | break; | ||
733 | 370 | ||
734 | call->call_idx = call_idx; | 371 | case IDCapabilities: |
372 | CHECK_SIZE(it, size_constraint, 1); | ||
373 | SET_UINT8(it, dest->capabilities); | ||
374 | break; | ||
735 | 375 | ||
736 | if ( !(call->csettings_peer = calloc ( sizeof ( MSICSettings ), peers )) ) { | 376 | default: |
737 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | 377 | LOGGER_ERROR("Invalid id byte"); |
738 | free(call); | 378 | return -1; |
739 | return NULL; | 379 | break; |
380 | } | ||
740 | } | 381 | } |
741 | 382 | ||
742 | call->session = session; | 383 | if (dest->request.exists == false) { |
743 | 384 | LOGGER_ERROR("Invalid request field!"); | |
744 | call->request_timer_id = 0; | ||
745 | call->ringing_timer_id = 0; | ||
746 | |||
747 | call->ringing_tout_ms = ringing_timeout; | ||
748 | |||
749 | LOGGER_DEBUG("Started new call with index: %u", call_idx); | ||
750 | return call; | ||
751 | } | ||
752 | |||
753 | static int terminate_call ( MSISession *session, MSICall *call ) | ||
754 | { | ||
755 | if ( !call ) { | ||
756 | LOGGER_WARNING("Tried to terminate non-existing call!"); | ||
757 | return -1; | 385 | return -1; |
758 | } | 386 | } |
759 | 387 | ||
760 | /* Check event loop and cancel timed events if there are any | ||
761 | */ | ||
762 | timer_release ( session->timer_handler, call->request_timer_id); | ||
763 | timer_release ( session->timer_handler, call->ringing_timer_id); | ||
764 | |||
765 | session->calls[call->call_idx] = NULL; | ||
766 | |||
767 | LOGGER_DEBUG("Terminated call id: %d", call->call_idx); | ||
768 | |||
769 | free ( call->csettings_peer ); | ||
770 | free ( call->peers ); | ||
771 | free ( call ); | ||
772 | |||
773 | return 0; | 388 | return 0; |
774 | } | ||
775 | 389 | ||
776 | static void handle_remote_connection_change(Messenger *messenger, uint32_t friend_num, uint8_t status, void *session_p) | 390 | #undef CHECK_SIZE |
391 | #undef CHECK_ENUM_HIGH | ||
392 | #undef SET_UINT8 | ||
393 | #undef SET_UINT16 | ||
394 | } | ||
395 | uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length) | ||
777 | { | 396 | { |
778 | (void)messenger; | 397 | /* Parse a single header for sending */ |
779 | MSISession *session = session_p; | 398 | assert(dest); |
399 | assert(value); | ||
400 | assert(value_len); | ||
780 | 401 | ||
781 | switch ( status ) { | 402 | *dest = id; |
782 | case 0: { /* Went offline */ | 403 | dest ++; |
783 | int32_t j = 0; | 404 | *dest = value_len; |
784 | 405 | dest ++; | |
785 | for ( ; j < session->max_calls; j ++ ) { | ||
786 | |||
787 | if ( !session->calls[j] ) continue; | ||
788 | 406 | ||
789 | uint16_t i = 0; | 407 | memcpy(dest, value, value_len); |
790 | 408 | ||
791 | for ( ; i < session->calls[j]->peer_count; i ++ ) | 409 | *length += (2 + value_len); |
792 | if ( session->calls[j]->peers[i] == (uint32_t)friend_num ) { | ||
793 | invoke_callback(session, j, msi_OnPeerTimeout); | ||
794 | terminate_call(session, session->calls[j]); | ||
795 | LOGGER_DEBUG("Remote: %d timed out!", friend_num); | ||
796 | return; /* TODO: On group calls change behaviour */ | ||
797 | } | ||
798 | } | ||
799 | } | ||
800 | break; | ||
801 | 410 | ||
802 | default: | 411 | return dest + value_len; /* Set to next position ready to be written */ |
803 | break; | ||
804 | } | ||
805 | } | 412 | } |
806 | 413 | int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg) | |
807 | /** | ||
808 | * Function called at request timeout | ||
809 | */ | ||
810 | static void handle_timeout ( Timer *timer ) | ||
811 | { | 414 | { |
812 | /* TODO: Cancel might not arrive there; set up | 415 | /* Parse and send message */ |
813 | * timers on these cancels and terminate call on | 416 | assert(m); |
814 | * their timeout | ||
815 | */ | ||
816 | MSICall *call = timer->session->calls[timer->call_idx]; | ||
817 | 417 | ||
418 | uint8_t parsed [MSI_MAXMSG_SIZE]; | ||
818 | 419 | ||
819 | if (call) { | 420 | uint8_t *it = parsed; |
820 | LOGGER_DEBUG("[Call: %d] Request timed out!", call->call_idx); | 421 | uint16_t size = 0; |
821 | 422 | ||
822 | invoke_callback(timer->session, timer->call_idx, msi_OnRequestTimeout); | 423 | if (msg->request.exists) { |
823 | msi_cancel(timer->session, timer->call_idx, call->peers [0], "Request timed out"); | 424 | uint8_t cast = msg->request.value; |
425 | it = msg_parse_header_out(IDRequest, it, &cast, | ||
426 | sizeof(cast), &size); | ||
427 | } else { | ||
428 | LOGGER_DEBUG("Must have request field"); | ||
429 | return -1; | ||
824 | } | 430 | } |
825 | } | ||
826 | |||
827 | |||
828 | /********** Request handlers **********/ | ||
829 | static int handle_recv_invite ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
830 | { | ||
831 | LOGGER_DEBUG("Session: %p Handling 'invite' on call: %d", session, call ? call->call_idx : -1); | ||
832 | 431 | ||
833 | 432 | if (msg->error.exists) { | |
834 | if (!msg->csettings.exists) {/**/ | 433 | uint8_t cast = msg->error.value; |
835 | LOGGER_WARNING("Peer sent invalid codec settings!"); | 434 | it = msg_parse_header_out(IDError, it, &cast, |
836 | send_error ( session, call, error_no_callid, msg->friend_id ); | 435 | sizeof(cast), &size); |
837 | return 0; | ||
838 | } | 436 | } |
839 | 437 | ||
840 | if ( call ) { | 438 | if (msg->capabilities.exists) { |
841 | if ( call->peers[0] == (uint32_t)msg->friend_id ) { | 439 | it = msg_parse_header_out(IDCapabilities, it, &msg->capabilities.value, |
842 | if (call->state == msi_CallInviting) { | 440 | sizeof(msg->capabilities.value), &size); |
843 | /* The glare case. A calls B when at the same time | ||
844 | * B calls A. Who has advantage is set bey calculating | ||
845 | * 'bigger' Call id and then that call id is being used in | ||
846 | * future. User with 'bigger' Call id has the advantage | ||
847 | * as in he will wait the response from the other. | ||
848 | */ | ||
849 | LOGGER_DEBUG("Glare case; Peer: %d", call->peers[0]); | ||
850 | |||
851 | if ( call_id_bigger (call->id, msg->callid.value) == 1 ) { /* Peer has advantage */ | ||
852 | |||
853 | /* Terminate call; peer will timeout(call) if call initialization fails */ | ||
854 | terminate_call(session, call); | ||
855 | |||
856 | call = init_call ( session, 1, 0 ); | ||
857 | |||
858 | if ( !call ) { | ||
859 | LOGGER_ERROR("Starting call"); | ||
860 | return 0; | ||
861 | } | ||
862 | |||
863 | } else { | ||
864 | return 0; /* Wait for ringing from peer */ | ||
865 | } | ||
866 | } else if (call->state == msi_CallActive) { | ||
867 | /* Request for media change; call callback and send starting response */ | ||
868 | if (flush_peer_csettings(call, msg, 0) != 0) { /**/ | ||
869 | LOGGER_WARNING("Peer sent invalid csetting!"); | ||
870 | send_error ( session, call, error_no_callid, msg->friend_id ); | ||
871 | return 0; | ||
872 | } | ||
873 | |||
874 | LOGGER_DEBUG("Set new call type: %s", call->csettings_peer[0].call_type == msi_TypeAudio ? "audio" : "video"); | ||
875 | send_reponse(session, call, starting, msg->friend_id); | ||
876 | invoke_callback(session, call->call_idx, msi_OnPeerCSChange); | ||
877 | return 1; | ||
878 | } | ||
879 | } else { | ||
880 | send_error ( session, call, error_busy, msg->friend_id ); /* TODO: Ugh*/ | ||
881 | terminate_call(session, call); | ||
882 | return 0; | ||
883 | } | ||
884 | } else { | ||
885 | call = init_call ( session, 1, 0 ); | ||
886 | |||
887 | if ( !call ) { | ||
888 | LOGGER_ERROR("Starting call"); | ||
889 | return 0; | ||
890 | } | ||
891 | } | 441 | } |
892 | 442 | ||
893 | if ( !msg->callid.exists ) { | 443 | if (it == parsed) { |
894 | send_error ( session, call, error_no_callid, msg->friend_id ); | 444 | LOGGER_WARNING("Parsing message failed; empty message"); |
895 | terminate_call(session, call); | 445 | return -1; |
896 | return 0; | ||
897 | } | 446 | } |
898 | 447 | ||
899 | memcpy ( call->id, msg->callid.value, sizeof(msg->callid.value) ); | 448 | *it = 0; |
900 | call->state = msi_CallStarting; | 449 | size ++; |
901 | |||
902 | add_peer( call, msg->friend_id); | ||
903 | flush_peer_csettings ( call, msg, 0 ); | ||
904 | send_reponse(session, call, ringing, msg->friend_id); | ||
905 | invoke_callback(session, call->call_idx, msi_OnInvite); | ||
906 | |||
907 | return 1; | ||
908 | } | ||
909 | 450 | ||
910 | static int handle_recv_start ( MSISession *session, MSICall *call, MSIMessage *msg ) | 451 | if (m_msi_packet(m, friend_number, parsed, size)) { |
911 | { | 452 | LOGGER_DEBUG("Sent message"); |
912 | if ( !call ) { | ||
913 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
914 | return 0; | 453 | return 0; |
915 | } | 454 | } |
916 | 455 | ||
917 | (void)msg; | 456 | return -1; |
918 | |||
919 | LOGGER_DEBUG("Session: %p Handling 'start' on call: %d, friend id: %d", session, call->call_idx, msg->friend_id ); | ||
920 | |||
921 | call->state = msi_CallActive; | ||
922 | invoke_callback(session, call->call_idx, msi_OnStart); | ||
923 | return 1; | ||
924 | } | 457 | } |
925 | 458 | int send_error (Messenger *m, uint32_t friend_number, MSIError error) | |
926 | static int handle_recv_reject ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
927 | { | 459 | { |
928 | if ( !call ) { | 460 | /* Send error message */ |
929 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | 461 | assert(m); |
930 | return 0; | ||
931 | } | ||
932 | 462 | ||
933 | LOGGER_DEBUG("Session: %p Handling 'reject' on call: %u", session, call->call_idx); | 463 | LOGGER_DEBUG("Sending error: %d to friend: %d", error, friend_number); |
934 | 464 | ||
935 | invoke_callback(session, call->call_idx, msi_OnReject); | 465 | MSIMessage msg; |
466 | msg_init(&msg, requ_pop); | ||
936 | 467 | ||
937 | send_reponse(session, call, ending, msg->friend_id); | 468 | msg.error.exists = true; |
938 | terminate_call(session, call); | 469 | msg.error.value = error; |
939 | 470 | ||
940 | return 1; | 471 | send_message (m, friend_number, &msg); |
472 | return 0; | ||
941 | } | 473 | } |
942 | 474 | int invoke_callback(MSICall *call, MSICallbackID cb) | |
943 | static int handle_recv_cancel ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
944 | { | 475 | { |
945 | if ( !call ) { | 476 | assert(call); |
946 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
947 | return 0; | ||
948 | } | ||
949 | |||
950 | (void)msg; | ||
951 | 477 | ||
952 | LOGGER_DEBUG("Session: %p Handling 'cancel' on call: %u", session, call->call_idx); | 478 | if (call->session->callbacks[cb]) { |
479 | LOGGER_DEBUG("Invoking callback function: %d", cb); | ||
953 | 480 | ||
954 | invoke_callback(session, call->call_idx, msi_OnCancel); | 481 | if (call->session->callbacks[cb] (call->session->av, call) != 0) { |
955 | terminate_call ( session, call ); | 482 | LOGGER_WARNING("Callback state handling failed, sending error"); |
956 | 483 | goto FAILURE; | |
957 | return 1; | 484 | } |
958 | } | ||
959 | 485 | ||
960 | static int handle_recv_end ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
961 | { | ||
962 | if ( !call ) { | ||
963 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
964 | return 0; | 486 | return 0; |
965 | } | 487 | } |
966 | 488 | ||
967 | LOGGER_DEBUG("Session: %p Handling 'end' on call: %d", session, call->call_idx); | 489 | FAILURE: |
490 | /* If no callback present or error happened while handling, | ||
491 | * an error message will be sent to friend | ||
492 | */ | ||
968 | 493 | ||
969 | invoke_callback(session, call->call_idx, msi_OnEnd); | 494 | if (call->error == msi_ENone) |
970 | send_reponse(session, call, ending, msg->friend_id); | 495 | call->error = msi_EHandle; |
971 | terminate_call ( session, call ); | ||
972 | 496 | ||
973 | return 1; | 497 | return -1; |
974 | } | 498 | } |
975 | 499 | static MSICall *get_call (MSISession *session, uint32_t friend_number) | |
976 | /********** Response handlers **********/ | ||
977 | static int handle_recv_ringing ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
978 | { | 500 | { |
979 | if ( !call ) { | 501 | assert(session); |
980 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
981 | return 0; | ||
982 | } | ||
983 | |||
984 | (void)msg; | ||
985 | 502 | ||
986 | if ( call->ringing_timer_id ) { | 503 | if (session->calls == NULL || session->calls_tail < friend_number) |
987 | LOGGER_WARNING("Call already ringing"); | 504 | return NULL; |
988 | return 0; | ||
989 | } | ||
990 | |||
991 | LOGGER_DEBUG("Session: %p Handling 'ringing' on call: %d", session, call->call_idx ); | ||
992 | 505 | ||
993 | call->ringing_timer_id = timer_alloc | 506 | return session->calls[friend_number]; |
994 | ( session, handle_timeout, call->call_idx, call->ringing_tout_ms ); | ||
995 | invoke_callback(session, call->call_idx, msi_OnRinging); | ||
996 | return 1; | ||
997 | } | 507 | } |
998 | static int handle_recv_starting ( MSISession *session, MSICall *call, MSIMessage *msg ) | 508 | MSICall *new_call (MSISession *session, uint32_t friend_number) |
999 | { | 509 | { |
1000 | if ( !call ) { | 510 | assert(session); |
1001 | LOGGER_WARNING("Session: %p Handling 'starting' on non-existing call"); | ||
1002 | return 0; | ||
1003 | } | ||
1004 | |||
1005 | if ( call->state == msi_CallActive ) { /* Change media */ | ||
1006 | 511 | ||
1007 | LOGGER_DEBUG("Session: %p Changing media on call: %d", session, call->call_idx ); | 512 | MSICall *rc = calloc(sizeof(MSICall), 1); |
1008 | 513 | ||
1009 | invoke_callback(session, call->call_idx, msi_OnSelfCSChange); | 514 | if (rc == NULL) |
1010 | 515 | return NULL; | |
1011 | } else if ( call->state == msi_CallInviting ) { | ||
1012 | LOGGER_DEBUG("Session: %p Handling 'starting' on call: %d", session, call->call_idx ); | ||
1013 | 516 | ||
1014 | call->state = msi_CallActive; | 517 | rc->session = session; |
518 | rc->friend_number = friend_number; | ||
1015 | 519 | ||
1016 | MSIMessage *msg_start = msi_new_message ( TypeRequest, start ); | 520 | if (session->calls == NULL) { /* Creating */ |
1017 | send_message ( session, call, msg_start, msg->friend_id ); | 521 | session->calls = calloc (sizeof(MSICall *), friend_number + 1); |
1018 | free ( msg_start ); | ||
1019 | 522 | ||
523 | if (session->calls == NULL) { | ||
524 | free(rc); | ||
525 | return NULL; | ||
526 | } | ||
1020 | 527 | ||
1021 | flush_peer_csettings ( call, msg, 0 ); | 528 | session->calls_tail = session->calls_head = friend_number; |
1022 | 529 | ||
1023 | /* This is here in case of glare */ | 530 | } else if (session->calls_tail < friend_number) { /* Appending */ |
1024 | timer_release(session->timer_handler, call->ringing_timer_id); | 531 | void *tmp = realloc(session->calls, sizeof(MSICall *) * (friend_number + 1)); |
1025 | invoke_callback(session, call->call_idx, msi_OnStart); | ||
1026 | } else { | ||
1027 | LOGGER_ERROR("Invalid call state"); | ||
1028 | terminate_call(session, call ); | ||
1029 | return 0; | ||
1030 | } | ||
1031 | 532 | ||
1032 | return 1; | 533 | if (tmp == NULL) { |
1033 | } | 534 | free(rc); |
1034 | static int handle_recv_ending ( MSISession *session, MSICall *call, MSIMessage *msg ) | 535 | return NULL; |
1035 | { | 536 | } |
1036 | if ( !call ) { | ||
1037 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
1038 | return 0; | ||
1039 | } | ||
1040 | |||
1041 | (void)msg; | ||
1042 | 537 | ||
1043 | LOGGER_DEBUG("Session: %p Handling 'ending' on call: %d", session, call->call_idx ); | 538 | session->calls = tmp; |
1044 | 539 | ||
1045 | invoke_callback(session, call->call_idx, msi_OnEnd); | 540 | /* Set fields in between to null */ |
1046 | terminate_call ( session, call ); | 541 | uint32_t i = session->calls_tail + 1; |
1047 | 542 | ||
1048 | return 1; | 543 | for (; i < friend_number; i ++) |
1049 | } | 544 | session->calls[i] = NULL; |
1050 | static int handle_recv_error ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
1051 | { | ||
1052 | if ( !call ) { | ||
1053 | LOGGER_WARNING("Handling 'error' on non-existing call!"); | ||
1054 | return -1; | ||
1055 | } | ||
1056 | 545 | ||
1057 | LOGGER_DEBUG("Session: %p Handling 'error' on call: %d", session, call->call_idx ); | 546 | rc->prev = session->calls[session->calls_tail]; |
547 | session->calls[session->calls_tail]->next = rc; | ||
1058 | 548 | ||
1059 | invoke_callback(session, call->call_idx, msi_OnEnd); | 549 | session->calls_tail = friend_number; |
1060 | 550 | ||
1061 | /* Handle error accordingly */ | 551 | } else if (session->calls_head > friend_number) { /* Inserting at front */ |
1062 | if ( msg->reason.exists ) { | 552 | rc->next = session->calls[session->calls_head]; |
1063 | /* TODO */ | 553 | session->calls[session->calls_head]->prev = rc; |
554 | session->calls_head = friend_number; | ||
1064 | } | 555 | } |
1065 | 556 | ||
1066 | terminate_call ( session, call ); | 557 | session->calls[friend_number] = rc; |
1067 | 558 | return rc; | |
1068 | return 1; | ||
1069 | } | 559 | } |
1070 | 560 | void kill_call (MSICall *call) | |
1071 | /** | ||
1072 | * BASIC call flow: | ||
1073 | * | ||
1074 | * ALICE BOB | ||
1075 | * | invite --> | | ||
1076 | * | | | ||
1077 | * | <-- ringing | | ||
1078 | * | | | ||
1079 | * | <-- starting | | ||
1080 | * | | | ||
1081 | * | start --> | | ||
1082 | * | | | ||
1083 | * | <-- MEDIA TRANS --> | | ||
1084 | * | | | ||
1085 | * | end --> | | ||
1086 | * | | | ||
1087 | * | <-- ending | | ||
1088 | * | ||
1089 | * Alice calls Bob by sending invite packet. | ||
1090 | * Bob recvs the packet and sends an ringing packet; | ||
1091 | * which notifies Alice that her invite is acknowledged. | ||
1092 | * Ringing screen shown on both sides. | ||
1093 | * Bob accepts the invite for a call by sending starting packet. | ||
1094 | * Alice recvs the starting packet and sends the started packet to | ||
1095 | * inform Bob that she recved the starting packet. | ||
1096 | * Now the media transmission is established ( i.e. RTP transmission ). | ||
1097 | * Alice hangs up and sends end packet. | ||
1098 | * Bob recves the end packet and sends ending packet | ||
1099 | * as the acknowledgement that the call is ending. | ||
1100 | * | ||
1101 | * | ||
1102 | */ | ||
1103 | static void msi_handle_packet ( Messenger *messenger, uint32_t source, const uint8_t *data, uint16_t length, | ||
1104 | void *object ) | ||
1105 | { | 561 | { |
1106 | LOGGER_DEBUG("Got msi message"); | 562 | /* Assume that session mutex is locked */ |
1107 | /* Unused */ | 563 | if (call == NULL) |
1108 | (void)messenger; | ||
1109 | |||
1110 | MSISession *session = object; | ||
1111 | MSIMessage *msg; | ||
1112 | |||
1113 | if ( !length ) { | ||
1114 | LOGGER_WARNING("Length param negative"); | ||
1115 | return; | 564 | return; |
1116 | } | ||
1117 | 565 | ||
1118 | msg = parse_recv ( data, length ); | 566 | LOGGER_DEBUG("Killing call: %p", call); |
1119 | 567 | ||
1120 | if ( !msg ) { | 568 | MSISession *session = call->session; |
1121 | LOGGER_WARNING("Error parsing message"); | ||
1122 | return; | ||
1123 | } else { | ||
1124 | LOGGER_DEBUG("Successfully parsed message"); | ||
1125 | } | ||
1126 | 569 | ||
1127 | msg->friend_id = source; | 570 | MSICall *prev = call->prev; |
571 | MSICall *next = call->next; | ||
1128 | 572 | ||
1129 | pthread_mutex_lock(session->mutex); | 573 | if (prev) |
574 | prev->next = next; | ||
575 | else if (next) | ||
576 | session->calls_head = next->friend_number; | ||
577 | else goto CLEAR_CONTAINER; | ||
1130 | 578 | ||
1131 | /* Find what call */ | 579 | if (next) |
1132 | MSICall *call = msg->callid.exists ? find_call(session, msg->callid.value ) : NULL; | 580 | next->prev = prev; |
581 | else if (prev) | ||
582 | session->calls_tail = prev->friend_number; | ||
583 | else goto CLEAR_CONTAINER; | ||
1133 | 584 | ||
1134 | /* Now handle message */ | 585 | session->calls[call->friend_number] = NULL; |
586 | free(call); | ||
587 | return; | ||
1135 | 588 | ||
1136 | if ( msg->request.exists ) { /* Handle request */ | 589 | CLEAR_CONTAINER: |
1137 | 590 | session->calls_head = session->calls_tail = 0; | |
1138 | switch (msg->request.value) { | 591 | free(session->calls); |
1139 | case invite: | 592 | free(call); |
1140 | handle_recv_invite ( session, call, msg ); | 593 | session->calls = NULL; |
1141 | break; | ||
1142 | |||
1143 | case start: | ||
1144 | handle_recv_start ( session, call, msg ); | ||
1145 | break; | ||
1146 | |||
1147 | case cancel: | ||
1148 | handle_recv_cancel ( session, call, msg ); | ||
1149 | break; | ||
1150 | |||
1151 | case reject: | ||
1152 | handle_recv_reject ( session, call, msg ); | ||
1153 | break; | ||
1154 | |||
1155 | case end: | ||
1156 | handle_recv_end ( session, call, msg ); | ||
1157 | break; | ||
1158 | } | ||
1159 | |||
1160 | } else if ( msg->response.exists ) { /* Handle response */ | ||
1161 | |||
1162 | /* Got response so cancel timer */ | ||
1163 | if ( call ) timer_release(session->timer_handler, call->request_timer_id); | ||
1164 | |||
1165 | switch (msg->response.value) { | ||
1166 | case ringing: | ||
1167 | handle_recv_ringing ( session, call, msg ); | ||
1168 | break; | ||
1169 | |||
1170 | case starting: | ||
1171 | handle_recv_starting ( session, call, msg ); | ||
1172 | break; | ||
1173 | |||
1174 | case ending: | ||
1175 | handle_recv_ending ( session, call, msg ); | ||
1176 | break; | ||
1177 | |||
1178 | case error: | ||
1179 | handle_recv_error ( session, call, msg ); | ||
1180 | break; | ||
1181 | } | ||
1182 | |||
1183 | } else { | ||
1184 | LOGGER_WARNING("Invalid message: no resp nor requ headers"); | ||
1185 | } | ||
1186 | |||
1187 | free ( msg ); | ||
1188 | |||
1189 | pthread_mutex_unlock(session->mutex); | ||
1190 | } | 594 | } |
1191 | 595 | void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data) | |
1192 | |||
1193 | |||
1194 | /********** User functions **********/ | ||
1195 | void msi_register_callback ( MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata ) | ||
1196 | { | 596 | { |
1197 | session->callbacks[id].first = callback; | 597 | (void)m; |
1198 | session->callbacks[id].second = userdata; | 598 | MSISession *session = data; |
1199 | } | ||
1200 | 599 | ||
600 | switch (status) { | ||
601 | case 0: { /* Friend is now offline */ | ||
602 | LOGGER_DEBUG("Friend %d is now offline", friend_number); | ||
1201 | 603 | ||
1202 | MSISession *msi_new ( Messenger *messenger, int32_t max_calls ) | 604 | pthread_mutex_lock(session->mutex); |
1203 | { | 605 | MSICall *call = get_call(session, friend_number); |
1204 | if (messenger == NULL) { | ||
1205 | LOGGER_ERROR("Could not init session on empty messenger!"); | ||
1206 | return NULL; | ||
1207 | } | ||
1208 | 606 | ||
1209 | if ( !max_calls ) { | 607 | if (call == NULL) { |
1210 | LOGGER_WARNING("Invalid max call treshold!"); | 608 | pthread_mutex_unlock(session->mutex); |
1211 | return NULL; | 609 | return; |
1212 | } | 610 | } |
1213 | |||
1214 | MSISession *retu = calloc ( sizeof ( MSISession ), 1 ); | ||
1215 | |||
1216 | if (retu == NULL) { | ||
1217 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1218 | return NULL; | ||
1219 | } | ||
1220 | |||
1221 | if (!(retu->calls = calloc( sizeof (MSICall *), max_calls ))) { | ||
1222 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1223 | goto error; | ||
1224 | } | ||
1225 | |||
1226 | retu->timer_handler = calloc(1, sizeof(TimerHandler)); | ||
1227 | |||
1228 | if (retu->timer_handler == NULL) { | ||
1229 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1230 | goto error; | ||
1231 | } | ||
1232 | |||
1233 | /* Allocate space for timers */ | ||
1234 | ((TimerHandler *)retu->timer_handler)->max_capacity = max_calls * 10; | ||
1235 | |||
1236 | if (!(((TimerHandler *)retu->timer_handler)->timers = calloc(max_calls * 10, sizeof(Timer *)))) { | ||
1237 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1238 | goto error; | ||
1239 | } | ||
1240 | |||
1241 | if (create_recursive_mutex(retu->mutex) != 0) { | ||
1242 | LOGGER_ERROR("Failed to init mutex! Program might misbehave"); | ||
1243 | goto error; | ||
1244 | } | ||
1245 | |||
1246 | retu->messenger_handle = messenger; | ||
1247 | retu->agent_handler = NULL; | ||
1248 | retu->max_calls = max_calls; | ||
1249 | retu->frequ = 10000; /* default value? */ | ||
1250 | retu->call_timeout = 30000; /* default value? */ | ||
1251 | |||
1252 | m_callback_msi_packet(messenger, msi_handle_packet, retu ); | ||
1253 | |||
1254 | /* This is called when remote terminates session */ | ||
1255 | m_callback_connectionstatus_internal_av(messenger, handle_remote_connection_change, retu); | ||
1256 | |||
1257 | LOGGER_DEBUG("New msi session: %p max calls: %u", retu, max_calls); | ||
1258 | return retu; | ||
1259 | 611 | ||
1260 | error: | 612 | invoke_callback(call, msi_OnPeerTimeout); /* Failure is ignored */ |
613 | kill_call(call); | ||
614 | pthread_mutex_unlock(session->mutex); | ||
615 | } | ||
616 | break; | ||
1261 | 617 | ||
1262 | if (retu->timer_handler) { | 618 | default: |
1263 | free(((TimerHandler *)retu->timer_handler)->timers); | 619 | break; |
1264 | free(retu->timer_handler); | ||
1265 | } | 620 | } |
1266 | |||
1267 | free(retu->calls); | ||
1268 | free(retu); | ||
1269 | return NULL; | ||
1270 | } | 621 | } |
1271 | 622 | void handle_init (MSICall *call, const MSIMessage *msg) | |
1272 | |||
1273 | int msi_kill ( MSISession *session ) | ||
1274 | { | 623 | { |
1275 | if (session == NULL) { | 624 | assert(call); |
1276 | LOGGER_ERROR("Tried to terminate non-existing session"); | 625 | LOGGER_DEBUG("Session: %p Handling 'init' friend: %d", call->session, call->friend_number); |
1277 | return -1; | 626 | |
627 | if (!msg->capabilities.exists) { | ||
628 | LOGGER_WARNING("Session: %p Invalid capabilities on 'init'"); | ||
629 | call->error = msi_EInvalidMessage; | ||
630 | goto FAILURE; | ||
1278 | } | 631 | } |
1279 | 632 | ||
1280 | m_callback_msi_packet((struct Messenger *) session->messenger_handle, NULL, NULL); | 633 | switch (call->state) { |
1281 | pthread_mutex_lock(session->mutex); | 634 | case msi_CallInactive: { |
635 | /* Call requested */ | ||
636 | call->peer_capabilities = msg->capabilities.value; | ||
637 | call->state = msi_CallRequested; | ||
1282 | 638 | ||
1283 | /* Cancel active calls */ | 639 | if (invoke_callback(call, msi_OnInvite) == -1) |
1284 | int32_t idx = 0; | 640 | goto FAILURE; |
1285 | |||
1286 | for (; idx < session->max_calls; idx ++) if ( session->calls[idx] ) { | ||
1287 | /* Cancel all? */ | ||
1288 | uint16_t _it = 0; | ||
1289 | /*for ( ; _it < session->calls[idx]->peer_count; _it++ ) | ||
1290 | * FIXME: will not work on multiple peers, must cancel call for all peers | ||
1291 | */ | ||
1292 | MSICallState state = session->calls[idx]->state; | ||
1293 | |||
1294 | if (state == msi_CallInviting) { | ||
1295 | msi_cancel( session, idx, session->calls[idx]->peers [_it], "MSI session terminated!" ); | ||
1296 | } else { | ||
1297 | msi_stopcall(session, idx); | ||
1298 | } | ||
1299 | } | 641 | } |
642 | break; | ||
1300 | 643 | ||
1301 | free(((TimerHandler *)session->timer_handler)->timers); | 644 | case msi_CallActive: { |
1302 | free(session->timer_handler); | 645 | /* If peer sent init while the call is already |
646 | * active it's probable that he is trying to | ||
647 | * re-call us while the call is not terminated | ||
648 | * on our side. We can assume that in this case | ||
649 | * we can automatically answer the re-call. | ||
650 | */ | ||
1303 | 651 | ||
1304 | free ( session->calls ); | 652 | LOGGER_INFO("Friend is recalling us"); |
1305 | pthread_mutex_unlock(session->mutex); | ||
1306 | pthread_mutex_destroy(session->mutex); | ||
1307 | 653 | ||
1308 | LOGGER_DEBUG("Terminated session: %p", session); | 654 | MSIMessage msg; |
1309 | free ( session ); | 655 | msg_init(&msg, requ_push); |
1310 | return 0; | ||
1311 | } | ||
1312 | |||
1313 | int msi_invite ( MSISession *session, | ||
1314 | int32_t *call_index, | ||
1315 | const MSICSettings *csettings, | ||
1316 | uint32_t rngsec, | ||
1317 | uint32_t friend_id ) | ||
1318 | { | ||
1319 | pthread_mutex_lock(session->mutex); | ||
1320 | |||
1321 | LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_id); | ||
1322 | 656 | ||
657 | msg.capabilities.exists = true; | ||
658 | msg.capabilities.value = call->self_capabilities; | ||
1323 | 659 | ||
1324 | int i = 0; | 660 | send_message (call->session->messenger, call->friend_number, &msg); |
1325 | 661 | ||
1326 | for (; i < session->max_calls; i ++) | 662 | /* If peer changed capabilities during re-call they will |
1327 | if (session->calls[i] && session->calls[i]->peers[0] == friend_id) { | 663 | * be handled accordingly during the next step |
1328 | LOGGER_ERROR("Already in a call with friend %d", friend_id); | 664 | */ |
1329 | pthread_mutex_unlock(session->mutex); | ||
1330 | return msi_ErrorAlreadyInCallWithPeer; | ||
1331 | } | 665 | } |
666 | break; | ||
1332 | 667 | ||
1333 | 668 | default: { | |
1334 | MSICall *call = init_call ( session, 1, rngsec ); /* Just one peer for now */ | 669 | LOGGER_WARNING("Session: %p Invalid state on 'init'"); |
1335 | 670 | call->error = msi_EInvalidState; | |
1336 | if ( !call ) { | 671 | goto FAILURE; |
1337 | pthread_mutex_unlock(session->mutex); | 672 | } |
1338 | LOGGER_ERROR("Cannot handle more calls"); | 673 | break; |
1339 | return msi_ErrorReachedCallLimit; | ||
1340 | } | 674 | } |
1341 | 675 | ||
1342 | *call_index = call->call_idx; | 676 | return; |
1343 | 677 | FAILURE: | |
1344 | t_randomstr ( call->id, sizeof(call->id) ); | 678 | send_error(call->session->messenger, call->friend_number, call->error); |
1345 | 679 | kill_call(call); | |
1346 | add_peer ( call, friend_id ); | ||
1347 | |||
1348 | call->csettings_local = *csettings; | ||
1349 | |||
1350 | MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); | ||
1351 | |||
1352 | msi_msg_set_csettings(msg_invite, csettings); | ||
1353 | send_message ( session, call, msg_invite, friend_id ); | ||
1354 | free( msg_invite ); | ||
1355 | |||
1356 | call->state = msi_CallInviting; | ||
1357 | |||
1358 | call->request_timer_id = timer_alloc ( session, handle_timeout, call->call_idx, m_deftout ); | ||
1359 | |||
1360 | LOGGER_DEBUG("Invite sent"); | ||
1361 | |||
1362 | pthread_mutex_unlock(session->mutex); | ||
1363 | |||
1364 | return 0; | ||
1365 | } | 680 | } |
1366 | 681 | void handle_push (MSICall *call, const MSIMessage *msg) | |
1367 | int msi_hangup ( MSISession *session, int32_t call_index ) | ||
1368 | { | 682 | { |
1369 | pthread_mutex_lock(session->mutex); | 683 | assert(call); |
1370 | LOGGER_DEBUG("Session: %p Hanging up call: %u", session, call_index); | ||
1371 | 684 | ||
1372 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | 685 | LOGGER_DEBUG("Session: %p Handling 'push' friend: %d", call->session, call->friend_number); |
1373 | LOGGER_ERROR("Invalid call index!"); | ||
1374 | pthread_mutex_unlock(session->mutex); | ||
1375 | return msi_ErrorNoCall; | ||
1376 | } | ||
1377 | 686 | ||
1378 | if ( session->calls[call_index]->state != msi_CallActive ) { | 687 | if (!msg->capabilities.exists) { |
1379 | LOGGER_ERROR("Call is not active!"); | 688 | LOGGER_WARNING("Session: %p Invalid capabilities on 'push'"); |
1380 | pthread_mutex_unlock(session->mutex); | 689 | call->error = msi_EInvalidMessage; |
1381 | return msi_ErrorInvalidState; | 690 | goto FAILURE; |
1382 | } | 691 | } |
1383 | 692 | ||
1384 | MSIMessage *msg_end = msi_new_message ( TypeRequest, end ); | 693 | switch (call->state) { |
1385 | 694 | case msi_CallActive: { | |
1386 | /* hangup for each peer */ | 695 | /* Only act if capabilities changed */ |
1387 | int it = 0; | 696 | if (call->peer_capabilities != msg->capabilities.value) { |
697 | LOGGER_INFO("Friend is changing capabilities to: %u", msg->capabilities.value); | ||
1388 | 698 | ||
1389 | for ( ; it < session->calls[call_index]->peer_count; it ++ ) | 699 | call->peer_capabilities = msg->capabilities.value; |
1390 | send_message ( session, session->calls[call_index], msg_end, session->calls[call_index]->peers[it] ); | ||
1391 | 700 | ||
1392 | session->calls[call_index]->state = msi_CallOver; | 701 | if (invoke_callback(call, msi_OnCapabilities) == -1) |
702 | goto FAILURE; | ||
703 | } | ||
704 | } | ||
705 | break; | ||
1393 | 706 | ||
1394 | free ( msg_end ); | 707 | case msi_CallRequesting: { |
708 | LOGGER_INFO("Friend answered our call"); | ||
1395 | 709 | ||
1396 | session->calls[call_index]->request_timer_id = | 710 | /* Call started */ |
1397 | timer_alloc ( session, handle_timeout, call_index, m_deftout ); | 711 | call->peer_capabilities = msg->capabilities.value; |
712 | call->state = msi_CallActive; | ||
1398 | 713 | ||
1399 | pthread_mutex_unlock(session->mutex); | 714 | if (invoke_callback(call, msi_OnStart) == -1) |
1400 | return 0; | 715 | goto FAILURE; |
1401 | } | ||
1402 | 716 | ||
1403 | int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ) | 717 | } |
1404 | { | 718 | break; |
1405 | pthread_mutex_lock(session->mutex); | ||
1406 | LOGGER_DEBUG("Session: %p Answering call: %u", session, call_index); | ||
1407 | |||
1408 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1409 | LOGGER_ERROR("Invalid call index!"); | ||
1410 | pthread_mutex_unlock(session->mutex); | ||
1411 | return msi_ErrorNoCall; | ||
1412 | } | ||
1413 | 719 | ||
1414 | if ( session->calls[call_index]->state != msi_CallStarting ) { | 720 | /* Pushes during initialization state are ignored */ |
1415 | LOGGER_ERROR("Call is in invalid state!"); | 721 | case msi_CallInactive: |
1416 | pthread_mutex_unlock(session->mutex); | 722 | case msi_CallRequested: { |
1417 | return msi_ErrorInvalidState; | 723 | LOGGER_WARNING("Ignoring invalid push"); |
724 | } | ||
725 | break; | ||
1418 | } | 726 | } |
1419 | 727 | ||
1420 | MSIMessage *msg_starting = msi_new_message ( TypeResponse, starting ); | 728 | return; |
1421 | |||
1422 | session->calls[call_index]->csettings_local = *csettings; | ||
1423 | |||
1424 | msi_msg_set_csettings(msg_starting, csettings); | ||
1425 | |||
1426 | send_message ( session, session->calls[call_index], msg_starting, session->calls[call_index]->peers[0] ); | ||
1427 | free ( msg_starting ); | ||
1428 | |||
1429 | session->calls[call_index]->state = msi_CallActive; | ||
1430 | 729 | ||
1431 | pthread_mutex_unlock(session->mutex); | 730 | FAILURE: |
1432 | return 0; | 731 | send_error(call->session->messenger, call->friend_number, call->error); |
732 | kill_call(call); | ||
1433 | } | 733 | } |
1434 | 734 | void handle_pop (MSICall *call, const MSIMessage *msg) | |
1435 | int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ) | ||
1436 | { | 735 | { |
1437 | pthread_mutex_lock(session->mutex); | 736 | assert(call); |
1438 | LOGGER_DEBUG("Session: %p Canceling call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); | ||
1439 | 737 | ||
1440 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | 738 | LOGGER_DEBUG("Session: %p Handling 'pop', friend id: %d", call->session, call->friend_number); |
1441 | LOGGER_ERROR("Invalid call index!"); | ||
1442 | pthread_mutex_unlock(session->mutex); | ||
1443 | return msi_ErrorNoCall; | ||
1444 | } | ||
1445 | |||
1446 | if ( session->calls[call_index]->state != msi_CallInviting ) { | ||
1447 | LOGGER_ERROR("Call is in invalid state: %u", session->calls[call_index]->state); | ||
1448 | pthread_mutex_unlock(session->mutex); | ||
1449 | return msi_ErrorInvalidState; | ||
1450 | } | ||
1451 | 739 | ||
1452 | MSIMessage *msg_cancel = msi_new_message ( TypeRequest, cancel ); | 740 | /* callback errors are ignored */ |
1453 | 741 | ||
1454 | /* FIXME */ | 742 | if (msg->error.exists) { |
1455 | #if 0 | 743 | LOGGER_WARNING("Friend detected an error: %d", msg->error.value); |
744 | call->error = msg->error.value; | ||
745 | invoke_callback(call, msi_OnError); | ||
1456 | 746 | ||
1457 | if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { | 747 | } else switch (call->state) { |
1458 | MSIReasonStrType reason_cast; | 748 | case msi_CallInactive: { |
1459 | memset(reason_cast, '\0', sizeof(MSIReasonStrType)); | 749 | LOGGER_ERROR("Handling what should be impossible case"); |
1460 | memcpy(reason_cast, reason, strlen(reason)); | 750 | abort(); |
1461 | msi_msg_set_reason(msg_cancel, reason_cast); | 751 | } |
1462 | } | 752 | break; |
1463 | |||
1464 | #else | ||
1465 | (void)reason; | ||
1466 | 753 | ||
1467 | #endif | 754 | case msi_CallActive: { |
755 | /* Hangup */ | ||
756 | LOGGER_INFO("Friend hung up on us"); | ||
757 | invoke_callback(call, msi_OnEnd); | ||
758 | } | ||
759 | break; | ||
1468 | 760 | ||
1469 | send_message ( session, session->calls[call_index], msg_cancel, peer ); | 761 | case msi_CallRequesting: { |
1470 | free ( msg_cancel ); | 762 | /* Reject */ |
763 | LOGGER_INFO("Friend rejected our call"); | ||
764 | invoke_callback(call, msi_OnEnd); | ||
765 | } | ||
766 | break; | ||
1471 | 767 | ||
1472 | terminate_call ( session, session->calls[call_index] ); | 768 | case msi_CallRequested: { |
1473 | pthread_mutex_unlock(session->mutex); | 769 | /* Cancel */ |
770 | LOGGER_INFO("Friend canceled call invite"); | ||
771 | invoke_callback(call, msi_OnEnd); | ||
772 | } | ||
773 | break; | ||
774 | } | ||
1474 | 775 | ||
1475 | return 0; | 776 | kill_call (call); |
1476 | } | 777 | } |
1477 | 778 | void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object) | |
1478 | int msi_reject ( MSISession *session, int32_t call_index, const char *reason ) | ||
1479 | { | 779 | { |
1480 | pthread_mutex_lock(session->mutex); | 780 | LOGGER_DEBUG("Got msi message"); |
1481 | LOGGER_DEBUG("Session: %p Rejecting call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); | ||
1482 | |||
1483 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1484 | LOGGER_ERROR("Invalid call index!"); | ||
1485 | pthread_mutex_unlock(session->mutex); | ||
1486 | return msi_ErrorNoCall; | ||
1487 | } | ||
1488 | |||
1489 | if ( session->calls[call_index]->state != msi_CallStarting ) { | ||
1490 | LOGGER_ERROR("Call is in invalid state!"); | ||
1491 | pthread_mutex_unlock(session->mutex); | ||
1492 | return msi_ErrorInvalidState; | ||
1493 | } | ||
1494 | |||
1495 | MSIMessage *msg_reject = msi_new_message ( TypeRequest, reject ); | ||
1496 | |||
1497 | /* FIXME */ | ||
1498 | #if 0 | ||
1499 | |||
1500 | if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { | ||
1501 | MSIReasonStrType reason_cast; | ||
1502 | memset(reason_cast, '\0', sizeof(MSIReasonStrType)); | ||
1503 | memcpy(reason_cast, reason, strlen(reason)); | ||
1504 | msi_msg_set_reason(msg_reject, reason_cast); | ||
1505 | } | ||
1506 | |||
1507 | #else | ||
1508 | (void)reason; | ||
1509 | |||
1510 | #endif | ||
1511 | |||
1512 | send_message ( session, session->calls[call_index], msg_reject, | ||
1513 | session->calls[call_index]->peers[session->calls[call_index]->peer_count - 1] ); | ||
1514 | free ( msg_reject ); | ||
1515 | |||
1516 | session->calls[call_index]->state = msi_CallOver; | ||
1517 | session->calls[call_index]->request_timer_id = | ||
1518 | timer_alloc ( session, handle_timeout, call_index, m_deftout ); | ||
1519 | |||
1520 | pthread_mutex_unlock(session->mutex); | ||
1521 | return 0; | ||
1522 | } | ||
1523 | 781 | ||
1524 | int msi_stopcall ( MSISession *session, int32_t call_index ) | 782 | MSISession *session = object; |
1525 | { | 783 | MSIMessage msg; |
1526 | pthread_mutex_lock(session->mutex); | ||
1527 | LOGGER_DEBUG("Session: %p Stopping call index: %u", session, call_index); | ||
1528 | 784 | ||
1529 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | 785 | if (msg_parse_in (&msg, data, length) == -1) { |
1530 | pthread_mutex_unlock(session->mutex); | 786 | LOGGER_WARNING("Error parsing message"); |
1531 | return msi_ErrorNoCall; | 787 | send_error(m, friend_number, msi_EInvalidMessage); |
788 | return; | ||
789 | } else { | ||
790 | LOGGER_DEBUG("Successfully parsed message"); | ||
1532 | } | 791 | } |
1533 | 792 | ||
1534 | /* just terminate it */ | ||
1535 | |||
1536 | terminate_call ( session, session->calls[call_index] ); | ||
1537 | |||
1538 | pthread_mutex_unlock(session->mutex); | ||
1539 | return 0; | ||
1540 | } | ||
1541 | |||
1542 | int msi_change_csettings(MSISession *session, int32_t call_index, const MSICSettings *csettings) | ||
1543 | { | ||
1544 | pthread_mutex_lock(session->mutex); | 793 | pthread_mutex_lock(session->mutex); |
794 | MSICall *call = get_call(session, friend_number); | ||
1545 | 795 | ||
1546 | LOGGER_DEBUG("Changing media on call: %d", call_index); | 796 | if (call == NULL) { |
1547 | 797 | if (msg.request.value != requ_init) { | |
1548 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | 798 | send_error(m, friend_number, msi_EStrayMessage); |
1549 | LOGGER_ERROR("Invalid call index!"); | 799 | pthread_mutex_unlock(session->mutex); |
1550 | pthread_mutex_unlock(session->mutex); | 800 | return; |
1551 | return msi_ErrorNoCall; | 801 | } |
1552 | } | ||
1553 | 802 | ||
1554 | MSICall *call = session->calls[call_index]; | 803 | call = new_call(session, friend_number); |
1555 | 804 | ||
1556 | if ( call->state != msi_CallActive ) { | 805 | if (call == NULL) { |
1557 | LOGGER_ERROR("Call is not active!"); | 806 | send_error(m, friend_number, msi_ESystem); |
1558 | pthread_mutex_unlock(session->mutex); | 807 | pthread_mutex_unlock(session->mutex); |
1559 | return msi_ErrorInvalidState; | 808 | return; |
1560 | } | 809 | } |
1561 | |||
1562 | MSICSettings *local = &call->csettings_local; | ||
1563 | |||
1564 | if ( | ||
1565 | local->call_type == csettings->call_type && | ||
1566 | local->video_bitrate == csettings->video_bitrate && | ||
1567 | local->max_video_width == csettings->max_video_width && | ||
1568 | local->max_video_height == csettings->max_video_height && | ||
1569 | local->audio_bitrate == csettings->audio_bitrate && | ||
1570 | local->audio_frame_duration == csettings->audio_frame_duration && | ||
1571 | local->audio_sample_rate == csettings->audio_sample_rate && | ||
1572 | local->audio_channels == csettings->audio_channels ) { | ||
1573 | LOGGER_ERROR("Call is already set accordingly!"); | ||
1574 | pthread_mutex_unlock(session->mutex); | ||
1575 | return -1; | ||
1576 | } | 810 | } |
1577 | 811 | ||
1578 | *local = *csettings; | 812 | switch (msg.request.value) { |
1579 | 813 | case requ_init: | |
1580 | MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); | 814 | handle_init(call, &msg); |
1581 | 815 | break; | |
1582 | msi_msg_set_csettings ( msg_invite, local ); | ||
1583 | send_message ( session, call, msg_invite, call->peers[0] ); | ||
1584 | free ( msg_invite ); | ||
1585 | |||
1586 | LOGGER_DEBUG("Request for media change sent"); | ||
1587 | |||
1588 | pthread_mutex_unlock(session->mutex); | ||
1589 | |||
1590 | return 0; | ||
1591 | } | ||
1592 | |||
1593 | void msi_do(MSISession *session) | ||
1594 | { | ||
1595 | pthread_mutex_lock(session->mutex); | ||
1596 | |||
1597 | TimerHandler *timer = session->timer_handler; | ||
1598 | |||
1599 | uint64_t time = current_time_monotonic(); | ||
1600 | |||
1601 | while ( timer->timers[0] && timer->timers[0]->timeout < time ) { | ||
1602 | LOGGER_DEBUG("Executing timer assigned at: %d", timer->timers[0]->timeout); | ||
1603 | 816 | ||
1604 | int id = timer->timers[0]->id; | 817 | case requ_push: |
1605 | timer->timers[0]->func(timer->timers[0]); | 818 | handle_push(call, &msg); |
819 | break; | ||
1606 | 820 | ||
1607 | /* In case function has released timer */ | 821 | case requ_pop: |
1608 | if (timer->timers[0] && timer->timers[0]->id == id) | 822 | handle_pop(call, &msg); /* always kills the call */ |
1609 | timer_release(timer, id); | 823 | break; |
1610 | } | 824 | } |
1611 | 825 | ||
1612 | pthread_mutex_unlock(session->mutex); | 826 | pthread_mutex_unlock(session->mutex); |
diff --git a/toxav/msi.h b/toxav/msi.h index 6a00def6..e69581d1 100644 --- a/toxav/msi.h +++ b/toxav/msi.h | |||
@@ -1,6 +1,6 @@ | |||
1 | /** msi.h | 1 | /** msi.h |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
@@ -19,184 +19,133 @@ | |||
19 | * | 19 | * |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #ifndef __TOXMSI | 22 | #ifndef MSI_H |
23 | #define __TOXMSI | 23 | #define MSI_H |
24 | 24 | ||
25 | #include "codec.h" | 25 | #include <inttypes.h> |
26 | #include <pthread.h> | ||
27 | |||
28 | #include "audio.h" | ||
29 | #include "video.h" | ||
26 | #include "../toxcore/Messenger.h" | 30 | #include "../toxcore/Messenger.h" |
27 | 31 | ||
28 | typedef uint8_t MSICallIDType[12]; | 32 | /** |
29 | typedef uint8_t MSIReasonStrType[255]; | 33 | * Error codes. |
30 | typedef void ( *MSICallbackType ) ( void *agent, int32_t call_idx, void *arg ); | 34 | */ |
35 | typedef enum { | ||
36 | msi_ENone, | ||
37 | msi_EInvalidMessage, | ||
38 | msi_EInvalidParam, | ||
39 | msi_EInvalidState, | ||
40 | msi_EStrayMessage, | ||
41 | msi_ESystem, | ||
42 | msi_EHandle, | ||
43 | msi_EUndisclosed, /* NOTE: must be last enum otherwise parsing will not work */ | ||
44 | } MSIError; | ||
31 | 45 | ||
32 | /** | 46 | /** |
33 | * Call type identifier. Also used as rtp callback prefix. | 47 | * Supported capabilities |
34 | */ | 48 | */ |
35 | typedef enum { | 49 | typedef enum { |
36 | msi_TypeAudio = 192, | 50 | msi_CapSAudio = 4, /* sending audio */ |
37 | msi_TypeVideo | 51 | msi_CapSVideo = 8, /* sending video */ |
38 | } MSICallType; | 52 | msi_CapRAudio = 16, /* receiving audio */ |
53 | msi_CapRVideo = 32, /* receiving video */ | ||
54 | } MSICapabilities; | ||
39 | 55 | ||
40 | 56 | ||
41 | /** | 57 | /** |
42 | * Call state identifiers. | 58 | * Call state identifiers. |
43 | */ | 59 | */ |
44 | typedef enum { | 60 | typedef enum { |
45 | msi_CallInviting, /* when sending call invite */ | 61 | msi_CallInactive, /* Default */ |
46 | msi_CallStarting, /* when getting call invite */ | ||
47 | msi_CallActive, | 62 | msi_CallActive, |
48 | msi_CallHold, | 63 | msi_CallRequesting, /* when sending call invite */ |
49 | msi_CallOver | 64 | msi_CallRequested, /* when getting call invite */ |
50 | |||
51 | } MSICallState; | 65 | } MSICallState; |
52 | 66 | ||
53 | |||
54 | /** | ||
55 | * Encoding settings. | ||
56 | */ | ||
57 | typedef struct _MSICodecSettings { | ||
58 | MSICallType call_type; | ||
59 | |||
60 | uint32_t video_bitrate; /* In kbits/s */ | ||
61 | uint16_t max_video_width; /* In px */ | ||
62 | uint16_t max_video_height; /* In px */ | ||
63 | |||
64 | uint32_t audio_bitrate; /* In bits/s */ | ||
65 | uint16_t audio_frame_duration; /* In ms */ | ||
66 | uint32_t audio_sample_rate; /* In Hz */ | ||
67 | uint32_t audio_channels; | ||
68 | } MSICSettings; | ||
69 | |||
70 | |||
71 | /** | 67 | /** |
72 | * Callbacks ids that handle the states | 68 | * Callbacks ids that handle the states |
73 | */ | 69 | */ |
74 | typedef enum { | 70 | typedef enum { |
75 | msi_OnInvite, /* Incoming call */ | 71 | msi_OnInvite, /* Incoming call */ |
76 | msi_OnRinging, /* When peer is ready to accept/reject the call */ | ||
77 | msi_OnStart, /* Call (RTP transmission) started */ | 72 | msi_OnStart, /* Call (RTP transmission) started */ |
78 | msi_OnCancel, /* The side that initiated call canceled invite */ | ||
79 | msi_OnReject, /* The side that was invited rejected the call */ | ||
80 | msi_OnEnd, /* Call that was active ended */ | 73 | msi_OnEnd, /* Call that was active ended */ |
81 | msi_OnRequestTimeout, /* When the requested action didn't get response in specified time */ | 74 | msi_OnError, /* On protocol error */ |
82 | msi_OnPeerTimeout, /* Peer timed out; stop the call */ | 75 | msi_OnPeerTimeout, /* Peer timed out; stop the call */ |
83 | msi_OnPeerCSChange, /* Peer requested Csettings change */ | 76 | msi_OnCapabilities, /* Peer requested capabilities change */ |
84 | msi_OnSelfCSChange /* Csettings change confirmation */ | ||
85 | } MSICallbackID; | 77 | } MSICallbackID; |
86 | 78 | ||
87 | /** | 79 | /** |
88 | * Errors | 80 | * The call struct. Please do not modify outside msi.c |
89 | */ | 81 | */ |
90 | typedef enum { | 82 | typedef struct MSICall_s { |
91 | msi_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ | 83 | struct MSISession_s *session; /* Session pointer */ |
92 | msi_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ | ||
93 | msi_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ | ||
94 | msi_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ | ||
95 | } MSIError; | ||
96 | |||
97 | /** | ||
98 | * The call struct. | ||
99 | */ | ||
100 | typedef struct _MSICall { /* Call info structure */ | ||
101 | struct _MSISession *session; /* Session pointer */ | ||
102 | |||
103 | MSICallState state; | ||
104 | |||
105 | MSICSettings csettings_local; /* Local call settings */ | ||
106 | MSICSettings *csettings_peer; /* Peers call settings */ | ||
107 | |||
108 | MSICallIDType id; /* Random value identifying the call */ | ||
109 | |||
110 | int ringing_tout_ms; /* Ringing timeout in ms */ | ||
111 | 84 | ||
112 | int request_timer_id; /* Timer id for outgoing request/action */ | 85 | MSICallState state; |
113 | int ringing_timer_id; /* Timer id for ringing timeout */ | 86 | uint8_t peer_capabilities; /* Peer capabilities */ |
87 | uint8_t self_capabilities; /* Self capabilities */ | ||
88 | uint16_t peer_vfpsz; /* Video frame piece size */ | ||
89 | uint32_t friend_number; /* Index of this call in MSISession */ | ||
90 | MSIError error; /* Last error */ | ||
114 | 91 | ||
115 | uint32_t *peers; | 92 | void *av_call; /* Pointer to av call handler */ |
116 | uint16_t peer_count; | ||
117 | 93 | ||
118 | int32_t call_idx; /* Index of this call in MSISession */ | 94 | struct MSICall_s *next; |
95 | struct MSICall_s *prev; | ||
119 | } MSICall; | 96 | } MSICall; |
120 | 97 | ||
121 | 98 | ||
122 | /** | 99 | /** |
123 | * Control session struct | 100 | * Expected return on success is 0, if any other number is |
101 | * returned the call is considered errored and will be handled | ||
102 | * as such which means it will be terminated without any notice. | ||
124 | */ | 103 | */ |
125 | typedef struct _MSISession { | 104 | typedef int msi_action_cb (void *av, MSICall *call); |
126 | 105 | ||
106 | /** | ||
107 | * Control session struct. Please do not modify outside msi.c | ||
108 | */ | ||
109 | typedef struct MSISession_s { | ||
127 | /* Call handlers */ | 110 | /* Call handlers */ |
128 | MSICall **calls; | 111 | MSICall **calls; |
129 | int32_t max_calls; | 112 | uint32_t calls_tail; |
130 | 113 | uint32_t calls_head; | |
131 | void *agent_handler; | ||
132 | Messenger *messenger_handle; | ||
133 | 114 | ||
134 | uint32_t frequ; | 115 | void *av; |
135 | uint32_t call_timeout; /* Time of the timeout for some action to end; 0 if infinite */ | 116 | Messenger *messenger; |
136 | 117 | ||
137 | pthread_mutex_t mutex[1]; | 118 | pthread_mutex_t mutex[1]; |
138 | 119 | msi_action_cb *callbacks[7]; | |
139 | void *timer_handler; | ||
140 | PAIR(MSICallbackType, void *) callbacks[10]; | ||
141 | } MSISession; | 120 | } MSISession; |
142 | 121 | ||
143 | /** | 122 | /** |
144 | * Start the control session. | 123 | * Start the control session. |
145 | */ | 124 | */ |
146 | MSISession *msi_new ( Messenger *messenger, int32_t max_calls ); | 125 | MSISession *msi_new(Messenger *m); |
147 | |||
148 | /** | 126 | /** |
149 | * Terminate control session. | 127 | * Terminate control session. NOTE: all calls will be freed |
150 | */ | 128 | */ |
151 | int msi_kill ( MSISession *session ); | 129 | int msi_kill(MSISession *session); |
152 | |||
153 | /** | 130 | /** |
154 | * Callback setter. | 131 | * Callback setter. |
155 | */ | 132 | */ |
156 | void msi_register_callback(MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata); | 133 | void msi_register_callback(MSISession *session, msi_action_cb *callback, MSICallbackID id); |
157 | |||
158 | /** | ||
159 | * Send invite request to friend_id. | ||
160 | */ | ||
161 | int msi_invite ( MSISession *session, | ||
162 | int32_t *call_index, | ||
163 | const MSICSettings *csettings, | ||
164 | uint32_t rngsec, | ||
165 | uint32_t friend_id ); | ||
166 | |||
167 | /** | ||
168 | * Hangup active call. | ||
169 | */ | ||
170 | int msi_hangup ( MSISession *session, int32_t call_index ); | ||
171 | |||
172 | /** | 134 | /** |
173 | * Answer active call request. | 135 | * Send invite request to friend_number. |
174 | */ | 136 | */ |
175 | int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); | 137 | int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities); |
176 | |||
177 | /** | 138 | /** |
178 | * Cancel request. | 139 | * Hangup call. NOTE: 'call' will be freed |
179 | */ | 140 | */ |
180 | int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ); | 141 | int msi_hangup(MSICall *call); |
181 | |||
182 | /** | 142 | /** |
183 | * Reject incoming call. | 143 | * Answer call request. |
184 | */ | 144 | */ |
185 | int msi_reject ( MSISession *session, int32_t call_index, const char *reason ); | 145 | int msi_answer(MSICall *call, uint8_t capabilities); |
186 | |||
187 | /** | ||
188 | * Terminate the call. | ||
189 | */ | ||
190 | int msi_stopcall ( MSISession *session, int32_t call_index ); | ||
191 | |||
192 | /** | ||
193 | * Change codec settings of the current call. | ||
194 | */ | ||
195 | int msi_change_csettings ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); | ||
196 | |||
197 | /** | 146 | /** |
198 | * Main msi loop | 147 | * Change capabilities of the call. |
199 | */ | 148 | */ |
200 | void msi_do( MSISession *session ); | 149 | int msi_change_capabilities(MSICall *call, uint8_t capabilities); |
201 | 150 | ||
202 | #endif /* __TOXMSI */ | 151 | #endif /* MSI_H */ |
diff --git a/toxav/rtp.c b/toxav/rtp.c index 95d3bf3d..1813725a 100644 --- a/toxav/rtp.c +++ b/toxav/rtp.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /** rtp.c | 1 | /** rtp.c |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
@@ -24,505 +24,370 @@ | |||
24 | #endif /* HAVE_CONFIG_H */ | 24 | #endif /* HAVE_CONFIG_H */ |
25 | 25 | ||
26 | #include "rtp.h" | 26 | #include "rtp.h" |
27 | #include "bwcontroler.h" | ||
27 | #include "../toxcore/logger.h" | 28 | #include "../toxcore/logger.h" |
28 | #include "../toxcore/util.h" | 29 | #include "../toxcore/util.h" |
30 | #include "../toxcore/Messenger.h" | ||
29 | 31 | ||
30 | #include <stdlib.h> | 32 | #include <stdlib.h> |
31 | void queue_message(RTPSession *_session, RTPMessage *_msg); | 33 | #include <assert.h> |
32 | |||
33 | #define size_32 4 | ||
34 | |||
35 | #define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0) | ||
36 | #define ADD_FLAG_PADDING(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xDF; ( _h->flags ) |= ( ( ( _v ) << 5 ) & 0x20 ); } while(0) | ||
37 | #define ADD_FLAG_EXTENSION(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xEF;( _h->flags ) |= ( ( ( _v ) << 4 ) & 0x10 ); } while(0) | ||
38 | #define ADD_FLAG_CSRCC(_h, _v) do { ( _h->flags ) &= 0xF0; ( _h->flags ) |= ( ( _v ) & 0x0F ); } while(0) | ||
39 | #define ADD_SETTING_MARKER(_h, _v) do { if ( _v > 1 ) _v = 1; ( _h->marker_payloadt ) &= 0x7F; ( _h->marker_payloadt ) |= ( ( ( _v ) << 7 ) /*& 0x80 */ ); } while(0) | ||
40 | #define ADD_SETTING_PAYLOAD(_h, _v) do { if ( _v > 127 ) _v = 127; ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0) | ||
41 | |||
42 | #define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) | ||
43 | #define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) | ||
44 | #define GET_FLAG_EXTENSION(_h) (( _h->flags & 0x10 ) >> 4) | ||
45 | #define GET_FLAG_CSRCC(_h) ( _h->flags & 0x0f ) | ||
46 | #define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) | ||
47 | #define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) | ||
48 | |||
49 | /** | ||
50 | * Checks if message came in late. | ||
51 | */ | ||
52 | static int check_late_message (RTPSession *session, RTPMessage *msg) | ||
53 | { | ||
54 | /* | ||
55 | * Check Sequence number. If this new msg has lesser number then the session->rsequnum | ||
56 | * it shows that the message came in late. Also check timestamp to be 100% certain. | ||
57 | * | ||
58 | */ | ||
59 | return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1; | ||
60 | } | ||
61 | |||
62 | |||
63 | /** | ||
64 | * Extracts header from payload. | ||
65 | */ | ||
66 | RTPHeader *extract_header ( const uint8_t *payload, int length ) | ||
67 | { | ||
68 | if ( !payload || !length ) { | ||
69 | LOGGER_WARNING("No payload to extract!"); | ||
70 | return NULL; | ||
71 | } | ||
72 | |||
73 | RTPHeader *retu = calloc(1, sizeof (RTPHeader)); | ||
74 | |||
75 | if ( !retu ) { | ||
76 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
77 | return NULL; | ||
78 | } | ||
79 | |||
80 | memcpy(&retu->sequnum, payload, sizeof(retu->sequnum)); | ||
81 | retu->sequnum = ntohs(retu->sequnum); | ||
82 | |||
83 | const uint8_t *it = payload + 2; | ||
84 | |||
85 | retu->flags = *it; | ||
86 | ++it; | ||
87 | |||
88 | /* This indicates if the first 2 bits are valid. | ||
89 | * Now it may happen that this is out of order but | ||
90 | * it cuts down chances of parsing some invalid value | ||
91 | */ | ||
92 | |||
93 | if ( GET_FLAG_VERSION(retu) != RTP_VERSION ) { | ||
94 | /* Deallocate */ | ||
95 | LOGGER_WARNING("Invalid version!"); | ||
96 | free(retu); | ||
97 | return NULL; | ||
98 | } | ||
99 | |||
100 | /* | ||
101 | * Added a check for the size of the header little sooner so | ||
102 | * I don't need to parse the other stuff if it's bad | ||
103 | */ | ||
104 | uint8_t cc = GET_FLAG_CSRCC ( retu ); | ||
105 | int total = 12 /* Minimum header len */ + ( cc * 4 ); | ||
106 | |||
107 | if ( length < total ) { | ||
108 | /* Deallocate */ | ||
109 | LOGGER_WARNING("Length invalid!"); | ||
110 | free(retu); | ||
111 | return NULL; | ||
112 | } | ||
113 | |||
114 | memset(retu->csrc, 0, 16 * sizeof (uint32_t)); | ||
115 | |||
116 | retu->marker_payloadt = *it; | ||
117 | ++it; | ||
118 | retu->length = total; | ||
119 | |||
120 | 34 | ||
121 | memcpy(&retu->timestamp, it, sizeof(retu->timestamp)); | ||
122 | retu->timestamp = ntohl(retu->timestamp); | ||
123 | it += 4; | ||
124 | memcpy(&retu->ssrc, it, sizeof(retu->ssrc)); | ||
125 | retu->ssrc = ntohl(retu->ssrc); | ||
126 | 35 | ||
127 | uint8_t x; | 36 | int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); |
128 | 37 | ||
129 | for ( x = 0; x < cc; x++ ) { | ||
130 | it += 4; | ||
131 | memcpy(&retu->csrc[x], it, sizeof(retu->csrc[x])); | ||
132 | retu->csrc[x] = ntohl(retu->csrc[x]); | ||
133 | } | ||
134 | 38 | ||
135 | return retu; | 39 | RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friendnumber, |
136 | } | 40 | BWControler *bwc, void *cs, |
137 | 41 | int (*mcb) (void *, struct RTPMessage *)) | |
138 | /** | ||
139 | * Extracts external header from payload. Must be called AFTER extract_header()! | ||
140 | */ | ||
141 | RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length ) | ||
142 | { | 42 | { |
143 | const uint8_t *it = payload; | 43 | assert(mcb); |
44 | assert(cs); | ||
45 | assert(m); | ||
144 | 46 | ||
145 | RTPExtHeader *retu = calloc(1, sizeof (RTPExtHeader)); | 47 | RTPSession *retu = calloc(1, sizeof(RTPSession)); |
146 | 48 | ||
147 | if ( !retu ) { | 49 | if (!retu) { |
148 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 50 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); |
149 | return NULL; | 51 | return NULL; |
150 | } | 52 | } |
151 | 53 | ||
152 | uint16_t ext_length; | 54 | retu->ssrc = random_int(); |
153 | memcpy(&ext_length, it, sizeof(ext_length)); | 55 | retu->payload_type = payload_type; |
154 | ext_length = ntohs(ext_length); | ||
155 | it += 2; | ||
156 | 56 | ||
57 | retu->m = m; | ||
58 | retu->friend_number = friendnumber; | ||
157 | 59 | ||
158 | if ( length < ( ext_length * sizeof(uint32_t) ) ) { | 60 | /* Also set payload type as prefix */ |
159 | LOGGER_WARNING("Length invalid!"); | ||
160 | free(retu); | ||
161 | return NULL; | ||
162 | } | ||
163 | 61 | ||
164 | retu->length = ext_length; | 62 | retu->bwc = bwc; |
165 | memcpy(&retu->type, it, sizeof(retu->type)); | 63 | retu->cs = cs; |
166 | retu->type = ntohs(retu->type); | 64 | retu->mcb = mcb; |
167 | it += 2; | ||
168 | 65 | ||
169 | if ( !(retu->table = calloc(ext_length, sizeof (uint32_t))) ) { | 66 | if (-1 == rtp_allow_receiving(retu)) { |
170 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 67 | LOGGER_WARNING("Failed to start rtp receiving mode"); |
171 | free(retu); | 68 | free(retu); |
172 | return NULL; | 69 | return NULL; |
173 | } | 70 | } |
174 | 71 | ||
175 | uint16_t x; | ||
176 | |||
177 | for ( x = 0; x < ext_length; x++ ) { | ||
178 | it += 4; | ||
179 | memcpy(&(retu->table[x]), it, sizeof(retu->table[x])); | ||
180 | retu->table[x] = ntohl(retu->table[x]); | ||
181 | } | ||
182 | |||
183 | return retu; | 72 | return retu; |
184 | } | 73 | } |
185 | 74 | void rtp_kill (RTPSession *session) | |
186 | /** | ||
187 | * Adds header to payload. Make sure _payload_ has enough space. | ||
188 | */ | ||
189 | uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) | ||
190 | { | 75 | { |
191 | uint8_t cc = GET_FLAG_CSRCC ( header ); | 76 | if (!session) |
192 | uint8_t *it = payload; | 77 | return; |
193 | uint16_t sequnum; | ||
194 | uint32_t timestamp; | ||
195 | uint32_t ssrc; | ||
196 | uint32_t csrc; | ||
197 | |||
198 | |||
199 | /* Add sequence number first */ | ||
200 | sequnum = htons(header->sequnum); | ||
201 | memcpy(it, &sequnum, sizeof(sequnum)); | ||
202 | it += 2; | ||
203 | |||
204 | *it = header->flags; | ||
205 | ++it; | ||
206 | *it = header->marker_payloadt; | ||
207 | ++it; | ||
208 | |||
209 | 78 | ||
210 | timestamp = htonl(header->timestamp); | 79 | LOGGER_DEBUG("Terminated RTP session: %p", session); |
211 | memcpy(it, ×tamp, sizeof(timestamp)); | ||
212 | it += 4; | ||
213 | ssrc = htonl(header->ssrc); | ||
214 | memcpy(it, &ssrc, sizeof(ssrc)); | ||
215 | 80 | ||
216 | uint8_t x; | 81 | rtp_stop_receiving (session); |
82 | free (session); | ||
83 | } | ||
84 | int rtp_allow_receiving(RTPSession *session) | ||
85 | { | ||
86 | if (session == NULL) | ||
87 | return -1; | ||
217 | 88 | ||
218 | for ( x = 0; x < cc; x++ ) { | 89 | if (m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, |
219 | it += 4; | 90 | handle_rtp_packet, session) == -1) { |
220 | csrc = htonl(header->csrc[x]); | 91 | LOGGER_WARNING("Failed to register rtp receive handler"); |
221 | memcpy(it, &csrc, sizeof(csrc)); | 92 | return -1; |
222 | } | 93 | } |
223 | 94 | ||
224 | return it + 4; | 95 | LOGGER_DEBUG("Started receiving on session: %p", session); |
96 | return 0; | ||
225 | } | 97 | } |
226 | 98 | int rtp_stop_receiving(RTPSession *session) | |
227 | /** | ||
228 | * Adds extension header to payload. Make sure _payload_ has enough space. | ||
229 | */ | ||
230 | uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) | ||
231 | { | 99 | { |
232 | uint8_t *it = payload; | 100 | if (session == NULL) |
233 | uint16_t length; | 101 | return -1; |
234 | uint16_t type; | ||
235 | uint32_t entry; | ||
236 | |||
237 | length = htons(header->length); | ||
238 | memcpy(it, &length, sizeof(length)); | ||
239 | it += 2; | ||
240 | type = htons(header->type); | ||
241 | memcpy(it, &type, sizeof(type)); | ||
242 | it -= 2; /* Return to 0 position */ | ||
243 | |||
244 | if ( header->table ) { | ||
245 | |||
246 | uint16_t x; | ||
247 | |||
248 | for ( x = 0; x < header->length; x++ ) { | ||
249 | it += 4; | ||
250 | entry = htonl(header->table[x]); | ||
251 | memcpy(it, &entry, sizeof(entry)); | ||
252 | } | ||
253 | } | ||
254 | 102 | ||
255 | return it + 4; | 103 | m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, NULL, NULL); |
256 | } | ||
257 | 104 | ||
258 | /** | 105 | LOGGER_DEBUG("Stopped receiving on session: %p", session); |
259 | * Builds header from control session values. | 106 | return 0; |
260 | */ | 107 | } |
261 | RTPHeader *build_header ( RTPSession *session ) | 108 | int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length) |
262 | { | 109 | { |
263 | RTPHeader *retu = calloc ( 1, sizeof (RTPHeader) ); | 110 | if (!session) { |
264 | 111 | LOGGER_WARNING("No session!"); | |
265 | if ( !retu ) { | 112 | return -1; |
266 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
267 | return NULL; | ||
268 | } | 113 | } |
269 | 114 | ||
270 | ADD_FLAG_VERSION ( retu, session->version ); | 115 | uint8_t rdata[length + sizeof(struct RTPHeader) + 1]; |
271 | ADD_FLAG_PADDING ( retu, session->padding ); | 116 | memset(rdata, 0, sizeof(rdata)); |
272 | ADD_FLAG_EXTENSION ( retu, session->extension ); | ||
273 | ADD_FLAG_CSRCC ( retu, session->cc ); | ||
274 | ADD_SETTING_MARKER ( retu, session->marker ); | ||
275 | ADD_SETTING_PAYLOAD ( retu, session->payload_type ); | ||
276 | 117 | ||
277 | retu->sequnum = session->sequnum; | 118 | rdata[0] = session->payload_type; |
278 | retu->timestamp = current_time_monotonic(); /* milliseconds */ | ||
279 | retu->ssrc = session->ssrc; | ||
280 | 119 | ||
281 | int i; | 120 | struct RTPHeader *header = (struct RTPHeader *)(rdata + 1); |
282 | 121 | ||
283 | for ( i = 0; i < session->cc; i++ ) | 122 | header->ve = 2; |
284 | retu->csrc[i] = session->csrc[i]; | 123 | header->pe = 0; |
124 | header->xe = 0; | ||
125 | header->cc = 0; | ||
285 | 126 | ||
286 | retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); | 127 | header->ma = 0; |
128 | header->pt = session->payload_type % 128; | ||
287 | 129 | ||
288 | return retu; | 130 | header->sequnum = htons(session->sequnum); |
289 | } | 131 | header->timestamp = htonl(current_time_monotonic()); |
290 | 132 | header->ssrc = htonl(session->ssrc); | |
291 | |||
292 | /** | ||
293 | * Parses data into RTPMessage struct. Stores headers separately from the payload data | ||
294 | * and so the length variable is set accordingly. | ||
295 | */ | ||
296 | RTPMessage *msg_parse ( const uint8_t *data, int length ) | ||
297 | { | ||
298 | RTPMessage *retu = calloc(1, sizeof (RTPMessage)); | ||
299 | |||
300 | retu->header = extract_header ( data, length ); /* It allocates memory and all */ | ||
301 | 133 | ||
302 | if ( !retu->header ) { | 134 | header->cpart = 0; |
303 | LOGGER_WARNING("Header failed to extract!"); | 135 | header->tlen = htons(length); |
304 | free(retu); | ||
305 | return NULL; | ||
306 | } | ||
307 | 136 | ||
308 | uint16_t from_pos = retu->header->length; | 137 | if (MAX_CRYPTO_DATA_SIZE > length + sizeof(struct RTPHeader) + 1) { |
309 | retu->length = length - from_pos; | ||
310 | 138 | ||
139 | /** | ||
140 | * The lenght is lesser than the maximum allowed lenght (including header) | ||
141 | * Send the packet in single piece. | ||
142 | */ | ||
311 | 143 | ||
144 | memcpy(rdata + 1 + sizeof(struct RTPHeader), data, length); | ||
312 | 145 | ||
313 | if ( GET_FLAG_EXTENSION ( retu->header ) ) { | 146 | if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata, sizeof(rdata))) |
314 | retu->ext_header = extract_ext_header ( data + from_pos, length ); | 147 | LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", sizeof(rdata), strerror(errno)); |
315 | |||
316 | if ( retu->ext_header ) { | ||
317 | retu->length -= ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | ||
318 | from_pos += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | ||
319 | } else { /* Error */ | ||
320 | LOGGER_WARNING("Ext Header failed to extract!"); | ||
321 | rtp_free_msg(NULL, retu); | ||
322 | return NULL; | ||
323 | } | ||
324 | } else { | 148 | } else { |
325 | retu->ext_header = NULL; | ||
326 | } | ||
327 | 149 | ||
328 | if ( length - from_pos <= MAX_RTP_SIZE ) | 150 | /** |
329 | memcpy ( retu->data, data + from_pos, length - from_pos ); | 151 | * The lenght is greater than the maximum allowed lenght (including header) |
330 | else { | 152 | * Send the packet in multiple pieces. |
331 | LOGGER_WARNING("Invalid length!"); | 153 | */ |
332 | rtp_free_msg(NULL, retu); | ||
333 | return NULL; | ||
334 | } | ||
335 | 154 | ||
336 | retu->next = NULL; | 155 | uint16_t sent = 0; |
156 | uint16_t piece = MAX_CRYPTO_DATA_SIZE - (sizeof(struct RTPHeader) + 1); | ||
337 | 157 | ||
338 | return retu; | 158 | while ((length - sent) + sizeof(struct RTPHeader) + 1 > MAX_CRYPTO_DATA_SIZE) { |
339 | } | 159 | memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece); |
340 | 160 | ||
341 | /** | 161 | if (-1 == send_custom_lossy_packet(session->m, session->friend_number, |
342 | * Callback for networking core. | 162 | rdata, piece + sizeof(struct RTPHeader) + 1)) |
343 | */ | 163 | LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", |
344 | int rtp_handle_packet ( Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object ) | 164 | piece + sizeof(struct RTPHeader) + 1, strerror(errno)); |
345 | { | ||
346 | RTPSession *session = object; | ||
347 | RTPMessage *msg; | ||
348 | 165 | ||
349 | if ( !session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */ | 166 | sent += piece; |
350 | LOGGER_WARNING("No session or invalid length of received buffer!"); | 167 | header->cpart = htons(sent); |
351 | return -1; | 168 | } |
352 | } | ||
353 | 169 | ||
354 | msg = msg_parse ( data + 1, length - 1 ); | 170 | /* Send remaining */ |
171 | piece = length - sent; | ||
355 | 172 | ||
356 | if ( !msg ) { | 173 | if (piece) { |
357 | LOGGER_WARNING("Could not parse message!"); | 174 | memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece); |
358 | return -1; | ||
359 | } | ||
360 | 175 | ||
361 | /* Check if message came in late */ | 176 | if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata, |
362 | if ( check_late_message(session, msg) < 0 ) { /* Not late */ | 177 | piece + sizeof(struct RTPHeader) + 1)) |
363 | session->rsequnum = msg->header->sequnum; | 178 | LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", |
364 | session->timestamp = msg->header->timestamp; | 179 | piece + sizeof(struct RTPHeader) + 1, strerror(errno)); |
180 | } | ||
365 | } | 181 | } |
366 | 182 | ||
367 | queue_message(session, msg); | 183 | session->sequnum ++; |
368 | |||
369 | return 0; | 184 | return 0; |
370 | } | 185 | } |
371 | 186 | ||
372 | /** | ||
373 | * Allocate message and store data there | ||
374 | */ | ||
375 | RTPMessage *rtp_new_message ( RTPSession *session, const uint8_t *data, uint32_t length ) | ||
376 | { | ||
377 | if ( !session ) { | ||
378 | LOGGER_WARNING("No session!"); | ||
379 | return NULL; | ||
380 | } | ||
381 | |||
382 | uint8_t *from_pos; | ||
383 | RTPMessage *retu = calloc(1, sizeof (RTPMessage)); | ||
384 | |||
385 | if ( !retu ) { | ||
386 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
387 | return NULL; | ||
388 | } | ||
389 | 187 | ||
390 | /* Sets header values and copies the extension header in retu */ | 188 | bool chloss (const RTPSession *session, const struct RTPHeader *header) |
391 | retu->header = build_header ( session ); /* It allocates memory and all */ | 189 | { |
392 | retu->ext_header = session->ext_header; | 190 | if (ntohl(header->timestamp) < session->rtimestamp) { |
191 | uint16_t hosq, lost = 0; | ||
393 | 192 | ||
193 | hosq = ntohs(header->sequnum); | ||
394 | 194 | ||
395 | uint32_t total_length = length + retu->header->length + 1; | 195 | lost = (hosq > session->rsequnum) ? |
196 | (session->rsequnum + 65535) - hosq : | ||
197 | session->rsequnum - hosq; | ||
396 | 198 | ||
397 | retu->data[0] = session->prefix; | 199 | puts ("Lost packet"); |
398 | 200 | ||
399 | if ( retu->ext_header ) { | 201 | while (lost --) |
400 | total_length += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | 202 | bwc_add_lost(session->bwc , 0); |
401 | 203 | ||
402 | from_pos = add_header ( retu->header, retu->data + 1 ); | 204 | return true; |
403 | from_pos = add_ext_header ( retu->ext_header, from_pos + 1 ); | ||
404 | } else { | ||
405 | from_pos = add_header ( retu->header, retu->data + 1 ); | ||
406 | } | 205 | } |
407 | 206 | ||
408 | /* | 207 | return false; |
409 | * Parses the extension header into the message | ||
410 | * Of course if any | ||
411 | */ | ||
412 | |||
413 | /* Appends data on to retu->data */ | ||
414 | memcpy ( from_pos, data, length ); | ||
415 | |||
416 | retu->length = total_length; | ||
417 | |||
418 | retu->next = NULL; | ||
419 | |||
420 | return retu; | ||
421 | } | 208 | } |
422 | 209 | struct RTPMessage *new_message (size_t allocate_len, const uint8_t *data, uint16_t data_length) | |
423 | |||
424 | |||
425 | int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ) | ||
426 | { | 210 | { |
427 | RTPMessage *msg = rtp_new_message (session, data, length); | 211 | assert(allocate_len >= data_length); |
428 | 212 | ||
429 | if ( !msg ) return -1; | 213 | struct RTPMessage *msg = calloc(sizeof(struct RTPMessage) + (allocate_len - sizeof(struct RTPHeader)), 1); |
430 | 214 | ||
431 | int ret = send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length); | 215 | msg->len = data_length - sizeof(struct RTPHeader); |
216 | memcpy(&msg->header, data, data_length); | ||
432 | 217 | ||
433 | if ( 0 != ret) { | 218 | msg->header.sequnum = ntohs(msg->header.sequnum); |
434 | LOGGER_WARNING("Failed to send full packet (len: %d)! error: %i", length, ret); | 219 | msg->header.timestamp = ntohl(msg->header.timestamp); |
435 | rtp_free_msg ( session, msg ); | 220 | msg->header.ssrc = ntohl(msg->header.ssrc); |
436 | return rtp_ErrorSending; | ||
437 | } | ||
438 | 221 | ||
439 | /* Set sequ number */ | 222 | msg->header.cpart = ntohs(msg->header.cpart); |
440 | session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1; | 223 | msg->header.tlen = ntohs(msg->header.tlen); |
441 | rtp_free_msg ( session, msg ); | ||
442 | 224 | ||
443 | return 0; | 225 | return msg; |
444 | } | 226 | } |
445 | 227 | int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) | |
446 | void rtp_free_msg ( RTPSession *session, RTPMessage *msg ) | ||
447 | { | 228 | { |
448 | if ( !session ) { | 229 | (void) m; |
449 | if ( msg->ext_header ) { | 230 | (void) friendnumber; |
450 | free ( msg->ext_header->table ); | ||
451 | free ( msg->ext_header ); | ||
452 | } | ||
453 | } else { | ||
454 | if ( msg->ext_header && session->ext_header != msg->ext_header ) { | ||
455 | free ( msg->ext_header->table ); | ||
456 | free ( msg->ext_header ); | ||
457 | } | ||
458 | } | ||
459 | 231 | ||
460 | free ( msg->header ); | 232 | RTPSession *session = object; |
461 | free ( msg ); | ||
462 | } | ||
463 | |||
464 | RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ) | ||
465 | { | ||
466 | RTPSession *retu = calloc(1, sizeof(RTPSession)); | ||
467 | 233 | ||
468 | if ( !retu ) { | 234 | data ++; |
469 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 235 | length--; |
470 | return NULL; | ||
471 | } | ||
472 | 236 | ||
473 | if ( -1 == m_callback_rtp_packet(messenger, friend_num, payload_type, rtp_handle_packet, retu)) { | 237 | if (!session || length < sizeof (struct RTPHeader)) { |
474 | LOGGER_ERROR("Error setting custom register handler for rtp session"); | 238 | LOGGER_WARNING("No session or invalid length of received buffer!"); |
475 | free(retu); | 239 | return -1; |
476 | return NULL; | ||
477 | } | 240 | } |
478 | 241 | ||
479 | LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num); | 242 | const struct RTPHeader *header = (struct RTPHeader *) data; |
480 | |||
481 | retu->version = RTP_VERSION; /* It's always 2 */ | ||
482 | retu->padding = 0; /* If some additional data is needed about the packet */ | ||
483 | retu->extension = 0; /* If extension to header is needed */ | ||
484 | retu->cc = 1; /* Amount of contributors */ | ||
485 | retu->csrc = NULL; /* Container */ | ||
486 | retu->ssrc = random_int(); | ||
487 | retu->marker = 0; | ||
488 | retu->payload_type = payload_type % 128; | ||
489 | |||
490 | retu->dest = friend_num; | ||
491 | |||
492 | retu->rsequnum = retu->sequnum = 0; | ||
493 | |||
494 | retu->ext_header = NULL; /* When needed allocate */ | ||
495 | 243 | ||
244 | if (header->pt != session->payload_type % 128) { | ||
245 | LOGGER_WARNING("Invalid payload type with the session"); | ||
246 | return -1; | ||
247 | } | ||
496 | 248 | ||
497 | if ( !(retu->csrc = calloc(1, sizeof (uint32_t))) ) { | 249 | if (ntohs(header->cpart) >= ntohs(header->tlen)) { |
498 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 250 | /* Never allow this case to happen */ |
499 | free(retu); | 251 | return -1; |
500 | return NULL; | ||
501 | } | 252 | } |
502 | 253 | ||
503 | retu->csrc[0] = retu->ssrc; /* Set my ssrc to the list receive */ | 254 | bwc_feed_avg(session->bwc, length); |
504 | 255 | ||
505 | /* Also set payload type as prefix */ | 256 | if (ntohs(header->tlen) == length - sizeof (struct RTPHeader)) { |
506 | retu->prefix = payload_type; | 257 | /* The message is sent in single part */ |
507 | 258 | ||
508 | /* | 259 | /* Only allow messages which have arrived in order; |
509 | * | 260 | * drop late messages |
510 | */ | 261 | */ |
511 | return retu; | 262 | if (chloss(session, header)) { |
512 | } | 263 | return 0; |
264 | } else { | ||
265 | /* Message is not late; pick up the latest parameters */ | ||
266 | session->rsequnum = ntohs(header->sequnum); | ||
267 | session->rtimestamp = ntohl(header->timestamp); | ||
268 | } | ||
513 | 269 | ||
514 | void rtp_kill ( RTPSession *session, Messenger *messenger ) | 270 | bwc_add_recv(session->bwc, length); |
515 | { | ||
516 | if ( !session ) return; | ||
517 | 271 | ||
518 | m_callback_rtp_packet(messenger, session->dest, session->prefix, NULL, NULL); | 272 | /* Invoke processing of active multiparted message */ |
273 | if (session->mp) { | ||
274 | if (session->mcb) | ||
275 | session->mcb (session->cs, session->mp); | ||
276 | else | ||
277 | free(session->mp); | ||
519 | 278 | ||
520 | free ( session->ext_header ); | 279 | session->mp = NULL; |
521 | free ( session->csrc ); | 280 | } |
522 | 281 | ||
523 | LOGGER_DEBUG("Terminated RTP session: %p", session); | 282 | /* The message came in the allowed time; |
283 | * process it only if handler for the session is present. | ||
284 | */ | ||
524 | 285 | ||
525 | /* And finally free session */ | 286 | if (!session->mcb) |
526 | free ( session ); | 287 | return 0; |
527 | 288 | ||
289 | return session->mcb (session->cs, new_message(length, data, length)); | ||
290 | } else { | ||
291 | /* The message is sent in multiple parts */ | ||
292 | |||
293 | if (session->mp) { | ||
294 | /* There are 2 possible situations in this case: | ||
295 | * 1) being that we got the part of already processing message. | ||
296 | * 2) being that we got the part of a new/old message. | ||
297 | * | ||
298 | * We handle them differently as we only allow a single multiparted | ||
299 | * processing message | ||
300 | */ | ||
301 | |||
302 | if (session->mp->header.sequnum == ntohs(header->sequnum) && | ||
303 | session->mp->header.timestamp == ntohl(header->timestamp)) { | ||
304 | /* First case */ | ||
305 | |||
306 | /* Make sure we have enough allocated memory */ | ||
307 | if (session->mp->header.tlen - session->mp->len < length - sizeof(struct RTPHeader) || | ||
308 | session->mp->header.tlen <= ntohs(header->cpart)) { | ||
309 | /* There happened to be some corruption on the stream; | ||
310 | * continue wihtout this part | ||
311 | */ | ||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | memcpy(session->mp->data + ntohs(header->cpart), data + sizeof(struct RTPHeader), | ||
316 | length - sizeof(struct RTPHeader)); | ||
317 | |||
318 | session->mp->len += length - sizeof(struct RTPHeader); | ||
319 | |||
320 | bwc_add_recv(session->bwc, length); | ||
321 | |||
322 | if (session->mp->len == session->mp->header.tlen) { | ||
323 | /* Received a full message; now push it for the further | ||
324 | * processing. | ||
325 | */ | ||
326 | if (session->mcb) | ||
327 | session->mcb (session->cs, session->mp); | ||
328 | else | ||
329 | free(session->mp); | ||
330 | |||
331 | session->mp = NULL; | ||
332 | } | ||
333 | } else { | ||
334 | /* Second case */ | ||
335 | |||
336 | if (session->mp->header.timestamp > ntohl(header->timestamp)) | ||
337 | /* The received message part is from the old message; | ||
338 | * discard it. | ||
339 | */ | ||
340 | return 0; | ||
341 | |||
342 | /* Measure missing parts of the old message */ | ||
343 | bwc_add_lost(session->bwc, | ||
344 | (session->mp->header.tlen - session->mp->len) + | ||
345 | |||
346 | /* Must account sizes of rtp headers too */ | ||
347 | ((session->mp->header.tlen - session->mp->len) / | ||
348 | MAX_CRYPTO_DATA_SIZE) * sizeof(struct RTPHeader) ); | ||
349 | |||
350 | /* Push the previous message for processing */ | ||
351 | if (session->mcb) | ||
352 | session->mcb (session->cs, session->mp); | ||
353 | else | ||
354 | free(session->mp); | ||
355 | |||
356 | session->mp = NULL; | ||
357 | goto NEW_MULTIPARTED; | ||
358 | } | ||
359 | } else { | ||
360 | /* In this case threat the message as if it was received in order | ||
361 | */ | ||
362 | |||
363 | /* This is also a point for new multiparted messages */ | ||
364 | NEW_MULTIPARTED: | ||
365 | |||
366 | /* Only allow messages which have arrived in order; | ||
367 | * drop late messages | ||
368 | */ | ||
369 | if (chloss(session, header)) { | ||
370 | return 0; | ||
371 | } else { | ||
372 | /* Message is not late; pick up the latest parameters */ | ||
373 | session->rsequnum = ntohs(header->sequnum); | ||
374 | session->rtimestamp = ntohl(header->timestamp); | ||
375 | } | ||
376 | |||
377 | bwc_add_recv(session->bwc, length); | ||
378 | |||
379 | /* Again, only store message if handler is present | ||
380 | */ | ||
381 | if (session->mcb) { | ||
382 | session->mp = new_message(ntohs(header->tlen) + sizeof(struct RTPHeader), data, length); | ||
383 | |||
384 | /* Reposition data if necessary */ | ||
385 | if (ntohs(header->cpart)); | ||
386 | |||
387 | memmove(session->mp->data + ntohs(header->cpart), session->mp->data, session->mp->len); | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | return 0; | ||
528 | } | 393 | } |
diff --git a/toxav/rtp.h b/toxav/rtp.h index c98840ac..0393ac27 100644 --- a/toxav/rtp.h +++ b/toxav/rtp.h | |||
@@ -1,6 +1,6 @@ | |||
1 | /** rtp.h | 1 | /** rtp.h |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
@@ -19,109 +19,91 @@ | |||
19 | * | 19 | * |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #ifndef __TOXRTP | 22 | #ifndef RTP_H |
23 | #define __TOXRTP | 23 | #define RTP_H |
24 | |||
25 | #define RTP_VERSION 2 | ||
26 | #include <inttypes.h> | ||
27 | // #include <pthread.h> | ||
28 | 24 | ||
25 | #include "bwcontroler.h" | ||
29 | #include "../toxcore/Messenger.h" | 26 | #include "../toxcore/Messenger.h" |
30 | 27 | #include "stdbool.h" | |
31 | #define MAX_SEQU_NUM 65535 | ||
32 | #define MAX_RTP_SIZE 65535 | ||
33 | |||
34 | typedef enum { | ||
35 | rtp_ErrorSending = -40 | ||
36 | } RTPError; | ||
37 | /** | ||
38 | * Standard rtp header | ||
39 | */ | ||
40 | typedef struct _RTPHeader { | ||
41 | uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */ | ||
42 | uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */ | ||
43 | uint16_t sequnum; /* Sequence Number */ | ||
44 | uint32_t timestamp; /* Timestamp */ | ||
45 | uint32_t ssrc; /* SSRC */ | ||
46 | uint32_t csrc[16]; /* CSRC's table */ | ||
47 | uint32_t length; /* Length of the header in payload string. */ | ||
48 | |||
49 | } RTPHeader; | ||
50 | 28 | ||
51 | /** | 29 | /** |
52 | * Standard rtp extension header. | 30 | * Payload type identifier. Also used as rtp callback prefix. |
53 | */ | 31 | */ |
54 | typedef struct _RTPExtHeader { | 32 | enum { |
55 | uint16_t type; /* Extension profile */ | 33 | rtp_TypeAudio = 192, |
56 | uint16_t length; /* Number of extensions */ | 34 | rtp_TypeVideo, |
57 | uint32_t *table; /* Extension's table */ | 35 | }; |
58 | 36 | ||
59 | } RTPExtHeader; | 37 | struct RTPHeader { |
60 | 38 | /* Standard RTP header */ | |
61 | /** | 39 | #ifndef WORDS_BIGENDIAN |
62 | * Standard rtp message. | 40 | uint16_t cc: 4; /* Contributing sources count */ |
63 | */ | 41 | uint16_t xe: 1; /* Extra header */ |
64 | typedef struct _RTPMessage { | 42 | uint16_t pe: 1; /* Padding */ |
65 | RTPHeader *header; | 43 | uint16_t ve: 2; /* Version */ |
66 | RTPExtHeader *ext_header; | 44 | |
67 | 45 | uint16_t pt: 7; /* Payload type */ | |
68 | uint8_t data[MAX_RTP_SIZE]; | 46 | uint16_t ma: 1; /* Marker */ |
69 | uint32_t length; | 47 | #else |
70 | 48 | uint16_t ve: 2; /* Version */ | |
71 | struct _RTPMessage *next; | 49 | uint16_t pe: 1; /* Padding */ |
72 | } RTPMessage; | 50 | uint16_t xe: 1; /* Extra header */ |
51 | uint16_t cc: 4; /* Contributing sources count */ | ||
52 | |||
53 | uint16_t ma: 1; /* Marker */ | ||
54 | uint16_t pt: 7; /* Payload type */ | ||
55 | #endif | ||
56 | |||
57 | uint16_t sequnum; | ||
58 | uint32_t timestamp; | ||
59 | uint32_t ssrc; | ||
60 | uint32_t csrc[16]; | ||
61 | |||
62 | /* Non-standard TOX-specific fields */ | ||
63 | uint16_t cpart;/* Data offset of the current part */ | ||
64 | uint16_t tlen; /* Total message lenght */ | ||
65 | } __attribute__ ((packed)); | ||
66 | |||
67 | /* Check alignment */ | ||
68 | typedef char __fail_if_misaligned [ sizeof(struct RTPHeader) == 80 ? 1 : -1 ]; | ||
69 | |||
70 | struct RTPMessage { | ||
71 | uint16_t len; | ||
72 | |||
73 | struct RTPHeader header; | ||
74 | uint8_t data[]; | ||
75 | } __attribute__ ((packed)); | ||
76 | |||
77 | /* Check alignment */ | ||
78 | typedef char __fail_if_misaligned [ sizeof(struct RTPMessage) == 82 ? 1 : -1 ]; | ||
73 | 79 | ||
74 | /** | 80 | /** |
75 | * RTP control session. | 81 | * RTP control session. |
76 | */ | 82 | */ |
77 | typedef struct _RTPSession { | 83 | typedef struct { |
78 | uint8_t version; | 84 | uint8_t payload_type; |
79 | uint8_t padding; | 85 | uint16_t sequnum; /* Sending sequence number */ |
80 | uint8_t extension; | 86 | uint16_t rsequnum; /* Receiving sequence number */ |
81 | uint8_t cc; | 87 | uint32_t rtimestamp; |
82 | uint8_t marker; | 88 | uint32_t ssrc; |
83 | uint8_t payload_type; | ||
84 | uint16_t sequnum; /* Set when sending */ | ||
85 | uint16_t rsequnum; /* Check when recving msg */ | ||
86 | uint32_t timestamp; | ||
87 | uint32_t ssrc; | ||
88 | uint32_t *csrc; | ||
89 | |||
90 | /* If some additional data must be sent via message | ||
91 | * apply it here. Only by allocating this member you will be | ||
92 | * automatically placing it within a message. | ||
93 | */ | ||
94 | RTPExtHeader *ext_header; | ||
95 | |||
96 | /* Msg prefix for core to know when recving */ | ||
97 | uint8_t prefix; | ||
98 | |||
99 | int dest; | ||
100 | |||
101 | struct _CSSession *cs; | ||
102 | 89 | ||
103 | } RTPSession; | 90 | struct RTPMessage *mp; /* Expected parted message */ |
104 | |||
105 | /** | ||
106 | * Must be called before calling any other rtp function. | ||
107 | */ | ||
108 | RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ); | ||
109 | |||
110 | /** | ||
111 | * Terminate the session. | ||
112 | */ | ||
113 | void rtp_kill ( RTPSession *session, Messenger *messenger ); | ||
114 | 91 | ||
115 | /** | 92 | Messenger *m; |
116 | * Sends msg to _RTPSession::dest | 93 | uint32_t friend_number; |
117 | */ | ||
118 | int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ); | ||
119 | 94 | ||
120 | /** | 95 | BWControler *bwc; |
121 | * Dealloc msg. | 96 | void *cs; |
122 | */ | 97 | int (*mcb) (void *, struct RTPMessage *msg); |
123 | void rtp_free_msg ( RTPSession *session, RTPMessage *msg ); | 98 | } RTPSession; |
124 | 99 | ||
125 | 100 | ||
101 | RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friend_num, | ||
102 | BWControler *bwc, void *cs, | ||
103 | int (*mcb) (void *, struct RTPMessage *)); | ||
104 | void rtp_kill (RTPSession *session); | ||
105 | int rtp_allow_receiving (RTPSession *session); | ||
106 | int rtp_stop_receiving (RTPSession *session); | ||
107 | int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length); | ||
126 | 108 | ||
127 | #endif /* __TOXRTP */ | 109 | #endif /* RTP_H */ |
diff --git a/toxav/toxav.c b/toxav/toxav.c index a51ec5e3..9eda3412 100644 --- a/toxav/toxav.c +++ b/toxav/toxav.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /** toxav.c | 1 | /** toxav.c |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
@@ -23,15 +23,10 @@ | |||
23 | #include "config.h" | 23 | #include "config.h" |
24 | #endif /* HAVE_CONFIG_H */ | 24 | #endif /* HAVE_CONFIG_H */ |
25 | 25 | ||
26 | #define TOX_DEFINED | ||
27 | typedef struct Messenger Tox; | ||
28 | |||
29 | #define _GNU_SOURCE /* implicit declaration warning */ | ||
30 | |||
31 | #include "codec.h" | ||
32 | #include "msi.h" | 26 | #include "msi.h" |
33 | #include "group.h" | 27 | #include "rtp.h" |
34 | 28 | ||
29 | #include "../toxcore/Messenger.h" | ||
35 | #include "../toxcore/logger.h" | 30 | #include "../toxcore/logger.h" |
36 | #include "../toxcore/util.h" | 31 | #include "../toxcore/util.h" |
37 | 32 | ||
@@ -39,659 +34,1235 @@ typedef struct Messenger Tox; | |||
39 | #include <stdlib.h> | 34 | #include <stdlib.h> |
40 | #include <string.h> | 35 | #include <string.h> |
41 | 36 | ||
42 | /* Assume 24 fps*/ | ||
43 | #define MAX_ENCODE_TIME_US ((1000 / 24) * 1000) | 37 | #define MAX_ENCODE_TIME_US ((1000 / 24) * 1000) |
44 | 38 | ||
45 | /* true if invalid call index */ | 39 | typedef struct ToxAVCall_s { |
46 | #define CALL_INVALID_INDEX(idx, max) (idx < 0 || idx >= max) | 40 | ToxAV *av; |
47 | 41 | ||
48 | const ToxAvCSettings av_DefaultSettings = { | 42 | pthread_mutex_t mutex_audio[1]; |
49 | av_TypeAudio, | 43 | PAIR(RTPSession *, ACSession *) audio; |
50 | 44 | ||
51 | 500, | 45 | pthread_mutex_t mutex_video[1]; |
52 | 1280, | 46 | PAIR(RTPSession *, VCSession *) video; |
53 | 720, | ||
54 | 47 | ||
55 | 32000, | 48 | BWControler *bwc; |
56 | 20, | 49 | |
57 | 48000, | 50 | bool active; |
58 | 1 | 51 | MSICall *msi_call; |
59 | }; | 52 | uint32_t friend_number; |
53 | |||
54 | uint32_t audio_bit_rate; /* Sending audio bit rate */ | ||
55 | uint32_t video_bit_rate; /* Sending video bit rate */ | ||
60 | 56 | ||
61 | static const uint32_t jbuf_capacity = 6; | 57 | /** Required for monitoring changes in states */ |
62 | static const uint8_t audio_index = 0, video_index = 1; | 58 | uint8_t previous_self_capabilities; |
63 | 59 | ||
64 | typedef struct _ToxAvCall { | ||
65 | pthread_mutex_t mutex[1]; | 60 | pthread_mutex_t mutex[1]; |
66 | pthread_mutex_t mutex_encoding_audio[1]; | 61 | |
67 | pthread_mutex_t mutex_encoding_video[1]; | 62 | struct ToxAVCall_s *prev; |
68 | pthread_mutex_t mutex_do[1]; | 63 | struct ToxAVCall_s *next; |
69 | RTPSession *crtps[2]; /** Audio is first and video is second */ | 64 | } ToxAVCall; |
70 | CSSession *cs; | 65 | |
71 | _Bool active; | 66 | struct ToxAV { |
72 | } ToxAvCall; | 67 | Messenger *m; |
73 | 68 | MSISession *msi; | |
74 | struct _ToxAv { | 69 | |
75 | Messenger *messenger; | 70 | /* Two-way storage: first is array of calls and second is list of calls with head and tail */ |
76 | MSISession *msi_session; /** Main msi session */ | 71 | ToxAVCall **calls; |
77 | ToxAvCall *calls; /** Per-call params */ | 72 | uint32_t calls_tail; |
78 | uint32_t max_calls; | 73 | uint32_t calls_head; |
79 | 74 | pthread_mutex_t mutex[1]; | |
80 | PAIR(ToxAvAudioCallback, void *) acb; | 75 | |
81 | PAIR(ToxAvVideoCallback, void *) vcb; | 76 | PAIR(toxav_call_cb *, void *) ccb; /* Call callback */ |
82 | 77 | PAIR(toxav_call_state_cb *, void *) scb; /* Call state callback */ | |
83 | /* Decode time measure */ | 78 | PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ |
84 | int32_t dectmsscount; /** Measure count */ | 79 | PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ |
85 | int32_t dectmsstotal; /** Last cycle total */ | 80 | PAIR(toxav_bit_rate_status_cb *, void *) bcb; /* Bit rate control callback */ |
86 | int32_t avgdectms; /** Average decoding time in ms */ | 81 | |
82 | /** Decode time measures */ | ||
83 | int32_t dmssc; /** Measure count */ | ||
84 | int32_t dmsst; /** Last cycle total */ | ||
85 | int32_t dmssa; /** Average decoding time in ms */ | ||
86 | |||
87 | uint32_t interval; /** Calculated interval */ | ||
87 | }; | 88 | }; |
88 | 89 | ||
89 | static const MSICSettings *msicsettings_cast (const ToxAvCSettings *from) | 90 | void callback_bwc (BWControler *bwc, uint32_t friend_number, float loss, void *user_data); |
91 | |||
92 | int callback_invite(void *toxav_inst, MSICall *call); | ||
93 | int callback_start(void *toxav_inst, MSICall *call); | ||
94 | int callback_end(void *toxav_inst, MSICall *call); | ||
95 | int callback_error(void *toxav_inst, MSICall *call); | ||
96 | int callback_capabilites(void *toxav_inst, MSICall *call); | ||
97 | |||
98 | bool audio_bit_rate_invalid(uint32_t bit_rate); | ||
99 | bool video_bit_rate_invalid(uint32_t bit_rate); | ||
100 | bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state); | ||
101 | ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error); | ||
102 | ToxAVCall *call_get(ToxAV *av, uint32_t friend_number); | ||
103 | ToxAVCall *call_remove(ToxAVCall *call); | ||
104 | bool call_prepare_transmission(ToxAVCall *call); | ||
105 | void call_kill_transmission(ToxAVCall *call); | ||
106 | |||
107 | uint32_t toxav_version_major(void) | ||
90 | { | 108 | { |
91 | assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); | 109 | return 0; |
92 | return (const MSICSettings *) from; | ||
93 | } | 110 | } |
94 | 111 | uint32_t toxav_version_minor(void) | |
95 | static const ToxAvCSettings *toxavcsettings_cast (const MSICSettings *from) | ||
96 | { | 112 | { |
97 | assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); | 113 | return 0; |
98 | return (const ToxAvCSettings *) from; | 114 | } |
99 | 115 | uint32_t toxav_version_patch(void) | |
116 | { | ||
117 | return 0; | ||
100 | } | 118 | } |
119 | bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) | ||
120 | { | ||
121 | (void)major; | ||
122 | (void)minor; | ||
123 | (void)patch; | ||
101 | 124 | ||
102 | ToxAv *toxav_new( Tox *messenger, int32_t max_calls) | 125 | return 1; |
126 | } | ||
127 | ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error) | ||
103 | { | 128 | { |
104 | ToxAv *av = calloc ( sizeof(ToxAv), 1); | 129 | TOXAV_ERR_NEW rc = TOXAV_ERR_NEW_OK; |
130 | ToxAV *av = NULL; | ||
131 | |||
132 | if (tox == NULL) { | ||
133 | rc = TOXAV_ERR_NEW_NULL; | ||
134 | goto END; | ||
135 | } | ||
136 | |||
137 | if (((Messenger *)tox)->msi_packet) { | ||
138 | rc = TOXAV_ERR_NEW_MULTIPLE; | ||
139 | goto END; | ||
140 | } | ||
141 | |||
142 | av = calloc (sizeof(ToxAV), 1); | ||
105 | 143 | ||
106 | if (av == NULL) { | 144 | if (av == NULL) { |
107 | LOGGER_WARNING("Allocation failed!"); | 145 | LOGGER_WARNING("Allocation failed!"); |
108 | return NULL; | 146 | rc = TOXAV_ERR_NEW_MALLOC; |
147 | goto END; | ||
148 | } | ||
149 | |||
150 | if (create_recursive_mutex(av->mutex) != 0) { | ||
151 | LOGGER_WARNING("Mutex creation failed!"); | ||
152 | rc = TOXAV_ERR_NEW_MALLOC; | ||
153 | goto END; | ||
109 | } | 154 | } |
110 | 155 | ||
111 | av->messenger = (Messenger *)messenger; | 156 | av->m = (Messenger *)tox; |
112 | av->msi_session = msi_new(av->messenger, max_calls); | 157 | av->msi = msi_new(av->m); |
113 | av->msi_session->agent_handler = av; | ||
114 | av->calls = calloc(sizeof(ToxAvCall), max_calls); | ||
115 | av->max_calls = max_calls; | ||
116 | 158 | ||
117 | unsigned int i; | 159 | if (av->msi == NULL) { |
160 | pthread_mutex_destroy(av->mutex); | ||
161 | rc = TOXAV_ERR_NEW_MALLOC; | ||
162 | goto END; | ||
163 | } | ||
118 | 164 | ||
119 | for (i = 0; i < max_calls; ++i) { | 165 | av->interval = 200; |
120 | if (create_recursive_mutex(av->calls[i].mutex) != 0 ) { | 166 | av->msi->av = av; |
121 | LOGGER_WARNING("Failed to init call(%u) mutex!", i); | ||
122 | msi_kill(av->msi_session); | ||
123 | 167 | ||
124 | free(av->calls); | 168 | msi_register_callback(av->msi, callback_invite, msi_OnInvite); |
125 | free(av); | 169 | msi_register_callback(av->msi, callback_start, msi_OnStart); |
126 | return NULL; | 170 | msi_register_callback(av->msi, callback_end, msi_OnEnd); |
127 | } | 171 | msi_register_callback(av->msi, callback_error, msi_OnError); |
172 | msi_register_callback(av->msi, callback_error, msi_OnPeerTimeout); | ||
173 | msi_register_callback(av->msi, callback_capabilites, msi_OnCapabilities); | ||
174 | |||
175 | END: | ||
176 | |||
177 | if (error) | ||
178 | *error = rc; | ||
179 | |||
180 | if (rc != TOXAV_ERR_NEW_OK) { | ||
181 | free(av); | ||
182 | av = NULL; | ||
128 | } | 183 | } |
129 | 184 | ||
130 | return av; | 185 | return av; |
131 | } | 186 | } |
132 | 187 | void toxav_kill(ToxAV *av) | |
133 | void toxav_kill ( ToxAv *av ) | ||
134 | { | 188 | { |
135 | uint32_t i; | 189 | if (av == NULL) |
190 | return; | ||
136 | 191 | ||
137 | for (i = 0; i < av->max_calls; i ++) { | 192 | pthread_mutex_lock(av->mutex); |
138 | if ( av->calls[i].crtps[audio_index] ) | ||
139 | rtp_kill(av->calls[i].crtps[audio_index], av->msi_session->messenger_handle); | ||
140 | 193 | ||
194 | /* To avoid possible deadlocks */ | ||
195 | while (av->msi && msi_kill(av->msi) != 0) { | ||
196 | pthread_mutex_unlock(av->mutex); | ||
197 | pthread_mutex_lock(av->mutex); | ||
198 | } | ||
141 | 199 | ||
142 | if ( av->calls[i].crtps[video_index] ) | 200 | /* Msi kill will hang up all calls so just clean these calls */ |
143 | rtp_kill(av->calls[i].crtps[video_index], av->msi_session->messenger_handle); | 201 | if (av->calls) { |
144 | 202 | ToxAVCall *it = call_get(av, av->calls_head); | |
145 | if ( av->calls[i].cs ) | ||
146 | cs_kill(av->calls[i].cs); | ||
147 | 203 | ||
148 | pthread_mutex_destroy(av->calls[i].mutex); | 204 | while (it) { |
205 | call_kill_transmission(it); | ||
206 | it = call_remove(it); /* This will eventually free av->calls */ | ||
207 | } | ||
149 | } | 208 | } |
150 | 209 | ||
151 | msi_kill(av->msi_session); | 210 | pthread_mutex_unlock(av->mutex); |
211 | pthread_mutex_destroy(av->mutex); | ||
152 | 212 | ||
153 | free(av->calls); | ||
154 | free(av); | 213 | free(av); |
155 | } | 214 | } |
156 | 215 | Tox *toxav_get_tox(const ToxAV *av) | |
157 | uint32_t toxav_do_interval(ToxAv *av) | 216 | { |
217 | return (Tox *) av->m; | ||
218 | } | ||
219 | uint32_t toxav_iteration_interval(const ToxAV *av) | ||
158 | { | 220 | { |
159 | int i = 0; | 221 | /* If no call is active interval is 200 */ |
160 | uint32_t rc = 200 + av->avgdectms; /* Return 200 if no call is active */ | 222 | return av->calls ? av->interval : 200; |
223 | } | ||
224 | void toxav_iterate(ToxAV *av) | ||
225 | { | ||
226 | pthread_mutex_lock(av->mutex); | ||
161 | 227 | ||
162 | for (; i < av->max_calls; i ++) { | 228 | if (av->calls == NULL) { |
163 | pthread_mutex_lock(av->calls[i].mutex); | 229 | pthread_mutex_unlock(av->mutex); |
230 | return; | ||
231 | } | ||
164 | 232 | ||
165 | if (av->calls[i].active) { | 233 | uint64_t start = current_time_monotonic(); |
166 | /* This should work. Video payload will always come in greater intervals */ | 234 | int32_t rc = 500; |
167 | rc = MIN(av->calls[i].cs->audio_decoder_frame_duration, rc); | ||
168 | } | ||
169 | 235 | ||
170 | pthread_mutex_unlock(av->calls[i].mutex); | 236 | ToxAVCall *i = av->calls[av->calls_head]; |
171 | } | ||
172 | 237 | ||
173 | return rc < av->avgdectms ? 0 : rc - av->avgdectms; | 238 | for (; i; i = i->next) { |
174 | } | 239 | if (i->active) { |
240 | pthread_mutex_lock(i->mutex); | ||
241 | pthread_mutex_unlock(av->mutex); | ||
175 | 242 | ||
176 | void toxav_do(ToxAv *av) | 243 | ac_iterate(i->audio.second); |
177 | { | 244 | vc_iterate(i->video.second); |
178 | msi_do(av->msi_session); | ||
179 | 245 | ||
180 | uint64_t start = current_time_monotonic(); | 246 | if (i->msi_call->self_capabilities & msi_CapRAudio && |
247 | i->msi_call->peer_capabilities & msi_CapSAudio) | ||
248 | rc = MIN(i->audio.second->lp_frame_duration, rc); | ||
181 | 249 | ||
182 | uint32_t i = 0; | 250 | if (i->msi_call->self_capabilities & msi_CapRVideo && |
251 | i->msi_call->peer_capabilities & msi_CapSVideo) | ||
252 | rc = MIN(i->video.second->lcfd, (uint32_t) rc); | ||
183 | 253 | ||
184 | for (; i < av->max_calls; i ++) { | 254 | uint32_t fid = i->friend_number; |
185 | pthread_mutex_lock(av->calls[i].mutex); | ||
186 | 255 | ||
187 | if (av->calls[i].active) { | 256 | pthread_mutex_unlock(i->mutex); |
188 | pthread_mutex_lock(av->calls[i].mutex_do); | 257 | pthread_mutex_lock(av->mutex); |
189 | pthread_mutex_unlock(av->calls[i].mutex); | 258 | |
190 | cs_do(av->calls[i].cs); | 259 | /* In case this call is popped from container stop iteration */ |
191 | pthread_mutex_unlock(av->calls[i].mutex_do); | 260 | if (call_get(av, fid) != i) |
192 | } else { | 261 | break; |
193 | pthread_mutex_unlock(av->calls[i].mutex); | ||
194 | } | 262 | } |
195 | } | 263 | } |
196 | 264 | ||
197 | uint64_t end = current_time_monotonic(); | 265 | pthread_mutex_unlock(av->mutex); |
198 | 266 | ||
199 | /* TODO maybe use variable for sizes */ | 267 | av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa); |
200 | av->dectmsstotal += end - start; | 268 | av->dmsst += current_time_monotonic() - start; |
201 | 269 | ||
202 | if (++av->dectmsscount == 3) { | 270 | if (++av->dmssc == 3) { |
203 | av->avgdectms = av->dectmsstotal / 3 + 2 /* NOTE Magic Offset */; | 271 | av->dmssa = av->dmsst / 3 + 5 /* NOTE Magic Offset for precission */; |
204 | av->dectmsscount = 0; | 272 | av->dmssc = 0; |
205 | av->dectmsstotal = 0; | 273 | av->dmsst = 0; |
206 | } | 274 | } |
207 | } | 275 | } |
208 | 276 | bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, | |
209 | void toxav_register_callstate_callback ( ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata ) | 277 | TOXAV_ERR_CALL *error) |
210 | { | 278 | { |
211 | msi_register_callback(av->msi_session, (MSICallbackType)cb, (MSICallbackID) id, userdata); | 279 | TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK; |
212 | } | ||
213 | 280 | ||
214 | void toxav_register_audio_callback(ToxAv *av, ToxAvAudioCallback cb, void *userdata) | 281 | pthread_mutex_lock(av->mutex); |
215 | { | ||
216 | av->acb.first = cb; | ||
217 | av->acb.second = userdata; | ||
218 | } | ||
219 | 282 | ||
220 | void toxav_register_video_callback(ToxAv *av, ToxAvVideoCallback cb, void *userdata) | 283 | if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) |
221 | { | 284 | || (video_bit_rate && video_bit_rate_invalid(video_bit_rate))) { |
222 | av->vcb.first = cb; | 285 | rc = TOXAV_ERR_CALL_INVALID_BIT_RATE; |
223 | av->vcb.second = userdata; | 286 | goto END; |
224 | } | 287 | } |
225 | 288 | ||
226 | int toxav_call (ToxAv *av, | 289 | ToxAVCall *call = call_new(av, friend_number, error); |
227 | int32_t *call_index, | ||
228 | int user, | ||
229 | const ToxAvCSettings *csettings, | ||
230 | int ringing_seconds ) | ||
231 | { | ||
232 | return msi_invite(av->msi_session, call_index, msicsettings_cast(csettings), ringing_seconds * 1000, user); | ||
233 | } | ||
234 | 290 | ||
235 | int toxav_hangup ( ToxAv *av, int32_t call_index ) | 291 | if (call == NULL) { |
236 | { | 292 | rc = TOXAV_ERR_CALL_MALLOC; |
237 | return msi_hangup(av->msi_session, call_index); | 293 | goto END; |
238 | } | 294 | } |
239 | 295 | ||
240 | int toxav_answer ( ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ) | 296 | call->audio_bit_rate = audio_bit_rate; |
241 | { | 297 | call->video_bit_rate = video_bit_rate; |
242 | return msi_answer(av->msi_session, call_index, msicsettings_cast(csettings)); | ||
243 | } | ||
244 | 298 | ||
245 | int toxav_reject ( ToxAv *av, int32_t call_index, const char *reason ) | 299 | call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; |
246 | { | ||
247 | return msi_reject(av->msi_session, call_index, reason); | ||
248 | } | ||
249 | 300 | ||
250 | int toxav_cancel ( ToxAv *av, int32_t call_index, int peer_id, const char *reason ) | 301 | call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; |
251 | { | 302 | call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; |
252 | return msi_cancel(av->msi_session, call_index, peer_id, reason); | ||
253 | } | ||
254 | 303 | ||
255 | int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings) | 304 | if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) { |
256 | { | 305 | call_remove(call); |
257 | return msi_change_csettings(av->msi_session, call_index, msicsettings_cast(csettings)); | 306 | rc = TOXAV_ERR_CALL_SYNC; |
258 | } | 307 | goto END; |
308 | } | ||
259 | 309 | ||
260 | int toxav_stop_call ( ToxAv *av, int32_t call_index ) | 310 | call->msi_call->av_call = call; |
311 | |||
312 | END: | ||
313 | pthread_mutex_unlock(av->mutex); | ||
314 | |||
315 | if (error) | ||
316 | *error = rc; | ||
317 | |||
318 | return rc == TOXAV_ERR_CALL_OK; | ||
319 | } | ||
320 | void toxav_callback_call(ToxAV *av, toxav_call_cb *function, void *user_data) | ||
261 | { | 321 | { |
262 | return msi_stopcall(av->msi_session, call_index); | 322 | pthread_mutex_lock(av->mutex); |
323 | av->ccb.first = function; | ||
324 | av->ccb.second = user_data; | ||
325 | pthread_mutex_unlock(av->mutex); | ||
263 | } | 326 | } |
264 | 327 | bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, | |
265 | int toxav_prepare_transmission ( ToxAv *av, int32_t call_index, int support_video ) | 328 | TOXAV_ERR_ANSWER *error) |
266 | { | 329 | { |
267 | if ( !av->msi_session || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || | 330 | pthread_mutex_lock(av->mutex); |
268 | !av->msi_session->calls[call_index] || !av->msi_session->calls[call_index]->csettings_peer) { | ||
269 | LOGGER_ERROR("Error while starting RTP session: invalid call!\n"); | ||
270 | return av_ErrorNoCall; | ||
271 | } | ||
272 | |||
273 | ToxAvCall *call = &av->calls[call_index]; | ||
274 | 331 | ||
275 | pthread_mutex_lock(call->mutex); | 332 | TOXAV_ERR_ANSWER rc = TOXAV_ERR_ANSWER_OK; |
276 | 333 | ||
277 | if (call->active) { | 334 | if (m_friend_exists(av->m, friend_number) == 0) { |
278 | pthread_mutex_unlock(call->mutex); | 335 | rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND; |
279 | LOGGER_ERROR("Error while starting RTP session: call already active!\n"); | 336 | goto END; |
280 | return av_ErrorAlreadyInCallWithPeer; | ||
281 | } | 337 | } |
282 | 338 | ||
283 | if (pthread_mutex_init(call->mutex_encoding_audio, NULL) != 0 | 339 | if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) |
284 | || pthread_mutex_init(call->mutex_encoding_video, NULL) != 0 || pthread_mutex_init(call->mutex_do, NULL) != 0) { | 340 | || (video_bit_rate && video_bit_rate_invalid(video_bit_rate)) |
285 | pthread_mutex_unlock(call->mutex); | 341 | ) { |
286 | LOGGER_ERROR("Error while starting RTP session: mutex initializing failed!\n"); | 342 | rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE; |
287 | return av_ErrorUnknown; | 343 | goto END; |
288 | } | 344 | } |
289 | 345 | ||
290 | const ToxAvCSettings *c_peer = toxavcsettings_cast | 346 | ToxAVCall *call = call_get(av, friend_number); |
291 | (&av->msi_session->calls[call_index]->csettings_peer[0]); | ||
292 | const ToxAvCSettings *c_self = toxavcsettings_cast | ||
293 | (&av->msi_session->calls[call_index]->csettings_local); | ||
294 | 347 | ||
295 | LOGGER_DEBUG( | 348 | if (call == NULL) { |
296 | "Type: %u(s) %u(p)\n" | 349 | rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; |
297 | "Video bitrate: %u(s) %u(p)\n" | 350 | goto END; |
298 | "Video height: %u(s) %u(p)\n" | 351 | } |
299 | "Video width: %u(s) %u(p)\n" | ||
300 | "Audio bitrate: %u(s) %u(p)\n" | ||
301 | "Audio framedur: %u(s) %u(p)\n" | ||
302 | "Audio sample rate: %u(s) %u(p)\n" | ||
303 | "Audio channels: %u(s) %u(p)\n", | ||
304 | c_self->call_type, c_peer->call_type, | ||
305 | c_self->video_bitrate, c_peer->video_bitrate, | ||
306 | c_self->max_video_height, c_peer->max_video_height, | ||
307 | c_self->max_video_width, c_peer->max_video_width, | ||
308 | c_self->audio_bitrate, c_peer->audio_bitrate, | ||
309 | c_self->audio_frame_duration, c_peer->audio_frame_duration, | ||
310 | c_self->audio_sample_rate, c_peer->audio_sample_rate, | ||
311 | c_self->audio_channels, c_peer->audio_channels ); | ||
312 | 352 | ||
313 | if ( !(call->cs = cs_new(c_self, c_peer, jbuf_capacity, support_video)) ) { | 353 | if (!call_prepare_transmission(call)) { |
314 | LOGGER_ERROR("Error while starting Codec State!\n"); | 354 | rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION; |
315 | pthread_mutex_unlock(call->mutex); | 355 | goto END; |
316 | return av_ErrorInitializingCodecs; | ||
317 | } | 356 | } |
318 | 357 | ||
319 | call->cs->agent = av; | 358 | call->audio_bit_rate = audio_bit_rate; |
320 | call->cs->call_idx = call_index; | 359 | call->video_bit_rate = video_bit_rate; |
321 | 360 | ||
322 | call->cs->acb.first = av->acb.first; | 361 | call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; |
323 | call->cs->acb.second = av->acb.second; | ||
324 | 362 | ||
325 | call->cs->vcb.first = av->vcb.first; | 363 | call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; |
326 | call->cs->vcb.second = av->vcb.second; | 364 | call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; |
327 | 365 | ||
366 | if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0) | ||
367 | rc = TOXAV_ERR_ANSWER_SYNC; | ||
328 | 368 | ||
329 | call->crtps[audio_index] = | 369 | END: |
330 | rtp_new(msi_TypeAudio, av->messenger, av->msi_session->calls[call_index]->peers[0]); | 370 | pthread_mutex_unlock(av->mutex); |
331 | 371 | ||
332 | if ( !call->crtps[audio_index] ) { | 372 | if (error) |
333 | LOGGER_ERROR("Error while starting audio RTP session!\n"); | 373 | *error = rc; |
334 | goto error; | 374 | |
375 | return rc == TOXAV_ERR_ANSWER_OK; | ||
376 | } | ||
377 | void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *function, void *user_data) | ||
378 | { | ||
379 | pthread_mutex_lock(av->mutex); | ||
380 | av->scb.first = function; | ||
381 | av->scb.second = user_data; | ||
382 | pthread_mutex_unlock(av->mutex); | ||
383 | } | ||
384 | bool toxav_call_control(ToxAV *av, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error) | ||
385 | { | ||
386 | pthread_mutex_lock(av->mutex); | ||
387 | TOXAV_ERR_CALL_CONTROL rc = TOXAV_ERR_CALL_CONTROL_OK; | ||
388 | |||
389 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
390 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; | ||
391 | goto END; | ||
335 | } | 392 | } |
336 | 393 | ||
337 | call->crtps[audio_index]->cs = call->cs; | 394 | ToxAVCall *call = call_get(av, friend_number); |
338 | 395 | ||
339 | if ( support_video ) { | 396 | if (call == NULL || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) { |
340 | call->crtps[video_index] = | 397 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; |
341 | rtp_new(msi_TypeVideo, av->messenger, av->msi_session->calls[call_index]->peers[0]); | 398 | goto END; |
399 | } | ||
342 | 400 | ||
343 | if ( !call->crtps[video_index] ) { | 401 | switch (control) { |
344 | LOGGER_ERROR("Error while starting video RTP session!\n"); | 402 | case TOXAV_CALL_CONTROL_RESUME: { |
345 | goto error; | 403 | /* Only act if paused and had media transfer active before */ |
404 | if (call->msi_call->self_capabilities == 0 && | ||
405 | call->previous_self_capabilities) { | ||
406 | |||
407 | if (msi_change_capabilities(call->msi_call, | ||
408 | call->previous_self_capabilities) == -1) { | ||
409 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
410 | goto END; | ||
411 | } | ||
412 | |||
413 | rtp_allow_receiving(call->audio.first); | ||
414 | rtp_allow_receiving(call->video.first); | ||
415 | } else { | ||
416 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
417 | goto END; | ||
418 | } | ||
346 | } | 419 | } |
420 | break; | ||
421 | |||
422 | case TOXAV_CALL_CONTROL_PAUSE: { | ||
423 | /* Only act if not already paused */ | ||
424 | if (call->msi_call->self_capabilities) { | ||
425 | call->previous_self_capabilities = call->msi_call->self_capabilities; | ||
426 | |||
427 | if (msi_change_capabilities(call->msi_call, 0) == -1) { | ||
428 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
429 | goto END; | ||
430 | } | ||
431 | |||
432 | rtp_stop_receiving(call->audio.first); | ||
433 | rtp_stop_receiving(call->video.first); | ||
434 | } else { | ||
435 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
436 | goto END; | ||
437 | } | ||
438 | } | ||
439 | break; | ||
440 | |||
441 | case TOXAV_CALL_CONTROL_CANCEL: { | ||
442 | /* Hang up */ | ||
443 | pthread_mutex_lock(call->mutex); | ||
444 | |||
445 | if (msi_hangup(call->msi_call) != 0) { | ||
446 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
447 | pthread_mutex_unlock(call->mutex); | ||
448 | goto END; | ||
449 | } | ||
450 | |||
451 | call->msi_call = NULL; | ||
452 | pthread_mutex_unlock(call->mutex); | ||
347 | 453 | ||
348 | call->crtps[video_index]->cs = call->cs; | 454 | /* No mather the case, terminate the call */ |
455 | call_kill_transmission(call); | ||
456 | call_remove(call); | ||
457 | } | ||
458 | break; | ||
459 | |||
460 | case TOXAV_CALL_CONTROL_MUTE_AUDIO: { | ||
461 | if (call->msi_call->self_capabilities & msi_CapRAudio) { | ||
462 | if (msi_change_capabilities(call->msi_call, call-> | ||
463 | msi_call->self_capabilities ^ msi_CapRAudio) == -1) { | ||
464 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
465 | goto END; | ||
466 | } | ||
467 | |||
468 | rtp_stop_receiving(call->audio.first); | ||
469 | } else { | ||
470 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
471 | goto END; | ||
472 | } | ||
473 | } | ||
474 | break; | ||
475 | |||
476 | case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: { | ||
477 | if (call->msi_call->self_capabilities ^ msi_CapRAudio) { | ||
478 | if (msi_change_capabilities(call->msi_call, call-> | ||
479 | msi_call->self_capabilities | msi_CapRAudio) == -1) { | ||
480 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
481 | goto END; | ||
482 | } | ||
483 | |||
484 | rtp_allow_receiving(call->audio.first); | ||
485 | } else { | ||
486 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
487 | goto END; | ||
488 | } | ||
489 | } | ||
490 | break; | ||
491 | |||
492 | case TOXAV_CALL_CONTROL_HIDE_VIDEO: { | ||
493 | if (call->msi_call->self_capabilities & msi_CapRVideo) { | ||
494 | if (msi_change_capabilities(call->msi_call, call-> | ||
495 | msi_call->self_capabilities ^ msi_CapRVideo) == -1) { | ||
496 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
497 | goto END; | ||
498 | } | ||
499 | |||
500 | rtp_stop_receiving(call->video.first); | ||
501 | } else { | ||
502 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
503 | goto END; | ||
504 | } | ||
505 | } | ||
506 | break; | ||
507 | |||
508 | case TOXAV_CALL_CONTROL_SHOW_VIDEO: { | ||
509 | if (call->msi_call->self_capabilities ^ msi_CapRVideo) { | ||
510 | if (msi_change_capabilities(call->msi_call, call-> | ||
511 | msi_call->self_capabilities | msi_CapRVideo) == -1) { | ||
512 | rc = TOXAV_ERR_CALL_CONTROL_SYNC; | ||
513 | goto END; | ||
514 | } | ||
515 | |||
516 | rtp_allow_receiving(call->audio.first); | ||
517 | } else { | ||
518 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
519 | goto END; | ||
520 | } | ||
521 | } | ||
522 | break; | ||
349 | } | 523 | } |
350 | 524 | ||
351 | call->active = 1; | 525 | END: |
352 | pthread_mutex_unlock(call->mutex); | 526 | pthread_mutex_unlock(av->mutex); |
353 | return av_ErrorNone; | ||
354 | error: | ||
355 | rtp_kill(call->crtps[audio_index], av->messenger); | ||
356 | call->crtps[audio_index] = NULL; | ||
357 | rtp_kill(call->crtps[video_index], av->messenger); | ||
358 | call->crtps[video_index] = NULL; | ||
359 | cs_kill(call->cs); | ||
360 | call->cs = NULL; | ||
361 | call->active = 0; | ||
362 | pthread_mutex_destroy(call->mutex_encoding_audio); | ||
363 | pthread_mutex_destroy(call->mutex_encoding_video); | ||
364 | pthread_mutex_destroy(call->mutex_do); | ||
365 | 527 | ||
366 | pthread_mutex_unlock(call->mutex); | 528 | if (error) |
367 | return av_ErrorCreatingRtpSessions; | 529 | *error = rc; |
368 | } | ||
369 | 530 | ||
370 | int toxav_kill_transmission ( ToxAv *av, int32_t call_index ) | 531 | return rc == TOXAV_ERR_CALL_CONTROL_OK; |
532 | } | ||
533 | bool toxav_bit_rate_set(ToxAV *av, uint32_t friend_number, int32_t audio_bit_rate, | ||
534 | int32_t video_bit_rate, TOXAV_ERR_BIT_RATE_SET *error) | ||
371 | { | 535 | { |
372 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 536 | TOXAV_ERR_BIT_RATE_SET rc = TOXAV_ERR_BIT_RATE_SET_OK; |
373 | LOGGER_WARNING("Invalid call index: %d", call_index); | 537 | ToxAVCall *call; |
374 | return av_ErrorNoCall; | 538 | |
539 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
540 | rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; | ||
541 | goto END; | ||
375 | } | 542 | } |
376 | 543 | ||
377 | ToxAvCall *call = &av->calls[call_index]; | 544 | if (audio_bit_rate > 0 && audio_bit_rate_invalid(audio_bit_rate)) { |
545 | rc = TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE; | ||
546 | goto END; | ||
547 | } | ||
378 | 548 | ||
379 | pthread_mutex_lock(call->mutex); | 549 | if (video_bit_rate > 0 && video_bit_rate_invalid(video_bit_rate)) { |
550 | rc = TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE; | ||
551 | goto END; | ||
552 | } | ||
553 | |||
554 | pthread_mutex_lock(av->mutex); | ||
555 | call = call_get(av, friend_number); | ||
380 | 556 | ||
381 | if (!call->active) { | 557 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { |
382 | pthread_mutex_unlock(call->mutex); | 558 | pthread_mutex_unlock(av->mutex); |
383 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 559 | rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; |
384 | return av_ErrorInvalidState; | 560 | goto END; |
385 | } | 561 | } |
386 | 562 | ||
387 | call->active = 0; | 563 | if (audio_bit_rate >= 0) { |
564 | LOGGER_DEBUG("Setting new audio bitrate to: %d", audio_bit_rate); | ||
388 | 565 | ||
389 | pthread_mutex_lock(call->mutex_encoding_audio); | 566 | if (call->audio_bit_rate == audio_bit_rate) { |
390 | pthread_mutex_unlock(call->mutex_encoding_audio); | 567 | LOGGER_DEBUG("Audio bitrate already set to: %d", audio_bit_rate); |
391 | pthread_mutex_lock(call->mutex_encoding_video); | 568 | } else if (audio_bit_rate == 0) { |
392 | pthread_mutex_unlock(call->mutex_encoding_video); | 569 | LOGGER_DEBUG("Turned off audio sending"); |
393 | pthread_mutex_lock(call->mutex_do); | ||
394 | pthread_mutex_unlock(call->mutex_do); | ||
395 | 570 | ||
396 | rtp_kill(call->crtps[audio_index], av->messenger); | 571 | if (msi_change_capabilities(call->msi_call, call->msi_call-> |
397 | call->crtps[audio_index] = NULL; | 572 | self_capabilities ^ msi_CapSAudio) != 0) { |
398 | rtp_kill(call->crtps[video_index], av->messenger); | 573 | pthread_mutex_unlock(av->mutex); |
399 | call->crtps[video_index] = NULL; | 574 | rc = TOXAV_ERR_BIT_RATE_SET_SYNC; |
400 | cs_kill(call->cs); | 575 | goto END; |
401 | call->cs = NULL; | 576 | } |
402 | 577 | ||
403 | pthread_mutex_destroy(call->mutex_encoding_audio); | 578 | /* Audio sending is turned off; notify peer */ |
404 | pthread_mutex_destroy(call->mutex_encoding_video); | 579 | call->audio_bit_rate = 0; |
405 | pthread_mutex_destroy(call->mutex_do); | 580 | } else { |
581 | pthread_mutex_lock(call->mutex); | ||
582 | |||
583 | if (call->audio_bit_rate == 0) { | ||
584 | LOGGER_DEBUG("Turned on audio sending"); | ||
585 | |||
586 | /* The audio has been turned off before this */ | ||
587 | if (msi_change_capabilities(call->msi_call, call-> | ||
588 | msi_call->self_capabilities | msi_CapSAudio) != 0) { | ||
589 | pthread_mutex_unlock(call->mutex); | ||
590 | pthread_mutex_unlock(av->mutex); | ||
591 | rc = TOXAV_ERR_BIT_RATE_SET_SYNC; | ||
592 | goto END; | ||
593 | } | ||
594 | } else | ||
595 | LOGGER_DEBUG("Set new audio bit rate %d", audio_bit_rate); | ||
596 | |||
597 | call->audio_bit_rate = audio_bit_rate; | ||
598 | pthread_mutex_unlock(call->mutex); | ||
599 | } | ||
600 | } | ||
406 | 601 | ||
407 | pthread_mutex_unlock(call->mutex); | 602 | if (video_bit_rate >= 0) { |
603 | LOGGER_DEBUG("Setting new video bitrate to: %d", video_bit_rate); | ||
408 | 604 | ||
409 | return av_ErrorNone; | 605 | if (call->video_bit_rate == video_bit_rate) { |
410 | } | 606 | LOGGER_DEBUG("Video bitrate already set to: %d", video_bit_rate); |
607 | } else if (video_bit_rate == 0) { | ||
608 | LOGGER_DEBUG("Turned off video sending"); | ||
411 | 609 | ||
412 | static int toxav_send_rtp_payload(ToxAv *av, | 610 | /* Video sending is turned off; notify peer */ |
413 | ToxAvCall *call, | 611 | if (msi_change_capabilities(call->msi_call, call->msi_call-> |
414 | ToxAvCallType type, | 612 | self_capabilities ^ msi_CapSVideo) != 0) { |
415 | const uint8_t *payload, | 613 | pthread_mutex_unlock(av->mutex); |
416 | unsigned int length) | 614 | rc = TOXAV_ERR_BIT_RATE_SET_SYNC; |
615 | goto END; | ||
616 | } | ||
617 | |||
618 | call->video_bit_rate = 0; | ||
619 | } else { | ||
620 | pthread_mutex_lock(call->mutex); | ||
621 | |||
622 | if (call->video_bit_rate == 0) { | ||
623 | LOGGER_DEBUG("Turned on video sending"); | ||
624 | |||
625 | /* The video has been turned off before this */ | ||
626 | if (msi_change_capabilities(call->msi_call, call-> | ||
627 | msi_call->self_capabilities | msi_CapSVideo) != 0) { | ||
628 | pthread_mutex_unlock(call->mutex); | ||
629 | pthread_mutex_unlock(av->mutex); | ||
630 | rc = TOXAV_ERR_BIT_RATE_SET_SYNC; | ||
631 | goto END; | ||
632 | } | ||
633 | } else | ||
634 | LOGGER_DEBUG("Set new video bit rate %d", video_bit_rate); | ||
635 | |||
636 | call->video_bit_rate = video_bit_rate; | ||
637 | pthread_mutex_unlock(call->mutex); | ||
638 | } | ||
639 | } | ||
640 | |||
641 | pthread_mutex_unlock(av->mutex); | ||
642 | END: | ||
643 | |||
644 | if (error) | ||
645 | *error = rc; | ||
646 | |||
647 | return rc == TOXAV_ERR_BIT_RATE_SET_OK; | ||
648 | } | ||
649 | void toxav_callback_bit_rate_status(ToxAV *av, toxav_bit_rate_status_cb *function, void *user_data) | ||
417 | { | 650 | { |
418 | if (call->crtps[type - av_TypeAudio]) { | 651 | pthread_mutex_lock(av->mutex); |
652 | av->bcb.first = function; | ||
653 | av->bcb.second = user_data; | ||
654 | pthread_mutex_unlock(av->mutex); | ||
655 | } | ||
656 | bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, | ||
657 | uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error) | ||
658 | { | ||
659 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; | ||
660 | ToxAVCall *call; | ||
661 | |||
662 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
663 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; | ||
664 | goto END; | ||
665 | } | ||
666 | |||
667 | if (pthread_mutex_trylock(av->mutex) != 0) { | ||
668 | rc = TOXAV_ERR_SEND_FRAME_SYNC; | ||
669 | goto END; | ||
670 | } | ||
419 | 671 | ||
420 | /* Audio */ | 672 | call = call_get(av, friend_number); |
421 | if (type == av_TypeAudio) | ||
422 | return rtp_send_msg(call->crtps[audio_index], av->messenger, payload, length); | ||
423 | 673 | ||
424 | /* Video */ | 674 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { |
425 | int parts = cs_split_video_payload(call->cs, payload, length); | 675 | pthread_mutex_unlock(av->mutex); |
676 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; | ||
677 | goto END; | ||
678 | } | ||
426 | 679 | ||
427 | if (parts < 0) return parts; | 680 | if (call->audio_bit_rate == 0 || |
681 | !(call->msi_call->self_capabilities & msi_CapSAudio) || | ||
682 | !(call->msi_call->peer_capabilities & msi_CapRAudio)) { | ||
683 | pthread_mutex_unlock(av->mutex); | ||
684 | rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; | ||
685 | goto END; | ||
686 | } | ||
428 | 687 | ||
429 | uint16_t part_size; | 688 | pthread_mutex_lock(call->mutex_audio); |
430 | const uint8_t *iter; | 689 | pthread_mutex_unlock(av->mutex); |
431 | 690 | ||
432 | int i; | 691 | if (pcm == NULL) { |
692 | pthread_mutex_unlock(call->mutex_audio); | ||
693 | rc = TOXAV_ERR_SEND_FRAME_NULL; | ||
694 | goto END; | ||
695 | } | ||
433 | 696 | ||
434 | for (i = 0; i < parts; i++) { | 697 | if (channels > 2) { |
435 | iter = cs_get_split_video_frame(call->cs, &part_size); | 698 | pthread_mutex_unlock(call->mutex_audio); |
699 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
700 | goto END; | ||
701 | } | ||
436 | 702 | ||
437 | if (rtp_send_msg(call->crtps[video_index], av->messenger, iter, part_size) < 0) | 703 | { /* Encode and send */ |
438 | return av_ErrorSendingPayload; | 704 | if (ac_reconfigure_encoder(call->audio.second, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { |
705 | pthread_mutex_unlock(call->mutex_audio); | ||
706 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
707 | goto END; | ||
439 | } | 708 | } |
440 | 709 | ||
441 | return av_ErrorNone; | 710 | uint8_t dest[sample_count + sizeof(sampling_rate)]; /* This is more than enough always */ |
442 | 711 | ||
443 | } else return av_ErrorNoRtpSession; | 712 | sampling_rate = htonl(sampling_rate); |
444 | } | 713 | memcpy(dest, &sampling_rate, sizeof(sampling_rate)); |
714 | int vrc = opus_encode(call->audio.second->encoder, pcm, sample_count, | ||
715 | dest + sizeof(sampling_rate), sizeof(dest) - sizeof(sampling_rate)); | ||
716 | |||
717 | if (vrc < 0) { | ||
718 | LOGGER_WARNING("Failed to encode frame %s", opus_strerror(vrc)); | ||
719 | pthread_mutex_unlock(call->mutex_audio); | ||
720 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
721 | goto END; | ||
722 | } | ||
445 | 723 | ||
446 | int toxav_prepare_video_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input) | 724 | if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate)) != 0) { |
725 | LOGGER_WARNING("Failed to send audio packet"); | ||
726 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
727 | } | ||
728 | } | ||
729 | |||
730 | |||
731 | pthread_mutex_unlock(call->mutex_audio); | ||
732 | |||
733 | END: | ||
734 | |||
735 | if (error) | ||
736 | *error = rc; | ||
737 | |||
738 | return rc == TOXAV_ERR_SEND_FRAME_OK; | ||
739 | } | ||
740 | bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, | ||
741 | const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error) | ||
447 | { | 742 | { |
448 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 743 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; |
449 | LOGGER_WARNING("Invalid call index: %d", call_index); | 744 | ToxAVCall *call; |
450 | return av_ErrorNoCall; | 745 | |
746 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
747 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; | ||
748 | goto END; | ||
451 | } | 749 | } |
452 | 750 | ||
751 | if (pthread_mutex_trylock(av->mutex) != 0) { | ||
752 | rc = TOXAV_ERR_SEND_FRAME_SYNC; | ||
753 | goto END; | ||
754 | } | ||
453 | 755 | ||
454 | ToxAvCall *call = &av->calls[call_index]; | 756 | call = call_get(av, friend_number); |
455 | pthread_mutex_lock(call->mutex); | ||
456 | 757 | ||
457 | if (!call->active) { | 758 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { |
458 | pthread_mutex_unlock(call->mutex); | 759 | pthread_mutex_unlock(av->mutex); |
459 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 760 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; |
460 | return av_ErrorInvalidState; | 761 | goto END; |
461 | } | 762 | } |
462 | 763 | ||
463 | if (!(call->cs->capabilities & cs_VideoEncoding)) { | 764 | if (call->video_bit_rate == 0 || |
464 | pthread_mutex_unlock(call->mutex); | 765 | !(call->msi_call->self_capabilities & msi_CapSVideo) || |
465 | LOGGER_WARNING("Call doesn't support encoding video: %d", call_index); | 766 | !(call->msi_call->peer_capabilities & msi_CapRVideo)) { |
466 | return av_ErrorInvalidState; | 767 | pthread_mutex_unlock(av->mutex); |
768 | rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; | ||
769 | goto END; | ||
467 | } | 770 | } |
468 | 771 | ||
469 | if (cs_set_video_encoder_resolution(call->cs, input->w, input->h) < 0) { | 772 | pthread_mutex_lock(call->mutex_video); |
470 | pthread_mutex_unlock(call->mutex); | 773 | pthread_mutex_unlock(av->mutex); |
471 | return av_ErrorSettingVideoResolution; | 774 | |
775 | if (y == NULL || u == NULL || v == NULL) { | ||
776 | pthread_mutex_unlock(call->mutex_video); | ||
777 | rc = TOXAV_ERR_SEND_FRAME_NULL; | ||
778 | goto END; | ||
472 | } | 779 | } |
473 | 780 | ||
474 | pthread_mutex_lock(call->mutex_encoding_video); | 781 | if (vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height) != 0) { |
475 | pthread_mutex_unlock(call->mutex); | 782 | pthread_mutex_unlock(call->mutex_video); |
783 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
784 | goto END; | ||
785 | } | ||
786 | |||
787 | { /* Encode */ | ||
788 | vpx_image_t img; | ||
789 | img.w = img.h = img.d_w = img.d_h = 0; | ||
790 | vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); | ||
791 | |||
792 | /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." | ||
793 | * http://fourcc.org/yuv.php#IYUV | ||
794 | */ | ||
795 | memcpy(img.planes[VPX_PLANE_Y], y, width * height); | ||
796 | memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); | ||
797 | memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); | ||
476 | 798 | ||
477 | int rc = vpx_codec_encode(&call->cs->v_encoder, input, call->cs->frame_counter, 1, 0, MAX_ENCODE_TIME_US); | 799 | int vrc = vpx_codec_encode(call->video.second->encoder, &img, |
800 | call->video.second->frame_counter, 1, 0, MAX_ENCODE_TIME_US); | ||
478 | 801 | ||
479 | if ( rc != VPX_CODEC_OK) { | 802 | vpx_img_free(&img); |
480 | LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(rc)); | 803 | |
481 | pthread_mutex_unlock(call->mutex_encoding_video); | 804 | if (vrc != VPX_CODEC_OK) { |
482 | return av_ErrorEncodingVideo; | 805 | pthread_mutex_unlock(call->mutex_video); |
806 | LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(vrc)); | ||
807 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
808 | goto END; | ||
809 | } | ||
483 | } | 810 | } |
484 | 811 | ||
485 | ++call->cs->frame_counter; | 812 | ++call->video.second->frame_counter; |
486 | 813 | ||
487 | vpx_codec_iter_t iter = NULL; | 814 | { /* Send frames */ |
488 | const vpx_codec_cx_pkt_t *pkt; | 815 | vpx_codec_iter_t iter = NULL; |
489 | int copied = 0; | 816 | const vpx_codec_cx_pkt_t *pkt; |
490 | 817 | ||
491 | while ( (pkt = vpx_codec_get_cx_data(&call->cs->v_encoder, &iter)) ) { | 818 | while ((pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter))) { |
492 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { | 819 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT && |
493 | if ( copied + pkt->data.frame.sz > dest_max ) { | 820 | rtp_send_data(call->video.first, pkt->data.frame.buf, pkt->data.frame.sz) < 0) { |
494 | pthread_mutex_unlock(call->mutex_encoding_video); | ||
495 | return av_ErrorPacketTooLarge; | ||
496 | } | ||
497 | 821 | ||
498 | memcpy(dest + copied, pkt->data.frame.buf, pkt->data.frame.sz); | 822 | pthread_mutex_unlock(call->mutex_video); |
499 | copied += pkt->data.frame.sz; | 823 | LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno)); |
824 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
825 | goto END; | ||
826 | } | ||
500 | } | 827 | } |
501 | } | 828 | } |
502 | 829 | ||
503 | pthread_mutex_unlock(call->mutex_encoding_video); | 830 | pthread_mutex_unlock(call->mutex_video); |
504 | return copied; | 831 | |
832 | END: | ||
833 | |||
834 | if (error) | ||
835 | *error = rc; | ||
836 | |||
837 | return rc == TOXAV_ERR_SEND_FRAME_OK; | ||
838 | } | ||
839 | void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *function, void *user_data) | ||
840 | { | ||
841 | pthread_mutex_lock(av->mutex); | ||
842 | av->acb.first = function; | ||
843 | av->acb.second = user_data; | ||
844 | pthread_mutex_unlock(av->mutex); | ||
845 | } | ||
846 | void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *function, void *user_data) | ||
847 | { | ||
848 | pthread_mutex_lock(av->mutex); | ||
849 | av->vcb.first = function; | ||
850 | av->vcb.second = user_data; | ||
851 | pthread_mutex_unlock(av->mutex); | ||
505 | } | 852 | } |
506 | 853 | ||
507 | int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size) | 854 | |
855 | /******************************************************************************* | ||
856 | * | ||
857 | * :: Internal | ||
858 | * | ||
859 | ******************************************************************************/ | ||
860 | void callback_bwc(BWControler *bwc, uint32_t friend_number, float loss, void *user_data) | ||
508 | { | 861 | { |
862 | /* Callback which is called when the internal measure mechanism reported packet loss. | ||
863 | * We report suggested lowered bitrate to an app. If app is sending both audio and video, | ||
864 | * we will report lowered bitrate for video only because in that case video probably | ||
865 | * takes more than 90% bandwidth. Otherwise, we report lowered bitrate on audio. | ||
866 | * The application may choose to disable video totally if the stream is too bad. | ||
867 | */ | ||
509 | 868 | ||
510 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 869 | ToxAVCall *call = user_data; |
511 | LOGGER_WARNING("Invalid call index: %d", call_index); | 870 | assert(call); |
512 | return av_ErrorNoCall; | ||
513 | } | ||
514 | 871 | ||
515 | ToxAvCall *call = &av->calls[call_index]; | 872 | LOGGER_DEBUG("Reported loss of %f%%", loss * 100); |
516 | pthread_mutex_lock(call->mutex); | ||
517 | 873 | ||
874 | if (loss < .01f) | ||
875 | return; | ||
518 | 876 | ||
519 | if (!call->active) { | 877 | pthread_mutex_lock(call->av->mutex); |
520 | pthread_mutex_unlock(call->mutex); | 878 | |
521 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 879 | if (!call->av->bcb.first) { |
522 | return av_ErrorInvalidState; | 880 | pthread_mutex_unlock(call->av->mutex); |
881 | LOGGER_WARNING("No callback to report loss on"); | ||
882 | return; | ||
523 | } | 883 | } |
524 | 884 | ||
525 | int rc = toxav_send_rtp_payload(av, call, av_TypeVideo, frame, frame_size); | 885 | if (call->video_bit_rate) |
526 | pthread_mutex_unlock(call->mutex); | 886 | (*call->av->bcb.first) (call->av, friend_number, call->audio_bit_rate, |
887 | call->video_bit_rate - (call->video_bit_rate * loss), | ||
888 | call->av->bcb.second); | ||
889 | else if (call->audio_bit_rate) | ||
890 | (*call->av->bcb.first) (call->av, friend_number, | ||
891 | call->audio_bit_rate - (call->audio_bit_rate * loss), | ||
892 | 0, call->av->bcb.second); | ||
527 | 893 | ||
528 | return rc; | 894 | pthread_mutex_unlock(call->av->mutex); |
529 | } | 895 | } |
530 | 896 | int callback_invite(void *toxav_inst, MSICall *call) | |
531 | int toxav_prepare_audio_frame ( ToxAv *av, | ||
532 | int32_t call_index, | ||
533 | uint8_t *dest, | ||
534 | int dest_max, | ||
535 | const int16_t *frame, | ||
536 | int frame_size) | ||
537 | { | 897 | { |
538 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 898 | ToxAV *toxav = toxav_inst; |
539 | LOGGER_WARNING("Action on nonexisting call: %d", call_index); | 899 | pthread_mutex_lock(toxav->mutex); |
540 | return av_ErrorNoCall; | ||
541 | } | ||
542 | 900 | ||
543 | ToxAvCall *call = &av->calls[call_index]; | 901 | ToxAVCall *av_call = call_new(toxav, call->friend_number, NULL); |
544 | pthread_mutex_lock(call->mutex); | ||
545 | 902 | ||
546 | if (!call->active) { | 903 | if (av_call == NULL) { |
547 | pthread_mutex_unlock(call->mutex); | 904 | LOGGER_WARNING("Failed to initialize call..."); |
548 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 905 | pthread_mutex_unlock(toxav->mutex); |
549 | return av_ErrorInvalidState; | 906 | return -1; |
550 | } | 907 | } |
551 | 908 | ||
552 | pthread_mutex_lock(call->mutex_encoding_audio); | 909 | call->av_call = av_call; |
553 | pthread_mutex_unlock(call->mutex); | 910 | av_call->msi_call = call; |
554 | int32_t rc = opus_encode(call->cs->audio_encoder, frame, frame_size, dest, dest_max); | ||
555 | pthread_mutex_unlock(call->mutex_encoding_audio); | ||
556 | 911 | ||
557 | if (rc < 0) { | 912 | if (toxav->ccb.first) |
558 | LOGGER_ERROR("Failed to encode payload: %s\n", opus_strerror(rc)); | 913 | toxav->ccb.first(toxav, call->friend_number, call->peer_capabilities & msi_CapSAudio, |
559 | return av_ErrorEncodingAudio; | 914 | call->peer_capabilities & msi_CapSVideo, toxav->ccb.second); |
915 | else { | ||
916 | /* No handler to capture the call request, send failure */ | ||
917 | pthread_mutex_unlock(toxav->mutex); | ||
918 | return -1; | ||
560 | } | 919 | } |
561 | 920 | ||
562 | return rc; | 921 | pthread_mutex_unlock(toxav->mutex); |
922 | return 0; | ||
563 | } | 923 | } |
564 | 924 | int callback_start(void *toxav_inst, MSICall *call) | |
565 | int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *data, unsigned int size) | ||
566 | { | 925 | { |
567 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 926 | ToxAV *toxav = toxav_inst; |
568 | LOGGER_WARNING("Action on nonexisting call: %d", call_index); | 927 | pthread_mutex_lock(toxav->mutex); |
569 | return av_ErrorNoCall; | ||
570 | } | ||
571 | 928 | ||
572 | ToxAvCall *call = &av->calls[call_index]; | 929 | ToxAVCall *av_call = call_get(toxav, call->friend_number); |
573 | pthread_mutex_lock(call->mutex); | ||
574 | 930 | ||
931 | if (av_call == NULL) { | ||
932 | /* Should this ever happen? */ | ||
933 | pthread_mutex_unlock(toxav->mutex); | ||
934 | return -1; | ||
935 | } | ||
575 | 936 | ||
576 | if (!call->active) { | 937 | if (!call_prepare_transmission(av_call)) { |
577 | pthread_mutex_unlock(call->mutex); | 938 | callback_error(toxav_inst, call); |
578 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 939 | pthread_mutex_unlock(toxav->mutex); |
579 | return av_ErrorInvalidState; | 940 | return -1; |
580 | } | 941 | } |
581 | 942 | ||
582 | int rc = toxav_send_rtp_payload(av, call, av_TypeAudio, data, size); | 943 | if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) { |
583 | pthread_mutex_unlock(call->mutex); | 944 | callback_error(toxav_inst, call); |
584 | return rc; | 945 | pthread_mutex_unlock(toxav->mutex); |
585 | } | 946 | return -1; |
947 | } | ||
586 | 948 | ||
587 | int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ) | 949 | pthread_mutex_unlock(toxav->mutex); |
950 | return 0; | ||
951 | } | ||
952 | int callback_end(void *toxav_inst, MSICall *call) | ||
588 | { | 953 | { |
589 | if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || | 954 | ToxAV *toxav = toxav_inst; |
590 | !av->msi_session->calls[call_index] || av->msi_session->calls[call_index]->peer_count <= peer ) | 955 | pthread_mutex_lock(toxav->mutex); |
591 | return av_ErrorNoCall; | ||
592 | 956 | ||
593 | *dest = *toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[peer]); | 957 | invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_FINISHED); |
594 | return av_ErrorNone; | ||
595 | } | ||
596 | 958 | ||
597 | int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ) | 959 | if (call->av_call) { |
598 | { | 960 | call_kill_transmission(call->av_call); |
599 | if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] | 961 | call_remove(call->av_call); |
600 | || av->msi_session->calls[call_index]->peer_count <= peer ) | 962 | } |
601 | return av_ErrorNoCall; | ||
602 | 963 | ||
603 | return av->msi_session->calls[call_index]->peers[peer]; | 964 | pthread_mutex_unlock(toxav->mutex); |
965 | return 0; | ||
604 | } | 966 | } |
605 | 967 | int callback_error(void *toxav_inst, MSICall *call) | |
606 | ToxAvCallState toxav_get_call_state(ToxAv *av, int32_t call_index) | ||
607 | { | 968 | { |
608 | if ( CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] ) | 969 | ToxAV *toxav = toxav_inst; |
609 | return av_CallNonExistent; | 970 | pthread_mutex_lock(toxav->mutex); |
610 | 971 | ||
611 | return av->msi_session->calls[call_index]->state; | 972 | invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR); |
612 | 973 | ||
974 | if (call->av_call) { | ||
975 | call_kill_transmission(call->av_call); | ||
976 | call_remove(call->av_call); | ||
977 | } | ||
978 | |||
979 | pthread_mutex_unlock(toxav->mutex); | ||
980 | return 0; | ||
613 | } | 981 | } |
982 | int callback_capabilites(void *toxav_inst, MSICall *call) | ||
983 | { | ||
984 | ToxAV *toxav = toxav_inst; | ||
985 | pthread_mutex_lock(toxav->mutex); | ||
614 | 986 | ||
615 | int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ) | 987 | if (call->peer_capabilities & msi_CapSAudio) |
988 | rtp_allow_receiving(((ToxAVCall *)call->av_call)->audio.first); | ||
989 | else | ||
990 | rtp_stop_receiving(((ToxAVCall *)call->av_call)->audio.first); | ||
991 | |||
992 | if (call->peer_capabilities & msi_CapSVideo) | ||
993 | rtp_allow_receiving(((ToxAVCall *)call->av_call)->video.first); | ||
994 | else | ||
995 | rtp_stop_receiving(((ToxAVCall *)call->av_call)->video.first); | ||
996 | |||
997 | invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities); | ||
998 | |||
999 | pthread_mutex_unlock(toxav->mutex); | ||
1000 | return 0; | ||
1001 | } | ||
1002 | bool audio_bit_rate_invalid(uint32_t bit_rate) | ||
616 | { | 1003 | { |
617 | return av->calls[call_index].cs ? av->calls[call_index].cs->capabilities & (CSCapabilities) capability : 0; | 1004 | /* Opus RFC 6716 section-2.1.1 dictates the following: |
618 | /* 0 is error here */ | 1005 | * Opus supports all bit rates from 6 kbit/s to 510 kbit/s. |
1006 | */ | ||
1007 | return bit_rate < 6 || bit_rate > 510; | ||
619 | } | 1008 | } |
620 | 1009 | bool video_bit_rate_invalid(uint32_t bit_rate) | |
621 | Tox *toxav_get_tox(ToxAv *av) | ||
622 | { | 1010 | { |
623 | return (Tox *)av->messenger; | 1011 | (void) bit_rate; |
1012 | /* TODO: If anyone knows the answer to this one please fill it up */ | ||
1013 | return false; | ||
624 | } | 1014 | } |
1015 | bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state) | ||
1016 | { | ||
1017 | if (av->scb.first) | ||
1018 | av->scb.first(av, friend_number, state, av->scb.second); | ||
1019 | else | ||
1020 | return false; | ||
625 | 1021 | ||
626 | int toxav_get_active_count(ToxAv *av) | 1022 | return true; |
1023 | } | ||
1024 | ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error) | ||
627 | { | 1025 | { |
628 | if (!av) return -1; | 1026 | /* Assumes mutex locked */ |
1027 | TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK; | ||
1028 | ToxAVCall *call = NULL; | ||
629 | 1029 | ||
630 | int rc = 0, i = 0; | 1030 | if (m_friend_exists(av->m, friend_number) == 0) { |
1031 | rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND; | ||
1032 | goto END; | ||
1033 | } | ||
631 | 1034 | ||
632 | for (; i < av->max_calls; i++) { | 1035 | if (m_get_friend_connectionstatus(av->m, friend_number) < 1) { |
633 | pthread_mutex_lock(av->calls[i].mutex); | 1036 | rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED; |
1037 | goto END; | ||
1038 | } | ||
1039 | |||
1040 | if (call_get(av, friend_number) != NULL) { | ||
1041 | rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL; | ||
1042 | goto END; | ||
1043 | } | ||
634 | 1044 | ||
635 | if (av->calls[i].active) rc++; | ||
636 | 1045 | ||
637 | pthread_mutex_unlock(av->calls[i].mutex); | 1046 | call = calloc(sizeof(ToxAVCall), 1); |
1047 | |||
1048 | if (call == NULL) { | ||
1049 | rc = TOXAV_ERR_CALL_MALLOC; | ||
1050 | goto END; | ||
638 | } | 1051 | } |
639 | 1052 | ||
640 | return rc; | 1053 | call->av = av; |
641 | } | 1054 | call->friend_number = friend_number; |
642 | 1055 | ||
643 | /* Create a new toxav group. | 1056 | if (av->calls == NULL) { /* Creating */ |
644 | * | 1057 | av->calls = calloc (sizeof(ToxAVCall *), friend_number + 1); |
645 | * return group number on success. | ||
646 | * return -1 on failure. | ||
647 | * | ||
648 | * Audio data callback format: | ||
649 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | ||
650 | * | ||
651 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
652 | */ | ||
653 | int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, | ||
654 | uint8_t, unsigned int, void *), void *userdata) | ||
655 | { | ||
656 | Messenger *m = tox; | ||
657 | return add_av_groupchat(m->group_chat_object, audio_callback, userdata); | ||
658 | } | ||
659 | 1058 | ||
660 | /* Join a AV group (you need to have been invited first.) | 1059 | if (av->calls == NULL) { |
661 | * | 1060 | free(call); |
662 | * returns group number on success | 1061 | call = NULL; |
663 | * returns -1 on failure. | 1062 | rc = TOXAV_ERR_CALL_MALLOC; |
664 | * | 1063 | goto END; |
665 | * Audio data callback format (same as the one for toxav_add_av_groupchat()): | 1064 | } |
666 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | 1065 | |
667 | * | 1066 | av->calls_tail = av->calls_head = friend_number; |
668 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 1067 | |
669 | */ | 1068 | } else if (av->calls_tail < friend_number) { /* Appending */ |
670 | int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, | 1069 | void *tmp = realloc(av->calls, sizeof(ToxAVCall *) * (friend_number + 1)); |
671 | void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), | 1070 | |
672 | void *userdata) | 1071 | if (tmp == NULL) { |
1072 | free(call); | ||
1073 | call = NULL; | ||
1074 | rc = TOXAV_ERR_CALL_MALLOC; | ||
1075 | goto END; | ||
1076 | } | ||
1077 | |||
1078 | av->calls = tmp; | ||
1079 | |||
1080 | /* Set fields in between to null */ | ||
1081 | uint32_t i = av->calls_tail + 1; | ||
1082 | |||
1083 | for (; i < friend_number; i ++) | ||
1084 | av->calls[i] = NULL; | ||
1085 | |||
1086 | call->prev = av->calls[av->calls_tail]; | ||
1087 | av->calls[av->calls_tail]->next = call; | ||
1088 | |||
1089 | av->calls_tail = friend_number; | ||
1090 | |||
1091 | } else if (av->calls_head > friend_number) { /* Inserting at front */ | ||
1092 | call->next = av->calls[av->calls_head]; | ||
1093 | av->calls[av->calls_head]->prev = call; | ||
1094 | av->calls_head = friend_number; | ||
1095 | } | ||
1096 | |||
1097 | av->calls[friend_number] = call; | ||
1098 | |||
1099 | END: | ||
1100 | |||
1101 | if (error) | ||
1102 | *error = rc; | ||
1103 | |||
1104 | return call; | ||
1105 | } | ||
1106 | ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) | ||
673 | { | 1107 | { |
674 | Messenger *m = tox; | 1108 | /* Assumes mutex locked */ |
675 | return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata); | 1109 | if (av->calls == NULL || av->calls_tail < friend_number) |
1110 | return NULL; | ||
1111 | |||
1112 | return av->calls[friend_number]; | ||
676 | } | 1113 | } |
1114 | ToxAVCall *call_remove(ToxAVCall *call) | ||
1115 | { | ||
1116 | if (call == NULL) | ||
1117 | return NULL; | ||
677 | 1118 | ||
678 | /* Send audio to the group chat. | 1119 | uint32_t friend_number = call->friend_number; |
679 | * | 1120 | ToxAV *av = call->av; |
680 | * return 0 on success. | 1121 | |
681 | * return -1 on failure. | 1122 | ToxAVCall *prev = call->prev; |
682 | * | 1123 | ToxAVCall *next = call->next; |
683 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 1124 | |
684 | * | 1125 | /* Set av call in msi to NULL in order to know if call if ToxAVCall is |
685 | * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) | 1126 | * removed from the msi call. |
686 | * Valid number of channels are 1 or 2. | 1127 | */ |
687 | * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. | 1128 | if (call->msi_call) { |
688 | * | 1129 | call->msi_call->av_call = NULL; |
689 | * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 | 1130 | } |
690 | */ | 1131 | |
691 | int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, | 1132 | free(call); |
692 | unsigned int sample_rate) | 1133 | |
1134 | if (prev) | ||
1135 | prev->next = next; | ||
1136 | else if (next) | ||
1137 | av->calls_head = next->friend_number; | ||
1138 | else goto CLEAR; | ||
1139 | |||
1140 | if (next) | ||
1141 | next->prev = prev; | ||
1142 | else if (prev) | ||
1143 | av->calls_tail = prev->friend_number; | ||
1144 | else goto CLEAR; | ||
1145 | |||
1146 | av->calls[friend_number] = NULL; | ||
1147 | return next; | ||
1148 | |||
1149 | CLEAR: | ||
1150 | av->calls_head = av->calls_tail = 0; | ||
1151 | free(av->calls); | ||
1152 | av->calls = NULL; | ||
1153 | |||
1154 | return NULL; | ||
1155 | } | ||
1156 | bool call_prepare_transmission(ToxAVCall *call) | ||
693 | { | 1157 | { |
694 | Messenger *m = tox; | 1158 | /* Assumes mutex locked */ |
695 | return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate); | 1159 | |
1160 | if (call == NULL) | ||
1161 | return false; | ||
1162 | |||
1163 | ToxAV *av = call->av; | ||
1164 | |||
1165 | if (!av->acb.first && !av->vcb.first) | ||
1166 | /* It makes no sense to have CSession without callbacks */ | ||
1167 | return false; | ||
1168 | |||
1169 | if (call->active) { | ||
1170 | LOGGER_WARNING("Call already active!\n"); | ||
1171 | return true; | ||
1172 | } | ||
1173 | |||
1174 | if (create_recursive_mutex(call->mutex_audio) != 0) | ||
1175 | return false; | ||
1176 | |||
1177 | if (create_recursive_mutex(call->mutex_video) != 0) | ||
1178 | goto FAILURE_3; | ||
1179 | |||
1180 | if (create_recursive_mutex(call->mutex) != 0) | ||
1181 | goto FAILURE_2; | ||
1182 | |||
1183 | /* Prepare bwc */ | ||
1184 | call->bwc = bwc_new(av->m, call->friend_number, callback_bwc, call); | ||
1185 | |||
1186 | { /* Prepare audio */ | ||
1187 | call->audio.second = ac_new(av, call->friend_number, av->acb.first, av->acb.second); | ||
1188 | |||
1189 | if (!call->audio.second) { | ||
1190 | LOGGER_ERROR("Failed to create audio codec session"); | ||
1191 | goto FAILURE; | ||
1192 | } | ||
1193 | |||
1194 | call->audio.first = rtp_new(rtp_TypeAudio, av->m, call->friend_number, call->bwc, | ||
1195 | call->audio.second, ac_queue_message); | ||
1196 | |||
1197 | if (!call->audio.first) { | ||
1198 | LOGGER_ERROR("Failed to create audio rtp session");; | ||
1199 | goto FAILURE; | ||
1200 | } | ||
1201 | } | ||
1202 | { /* Prepare video */ | ||
1203 | call->video.second = vc_new(av, call->friend_number, av->vcb.first, av->vcb.second); | ||
1204 | |||
1205 | if (!call->video.second) { | ||
1206 | LOGGER_ERROR("Failed to create video codec session"); | ||
1207 | goto FAILURE; | ||
1208 | } | ||
1209 | |||
1210 | call->video.first = rtp_new(rtp_TypeVideo, av->m, call->friend_number, call->bwc, | ||
1211 | call->video.second, vc_queue_message); | ||
1212 | |||
1213 | if (!call->video.first) { | ||
1214 | LOGGER_ERROR("Failed to create video rtp session"); | ||
1215 | goto FAILURE; | ||
1216 | } | ||
1217 | } | ||
1218 | |||
1219 | call->active = 1; | ||
1220 | return true; | ||
1221 | |||
1222 | FAILURE: | ||
1223 | bwc_kill(call->bwc); | ||
1224 | rtp_kill(call->audio.first); | ||
1225 | ac_kill(call->audio.second); | ||
1226 | call->audio.first = NULL; | ||
1227 | call->audio.second = NULL; | ||
1228 | rtp_kill(call->video.first); | ||
1229 | vc_kill(call->video.second); | ||
1230 | call->video.first = NULL; | ||
1231 | call->video.second = NULL; | ||
1232 | pthread_mutex_destroy(call->mutex); | ||
1233 | FAILURE_2: | ||
1234 | pthread_mutex_destroy(call->mutex_video); | ||
1235 | FAILURE_3: | ||
1236 | pthread_mutex_destroy(call->mutex_audio); | ||
1237 | return false; | ||
696 | } | 1238 | } |
1239 | void call_kill_transmission(ToxAVCall *call) | ||
1240 | { | ||
1241 | if (call == NULL || call->active == 0) | ||
1242 | return; | ||
697 | 1243 | ||
1244 | call->active = 0; | ||
1245 | |||
1246 | pthread_mutex_lock(call->mutex_audio); | ||
1247 | pthread_mutex_unlock(call->mutex_audio); | ||
1248 | pthread_mutex_lock(call->mutex_video); | ||
1249 | pthread_mutex_unlock(call->mutex_video); | ||
1250 | pthread_mutex_lock(call->mutex); | ||
1251 | pthread_mutex_unlock(call->mutex); | ||
1252 | |||
1253 | bwc_kill(call->bwc); | ||
1254 | |||
1255 | rtp_kill(call->audio.first); | ||
1256 | ac_kill(call->audio.second); | ||
1257 | call->audio.first = NULL; | ||
1258 | call->audio.second = NULL; | ||
1259 | |||
1260 | rtp_kill(call->video.first); | ||
1261 | vc_kill(call->video.second); | ||
1262 | call->video.first = NULL; | ||
1263 | call->video.second = NULL; | ||
1264 | |||
1265 | pthread_mutex_destroy(call->mutex_audio); | ||
1266 | pthread_mutex_destroy(call->mutex_video); | ||
1267 | pthread_mutex_destroy(call->mutex); | ||
1268 | } | ||
diff --git a/toxav/toxav.h b/toxav/toxav.h index 7285f45c..08a6d265 100644 --- a/toxav/toxav.h +++ b/toxav/toxav.h | |||
@@ -1,285 +1,698 @@ | |||
1 | /** toxav.h | 1 | /* toxav.h |
2 | * | 2 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. |
4 | * | 4 | * |
5 | * This file is part of Tox. | 5 | * This file is part of Tox. |
6 | * | 6 | * |
7 | * Tox is free software: you can redistribute it and/or modify | 7 | * Tox is free software: you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by | 8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation, either version 3 of the License, or | 9 | * the Free Software Foundation, either version 3 of the License, or |
10 | * (at your option) any later version. | 10 | * (at your option) any later version. |
11 | * | 11 | * |
12 | * Tox is distributed in the hope that it will be useful, | 12 | * Tox is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. | 15 | * GNU General Public License for more details. |
16 | * | 16 | * |
17 | * You should have received a copy of the GNU General Public License | 17 | * You should have received a copy of the GNU General Public License |
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | 18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. |
19 | * | 19 | * |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #ifndef TOXAV_H | ||
23 | #define TOXAV_H | ||
22 | 24 | ||
23 | #ifndef __TOXAV | 25 | #include <stdbool.h> |
24 | #define __TOXAV | 26 | #include <stddef.h> |
25 | #include <inttypes.h> | 27 | #include <stdint.h> |
26 | 28 | ||
27 | #ifdef __cplusplus | 29 | #ifdef __cplusplus |
28 | extern "C" { | 30 | extern "C" { |
29 | #endif | 31 | #endif |
30 | 32 | ||
31 | typedef struct _ToxAv ToxAv; | 33 | /** \page av Public audio/video API for Tox clients. |
32 | 34 | * | |
33 | /* vpx_image_t */ | 35 | * This API can handle multiple calls. Each call has its state, in very rare |
34 | #include <vpx/vpx_image.h> | 36 | * occasions the library can change the state of the call without apps knowledge. |
35 | 37 | * | |
36 | typedef void ( *ToxAVCallback ) ( void *agent, int32_t call_idx, void *arg ); | 38 | */ |
37 | typedef void ( *ToxAvAudioCallback ) (void *agent, int32_t call_idx, const int16_t *PCM, uint16_t size, void *data); | 39 | /** \subsection events Events and callbacks |
38 | typedef void ( *ToxAvVideoCallback ) (void *agent, int32_t call_idx, const vpx_image_t *img, void *data); | 40 | * |
39 | 41 | * As in Core API, events are handled by callbacks. One callback can be | |
42 | * registered per event. All events have a callback function type named | ||
43 | * `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`. | ||
44 | * Passing a NULL callback will result in no callback being registered for that | ||
45 | * event. Only one callback per event can be registered, so if a client needs | ||
46 | * multiple event listeners, it needs to implement the dispatch functionality | ||
47 | * itself. Unlike Core API, lack of some event handlers will cause the the | ||
48 | * library to drop calls before they are started. Hanging up call from a | ||
49 | * callback causes undefined behaviour. | ||
50 | * | ||
51 | */ | ||
52 | /** \subsection threading Threading implications | ||
53 | * | ||
54 | * Unlike the Core API, this API is fully thread-safe. The library will ensure | ||
55 | * the proper synchronization of parallel calls. | ||
56 | * | ||
57 | * A common way to run ToxAV (multiple or single instance) is to have a thread, | ||
58 | * separate from tox instance thread, running a simple toxav_iterate loop, | ||
59 | * sleeping for toxav_iteration_interval * milliseconds on each iteration. | ||
60 | * | ||
61 | * An important thing to note is that events are triggered from both tox and | ||
62 | * toxav thread (see above). Audio and video receive frame events are triggered | ||
63 | * from toxav thread while all the other events are triggered from tox thread. | ||
64 | * | ||
65 | * Tox thread has priority with mutex mechanisms. Any api function can | ||
66 | * fail if mutexes are held by tox thread in which case they will set SYNC | ||
67 | * error code. | ||
68 | */ | ||
69 | /** | ||
70 | * External Tox type. | ||
71 | */ | ||
40 | #ifndef TOX_DEFINED | 72 | #ifndef TOX_DEFINED |
41 | #define TOX_DEFINED | 73 | #define TOX_DEFINED |
42 | typedef struct Tox Tox; | 74 | typedef struct Tox Tox; |
43 | #endif | 75 | #endif /* TOX_DEFINED */ |
44 | |||
45 | #define RTP_PAYLOAD_SIZE 65535 | ||
46 | |||
47 | 76 | ||
48 | /** | 77 | /** |
49 | * Callbacks ids that handle the call states. | 78 | * ToxAV. |
50 | */ | 79 | */ |
51 | typedef enum { | ||
52 | av_OnInvite, /* Incoming call */ | ||
53 | av_OnRinging, /* When peer is ready to accept/reject the call */ | ||
54 | av_OnStart, /* Call (RTP transmission) started */ | ||
55 | av_OnCancel, /* The side that initiated call canceled invite */ | ||
56 | av_OnReject, /* The side that was invited rejected the call */ | ||
57 | av_OnEnd, /* Call that was active ended */ | ||
58 | av_OnRequestTimeout, /* When the requested action didn't get response in specified time */ | ||
59 | av_OnPeerTimeout, /* Peer timed out; stop the call */ | ||
60 | av_OnPeerCSChange, /* Peer changing Csettings. Prepare for changed AV */ | ||
61 | av_OnSelfCSChange /* Csettings change confirmation. Once triggered peer is ready to recv changed AV */ | ||
62 | } ToxAvCallbackID; | ||
63 | |||
64 | |||
65 | /** | 80 | /** |
66 | * Call type identifier. | 81 | * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox |
82 | * instance, and Tox instance can have only one ToxAV instance. One must make | ||
83 | * sure to close ToxAV instance prior closing Tox instance otherwise undefined | ||
84 | * behaviour occurs. Upon closing of ToxAV instance, all active calls will be | ||
85 | * forcibly terminated without notifying peers. | ||
86 | * | ||
67 | */ | 87 | */ |
68 | typedef enum { | 88 | #ifndef TOXAV_DEFINED |
69 | av_TypeAudio = 192, | 89 | #define TOXAV_DEFINED |
70 | av_TypeVideo | 90 | typedef struct ToxAV ToxAV; |
71 | } ToxAvCallType; | 91 | #endif /* TOXAV_DEFINED */ |
72 | |||
73 | 92 | ||
74 | typedef enum { | ||
75 | av_CallNonExistent = -1, | ||
76 | av_CallInviting, /* when sending call invite */ | ||
77 | av_CallStarting, /* when getting call invite */ | ||
78 | av_CallActive, | ||
79 | av_CallHold, | ||
80 | av_CallHungUp | ||
81 | } ToxAvCallState; | ||
82 | 93 | ||
94 | /******************************************************************************* | ||
95 | * | ||
96 | * :: API version | ||
97 | * | ||
98 | ******************************************************************************/ | ||
83 | /** | 99 | /** |
84 | * Error indicators. Values under -20 are reserved for toxcore. | 100 | * The major version number. Incremented when the API or ABI changes in an |
101 | * incompatible way. | ||
85 | */ | 102 | */ |
86 | typedef enum { | 103 | #define TOXAV_VERSION_MAJOR 0u |
87 | av_ErrorNone = 0, | ||
88 | av_ErrorUnknown = -1, /* Unknown error */ | ||
89 | av_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ | ||
90 | av_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ | ||
91 | av_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ | ||
92 | av_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ | ||
93 | av_ErrorInitializingCodecs = -30, /* Failed creating CSSession */ | ||
94 | av_ErrorSettingVideoResolution = -31, /* Error setting resolution */ | ||
95 | av_ErrorSettingVideoBitrate = -32, /* Error setting bitrate */ | ||
96 | av_ErrorSplittingVideoPayload = -33, /* Error splitting video payload */ | ||
97 | av_ErrorEncodingVideo = -34, /* vpx_codec_encode failed */ | ||
98 | av_ErrorEncodingAudio = -35, /* opus_encode failed */ | ||
99 | av_ErrorSendingPayload = -40, /* Sending lossy packet failed */ | ||
100 | av_ErrorCreatingRtpSessions = -41, /* One of the rtp sessions failed to initialize */ | ||
101 | av_ErrorNoRtpSession = -50, /* Trying to perform rtp action on invalid session */ | ||
102 | av_ErrorInvalidCodecState = -51, /* Codec state not initialized */ | ||
103 | av_ErrorPacketTooLarge = -52, /* Split packet exceeds it's limit */ | ||
104 | } ToxAvError; | ||
105 | |||
106 | 104 | ||
107 | /** | 105 | /** |
108 | * Locally supported capabilities. | 106 | * The minor version number. Incremented when functionality is added without |
107 | * breaking the API or ABI. Set to 0 when the major version number is | ||
108 | * incremented. | ||
109 | */ | 109 | */ |
110 | typedef enum { | 110 | #define TOXAV_VERSION_MINOR 0u |
111 | av_AudioEncoding = 1 << 0, | ||
112 | av_AudioDecoding = 1 << 1, | ||
113 | av_VideoEncoding = 1 << 2, | ||
114 | av_VideoDecoding = 1 << 3 | ||
115 | } ToxAvCapabilities; | ||
116 | |||
117 | 111 | ||
118 | /** | 112 | /** |
119 | * Encoding settings. | 113 | * The patch or revision number. Incremented when bugfixes are applied without |
114 | * changing any functionality or API or ABI. | ||
120 | */ | 115 | */ |
121 | typedef struct _ToxAvCSettings { | 116 | #define TOXAV_VERSION_PATCH 0u |
122 | ToxAvCallType call_type; | ||
123 | |||
124 | uint32_t video_bitrate; /* In kbits/s */ | ||
125 | uint16_t max_video_width; /* In px */ | ||
126 | uint16_t max_video_height; /* In px */ | ||
127 | |||
128 | uint32_t audio_bitrate; /* In bits/s */ | ||
129 | uint16_t audio_frame_duration; /* In ms */ | ||
130 | uint32_t audio_sample_rate; /* In Hz */ | ||
131 | uint32_t audio_channels; | ||
132 | } ToxAvCSettings; | ||
133 | |||
134 | extern const ToxAvCSettings av_DefaultSettings; | ||
135 | 117 | ||
136 | /** | 118 | /** |
137 | * Start new A/V session. There can only be one session at the time. | 119 | * A macro to check at preprocessing time whether the client code is compatible |
120 | * with the installed version of ToxAV. | ||
138 | */ | 121 | */ |
139 | ToxAv *toxav_new(Tox *messenger, int32_t max_calls); | 122 | #define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ |
123 | (TOXAV_VERSION_MAJOR == MAJOR && \ | ||
124 | (TOXAV_VERSION_MINOR > MINOR || \ | ||
125 | (TOXAV_VERSION_MINOR == MINOR && \ | ||
126 | TOXAV_VERSION_PATCH >= PATCH))) | ||
140 | 127 | ||
141 | /** | 128 | /** |
142 | * Remove A/V session. | 129 | * A macro to make compilation fail if the client code is not compatible with |
130 | * the installed version of ToxAV. | ||
143 | */ | 131 | */ |
144 | void toxav_kill(ToxAv *av); | 132 | #define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ |
133 | typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] | ||
145 | 134 | ||
146 | /** | 135 | /** |
147 | * Returns the interval in milliseconds when the next toxav_do() should be called. | 136 | * A convenience macro to call toxav_version_is_compatible with the currently |
148 | * If no call is active at the moment returns 200. | 137 | * compiling API version. |
149 | */ | 138 | */ |
150 | uint32_t toxav_do_interval(ToxAv *av); | 139 | #define TOXAV_VERSION_IS_ABI_COMPATIBLE() \ |
140 | toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH) | ||
151 | 141 | ||
152 | /** | 142 | /** |
153 | * Main loop for the session. Best called right after tox_do(); | 143 | * Return the major version number of the library. Can be used to display the |
144 | * ToxAV library version or to check whether the client is compatible with the | ||
145 | * dynamically linked version of ToxAV. | ||
154 | */ | 146 | */ |
155 | void toxav_do(ToxAv *av); | 147 | uint32_t toxav_version_major(void); |
156 | 148 | ||
157 | /** | 149 | /** |
158 | * Register callback for call state. | 150 | * Return the minor version number of the library. |
159 | */ | 151 | */ |
160 | void toxav_register_callstate_callback (ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata); | 152 | uint32_t toxav_version_minor(void); |
161 | 153 | ||
162 | /** | 154 | /** |
163 | * Register callback for audio data. | 155 | * Return the patch number of the library. |
164 | */ | 156 | */ |
165 | void toxav_register_audio_callback (ToxAv *av, ToxAvAudioCallback cb, void *userdata); | 157 | uint32_t toxav_version_patch(void); |
166 | 158 | ||
167 | /** | 159 | /** |
168 | * Register callback for video data. | 160 | * Return whether the compiled library version is compatible with the passed |
161 | * version numbers. | ||
169 | */ | 162 | */ |
170 | void toxav_register_video_callback (ToxAv *av, ToxAvVideoCallback cb, void *userdata); | 163 | bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); |
164 | |||
165 | |||
166 | /******************************************************************************* | ||
167 | * | ||
168 | * :: Creation and destruction | ||
169 | * | ||
170 | ******************************************************************************/ | ||
171 | typedef enum TOXAV_ERR_NEW { | ||
172 | /** | ||
173 | * The function returned successfully. | ||
174 | */ | ||
175 | TOXAV_ERR_NEW_OK, | ||
176 | /** | ||
177 | * One of the arguments to the function was NULL when it was not expected. | ||
178 | */ | ||
179 | TOXAV_ERR_NEW_NULL, | ||
180 | /** | ||
181 | * Memory allocation failure while trying to allocate structures required for | ||
182 | * the A/V session. | ||
183 | */ | ||
184 | TOXAV_ERR_NEW_MALLOC, | ||
185 | /** | ||
186 | * Attempted to create a second session for the same Tox instance. | ||
187 | */ | ||
188 | TOXAV_ERR_NEW_MULTIPLE, | ||
189 | } TOXAV_ERR_NEW; | ||
171 | 190 | ||
172 | /** | 191 | /** |
173 | * Call user. Use its friend_id. | 192 | * Start new A/V session. There can only be only one session per Tox instance. |
174 | */ | 193 | */ |
175 | int toxav_call(ToxAv *av, | 194 | ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error); |
176 | int32_t *call_index, | ||
177 | int friend_id, | ||
178 | const ToxAvCSettings *csettings, | ||
179 | int ringing_seconds); | ||
180 | 195 | ||
181 | /** | 196 | /** |
182 | * Hangup active call. | 197 | * Releases all resources associated with the A/V session. |
198 | * | ||
199 | * If any calls were ongoing, these will be forcibly terminated without | ||
200 | * notifying peers. After calling this function, no other functions may be | ||
201 | * called and the av pointer becomes invalid. | ||
183 | */ | 202 | */ |
184 | int toxav_hangup(ToxAv *av, int32_t call_index); | 203 | void toxav_kill(ToxAV *toxAV); |
185 | 204 | ||
186 | /** | 205 | /** |
187 | * Answer incoming call. Pass the csettings that you will use. | 206 | * Returns the Tox instance the A/V object was created for. |
188 | */ | 207 | */ |
189 | int toxav_answer(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ); | 208 | Tox *toxav_get_tox(const ToxAV *toxAV); |
209 | |||
190 | 210 | ||
211 | /******************************************************************************* | ||
212 | * | ||
213 | * :: A/V event loop | ||
214 | * | ||
215 | ******************************************************************************/ | ||
191 | /** | 216 | /** |
192 | * Reject incoming call. | 217 | * Returns the interval in milliseconds when the next toxav_iterate call should |
218 | * be. If no call is active at the moment, this function returns 200. | ||
193 | */ | 219 | */ |
194 | int toxav_reject(ToxAv *av, int32_t call_index, const char *reason); | 220 | uint32_t toxav_iteration_interval(const ToxAV *toxAV); |
195 | 221 | ||
196 | /** | 222 | /** |
197 | * Cancel outgoing request. | 223 | * Main loop for the session. This function needs to be called in intervals of |
224 | * toxav_iteration_interval() milliseconds. It is best called in the separate | ||
225 | * thread from tox_iterate. | ||
198 | */ | 226 | */ |
199 | int toxav_cancel(ToxAv *av, int32_t call_index, int peer_id, const char *reason); | 227 | void toxav_iterate(ToxAV *toxAV); |
228 | |||
229 | |||
230 | /******************************************************************************* | ||
231 | * | ||
232 | * :: Call setup | ||
233 | * | ||
234 | ******************************************************************************/ | ||
235 | typedef enum TOXAV_ERR_CALL { | ||
236 | /** | ||
237 | * The function returned successfully. | ||
238 | */ | ||
239 | TOXAV_ERR_CALL_OK, | ||
240 | /** | ||
241 | * A resource allocation error occurred while trying to create the structures | ||
242 | * required for the call. | ||
243 | */ | ||
244 | TOXAV_ERR_CALL_MALLOC, | ||
245 | /** | ||
246 | * Synchronization error occurred. | ||
247 | */ | ||
248 | TOXAV_ERR_CALL_SYNC, | ||
249 | /** | ||
250 | * The friend number did not designate a valid friend. | ||
251 | */ | ||
252 | TOXAV_ERR_CALL_FRIEND_NOT_FOUND, | ||
253 | /** | ||
254 | * The friend was valid, but not currently connected. | ||
255 | */ | ||
256 | TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED, | ||
257 | /** | ||
258 | * Attempted to call a friend while already in an audio or video call with | ||
259 | * them. | ||
260 | */ | ||
261 | TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL, | ||
262 | /** | ||
263 | * Audio or video bit rate is invalid. | ||
264 | */ | ||
265 | TOXAV_ERR_CALL_INVALID_BIT_RATE, | ||
266 | } TOXAV_ERR_CALL; | ||
200 | 267 | ||
201 | /** | 268 | /** |
202 | * Notify peer that we are changing codec settings. | 269 | * Call a friend. This will start ringing the friend. |
270 | * | ||
271 | * It is the client's responsibility to stop ringing after a certain timeout, | ||
272 | * if such behaviour is desired. If the client does not stop ringing, the | ||
273 | * library will not stop until the friend is disconnected. Audio and video | ||
274 | * receiving are both enabled by default. | ||
275 | * | ||
276 | * @param friend_number The friend number of the friend that should be called. | ||
277 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable | ||
278 | * audio sending. | ||
279 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
280 | * video sending. | ||
203 | */ | 281 | */ |
204 | int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings); | 282 | bool toxav_call(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, |
283 | uint32_t video_bit_rate, TOXAV_ERR_CALL *error); | ||
205 | 284 | ||
206 | /** | 285 | /** |
207 | * Terminate transmission. Note that transmission will be | 286 | * The function type for the call callback. |
208 | * terminated without informing remote peer. Usually called when we can't inform peer. | 287 | * |
288 | * @param friend_number The friend number from which the call is incoming. | ||
289 | * @param audio_enabled True if friend is sending audio. | ||
290 | * @param video_enabled True if friend is sending video. | ||
209 | */ | 291 | */ |
210 | int toxav_stop_call(ToxAv *av, int32_t call_index); | 292 | typedef void toxav_call_cb(ToxAV *toxAV, uint32_t friend_number, bool audio_enabled, |
293 | bool video_enabled, void *user_data); | ||
211 | 294 | ||
212 | /** | 295 | /** |
213 | * Allocates transmission data. Must be call before calling toxav_prepare_* and toxav_send_*. | 296 | * Set the callback for the `call` event. Pass NULL to unset. |
214 | * Also, it must be called when call is started | 297 | * |
215 | */ | 298 | */ |
216 | int toxav_prepare_transmission(ToxAv *av, int32_t call_index, int support_video); | 299 | void toxav_callback_call(ToxAV *toxAV, toxav_call_cb *callback, void *user_data); |
300 | |||
301 | typedef enum TOXAV_ERR_ANSWER { | ||
302 | /** | ||
303 | * The function returned successfully. | ||
304 | */ | ||
305 | TOXAV_ERR_ANSWER_OK, | ||
306 | /** | ||
307 | * Synchronization error occurred. | ||
308 | */ | ||
309 | TOXAV_ERR_ANSWER_SYNC, | ||
310 | /** | ||
311 | * Failed to initialize codecs for call session. Note that codec initiation | ||
312 | * will fail if there is no receive callback registered for either audio or | ||
313 | * video. | ||
314 | */ | ||
315 | TOXAV_ERR_ANSWER_CODEC_INITIALIZATION, | ||
316 | /** | ||
317 | * The friend number did not designate a valid friend. | ||
318 | */ | ||
319 | TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND, | ||
320 | /** | ||
321 | * The friend was valid, but they are not currently trying to initiate a call. | ||
322 | * This is also returned if this client is already in a call with the friend. | ||
323 | */ | ||
324 | TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING, | ||
325 | /** | ||
326 | * Audio or video bit rate is invalid. | ||
327 | */ | ||
328 | TOXAV_ERR_ANSWER_INVALID_BIT_RATE, | ||
329 | } TOXAV_ERR_ANSWER; | ||
217 | 330 | ||
218 | /** | 331 | /** |
219 | * Clears transmission data. Call this at the end of the transmission. | 332 | * Accept an incoming call. |
333 | * | ||
334 | * If answering fails for any reason, the call will still be pending and it is | ||
335 | * possible to try and answer it later. Audio and video receiving are both | ||
336 | * enabled by default. | ||
337 | * | ||
338 | * @param friend_number The friend number of the friend that is calling. | ||
339 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable | ||
340 | * audio sending. | ||
341 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
342 | * video sending. | ||
220 | */ | 343 | */ |
221 | int toxav_kill_transmission(ToxAv *av, int32_t call_index); | 344 | bool toxav_answer(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, |
345 | TOXAV_ERR_ANSWER *error); | ||
346 | |||
347 | |||
348 | /******************************************************************************* | ||
349 | * | ||
350 | * :: Call state graph | ||
351 | * | ||
352 | ******************************************************************************/ | ||
353 | enum TOXAV_FRIEND_CALL_STATE { | ||
354 | /** | ||
355 | * Set by the AV core if an error occurred on the remote end or if friend | ||
356 | * timed out. This is the final state after which no more state | ||
357 | * transitions can occur for the call. This call state will never be triggered | ||
358 | * in combination with other call states. | ||
359 | */ | ||
360 | TOXAV_FRIEND_CALL_STATE_ERROR = 1, | ||
361 | /** | ||
362 | * The call has finished. This is the final state after which no more state | ||
363 | * transitions can occur for the call. This call state will never be | ||
364 | * triggered in combination with other call states. | ||
365 | */ | ||
366 | TOXAV_FRIEND_CALL_STATE_FINISHED = 2, | ||
367 | /** | ||
368 | * The flag that marks that friend is sending audio. | ||
369 | */ | ||
370 | TOXAV_FRIEND_CALL_STATE_SENDING_A = 4, | ||
371 | /** | ||
372 | * The flag that marks that friend is sending video. | ||
373 | */ | ||
374 | TOXAV_FRIEND_CALL_STATE_SENDING_V = 8, | ||
375 | /** | ||
376 | * The flag that marks that friend is receiving audio. | ||
377 | */ | ||
378 | TOXAV_FRIEND_CALL_STATE_ACCEPTING_A = 16, | ||
379 | /** | ||
380 | * The flag that marks that friend is receiving video. | ||
381 | */ | ||
382 | TOXAV_FRIEND_CALL_STATE_ACCEPTING_V = 32, | ||
383 | }; | ||
222 | 384 | ||
223 | /** | 385 | /** |
224 | * Encode video frame. | 386 | * The function type for the call_state callback. |
387 | * | ||
388 | * @param friend_number The friend number for which the call state changed. | ||
389 | * @param state The bitmask of the new call state which is guaranteed to be | ||
390 | * different than the previous state. The state is set to 0 when the call is | ||
391 | * paused. The bitmask represents all the activities currently performed by the | ||
392 | * friend. | ||
225 | */ | 393 | */ |
226 | int toxav_prepare_video_frame ( ToxAv *av, | 394 | typedef void toxav_call_state_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t state, void *user_data); |
227 | int32_t call_index, | ||
228 | uint8_t *dest, | ||
229 | int dest_max, | ||
230 | vpx_image_t *input); | ||
231 | 395 | ||
232 | /** | 396 | /** |
233 | * Send encoded video packet. | 397 | * Set the callback for the `call_state` event. Pass NULL to unset. |
398 | * | ||
234 | */ | 399 | */ |
235 | int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, uint32_t frame_size); | 400 | void toxav_callback_call_state(ToxAV *toxAV, toxav_call_state_cb *callback, void *user_data); |
401 | |||
402 | /******************************************************************************* | ||
403 | * | ||
404 | * :: Call control | ||
405 | * | ||
406 | ******************************************************************************/ | ||
407 | typedef enum TOXAV_CALL_CONTROL { | ||
408 | /** | ||
409 | * Resume a previously paused call. Only valid if the pause was caused by this | ||
410 | * client, if not, this control is ignored. Not valid before the call is accepted. | ||
411 | */ | ||
412 | TOXAV_CALL_CONTROL_RESUME, | ||
413 | /** | ||
414 | * Put a call on hold. Not valid before the call is accepted. | ||
415 | */ | ||
416 | TOXAV_CALL_CONTROL_PAUSE, | ||
417 | /** | ||
418 | * Reject a call if it was not answered, yet. Cancel a call after it was | ||
419 | * answered. | ||
420 | */ | ||
421 | TOXAV_CALL_CONTROL_CANCEL, | ||
422 | /** | ||
423 | * Request that the friend stops sending audio. Regardless of the friend's | ||
424 | * compliance, this will cause the audio_receive_frame event to stop being | ||
425 | * triggered on receiving an audio frame from the friend. | ||
426 | */ | ||
427 | TOXAV_CALL_CONTROL_MUTE_AUDIO, | ||
428 | /** | ||
429 | * Calling this control will notify client to start sending audio again. | ||
430 | */ | ||
431 | TOXAV_CALL_CONTROL_UNMUTE_AUDIO, | ||
432 | /** | ||
433 | * Request that the friend stops sending video. Regardless of the friend's | ||
434 | * compliance, this will cause the video_receive_frame event to stop being | ||
435 | * triggered on receiving a video frame from the friend. | ||
436 | */ | ||
437 | TOXAV_CALL_CONTROL_HIDE_VIDEO, | ||
438 | /** | ||
439 | * Calling this control will notify client to start sending video again. | ||
440 | */ | ||
441 | TOXAV_CALL_CONTROL_SHOW_VIDEO, | ||
442 | } TOXAV_CALL_CONTROL; | ||
443 | |||
444 | typedef enum TOXAV_ERR_CALL_CONTROL { | ||
445 | /** | ||
446 | * The function returned successfully. | ||
447 | */ | ||
448 | TOXAV_ERR_CALL_CONTROL_OK, | ||
449 | /** | ||
450 | * Synchronization error occurred. | ||
451 | */ | ||
452 | TOXAV_ERR_CALL_CONTROL_SYNC, | ||
453 | /** | ||
454 | * The friend_number passed did not designate a valid friend. | ||
455 | */ | ||
456 | TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND, | ||
457 | /** | ||
458 | * This client is currently not in a call with the friend. Before the call is | ||
459 | * answered, only CANCEL is a valid control. | ||
460 | */ | ||
461 | TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL, | ||
462 | /** | ||
463 | * Happens if user tried to pause an already paused call or if trying to | ||
464 | * resume a call that is not paused. | ||
465 | */ | ||
466 | TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION, | ||
467 | } TOXAV_ERR_CALL_CONTROL; | ||
236 | 468 | ||
237 | /** | 469 | /** |
238 | * Encode audio frame. | 470 | * Sends a call control command to a friend. |
471 | * | ||
472 | * @param friend_number The friend number of the friend this client is in a call | ||
473 | * with. | ||
474 | * @param control The control command to send. | ||
475 | * | ||
476 | * @return true on success. | ||
239 | */ | 477 | */ |
240 | int toxav_prepare_audio_frame ( ToxAv *av, | 478 | bool toxav_call_control(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, |
241 | int32_t call_index, | 479 | TOXAV_ERR_CALL_CONTROL *error); |
242 | uint8_t *dest, | 480 | |
243 | int dest_max, | 481 | |
244 | const int16_t *frame, | 482 | /******************************************************************************* |
245 | int frame_size); | 483 | * |
484 | * :: Controlling bit rates | ||
485 | * | ||
486 | ******************************************************************************/ | ||
487 | typedef enum TOXAV_ERR_BIT_RATE_SET { | ||
488 | /** | ||
489 | * The function returned successfully. | ||
490 | */ | ||
491 | TOXAV_ERR_BIT_RATE_SET_OK, | ||
492 | /** | ||
493 | * Synchronization error occurred. | ||
494 | */ | ||
495 | TOXAV_ERR_BIT_RATE_SET_SYNC, | ||
496 | /** | ||
497 | * The audio bit rate passed was not one of the supported values. | ||
498 | */ | ||
499 | TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE, | ||
500 | /** | ||
501 | * The video bit rate passed was not one of the supported values. | ||
502 | */ | ||
503 | TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE, | ||
504 | /** | ||
505 | * The friend_number passed did not designate a valid friend. | ||
506 | */ | ||
507 | TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND, | ||
508 | /** | ||
509 | * This client is currently not in a call with the friend. | ||
510 | */ | ||
511 | TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL, | ||
512 | } TOXAV_ERR_BIT_RATE_SET; | ||
246 | 513 | ||
247 | /** | 514 | /** |
248 | * Send encoded audio frame. | 515 | * Set the bit rate to be used in subsequent audio/video frames. |
516 | * | ||
517 | * @param friend_number The friend number of the friend for which to set the | ||
518 | * bit rate. | ||
519 | * @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable | ||
520 | * audio sending. Set to -1 to leave unchanged. | ||
521 | * @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable | ||
522 | * video sending. Set to -1 to leave unchanged. | ||
523 | * | ||
249 | */ | 524 | */ |
250 | int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int size); | 525 | bool toxav_bit_rate_set(ToxAV *toxAV, uint32_t friend_number, int32_t audio_bit_rate, |
526 | int32_t video_bit_rate, TOXAV_ERR_BIT_RATE_SET *error); | ||
251 | 527 | ||
252 | /** | 528 | /** |
253 | * Get codec settings from the peer. These were exchanged during call initialization | 529 | * The function type for the bit_rate_status callback. The event is triggered |
254 | * or when peer send us new csettings. | 530 | * when the network becomes too saturated for current bit rates at which |
531 | * point core suggests new bit rates. | ||
532 | * | ||
533 | * @param friend_number The friend number of the friend for which to set the | ||
534 | * bit rate. | ||
535 | * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. | ||
536 | * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. | ||
255 | */ | 537 | */ |
256 | int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ); | 538 | typedef void toxav_bit_rate_status_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, |
539 | uint32_t video_bit_rate, void *user_data); | ||
257 | 540 | ||
258 | /** | 541 | /** |
259 | * Get friend id of peer participating in conversation. | 542 | * Set the callback for the `bit_rate_status` event. Pass NULL to unset. |
543 | * | ||
260 | */ | 544 | */ |
261 | int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ); | 545 | void toxav_callback_bit_rate_status(ToxAV *toxAV, toxav_bit_rate_status_cb *callback, void *user_data); |
546 | |||
547 | |||
548 | /******************************************************************************* | ||
549 | * | ||
550 | * :: A/V sending | ||
551 | * | ||
552 | ******************************************************************************/ | ||
553 | typedef enum TOXAV_ERR_SEND_FRAME { | ||
554 | /** | ||
555 | * The function returned successfully. | ||
556 | */ | ||
557 | TOXAV_ERR_SEND_FRAME_OK, | ||
558 | /** | ||
559 | * In case of video, one of Y, U, or V was NULL. In case of audio, the samples | ||
560 | * data pointer was NULL. | ||
561 | */ | ||
562 | TOXAV_ERR_SEND_FRAME_NULL, | ||
563 | /** | ||
564 | * The friend_number passed did not designate a valid friend. | ||
565 | */ | ||
566 | TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND, | ||
567 | /** | ||
568 | * This client is currently not in a call with the friend. | ||
569 | */ | ||
570 | TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL, | ||
571 | /** | ||
572 | * Synchronization error occurred. | ||
573 | */ | ||
574 | TOXAV_ERR_SEND_FRAME_SYNC, | ||
575 | /** | ||
576 | * One of the frame parameters was invalid. E.g. the resolution may be too | ||
577 | * small or too large, or the audio sampling rate may be unsupported. | ||
578 | */ | ||
579 | TOXAV_ERR_SEND_FRAME_INVALID, | ||
580 | /** | ||
581 | * Either friend turned off audio or video receiving or we turned off sending | ||
582 | * for the said payload. | ||
583 | */ | ||
584 | TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED, | ||
585 | /** | ||
586 | * Failed to push frame through rtp interface. | ||
587 | */ | ||
588 | TOXAV_ERR_SEND_FRAME_RTP_FAILED, | ||
589 | } TOXAV_ERR_SEND_FRAME; | ||
262 | 590 | ||
263 | /** | 591 | /** |
264 | * Get current call state. | 592 | * Send an audio frame to a friend. |
593 | * | ||
594 | * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... | ||
595 | * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... | ||
596 | * For mono audio, this has no meaning, every sample is subsequent. For stereo, | ||
597 | * this means the expected format is LRLRLR... with samples for left and right | ||
598 | * alternating. | ||
599 | * | ||
600 | * @param friend_number The friend number of the friend to which to send an | ||
601 | * audio frame. | ||
602 | * @param pcm An array of audio samples. The size of this array must be | ||
603 | * sample_count * channels. | ||
604 | * @param sample_count Number of samples in this frame. Valid numbers here are | ||
605 | * ((sample rate) * (audio length) / 1000), where audio length can be | ||
606 | * 2.5, 5, 10, 20, 40 or 60 millseconds. | ||
607 | * @param channels Number of audio channels. Supported values are 1 and 2. | ||
608 | * @param sampling_rate Audio sampling rate used in this frame. Valid sampling | ||
609 | * rates are 8000, 12000, 16000, 24000, or 48000. | ||
610 | */ | ||
611 | bool toxav_audio_send_frame(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, | ||
612 | size_t sample_count, uint8_t channels, uint32_t sampling_rate, | ||
613 | TOXAV_ERR_SEND_FRAME *error); | ||
614 | |||
615 | /** | ||
616 | * Send a video frame to a friend. | ||
617 | * | ||
618 | * Y - plane should be of size: height * width | ||
619 | * U - plane should be of size: (height/2) * (width/2) | ||
620 | * V - plane should be of size: (height/2) * (width/2) | ||
621 | * | ||
622 | * @param friend_number The friend number of the friend to which to send a video | ||
623 | * frame. | ||
624 | * @param width Width of the frame in pixels. | ||
625 | * @param height Height of the frame in pixels. | ||
626 | * @param y Y (Luminance) plane data. | ||
627 | * @param u U (Chroma) plane data. | ||
628 | * @param v V (Chroma) plane data. | ||
265 | */ | 629 | */ |
266 | ToxAvCallState toxav_get_call_state ( ToxAv *av, int32_t call_index ); | 630 | bool toxav_video_send_frame(ToxAV *toxAV, uint32_t friend_number, uint16_t width, |
631 | uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, | ||
632 | TOXAV_ERR_SEND_FRAME *error); | ||
267 | 633 | ||
634 | |||
635 | /******************************************************************************* | ||
636 | * | ||
637 | * :: A/V receiving | ||
638 | * | ||
639 | ******************************************************************************/ | ||
268 | /** | 640 | /** |
269 | * Is certain capability supported. Used to determine if encoding/decoding is ready. | 641 | * The function type for the audio_receive_frame callback. The callback can be |
642 | * called multiple times per single iteration depending on the amount of queued | ||
643 | * frames in the buffer. The received format is the same as in send function. | ||
644 | * | ||
645 | * @param friend_number The friend number of the friend who sent an audio frame. | ||
646 | * @param pcm An array of audio samples (sample_count * channels elements). | ||
647 | * @param sample_count The number of audio samples per channel in the PCM array. | ||
648 | * @param channels Number of audio channels. | ||
649 | * @param sampling_rate Sampling rate used in this frame. | ||
650 | * | ||
270 | */ | 651 | */ |
271 | int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ); | 652 | typedef void toxav_audio_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, |
653 | size_t sample_count, uint8_t channels, uint32_t sampling_rate, | ||
654 | void *user_data); | ||
272 | 655 | ||
273 | /** | 656 | /** |
274 | * Returns tox reference. | 657 | * Set the callback for the `audio_receive_frame` event. Pass NULL to unset. |
658 | * | ||
275 | */ | 659 | */ |
276 | Tox *toxav_get_tox (ToxAv *av); | 660 | void toxav_callback_audio_receive_frame(ToxAV *toxAV, toxav_audio_receive_frame_cb *callback, void *user_data); |
277 | 661 | ||
278 | /** | 662 | /** |
279 | * Returns number of active calls or -1 on error. | 663 | * The function type for the video_receive_frame callback. |
664 | * | ||
665 | * @param friend_number The friend number of the friend who sent a video frame. | ||
666 | * @param width Width of the frame in pixels. | ||
667 | * @param height Height of the frame in pixels. | ||
668 | * @param y | ||
669 | * @param u | ||
670 | * @param v Plane data. | ||
671 | * The size of plane data is derived from width and height where | ||
672 | * Y = MAX(width, abs(ystride)) * height, | ||
673 | * U = MAX(width/2, abs(ustride)) * (height/2) and | ||
674 | * V = MAX(width/2, abs(vstride)) * (height/2). | ||
675 | * @param ystride | ||
676 | * @param ustride | ||
677 | * @param vstride Strides data. Strides represent padding for each plane | ||
678 | * that may or may not be present. You must handle strides in | ||
679 | * your image processing code. Strides are negative if the | ||
680 | * image is bottom-up hence why you MUST abs() it when | ||
681 | * calculating plane buffer size. | ||
682 | */ | ||
683 | typedef void toxav_video_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, uint16_t width, | ||
684 | uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, | ||
685 | int32_t ystride, int32_t ustride, int32_t vstride, void *user_data); | ||
686 | |||
687 | /** | ||
688 | * Set the callback for the `video_receive_frame` event. Pass NULL to unset. | ||
689 | * | ||
280 | */ | 690 | */ |
281 | int toxav_get_active_count (ToxAv *av); | 691 | void toxav_callback_video_receive_frame(ToxAV *toxAV, toxav_video_receive_frame_cb *callback, void *user_data); |
282 | 692 | ||
693 | /** | ||
694 | * NOTE Compatibility with old toxav group calls TODO remove | ||
695 | */ | ||
283 | /* Create a new toxav group. | 696 | /* Create a new toxav group. |
284 | * | 697 | * |
285 | * return group number on success. | 698 | * return group number on success. |
@@ -290,7 +703,7 @@ int toxav_get_active_count (ToxAv *av); | |||
290 | * | 703 | * |
291 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 704 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). |
292 | */ | 705 | */ |
293 | int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, | 706 | int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, |
294 | unsigned int, void *), void *userdata); | 707 | unsigned int, void *), void *userdata); |
295 | 708 | ||
296 | /* Join a AV group (you need to have been invited first.) | 709 | /* Join a AV group (you need to have been invited first.) |
@@ -304,7 +717,7 @@ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Tox *, int, int, con | |||
304 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 717 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). |
305 | */ | 718 | */ |
306 | int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, | 719 | int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, |
307 | void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); | 720 | void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); |
308 | 721 | ||
309 | /* Send audio to the group chat. | 722 | /* Send audio to the group chat. |
310 | * | 723 | * |
@@ -325,5 +738,4 @@ int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsign | |||
325 | #ifdef __cplusplus | 738 | #ifdef __cplusplus |
326 | } | 739 | } |
327 | #endif | 740 | #endif |
328 | 741 | #endif /* TOXAV_H */ | |
329 | #endif /* __TOXAV */ | ||
diff --git a/toxav/toxav_old.c b/toxav/toxav_old.c new file mode 100644 index 00000000..7d7e5e7b --- /dev/null +++ b/toxav/toxav_old.c | |||
@@ -0,0 +1,81 @@ | |||
1 | /* toxav_old.h | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | /** | ||
22 | * This file contains the group chats code for the backwards compatibility. | ||
23 | */ | ||
24 | |||
25 | #include "toxav.h" | ||
26 | #include "group.h" | ||
27 | |||
28 | /* Create a new toxav group. | ||
29 | * | ||
30 | * return group number on success. | ||
31 | * return -1 on failure. | ||
32 | * | ||
33 | * Audio data callback format: | ||
34 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | ||
35 | * | ||
36 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
37 | */ | ||
38 | int toxav_add_av_groupchat(struct Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, | ||
39 | uint8_t, unsigned int, void *), void *userdata) | ||
40 | { | ||
41 | Messenger *m = (Messenger *)tox; | ||
42 | return add_av_groupchat(m->group_chat_object, audio_callback, userdata); | ||
43 | } | ||
44 | |||
45 | /* Join a AV group (you need to have been invited first.) | ||
46 | * | ||
47 | * returns group number on success | ||
48 | * returns -1 on failure. | ||
49 | * | ||
50 | * Audio data callback format (same as the one for toxav_add_av_groupchat()): | ||
51 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | ||
52 | * | ||
53 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
54 | */ | ||
55 | int toxav_join_av_groupchat(struct Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, | ||
56 | void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), | ||
57 | void *userdata) | ||
58 | { | ||
59 | Messenger *m = (Messenger *)tox; | ||
60 | return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata); | ||
61 | } | ||
62 | |||
63 | /* Send audio to the group chat. | ||
64 | * | ||
65 | * return 0 on success. | ||
66 | * return -1 on failure. | ||
67 | * | ||
68 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
69 | * | ||
70 | * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) | ||
71 | * Valid number of channels are 1 or 2. | ||
72 | * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. | ||
73 | * | ||
74 | * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 | ||
75 | */ | ||
76 | int toxav_group_send_audio(struct Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, | ||
77 | unsigned int sample_rate) | ||
78 | { | ||
79 | Messenger *m = (Messenger *)tox; | ||
80 | return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate); | ||
81 | } \ No newline at end of file | ||
diff --git a/toxav/video.c b/toxav/video.c new file mode 100644 index 00000000..8a832201 --- /dev/null +++ b/toxav/video.c | |||
@@ -0,0 +1,264 @@ | |||
1 | /** video.c | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifdef HAVE_CONFIG_H | ||
23 | #include "config.h" | ||
24 | #endif /* HAVE_CONFIG_H */ | ||
25 | |||
26 | #include <stdlib.h> | ||
27 | #include <assert.h> | ||
28 | |||
29 | #include "video.h" | ||
30 | #include "msi.h" | ||
31 | #include "rtp.h" | ||
32 | |||
33 | #include "../toxcore/logger.h" | ||
34 | #include "../toxcore/network.h" | ||
35 | |||
36 | #define MAX_DECODE_TIME_US 0 /* Good quality encode. */ | ||
37 | #define VIDEO_DECODE_BUFFER_SIZE 20 | ||
38 | |||
39 | VCSession *vc_new(ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data) | ||
40 | { | ||
41 | VCSession *vc = calloc(sizeof(VCSession), 1); | ||
42 | |||
43 | if (!vc) { | ||
44 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
45 | return NULL; | ||
46 | } | ||
47 | |||
48 | if (create_recursive_mutex(vc->queue_mutex) != 0) { | ||
49 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
50 | free(vc); | ||
51 | return NULL; | ||
52 | } | ||
53 | |||
54 | if (!(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE))) | ||
55 | goto BASE_CLEANUP; | ||
56 | |||
57 | int rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0); | ||
58 | |||
59 | if (rc != VPX_CODEC_OK) { | ||
60 | LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); | ||
61 | goto BASE_CLEANUP; | ||
62 | } | ||
63 | |||
64 | /* Set encoder to some initial values | ||
65 | */ | ||
66 | vpx_codec_enc_cfg_t cfg; | ||
67 | rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
68 | |||
69 | if (rc != VPX_CODEC_OK) { | ||
70 | LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc)); | ||
71 | goto BASE_CLEANUP_1; | ||
72 | } | ||
73 | |||
74 | cfg.rc_target_bitrate = 500000; | ||
75 | cfg.g_w = 800; | ||
76 | cfg.g_h = 600; | ||
77 | cfg.g_pass = VPX_RC_ONE_PASS; | ||
78 | /* FIXME If we set error resilience the app will crash due to bug in vp8. | ||
79 | Perhaps vp9 has solved it?*/ | ||
80 | // cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; | ||
81 | cfg.g_lag_in_frames = 0; | ||
82 | cfg.kf_min_dist = 0; | ||
83 | cfg.kf_max_dist = 48; | ||
84 | cfg.kf_mode = VPX_KF_AUTO; | ||
85 | |||
86 | rc = vpx_codec_enc_init(vc->encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
87 | |||
88 | if (rc != VPX_CODEC_OK) { | ||
89 | LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | ||
90 | goto BASE_CLEANUP_1; | ||
91 | } | ||
92 | |||
93 | rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, 8); | ||
94 | |||
95 | if (rc != VPX_CODEC_OK) { | ||
96 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
97 | vpx_codec_destroy(vc->encoder); | ||
98 | goto BASE_CLEANUP_1; | ||
99 | } | ||
100 | |||
101 | vc->linfts = current_time_monotonic(); | ||
102 | vc->lcfd = 60; | ||
103 | vc->vcb.first = cb; | ||
104 | vc->vcb.second = cb_data; | ||
105 | vc->friend_number = friend_number; | ||
106 | vc->av = av; | ||
107 | |||
108 | return vc; | ||
109 | |||
110 | BASE_CLEANUP_1: | ||
111 | vpx_codec_destroy(vc->decoder); | ||
112 | BASE_CLEANUP: | ||
113 | pthread_mutex_destroy(vc->queue_mutex); | ||
114 | rb_kill(vc->vbuf_raw); | ||
115 | free(vc); | ||
116 | return NULL; | ||
117 | } | ||
118 | void vc_kill(VCSession *vc) | ||
119 | { | ||
120 | if (!vc) | ||
121 | return; | ||
122 | |||
123 | vpx_codec_destroy(vc->encoder); | ||
124 | vpx_codec_destroy(vc->decoder); | ||
125 | |||
126 | void *p; | ||
127 | |||
128 | while (rb_read(vc->vbuf_raw, (void **)&p)) | ||
129 | free(p); | ||
130 | |||
131 | rb_kill(vc->vbuf_raw); | ||
132 | |||
133 | pthread_mutex_destroy(vc->queue_mutex); | ||
134 | |||
135 | LOGGER_DEBUG("Terminated video handler: %p", vc); | ||
136 | free(vc); | ||
137 | } | ||
138 | void vc_iterate(VCSession *vc) | ||
139 | { | ||
140 | if (!vc) | ||
141 | return; | ||
142 | |||
143 | struct RTPMessage *p; | ||
144 | int rc; | ||
145 | |||
146 | pthread_mutex_lock(vc->queue_mutex); | ||
147 | |||
148 | if (rb_read(vc->vbuf_raw, (void **)&p)) { | ||
149 | pthread_mutex_unlock(vc->queue_mutex); | ||
150 | |||
151 | rc = vpx_codec_decode(vc->decoder, p->data, p->len, NULL, MAX_DECODE_TIME_US); | ||
152 | free(p); | ||
153 | |||
154 | if (rc != VPX_CODEC_OK) | ||
155 | LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); | ||
156 | else { | ||
157 | vpx_codec_iter_t iter = NULL; | ||
158 | vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); | ||
159 | |||
160 | /* Play decoded images */ | ||
161 | for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) { | ||
162 | if (vc->vcb.first) | ||
163 | vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h, | ||
164 | (const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2], | ||
165 | dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second); | ||
166 | |||
167 | vpx_img_free(dest); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | return; | ||
172 | } | ||
173 | |||
174 | pthread_mutex_unlock(vc->queue_mutex); | ||
175 | } | ||
176 | int vc_queue_message(void *vcp, struct RTPMessage *msg) | ||
177 | { | ||
178 | /* This function does the reconstruction of video packets. | ||
179 | * See more info about video splitting in docs | ||
180 | */ | ||
181 | if (!vcp || !msg) | ||
182 | return -1; | ||
183 | |||
184 | if (msg->header.pt == (rtp_TypeVideo + 2) % 128) { | ||
185 | LOGGER_WARNING("Got dummy!"); | ||
186 | free(msg); | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | if (msg->header.pt != rtp_TypeVideo % 128) { | ||
191 | LOGGER_WARNING("Invalid payload type!"); | ||
192 | free(msg); | ||
193 | return -1; | ||
194 | } | ||
195 | |||
196 | VCSession *vc = vcp; | ||
197 | |||
198 | pthread_mutex_lock(vc->queue_mutex); | ||
199 | free(rb_write(vc->vbuf_raw, msg)); | ||
200 | { | ||
201 | /* Calculate time took for peer to send us this frame */ | ||
202 | uint32_t t_lcfd = current_time_monotonic() - vc->linfts; | ||
203 | vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; | ||
204 | vc->linfts = current_time_monotonic(); | ||
205 | } | ||
206 | pthread_mutex_unlock(vc->queue_mutex); | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height) | ||
211 | { | ||
212 | if (!vc) | ||
213 | return -1; | ||
214 | |||
215 | vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc; | ||
216 | int rc; | ||
217 | |||
218 | if (cfg.rc_target_bitrate == bit_rate && cfg.g_w == width && cfg.g_h == height) | ||
219 | return 0; /* Nothing changed */ | ||
220 | |||
221 | if (cfg.g_w == width && cfg.g_h == height) { | ||
222 | /* Only bit rate changed */ | ||
223 | cfg.rc_target_bitrate = bit_rate; | ||
224 | |||
225 | rc = vpx_codec_enc_config_set(vc->encoder, &cfg); | ||
226 | |||
227 | if (rc != VPX_CODEC_OK) { | ||
228 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
229 | return -1; | ||
230 | } | ||
231 | } else { | ||
232 | /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support | ||
233 | * reconfiguring encoder to use resolutions greater than initially set. | ||
234 | */ | ||
235 | |||
236 | LOGGER_DEBUG("Have to reinitialize vpx encoder on session %p", vc); | ||
237 | |||
238 | cfg.rc_target_bitrate = bit_rate; | ||
239 | cfg.g_w = width; | ||
240 | cfg.g_h = height; | ||
241 | |||
242 | vpx_codec_ctx_t new_c; | ||
243 | |||
244 | rc = vpx_codec_enc_init(&new_c, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
245 | |||
246 | if (rc != VPX_CODEC_OK) { | ||
247 | LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | ||
248 | return -1; | ||
249 | } | ||
250 | |||
251 | rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, 8); | ||
252 | |||
253 | if (rc != VPX_CODEC_OK) { | ||
254 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
255 | vpx_codec_destroy(&new_c); | ||
256 | return -1; | ||
257 | } | ||
258 | |||
259 | vpx_codec_destroy(vc->encoder); | ||
260 | memcpy(vc->encoder, &new_c, sizeof(new_c)); | ||
261 | } | ||
262 | |||
263 | return 0; | ||
264 | } | ||
diff --git a/toxav/video.h b/toxav/video.h new file mode 100644 index 00000000..fb836a35 --- /dev/null +++ b/toxav/video.h | |||
@@ -0,0 +1,67 @@ | |||
1 | /** video.h | ||
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
4 | * | ||
5 | * This file is part of Tox. | ||
6 | * | ||
7 | * Tox is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 3 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * Tox is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with Tox. If not, see <http://www.gnu.org/licenses/>. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #ifndef VIDEO_H | ||
23 | #define VIDEO_H | ||
24 | |||
25 | #include <vpx/vpx_decoder.h> | ||
26 | #include <vpx/vpx_encoder.h> | ||
27 | #include <vpx/vp8dx.h> | ||
28 | #include <vpx/vp8cx.h> | ||
29 | #include <vpx/vpx_image.h> | ||
30 | #define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) | ||
31 | #define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) | ||
32 | |||
33 | #include <pthread.h> | ||
34 | |||
35 | #include "toxav.h" | ||
36 | |||
37 | #include "../toxcore/util.h" | ||
38 | |||
39 | struct RTPMessage; | ||
40 | |||
41 | typedef struct VCSession_s { | ||
42 | /* encoding */ | ||
43 | vpx_codec_ctx_t encoder[1]; | ||
44 | uint32_t frame_counter; | ||
45 | |||
46 | /* decoding */ | ||
47 | vpx_codec_ctx_t decoder[1]; | ||
48 | void *vbuf_raw; /* Un-decoded data */ | ||
49 | |||
50 | uint64_t linfts; /* Last received frame time stamp */ | ||
51 | uint32_t lcfd; /* Last calculated frame duration for incoming video payload */ | ||
52 | |||
53 | ToxAV *av; | ||
54 | uint32_t friend_number; | ||
55 | |||
56 | PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ | ||
57 | |||
58 | pthread_mutex_t queue_mutex[1]; | ||
59 | } VCSession; | ||
60 | |||
61 | VCSession *vc_new(ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data); | ||
62 | void vc_kill(VCSession *vc); | ||
63 | void vc_iterate(VCSession *vc); | ||
64 | int vc_queue_message(void *vcp, struct RTPMessage *msg); | ||
65 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height); | ||
66 | |||
67 | #endif /* VIDEO_H */ | ||
diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 2ff2ac13..6d45077a 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c | |||
@@ -1264,7 +1264,7 @@ int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uin | |||
1264 | if (ft->status != FILESTATUS_NOT_ACCEPTED) | 1264 | if (ft->status != FILESTATUS_NOT_ACCEPTED) |
1265 | return -5; | 1265 | return -5; |
1266 | 1266 | ||
1267 | if (position > ft->size) { | 1267 | if (position >= ft->size) { |
1268 | return -6; | 1268 | return -6; |
1269 | } | 1269 | } |
1270 | 1270 | ||
@@ -1569,7 +1569,7 @@ static int handle_filecontrol(Messenger *m, int32_t friendnumber, uint8_t receiv | |||
1569 | return -1; | 1569 | return -1; |
1570 | } | 1570 | } |
1571 | 1571 | ||
1572 | /* seek can only be sent by the receiver to seek before resuming broken tranfers. */ | 1572 | /* seek can only be sent by the receiver to seek before resuming broken transfers. */ |
1573 | if (ft->status != FILESTATUS_NOT_ACCEPTED || !receive_send) { | 1573 | if (ft->status != FILESTATUS_NOT_ACCEPTED || !receive_send) { |
1574 | return -1; | 1574 | return -1; |
1575 | } | 1575 | } |
@@ -1577,7 +1577,7 @@ static int handle_filecontrol(Messenger *m, int32_t friendnumber, uint8_t receiv | |||
1577 | memcpy(&position, data, sizeof(position)); | 1577 | memcpy(&position, data, sizeof(position)); |
1578 | net_to_host((uint8_t *) &position, sizeof(position)); | 1578 | net_to_host((uint8_t *) &position, sizeof(position)); |
1579 | 1579 | ||
1580 | if (position > ft->size) { | 1580 | if (position >= ft->size) { |
1581 | return -1; | 1581 | return -1; |
1582 | } | 1582 | } |
1583 | 1583 | ||
@@ -2128,6 +2128,11 @@ static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len) | |||
2128 | file_data = data + 1; | 2128 | file_data = data + 1; |
2129 | } | 2129 | } |
2130 | 2130 | ||
2131 | /* Prevent more data than the filesize from being passed to clients. */ | ||
2132 | if ((ft->transferred + file_data_length) > ft->size) { | ||
2133 | file_data_length = ft->size - ft->transferred; | ||
2134 | } | ||
2135 | |||
2131 | if (m->file_filedata) | 2136 | if (m->file_filedata) |
2132 | (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, m->file_filedata_userdata); | 2137 | (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, m->file_filedata_userdata); |
2133 | 2138 | ||
@@ -2240,7 +2245,7 @@ static void connection_status_cb(Messenger *m) | |||
2240 | } | 2245 | } |
2241 | 2246 | ||
2242 | 2247 | ||
2243 | #ifdef LOGGING | 2248 | #ifdef TOX_LOGGER |
2244 | #define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL | 2249 | #define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL |
2245 | static time_t lastdump = 0; | 2250 | static time_t lastdump = 0; |
2246 | static char IDString[crypto_box_PUBLICKEYBYTES * 2 + 1]; | 2251 | static char IDString[crypto_box_PUBLICKEYBYTES * 2 + 1]; |
@@ -2316,7 +2321,7 @@ void do_messenger(Messenger *m) | |||
2316 | do_friends(m); | 2321 | do_friends(m); |
2317 | connection_status_cb(m); | 2322 | connection_status_cb(m); |
2318 | 2323 | ||
2319 | #ifdef LOGGING | 2324 | #ifdef TOX_LOGGER |
2320 | 2325 | ||
2321 | if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { | 2326 | if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { |
2322 | 2327 | ||
@@ -2415,7 +2420,7 @@ void do_messenger(Messenger *m) | |||
2415 | } | 2420 | } |
2416 | } | 2421 | } |
2417 | 2422 | ||
2418 | #endif /* LOGGING */ | 2423 | #endif /* TOX_LOGGER */ |
2419 | } | 2424 | } |
2420 | 2425 | ||
2421 | /* new messenger format for load/save, more robust and forward compatible */ | 2426 | /* new messenger format for load/save, more robust and forward compatible */ |
diff --git a/toxcore/assoc.c b/toxcore/assoc.c index 44c4cc30..932adc76 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) | |||
878 | } | 878 | } |
879 | } | 879 | } |
880 | 880 | ||
881 | #ifdef LOGGING | 881 | #ifdef TOX_LOGGER |
882 | static char *idpart2str(uint8_t *id, size_t len); | 882 | static char *idpart2str(uint8_t *id, size_t len); |
883 | #endif /* LOGGING */ | 883 | #endif /* TOX_LOGGER */ |
884 | 884 | ||
885 | /* refresh buckets */ | 885 | /* refresh buckets */ |
886 | void do_Assoc(Assoc *assoc, DHT *dht) | 886 | void do_Assoc(Assoc *assoc, DHT *dht) |
@@ -974,7 +974,7 @@ void kill_Assoc(Assoc *assoc) | |||
974 | } | 974 | } |
975 | } | 975 | } |
976 | 976 | ||
977 | #ifdef LOGGING | 977 | #ifdef TOX_LOGGER |
978 | 978 | ||
979 | static char buffer[crypto_box_PUBLICKEYBYTES * 2 + 1]; | 979 | static char buffer[crypto_box_PUBLICKEYBYTES * 2 + 1]; |
980 | static char *idpart2str(uint8_t *id, size_t len) | 980 | static char *idpart2str(uint8_t *id, size_t len) |
@@ -1028,4 +1028,4 @@ void Assoc_status(const Assoc *assoc) | |||
1028 | } | 1028 | } |
1029 | } | 1029 | } |
1030 | 1030 | ||
1031 | #endif /* LOGGING */ | 1031 | #endif /* TOX_LOGGER */ |
diff --git a/toxcore/assoc.h b/toxcore/assoc.h index 1b4e1ff9..65a2745d 100644 --- a/toxcore/assoc.h +++ b/toxcore/assoc.h | |||
@@ -97,8 +97,8 @@ void do_Assoc(Assoc *assoc, DHT *dht); | |||
97 | /* destroy */ | 97 | /* destroy */ |
98 | void kill_Assoc(Assoc *assoc); | 98 | void kill_Assoc(Assoc *assoc); |
99 | 99 | ||
100 | #ifdef LOGGING | 100 | #ifdef TOX_LOGGER |
101 | void Assoc_status(const Assoc *assoc); | 101 | void Assoc_status(const Assoc *assoc); |
102 | #endif /* LOGGING */ | 102 | #endif /* TOX_LOGGER */ |
103 | 103 | ||
104 | #endif /* !__ASSOC_H__ */ | 104 | #endif /* !__ASSOC_H__ */ |
diff --git a/toxcore/logger.c b/toxcore/logger.c index e8aef7e0..f19f76b1 100644 --- a/toxcore/logger.c +++ b/toxcore/logger.c | |||
@@ -44,7 +44,7 @@ | |||
44 | #endif | 44 | #endif |
45 | 45 | ||
46 | 46 | ||
47 | struct logger { | 47 | struct Logger { |
48 | FILE *log_file; | 48 | FILE *log_file; |
49 | LOG_LEVEL level; | 49 | LOG_LEVEL level; |
50 | uint64_t start_time; /* Time when lib loaded */ | 50 | uint64_t start_time; /* Time when lib loaded */ |
@@ -87,7 +87,7 @@ char *strtime(char *dest, size_t max_len) | |||
87 | */ | 87 | */ |
88 | Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) | 88 | Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) |
89 | { | 89 | { |
90 | #ifndef LOGGING /* Disabled */ | 90 | #ifndef TOX_LOGGER /* Disabled */ |
91 | return NULL; | 91 | return NULL; |
92 | #endif | 92 | #endif |
93 | 93 | ||
@@ -96,7 +96,7 @@ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) | |||
96 | if (!retu) | 96 | if (!retu) |
97 | return NULL; | 97 | return NULL; |
98 | 98 | ||
99 | if ( pthread_mutex_init(retu->mutex, NULL) != 0 ) { | 99 | if (pthread_mutex_init(retu->mutex, NULL) != 0) { |
100 | free(retu); | 100 | free(retu); |
101 | return NULL; | 101 | return NULL; |
102 | } | 102 | } |
@@ -110,7 +110,7 @@ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) | |||
110 | 110 | ||
111 | if (!(retu->tstr = calloc(16, sizeof (char))) || | 111 | if (!(retu->tstr = calloc(16, sizeof (char))) || |
112 | !(retu->posstr = calloc(300, sizeof (char))) || | 112 | !(retu->posstr = calloc(300, sizeof (char))) || |
113 | !(retu->msg = calloc(4096, sizeof (char))) ) | 113 | !(retu->msg = calloc(4096, sizeof (char)))) |
114 | goto FAILURE; | 114 | goto FAILURE; |
115 | 115 | ||
116 | if (id) { | 116 | if (id) { |
@@ -147,7 +147,7 @@ FAILURE: | |||
147 | 147 | ||
148 | void logger_kill(Logger *log) | 148 | void logger_kill(Logger *log) |
149 | { | 149 | { |
150 | #ifndef LOGGING /* Disabled */ | 150 | #ifndef TOX_LOGGER /* Disabled */ |
151 | return; | 151 | return; |
152 | #endif | 152 | #endif |
153 | 153 | ||
@@ -160,7 +160,7 @@ void logger_kill(Logger *log) | |||
160 | free(log->posstr); | 160 | free(log->posstr); |
161 | free(log->msg); | 161 | free(log->msg); |
162 | 162 | ||
163 | if (fclose(log->log_file) != 0 ) | 163 | if (fclose(log->log_file) != 0) |
164 | perror("Could not close log file"); | 164 | perror("Could not close log file"); |
165 | 165 | ||
166 | pthread_mutex_unlock(log->mutex); | 166 | pthread_mutex_unlock(log->mutex); |
@@ -177,7 +177,7 @@ void logger_kill_global(void) | |||
177 | 177 | ||
178 | void logger_set_global(Logger *log) | 178 | void logger_set_global(Logger *log) |
179 | { | 179 | { |
180 | #ifndef LOGGING /* Disabled */ | 180 | #ifndef TOX_LOGGER /* Disabled */ |
181 | return; | 181 | return; |
182 | #endif | 182 | #endif |
183 | 183 | ||
@@ -186,7 +186,7 @@ void logger_set_global(Logger *log) | |||
186 | 186 | ||
187 | Logger *logger_get_global(void) | 187 | Logger *logger_get_global(void) |
188 | { | 188 | { |
189 | #ifndef LOGGING /* Disabled */ | 189 | #ifndef TOX_LOGGER /* Disabled */ |
190 | return NULL; | 190 | return NULL; |
191 | #endif | 191 | #endif |
192 | 192 | ||
@@ -195,17 +195,17 @@ Logger *logger_get_global(void) | |||
195 | 195 | ||
196 | void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, const char *format, ...) | 196 | void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, const char *format, ...) |
197 | { | 197 | { |
198 | #ifndef LOGGING /* Disabled */ | 198 | #ifndef TOX_LOGGER /* Disabled */ |
199 | return; | 199 | return; |
200 | #endif | 200 | #endif |
201 | 201 | ||
202 | static const char *logger_format = | 202 | static const char *logger_format = |
203 | "%s " /* Logger id string */ | 203 | "%s " /* Logger id string */ |
204 | "%-16s" /* Time string of format: %m:%d %H:%M:%S */ | 204 | "%-16s" /* Time string of format: %m:%d %H:%M:%S */ |
205 | "%u " /* Thread id */ | 205 | "%-12u " /* Thread id */ |
206 | "%-5s " /* Logger lever string */ | 206 | "%-5s " /* Logger lever string */ |
207 | "%-20s " /* File:line string */ | 207 | "%-20s " /* File:line string */ |
208 | "- %s" /* Output message */ | 208 | "- %s" /* Output message */ |
209 | WIN_CR "\n"; /* Every new print new line */ | 209 | WIN_CR "\n"; /* Every new print new line */ |
210 | 210 | ||
211 | 211 | ||
diff --git a/toxcore/logger.h b/toxcore/logger.h index 0513b32c..4d3e3b54 100644 --- a/toxcore/logger.h +++ b/toxcore/logger.h | |||
@@ -43,7 +43,7 @@ typedef enum { | |||
43 | LOG_ERROR | 43 | LOG_ERROR |
44 | } LOG_LEVEL; | 44 | } LOG_LEVEL; |
45 | 45 | ||
46 | typedef struct logger Logger; | 46 | typedef struct Logger Logger; |
47 | 47 | ||
48 | /** | 48 | /** |
49 | * Set 'level' as the lowest printable level. If id == NULL, random number is used. | 49 | * 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 | |||
66 | 66 | ||
67 | 67 | ||
68 | /* To do some checks or similar only when logging, use this */ | 68 | /* To do some checks or similar only when logging, use this */ |
69 | #ifdef LOGGING | 69 | #ifdef TOX_LOGGER |
70 | # define LOGGER_SCOPE(__SCOPE_DO__) do { __SCOPE_DO__ } while(0) | 70 | # define LOGGER_SCOPE(__SCOPE_DO__) do { __SCOPE_DO__ } while(0) |
71 | # define LOGGER_WRITE(log, level, format, ...) \ | 71 | # define LOGGER_WRITE(log, level, format, ...) \ |
72 | logger_write(log, level, __FILE__, __LINE__, format, ##__VA_ARGS__ ) | 72 | logger_write(log, level, __FILE__, __LINE__, format, ##__VA_ARGS__) |
73 | #else | 73 | #else |
74 | /* # warning "Logging disabled" */ | ||
74 | # define LOGGER_SCOPE(__SCOPE_DO__) do {} while(0) | 75 | # define LOGGER_SCOPE(__SCOPE_DO__) do {} while(0) |
75 | # define LOGGER_WRITE(log, level, format, ...) do {} while(0) | 76 | # define LOGGER_WRITE(log, level, format, ...) do {} while(0) |
76 | #endif /* LOGGING */ | 77 | #endif /* TOX_LOGGER */ |
77 | 78 | ||
78 | /* To log with an logger */ | 79 | /* To log with an logger */ |
79 | #define LOGGER_TRACE_(log, format, ...) LOGGER_WRITE(log, LOG_TRACE, format, ##__VA_ARGS__ ) | 80 | #define LOGGER_TRACE_(log, format, ...) LOGGER_WRITE(log, LOG_TRACE, format, ##__VA_ARGS__) |
80 | #define LOGGER_DEBUG_(log, format, ...) LOGGER_WRITE(log, LOG_DEBUG, format, ##__VA_ARGS__ ) | 81 | #define LOGGER_DEBUG_(log, format, ...) LOGGER_WRITE(log, LOG_DEBUG, format, ##__VA_ARGS__) |
81 | #define LOGGER_INFO_(log, format, ...) LOGGER_WRITE(log, LOG_INFO, format, ##__VA_ARGS__ ) | 82 | #define LOGGER_INFO_(log, format, ...) LOGGER_WRITE(log, LOG_INFO, format, ##__VA_ARGS__) |
82 | #define LOGGER_WARNING_(log, format, ...) LOGGER_WRITE(log, LOG_WARNING, format, ##__VA_ARGS__ ) | 83 | #define LOGGER_WARNING_(log, format, ...) LOGGER_WRITE(log, LOG_WARNING, format, ##__VA_ARGS__) |
83 | #define LOGGER_ERROR_(log, format, ...) LOGGER_WRITE(log, LOG_ERROR, format, ##__VA_ARGS__ ) | 84 | #define LOGGER_ERROR_(log, format, ...) LOGGER_WRITE(log, LOG_ERROR, format, ##__VA_ARGS__) |
84 | 85 | ||
85 | /* To log with the global logger */ | 86 | /* To log with the global logger */ |
86 | #define LOGGER_TRACE(format, ...) LOGGER_TRACE_(NULL, format, ##__VA_ARGS__) | 87 | #define LOGGER_TRACE(format, ...) LOGGER_TRACE_(NULL, format, ##__VA_ARGS__) |
diff --git a/toxcore/network.c b/toxcore/network.c index 22ee4202..965e65f9 100644 --- a/toxcore/network.c +++ b/toxcore/network.c | |||
@@ -266,7 +266,7 @@ uint64_t current_time_monotonic(void) | |||
266 | } | 266 | } |
267 | 267 | ||
268 | /* In case no logging */ | 268 | /* In case no logging */ |
269 | #ifndef LOGGING | 269 | #ifndef TOX_LOGGER |
270 | #define loglogdata(__message__, __buffer__, __buflen__, __ip_port__, __res__) | 270 | #define loglogdata(__message__, __buffer__, __buflen__, __ip_port__, __res__) |
271 | #else | 271 | #else |
272 | #define data_0(__buflen__, __buffer__) __buflen__ > 4 ? ntohl(*(uint32_t *)&__buffer__[1]) : 0 | 272 | #define data_0(__buflen__, __buffer__) __buflen__ > 4 ? ntohl(*(uint32_t *)&__buffer__[1]) : 0 |
@@ -287,7 +287,7 @@ uint64_t current_time_monotonic(void) | |||
287 | __buffer__[0], __message__, (size_t)__res__, (!__res__ ? '!' : '>'), __buflen__, \ | 287 | __buffer__[0], __message__, (size_t)__res__, (!__res__ ? '!' : '>'), __buflen__, \ |
288 | ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), 0, "OK", data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); | 288 | ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), 0, "OK", data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); |
289 | 289 | ||
290 | #endif /* LOGGING */ | 290 | #endif /* TOX_LOGGER */ |
291 | 291 | ||
292 | /* Basic network functions: | 292 | /* Basic network functions: |
293 | * Function to send packet(data) of length length to ip_port. | 293 | * 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, | |||
615 | } | 615 | } |
616 | 616 | ||
617 | if (ip.family == AF_INET6) { | 617 | if (ip.family == AF_INET6) { |
618 | #ifdef LOGGING | 618 | #ifdef TOX_LOGGER |
619 | int is_dualstack = | 619 | int is_dualstack = |
620 | #endif /* LOGGING */ | 620 | #endif /* TOX_LOGGER */ |
621 | set_socket_dualstack(temp->sock); | 621 | set_socket_dualstack(temp->sock); |
622 | LOGGER_DEBUG( "Dual-stack socket: %s", | 622 | LOGGER_DEBUG( "Dual-stack socket: %s", |
623 | is_dualstack ? "enabled" : "Failed to enable, won't be able to receive from/send to IPv4 addresses" ); | 623 | 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, | |||
628 | mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; | 628 | mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; |
629 | mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; | 629 | mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; |
630 | mreq.ipv6mr_interface = 0; | 630 | mreq.ipv6mr_interface = 0; |
631 | #ifdef LOGGING | 631 | #ifdef TOX_LOGGER |
632 | int res = | 632 | int res = |
633 | #endif /* LOGGING */ | 633 | #endif /* TOX_LOGGER */ |
634 | setsockopt(temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)); | 634 | setsockopt(temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)); |
635 | 635 | ||
636 | LOGGER_DEBUG(res < 0 ? "Failed to activate local multicast membership. (%u, %s)" : | 636 | LOGGER_DEBUG(res < 0 ? "Failed to activate local multicast membership. (%u, %s)" : |
diff --git a/toxcore/onion_client.h b/toxcore/onion_client.h index ad28ac51..416d593a 100644 --- a/toxcore/onion_client.h +++ b/toxcore/onion_client.h | |||
@@ -29,7 +29,7 @@ | |||
29 | #include "ping_array.h" | 29 | #include "ping_array.h" |
30 | 30 | ||
31 | #define MAX_ONION_CLIENTS 8 | 31 | #define MAX_ONION_CLIENTS 8 |
32 | #define ONION_NODE_PING_INTERVAL 20 | 32 | #define ONION_NODE_PING_INTERVAL 15 |
33 | #define ONION_NODE_TIMEOUT (ONION_NODE_PING_INTERVAL * 3) | 33 | #define ONION_NODE_TIMEOUT (ONION_NODE_PING_INTERVAL * 3) |
34 | 34 | ||
35 | /* The interval in seconds at which to tell our friends where we are */ | 35 | /* The interval in seconds at which to tell our friends where we are */ |
diff --git a/toxcore/util.c b/toxcore/util.c index c320cdc4..28d8721c 100644 --- a/toxcore/util.c +++ b/toxcore/util.c | |||
@@ -192,3 +192,87 @@ int create_recursive_mutex(pthread_mutex_t *mutex) | |||
192 | 192 | ||
193 | return 0; | 193 | return 0; |
194 | } | 194 | } |
195 | |||
196 | |||
197 | struct RingBuffer { | ||
198 | uint16_t size; /* Max size */ | ||
199 | uint16_t start; | ||
200 | uint16_t end; | ||
201 | void **data; | ||
202 | }; | ||
203 | |||
204 | bool rb_full(const RingBuffer *b) | ||
205 | { | ||
206 | return (b->end + 1) % b->size == b->start; | ||
207 | } | ||
208 | bool rb_empty(const RingBuffer *b) | ||
209 | { | ||
210 | return b->end == b->start; | ||
211 | } | ||
212 | void *rb_write(RingBuffer *b, void *p) | ||
213 | { | ||
214 | void *rc = NULL; | ||
215 | |||
216 | if ((b->end + 1) % b->size == b->start) /* full */ | ||
217 | rc = b->data[b->start]; | ||
218 | |||
219 | b->data[b->end] = p; | ||
220 | b->end = (b->end + 1) % b->size; | ||
221 | |||
222 | if (b->end == b->start) | ||
223 | b->start = (b->start + 1) % b->size; | ||
224 | |||
225 | return rc; | ||
226 | } | ||
227 | bool rb_read(RingBuffer *b, void **p) | ||
228 | { | ||
229 | if (b->end == b->start) { /* Empty */ | ||
230 | *p = NULL; | ||
231 | return false; | ||
232 | } | ||
233 | |||
234 | *p = b->data[b->start]; | ||
235 | b->start = (b->start + 1) % b->size; | ||
236 | return true; | ||
237 | } | ||
238 | RingBuffer *rb_new(int size) | ||
239 | { | ||
240 | RingBuffer *buf = calloc(sizeof(RingBuffer), 1); | ||
241 | |||
242 | if (!buf) return NULL; | ||
243 | |||
244 | buf->size = size + 1; /* include empty elem */ | ||
245 | |||
246 | if (!(buf->data = calloc(buf->size, sizeof(void *)))) { | ||
247 | free(buf); | ||
248 | return NULL; | ||
249 | } | ||
250 | |||
251 | return buf; | ||
252 | } | ||
253 | void rb_kill(RingBuffer *b) | ||
254 | { | ||
255 | if (b) { | ||
256 | free(b->data); | ||
257 | free(b); | ||
258 | } | ||
259 | } | ||
260 | uint16_t rb_size(const RingBuffer *b) | ||
261 | { | ||
262 | if (rb_empty(b)) | ||
263 | return 0; | ||
264 | |||
265 | return | ||
266 | b->end > b->start ? | ||
267 | b->end - b->start : | ||
268 | (b->size - b->start) + b->end; | ||
269 | } | ||
270 | uint16_t rb_data(const RingBuffer *b, void **dest) | ||
271 | { | ||
272 | uint16_t i = 0; | ||
273 | |||
274 | for (; i < rb_size(b); i++) | ||
275 | dest[i] = b->data[(b->start + i) % b->size]; | ||
276 | |||
277 | return i; | ||
278 | } | ||
diff --git a/toxcore/util.h b/toxcore/util.h index fde9f03c..bdbf7d3b 100644 --- a/toxcore/util.h +++ b/toxcore/util.h | |||
@@ -30,6 +30,7 @@ | |||
30 | #include <pthread.h> | 30 | #include <pthread.h> |
31 | 31 | ||
32 | #define MIN(a,b) (((a)<(b))?(a):(b)) | 32 | #define MIN(a,b) (((a)<(b))?(a):(b)) |
33 | #define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } | ||
33 | 34 | ||
34 | void unix_time_update(); | 35 | void unix_time_update(); |
35 | uint64_t unix_time(); | 36 | uint64_t unix_time(); |
@@ -54,6 +55,18 @@ typedef int (*load_state_callback_func)(void *outer, const uint8_t *data, uint32 | |||
54 | int load_state(load_state_callback_func load_state_callback, void *outer, | 55 | int load_state(load_state_callback_func load_state_callback, void *outer, |
55 | const uint8_t *data, uint32_t length, uint16_t cookie_inner); | 56 | const uint8_t *data, uint32_t length, uint16_t cookie_inner); |
56 | 57 | ||
58 | /* Returns -1 if failed or 0 if success */ | ||
57 | int create_recursive_mutex(pthread_mutex_t *mutex); | 59 | int create_recursive_mutex(pthread_mutex_t *mutex); |
58 | 60 | ||
61 | /* Ring buffer */ | ||
62 | typedef struct RingBuffer RingBuffer; | ||
63 | bool rb_full(const RingBuffer *b); | ||
64 | bool rb_empty(const RingBuffer *b); | ||
65 | void *rb_write(RingBuffer *b, void *p); | ||
66 | bool rb_read(RingBuffer *b, void **p); | ||
67 | RingBuffer *rb_new(int size); | ||
68 | void rb_kill(RingBuffer *b); | ||
69 | uint16_t rb_size(const RingBuffer *b); | ||
70 | uint16_t rb_data(const RingBuffer *b, void **dest); | ||
71 | |||
59 | #endif /* __UTIL_H__ */ | 72 | #endif /* __UTIL_H__ */ |
diff --git a/toxdns/toxdns.c b/toxdns/toxdns.c index f7df5df3..1fbf4a00 100644 --- a/toxdns/toxdns.c +++ b/toxdns/toxdns.c | |||
@@ -44,7 +44,7 @@ uint8_t i = 0; \ | |||
44 | } \ | 44 | } \ |
45 | } \ | 45 | } \ |
46 | } \ | 46 | } \ |
47 | 47 | ||
48 | typedef struct { | 48 | typedef struct { |
49 | uint8_t temp_pk[crypto_box_PUBLICKEYBYTES]; | 49 | uint8_t temp_pk[crypto_box_PUBLICKEYBYTES]; |
50 | uint8_t temp_sk[crypto_box_SECRETKEYBYTES]; | 50 | uint8_t temp_sk[crypto_box_SECRETKEYBYTES]; |