diff options
Diffstat (limited to 'toxav')
-rw-r--r-- | toxav/Makefile.inc | 6 | ||||
-rw-r--r-- | toxav/audio.c | 424 | ||||
-rw-r--r-- | toxav/audio.h | 89 | ||||
-rw-r--r-- | toxav/codec.c | 688 | ||||
-rw-r--r-- | toxav/codec.h | 176 | ||||
-rw-r--r-- | toxav/msi.c | 2083 | ||||
-rw-r--r-- | toxav/msi.h | 195 | ||||
-rw-r--r-- | toxav/rtp.c | 709 | ||||
-rw-r--r-- | toxav/rtp.h | 122 | ||||
-rw-r--r-- | toxav/toxav.c | 1735 | ||||
-rw-r--r-- | toxav/toxav.h | 799 | ||||
-rw-r--r-- | toxav/video.c | 375 | ||||
-rw-r--r-- | toxav/video.h | 113 |
13 files changed, 3932 insertions, 3582 deletions
diff --git a/toxav/Makefile.inc b/toxav/Makefile.inc index 0b4b869d..3907951e 100644 --- a/toxav/Makefile.inc +++ b/toxav/Makefile.inc | |||
@@ -10,8 +10,10 @@ libtoxav_la_SOURCES = ../toxav/rtp.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/video.h \ | ||
16 | ../toxav/video.c \ | ||
15 | ../toxav/toxav.h \ | 17 | ../toxav/toxav.h \ |
16 | ../toxav/toxav.c | 18 | ../toxav/toxav.c |
17 | 19 | ||
diff --git a/toxav/audio.c b/toxav/audio.c new file mode 100644 index 00000000..0a6f04e3 --- /dev/null +++ b/toxav/audio.c | |||
@@ -0,0 +1,424 @@ | |||
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 | #include <stdlib.h> | ||
23 | |||
24 | #include "audio.h" | ||
25 | #include "rtp.h" | ||
26 | |||
27 | #include "../toxcore/logger.h" | ||
28 | |||
29 | static struct JitterBuffer *jbuf_new(uint32_t capacity); | ||
30 | static void jbuf_clear(struct JitterBuffer *q); | ||
31 | static void jbuf_free(struct JitterBuffer *q); | ||
32 | static int jbuf_write(struct JitterBuffer *q, RTPMessage *m); | ||
33 | static RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); | ||
34 | OpusEncoder* create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count); | ||
35 | bool reconfigure_audio_encoder(OpusEncoder** e, int32_t new_br, int32_t new_sr, uint8_t new_ch, | ||
36 | int32_t *old_br, int32_t *old_sr, int32_t *old_ch); | ||
37 | bool reconfigure_audio_decoder(ACSession* ac, int32_t sampling_rate, int8_t channels); | ||
38 | |||
39 | |||
40 | |||
41 | ACSession* ac_new(ToxAV* av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data) | ||
42 | { | ||
43 | ACSession *ac = calloc(sizeof(ACSession), 1); | ||
44 | |||
45 | if (!ac) { | ||
46 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
47 | return NULL; | ||
48 | } | ||
49 | |||
50 | if (create_recursive_mutex(ac->queue_mutex) != 0) { | ||
51 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
52 | free(ac); | ||
53 | return NULL; | ||
54 | } | ||
55 | |||
56 | int status; | ||
57 | ac->decoder = opus_decoder_create(48000, 2, &status ); | ||
58 | |||
59 | if ( status != OPUS_OK ) { | ||
60 | LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(status)); | ||
61 | goto BASE_CLEANUP; | ||
62 | } | ||
63 | |||
64 | if ( !(ac->j_buf = jbuf_new(3)) ) { | ||
65 | LOGGER_WARNING("Jitter buffer creaton failed!"); | ||
66 | opus_decoder_destroy(ac->decoder); | ||
67 | goto BASE_CLEANUP; | ||
68 | } | ||
69 | |||
70 | /* Initialize encoders with default values */ | ||
71 | ac->encoder = create_audio_encoder(48000, 48000, 2); | ||
72 | if (ac->encoder == NULL) | ||
73 | goto DECODER_CLEANUP; | ||
74 | |||
75 | ac->test_encoder = create_audio_encoder(48000, 48000, 2); | ||
76 | if (ac->test_encoder == NULL) { | ||
77 | opus_encoder_destroy(ac->encoder); | ||
78 | goto DECODER_CLEANUP; | ||
79 | } | ||
80 | |||
81 | ac->last_encoding_bit_rate = 48000; | ||
82 | ac->last_encoding_sampling_rate = 48000; | ||
83 | ac->last_encoding_channel_count = 2; | ||
84 | |||
85 | ac->last_test_encoding_bit_rate = 48000; | ||
86 | ac->last_test_encoding_sampling_rate = 48000; | ||
87 | ac->last_test_encoding_channel_count = 2; | ||
88 | |||
89 | ac->last_decoding_channel_count = 2; | ||
90 | ac->last_decoding_sampling_rate = 48000; | ||
91 | ac->last_decoder_reconfiguration = 0; /* Make it possible to reconfigure straight away */ | ||
92 | |||
93 | /* These need to be set in order to properly | ||
94 | * do error correction with opus */ | ||
95 | ac->last_packet_frame_duration = 120; | ||
96 | ac->last_packet_sampling_rate = 48000; | ||
97 | ac->last_packet_channel_count = 1; | ||
98 | |||
99 | ac->av = av; | ||
100 | ac->friend_number = friend_number; | ||
101 | ac->acb.first = cb; | ||
102 | ac->acb.second = cb_data; | ||
103 | |||
104 | return ac; | ||
105 | |||
106 | DECODER_CLEANUP: | ||
107 | opus_decoder_destroy(ac->decoder); | ||
108 | jbuf_free(ac->j_buf); | ||
109 | BASE_CLEANUP: | ||
110 | pthread_mutex_destroy(ac->queue_mutex); | ||
111 | free(ac); | ||
112 | return NULL; | ||
113 | } | ||
114 | void ac_kill(ACSession* ac) | ||
115 | { | ||
116 | if (!ac) | ||
117 | return; | ||
118 | |||
119 | opus_encoder_destroy(ac->encoder); | ||
120 | opus_decoder_destroy(ac->decoder); | ||
121 | jbuf_free(ac->j_buf); | ||
122 | |||
123 | pthread_mutex_destroy(ac->queue_mutex); | ||
124 | |||
125 | LOGGER_DEBUG("Terminated audio handler: %p", ac); | ||
126 | free(ac); | ||
127 | } | ||
128 | void ac_do(ACSession* ac) | ||
129 | { | ||
130 | if (!ac) | ||
131 | return; | ||
132 | |||
133 | /* Enough space for the maximum frame size (120 ms 48 KHz audio) */ | ||
134 | int16_t tmp[5760 * 2]; | ||
135 | |||
136 | RTPMessage *msg; | ||
137 | int rc = 0; | ||
138 | |||
139 | pthread_mutex_lock(ac->queue_mutex); | ||
140 | while ((msg = jbuf_read(ac->j_buf, &rc)) || rc == 2) { | ||
141 | pthread_mutex_unlock(ac->queue_mutex); | ||
142 | |||
143 | if (rc == 2) { | ||
144 | LOGGER_DEBUG("OPUS correction"); | ||
145 | int fs = (ac->last_packet_sampling_rate * ac->last_packet_frame_duration) / 1000; | ||
146 | rc = opus_decode(ac->decoder, NULL, 0, tmp, fs, 1); | ||
147 | } else { | ||
148 | /* Get values from packet and decode. */ | ||
149 | /* NOTE: This didn't work very well | ||
150 | rc = convert_bw_to_sampling_rate(opus_packet_get_bandwidth(msg->data)); | ||
151 | if (rc != -1) { | ||
152 | cs->last_packet_sampling_rate = rc; | ||
153 | } else { | ||
154 | LOGGER_WARNING("Failed to load packet values!"); | ||
155 | rtp_free_msg(msg); | ||
156 | continue; | ||
157 | }*/ | ||
158 | |||
159 | |||
160 | /* Pick up sampling rate from packet */ | ||
161 | memcpy(&ac->last_packet_sampling_rate, msg->data, 4); | ||
162 | ac->last_packet_sampling_rate = ntohl(ac->last_packet_sampling_rate); | ||
163 | |||
164 | ac->last_packet_channel_count = opus_packet_get_nb_channels(msg->data + 4); | ||
165 | |||
166 | /** NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa, | ||
167 | * it didn't work quite well. | ||
168 | */ | ||
169 | if (!reconfigure_audio_decoder(ac, ac->last_packet_sampling_rate, ac->last_packet_channel_count)) { | ||
170 | LOGGER_WARNING("Failed to reconfigure decoder!"); | ||
171 | rtp_free_msg(msg); | ||
172 | continue; | ||
173 | } | ||
174 | |||
175 | rc = opus_decode(ac->decoder, msg->data + 4, msg->length - 4, tmp, 5760, 0); | ||
176 | rtp_free_msg(msg); | ||
177 | } | ||
178 | |||
179 | if (rc < 0) { | ||
180 | LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); | ||
181 | } else if (ac->acb.first) { | ||
182 | ac->last_packet_frame_duration = (rc * 1000) / ac->last_packet_sampling_rate; | ||
183 | |||
184 | ac->acb.first(ac->av, ac->friend_number, tmp, rc, ac->last_packet_channel_count, | ||
185 | ac->last_packet_sampling_rate, ac->acb.second); | ||
186 | } | ||
187 | |||
188 | return; | ||
189 | } | ||
190 | pthread_mutex_unlock(ac->queue_mutex); | ||
191 | } | ||
192 | int ac_queue_message(void* acp, struct RTPMessage_s *msg) | ||
193 | { | ||
194 | if (!acp || !msg) | ||
195 | return -1; | ||
196 | |||
197 | if ((msg->header->marker_payloadt & 0x7f) == (rtp_TypeAudio + 2) % 128) { | ||
198 | LOGGER_WARNING("Got dummy!"); | ||
199 | rtp_free_msg(msg); | ||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | if ((msg->header->marker_payloadt & 0x7f) != rtp_TypeAudio % 128) { | ||
204 | LOGGER_WARNING("Invalid payload type!"); | ||
205 | rtp_free_msg(msg); | ||
206 | return -1; | ||
207 | } | ||
208 | |||
209 | ACSession* ac = acp; | ||
210 | |||
211 | pthread_mutex_lock(ac->queue_mutex); | ||
212 | int rc = jbuf_write(ac->j_buf, msg); | ||
213 | pthread_mutex_unlock(ac->queue_mutex); | ||
214 | |||
215 | if (rc == -1) { | ||
216 | LOGGER_WARNING("Could not queue the message!"); | ||
217 | rtp_free_msg(msg); | ||
218 | return -1; | ||
219 | } | ||
220 | |||
221 | return 0; | ||
222 | } | ||
223 | int ac_reconfigure_encoder(ACSession* ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels) | ||
224 | { | ||
225 | if (!ac || !reconfigure_audio_encoder(&ac->encoder, bit_rate, sampling_rate, channels, | ||
226 | &ac->last_encoding_bit_rate, &ac->last_encoding_sampling_rate, &ac->last_encoding_channel_count)) | ||
227 | return -1; | ||
228 | |||
229 | LOGGER_DEBUG ("Reconfigured audio encoder br: %d sr: %d cc:%d", bit_rate, sampling_rate, channels); | ||
230 | return 0; | ||
231 | } | ||
232 | int ac_reconfigure_test_encoder(ACSession* ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels) | ||
233 | { | ||
234 | if (!ac || !reconfigure_audio_encoder(&ac->test_encoder, bit_rate, sampling_rate, channels, | ||
235 | &ac->last_encoding_bit_rate, &ac->last_encoding_sampling_rate, &ac->last_encoding_channel_count)) | ||
236 | return -1; | ||
237 | |||
238 | LOGGER_DEBUG ("Reconfigured test audio encoder br: %d sr: %d cc:%d", bit_rate, sampling_rate, channels); | ||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | |||
243 | |||
244 | struct JitterBuffer { | ||
245 | RTPMessage **queue; | ||
246 | uint32_t size; | ||
247 | uint32_t capacity; | ||
248 | uint16_t bottom; | ||
249 | uint16_t top; | ||
250 | }; | ||
251 | |||
252 | static struct JitterBuffer *jbuf_new(uint32_t capacity) | ||
253 | { | ||
254 | unsigned int size = 1; | ||
255 | |||
256 | while (size <= (capacity * 4)) { | ||
257 | size *= 2; | ||
258 | } | ||
259 | |||
260 | struct JitterBuffer *q; | ||
261 | |||
262 | if ( !(q = calloc(sizeof(struct JitterBuffer), 1)) ) return NULL; | ||
263 | |||
264 | if (!(q->queue = calloc(sizeof(RTPMessage *), size))) { | ||
265 | free(q); | ||
266 | return NULL; | ||
267 | } | ||
268 | |||
269 | q->size = size; | ||
270 | q->capacity = capacity; | ||
271 | return q; | ||
272 | } | ||
273 | static void jbuf_clear(struct JitterBuffer *q) | ||
274 | { | ||
275 | for (; q->bottom != q->top; ++q->bottom) { | ||
276 | if (q->queue[q->bottom % q->size]) { | ||
277 | rtp_free_msg(q->queue[q->bottom % q->size]); | ||
278 | q->queue[q->bottom % q->size] = NULL; | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | static void jbuf_free(struct JitterBuffer *q) | ||
283 | { | ||
284 | if (!q) return; | ||
285 | |||
286 | jbuf_clear(q); | ||
287 | free(q->queue); | ||
288 | free(q); | ||
289 | } | ||
290 | static int jbuf_write(struct JitterBuffer *q, RTPMessage *m) | ||
291 | { | ||
292 | uint16_t sequnum = m->header->sequnum; | ||
293 | |||
294 | unsigned int num = sequnum % q->size; | ||
295 | |||
296 | if ((uint32_t)(sequnum - q->bottom) > q->size) { | ||
297 | LOGGER_DEBUG("Clearing filled jitter buffer: %p", q); | ||
298 | |||
299 | jbuf_clear(q); | ||
300 | q->bottom = sequnum - q->capacity; | ||
301 | q->queue[num] = m; | ||
302 | q->top = sequnum + 1; | ||
303 | return 0; | ||
304 | } | ||
305 | |||
306 | if (q->queue[num]) | ||
307 | return -1; | ||
308 | |||
309 | q->queue[num] = m; | ||
310 | |||
311 | if ((sequnum - q->bottom) >= (q->top - q->bottom)) | ||
312 | q->top = sequnum + 1; | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | static RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success) | ||
317 | { | ||
318 | if (q->top == q->bottom) { | ||
319 | *success = 0; | ||
320 | return NULL; | ||
321 | } | ||
322 | |||
323 | unsigned int num = q->bottom % q->size; | ||
324 | |||
325 | if (q->queue[num]) { | ||
326 | RTPMessage *ret = q->queue[num]; | ||
327 | q->queue[num] = NULL; | ||
328 | ++q->bottom; | ||
329 | *success = 1; | ||
330 | return ret; | ||
331 | } | ||
332 | |||
333 | if ((uint32_t)(q->top - q->bottom) > q->capacity) { | ||
334 | ++q->bottom; | ||
335 | *success = 2; | ||
336 | return NULL; | ||
337 | } | ||
338 | |||
339 | *success = 0; | ||
340 | return NULL; | ||
341 | } | ||
342 | OpusEncoder* create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count) | ||
343 | { | ||
344 | int status = OPUS_OK; | ||
345 | OpusEncoder* rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_AUDIO, &status); | ||
346 | |||
347 | if ( status != OPUS_OK ) { | ||
348 | LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(status)); | ||
349 | return NULL; | ||
350 | } | ||
351 | |||
352 | status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate)); | ||
353 | |||
354 | if ( status != OPUS_OK ) { | ||
355 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
356 | goto FAILURE; | ||
357 | } | ||
358 | |||
359 | status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(10)); | ||
360 | |||
361 | if ( status != OPUS_OK ) { | ||
362 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
363 | goto FAILURE; | ||
364 | } | ||
365 | |||
366 | return rc; | ||
367 | |||
368 | FAILURE: | ||
369 | opus_encoder_destroy(rc); | ||
370 | return NULL; | ||
371 | } | ||
372 | bool reconfigure_audio_encoder(OpusEncoder** e, int32_t new_br, int32_t new_sr, uint8_t new_ch, | ||
373 | int32_t* old_br, int32_t* old_sr, int32_t* old_ch) | ||
374 | { | ||
375 | /* Values are checked in toxav.c */ | ||
376 | if (*old_sr != new_sr || *old_ch != new_ch) { | ||
377 | OpusEncoder* new_encoder = create_audio_encoder(new_br, new_sr, new_ch); | ||
378 | if (new_encoder == NULL) | ||
379 | return false; | ||
380 | |||
381 | opus_encoder_destroy(*e); | ||
382 | *e = new_encoder; | ||
383 | } else if (*old_br == new_br) | ||
384 | return true; /* Nothing changed */ | ||
385 | else { | ||
386 | int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br)); | ||
387 | |||
388 | if ( status != OPUS_OK ) { | ||
389 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status)); | ||
390 | return false; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | *old_br = new_br; | ||
395 | *old_sr = new_sr; | ||
396 | *old_ch = new_ch; | ||
397 | |||
398 | return true; | ||
399 | } | ||
400 | bool reconfigure_audio_decoder(ACSession* ac, int32_t sampling_rate, int8_t channels) | ||
401 | { | ||
402 | if (sampling_rate != ac->last_decoding_sampling_rate || channels != ac->last_decoding_channel_count) { | ||
403 | if (current_time_monotonic() - ac->last_decoder_reconfiguration < 500) | ||
404 | return false; | ||
405 | |||
406 | int status; | ||
407 | OpusDecoder* new_dec = opus_decoder_create(sampling_rate, channels, &status ); | ||
408 | if ( status != OPUS_OK ) { | ||
409 | LOGGER_ERROR("Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status)); | ||
410 | return false; | ||
411 | } | ||
412 | |||
413 | ac->last_decoding_sampling_rate = sampling_rate; | ||
414 | ac->last_decoding_channel_count = channels; | ||
415 | ac->last_decoder_reconfiguration = current_time_monotonic(); | ||
416 | |||
417 | opus_decoder_destroy(ac->decoder); | ||
418 | ac->decoder = new_dec; | ||
419 | |||
420 | LOGGER_DEBUG("Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels); | ||
421 | } | ||
422 | |||
423 | return true; | ||
424 | } \ No newline at end of file | ||
diff --git a/toxav/audio.h b/toxav/audio.h new file mode 100644 index 00000000..9ef10ae4 --- /dev/null +++ b/toxav/audio.h | |||
@@ -0,0 +1,89 @@ | |||
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_s; | ||
33 | |||
34 | /* | ||
35 | * Base Audio Codec session type. | ||
36 | */ | ||
37 | typedef struct ACSession_s { | ||
38 | /* encoding */ | ||
39 | OpusEncoder *encoder; | ||
40 | int32_t last_encoding_sampling_rate; | ||
41 | int32_t last_encoding_channel_count; | ||
42 | int32_t last_encoding_bit_rate; | ||
43 | |||
44 | /* Testing encoder for dynamic bit rate streaming */ | ||
45 | OpusEncoder *test_encoder; | ||
46 | int32_t last_test_encoding_sampling_rate; | ||
47 | int32_t last_test_encoding_channel_count; | ||
48 | int32_t last_test_encoding_bit_rate; | ||
49 | |||
50 | /* decoding */ | ||
51 | OpusDecoder *decoder; | ||
52 | int32_t last_packet_channel_count; | ||
53 | int32_t last_packet_sampling_rate; | ||
54 | int32_t last_packet_frame_duration; | ||
55 | int32_t last_decoding_sampling_rate; | ||
56 | int32_t last_decoding_channel_count; | ||
57 | uint64_t last_decoder_reconfiguration; | ||
58 | void *j_buf; | ||
59 | |||
60 | pthread_mutex_t queue_mutex[1]; | ||
61 | |||
62 | ToxAV* av; | ||
63 | uint32_t friend_number; | ||
64 | PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ | ||
65 | } ACSession; | ||
66 | |||
67 | /* | ||
68 | * Create new Audio Codec session. | ||
69 | */ | ||
70 | ACSession* ac_new(ToxAV* av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data); | ||
71 | /* | ||
72 | * Kill the Audio Codec session. | ||
73 | */ | ||
74 | void ac_kill(ACSession* ac); | ||
75 | /* | ||
76 | * Do periodic work. Work is consisted out of decoding only. | ||
77 | */ | ||
78 | void ac_do(ACSession* ac); | ||
79 | /* | ||
80 | * Queue new rtp message. | ||
81 | */ | ||
82 | int ac_queue_message(void *acp, struct RTPMessage_s *msg); | ||
83 | /* | ||
84 | * Set new values to the encoders. | ||
85 | */ | ||
86 | int ac_reconfigure_encoder(ACSession* ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels); | ||
87 | int ac_reconfigure_test_encoder(ACSession* ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels); | ||
88 | |||
89 | #endif /* AUDIO_H */ \ No newline at end of file | ||
diff --git a/toxav/codec.c b/toxav/codec.c deleted file mode 100644 index de802526..00000000 --- a/toxav/codec.c +++ /dev/null | |||
@@ -1,688 +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 "../toxcore/logger.h" | ||
30 | #include "../toxcore/util.h" | ||
31 | |||
32 | #include <stdio.h> | ||
33 | #include <stdlib.h> | ||
34 | #include <math.h> | ||
35 | #include <assert.h> | ||
36 | #include <time.h> | ||
37 | |||
38 | #include "msi.h" | ||
39 | #include "rtp.h" | ||
40 | #include "codec.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_AUDIO, &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 | rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10)); | ||
312 | |||
313 | if ( rc != OPUS_OK ) { | ||
314 | LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); | ||
315 | return -1; | ||
316 | } | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | /* PUBLIC */ | ||
322 | int cs_split_video_payload(CSSession *cs, const uint8_t *payload, uint16_t length) | ||
323 | { | ||
324 | if (!cs || !length || length > cs->max_video_frame_size) { | ||
325 | LOGGER_ERROR("Invalid CodecState or video frame size: %u", length); | ||
326 | return cs_ErrorSplittingVideoPayload; | ||
327 | } | ||
328 | |||
329 | cs->split_video_frame[0] = cs->frameid_out++; | ||
330 | cs->split_video_frame[1] = 0; | ||
331 | cs->processing_video_frame = payload; | ||
332 | cs->processing_video_frame_size = length; | ||
333 | |||
334 | return ((length - 1) / cs->video_frame_piece_size) + 1; | ||
335 | } | ||
336 | |||
337 | const uint8_t *cs_get_split_video_frame(CSSession *cs, uint16_t *size) | ||
338 | { | ||
339 | if (!cs || !size) return NULL; | ||
340 | |||
341 | if (cs->processing_video_frame_size > cs->video_frame_piece_size) { | ||
342 | memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
343 | cs->processing_video_frame, | ||
344 | cs->video_frame_piece_size); | ||
345 | |||
346 | cs->processing_video_frame += cs->video_frame_piece_size; | ||
347 | cs->processing_video_frame_size -= cs->video_frame_piece_size; | ||
348 | |||
349 | *size = cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE; | ||
350 | } else { | ||
351 | memcpy(cs->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
352 | cs->processing_video_frame, | ||
353 | cs->processing_video_frame_size); | ||
354 | |||
355 | *size = cs->processing_video_frame_size + VIDEOFRAME_HEADER_SIZE; | ||
356 | } | ||
357 | |||
358 | cs->split_video_frame[1]++; | ||
359 | |||
360 | return cs->split_video_frame; | ||
361 | } | ||
362 | |||
363 | void cs_do(CSSession *cs) | ||
364 | { | ||
365 | /* Codec session should always be protected by call mutex so no need to check for cs validity | ||
366 | */ | ||
367 | |||
368 | if (!cs) return; | ||
369 | |||
370 | Payload *p; | ||
371 | int rc; | ||
372 | |||
373 | int success = 0; | ||
374 | |||
375 | pthread_mutex_lock(cs->queue_mutex); | ||
376 | RTPMessage *msg; | ||
377 | |||
378 | while ((msg = jbuf_read(cs->j_buf, &success)) || success == 2) { | ||
379 | pthread_mutex_unlock(cs->queue_mutex); | ||
380 | |||
381 | uint16_t fsize = ((cs->audio_decoder_sample_rate * cs->audio_decoder_frame_duration) / 1000); | ||
382 | int16_t tmp[fsize * cs->audio_decoder_channels]; | ||
383 | |||
384 | if (success == 2) { | ||
385 | rc = opus_decode(cs->audio_decoder, 0, 0, tmp, fsize, 1); | ||
386 | } else { | ||
387 | rc = opus_decode(cs->audio_decoder, msg->data, msg->length, tmp, fsize, 0); | ||
388 | rtp_free_msg(NULL, msg); | ||
389 | } | ||
390 | |||
391 | if (rc < 0) { | ||
392 | LOGGER_WARNING("Decoding error: %s", opus_strerror(rc)); | ||
393 | } else if (cs->acb.first) { | ||
394 | /* Play */ | ||
395 | cs->acb.first(cs->agent, cs->call_idx, tmp, rc, cs->acb.second); | ||
396 | } | ||
397 | |||
398 | pthread_mutex_lock(cs->queue_mutex); | ||
399 | } | ||
400 | |||
401 | if (cs->vbuf_raw && !buffer_empty(cs->vbuf_raw)) { | ||
402 | /* Decode video */ | ||
403 | buffer_read(cs->vbuf_raw, &p); | ||
404 | |||
405 | /* Leave space for (possibly) other thread to queue more data after we read it here */ | ||
406 | pthread_mutex_unlock(cs->queue_mutex); | ||
407 | |||
408 | rc = vpx_codec_decode(&cs->v_decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US); | ||
409 | free(p); | ||
410 | |||
411 | if (rc != VPX_CODEC_OK) { | ||
412 | LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); | ||
413 | } else { | ||
414 | vpx_codec_iter_t iter = NULL; | ||
415 | vpx_image_t *dest = vpx_codec_get_frame(&cs->v_decoder, &iter); | ||
416 | |||
417 | /* Play decoded images */ | ||
418 | for (; dest; dest = vpx_codec_get_frame(&cs->v_decoder, &iter)) { | ||
419 | if (cs->vcb.first) | ||
420 | cs->vcb.first(cs->agent, cs->call_idx, dest, cs->vcb.second); | ||
421 | |||
422 | vpx_img_free(dest); | ||
423 | } | ||
424 | } | ||
425 | |||
426 | return; | ||
427 | } | ||
428 | |||
429 | pthread_mutex_unlock(cs->queue_mutex); | ||
430 | } | ||
431 | |||
432 | int cs_set_video_encoder_resolution(CSSession *cs, uint16_t width, uint16_t height) | ||
433 | { | ||
434 | vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; | ||
435 | |||
436 | if (cfg.g_w == width && cfg.g_h == height) | ||
437 | return 0; | ||
438 | |||
439 | if (width * height > cs->max_width * cs->max_height) { | ||
440 | vpx_codec_ctx_t v_encoder = cs->v_encoder; | ||
441 | |||
442 | if (init_video_encoder(cs, width, height, cs->video_bitrate) == -1) { | ||
443 | cs->v_encoder = v_encoder; | ||
444 | return cs_ErrorSettingVideoResolution; | ||
445 | } | ||
446 | |||
447 | vpx_codec_destroy(&v_encoder); | ||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | LOGGER_DEBUG("New video resolution: %u %u", width, height); | ||
452 | cfg.g_w = width; | ||
453 | cfg.g_h = height; | ||
454 | int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); | ||
455 | |||
456 | if ( rc != VPX_CODEC_OK) { | ||
457 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
458 | return cs_ErrorSettingVideoResolution; | ||
459 | } | ||
460 | |||
461 | return 0; | ||
462 | } | ||
463 | |||
464 | int cs_set_video_encoder_bitrate(CSSession *cs, uint32_t video_bitrate) | ||
465 | { | ||
466 | vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; | ||
467 | |||
468 | if (cfg.rc_target_bitrate == video_bitrate) | ||
469 | return 0; | ||
470 | |||
471 | LOGGER_DEBUG("New video bitrate: %u", video_bitrate); | ||
472 | cfg.rc_target_bitrate = video_bitrate; | ||
473 | int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); | ||
474 | |||
475 | if ( rc != VPX_CODEC_OK) { | ||
476 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
477 | return cs_ErrorSettingVideoBitrate; | ||
478 | } | ||
479 | |||
480 | cs->video_bitrate = video_bitrate; | ||
481 | return 0; | ||
482 | } | ||
483 | |||
484 | CSSession *cs_new(const ToxAvCSettings *cs_self, const ToxAvCSettings *cs_peer, uint32_t jbuf_size, int has_video) | ||
485 | { | ||
486 | CSSession *cs = calloc(sizeof(CSSession), 1); | ||
487 | |||
488 | if (!cs) { | ||
489 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
490 | return NULL; | ||
491 | } | ||
492 | |||
493 | if (create_recursive_mutex(cs->queue_mutex) != 0) { | ||
494 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
495 | free(cs); | ||
496 | return NULL; | ||
497 | } | ||
498 | |||
499 | if ( !(cs->j_buf = jbuf_new(jbuf_size)) ) { | ||
500 | LOGGER_WARNING("Jitter buffer creaton failed!"); | ||
501 | goto error; | ||
502 | } | ||
503 | |||
504 | cs->audio_encoder_bitrate = cs_self->audio_bitrate; | ||
505 | cs->audio_encoder_sample_rate = cs_self->audio_sample_rate; | ||
506 | cs->audio_encoder_channels = cs_self->audio_channels; | ||
507 | cs->audio_encoder_frame_duration = cs_self->audio_frame_duration; | ||
508 | |||
509 | cs->audio_decoder_bitrate = cs_peer->audio_bitrate; | ||
510 | cs->audio_decoder_sample_rate = cs_peer->audio_sample_rate; | ||
511 | cs->audio_decoder_channels = cs_peer->audio_channels; | ||
512 | cs->audio_decoder_frame_duration = cs_peer->audio_frame_duration; | ||
513 | |||
514 | |||
515 | cs->capabilities |= ( 0 == init_audio_encoder(cs) ) ? cs_AudioEncoding : 0; | ||
516 | cs->capabilities |= ( 0 == init_audio_decoder(cs) ) ? cs_AudioDecoding : 0; | ||
517 | |||
518 | if ( !(cs->capabilities & cs_AudioEncoding) || !(cs->capabilities & cs_AudioDecoding) ) goto error; | ||
519 | |||
520 | if ((cs->support_video = has_video)) { | ||
521 | cs->max_video_frame_size = MAX_VIDEOFRAME_SIZE; | ||
522 | cs->video_frame_piece_size = VIDEOFRAME_PIECE_SIZE; | ||
523 | |||
524 | cs->capabilities |= ( 0 == init_video_encoder(cs, cs_self->max_video_width, | ||
525 | cs_self->max_video_height, cs_self->video_bitrate) ) ? cs_VideoEncoding : 0; | ||
526 | cs->capabilities |= ( 0 == init_video_decoder(cs) ) ? cs_VideoDecoding : 0; | ||
527 | |||
528 | if ( !(cs->capabilities & cs_VideoEncoding) || !(cs->capabilities & cs_VideoDecoding) ) goto error; | ||
529 | |||
530 | if ( !(cs->frame_buf = calloc(cs->max_video_frame_size, 1)) ) goto error; | ||
531 | |||
532 | if ( !(cs->split_video_frame = calloc(cs->video_frame_piece_size + VIDEOFRAME_HEADER_SIZE, 1)) ) | ||
533 | goto error; | ||
534 | |||
535 | if ( !(cs->vbuf_raw = buffer_new(VIDEO_DECODE_BUFFER_SIZE)) ) goto error; | ||
536 | } | ||
537 | |||
538 | return cs; | ||
539 | |||
540 | error: | ||
541 | LOGGER_WARNING("Error initializing codec session! Application might misbehave!"); | ||
542 | |||
543 | pthread_mutex_destroy(cs->queue_mutex); | ||
544 | |||
545 | if ( cs->audio_encoder ) opus_encoder_destroy(cs->audio_encoder); | ||
546 | |||
547 | if ( cs->audio_decoder ) opus_decoder_destroy(cs->audio_decoder); | ||
548 | |||
549 | |||
550 | if (has_video) { | ||
551 | if ( cs->capabilities & cs_VideoDecoding ) vpx_codec_destroy(&cs->v_decoder); | ||
552 | |||
553 | if ( cs->capabilities & cs_VideoEncoding ) vpx_codec_destroy(&cs->v_encoder); | ||
554 | |||
555 | buffer_free(cs->vbuf_raw); | ||
556 | |||
557 | free(cs->frame_buf); | ||
558 | free(cs->split_video_frame); | ||
559 | } | ||
560 | |||
561 | jbuf_free(cs->j_buf); | ||
562 | free(cs); | ||
563 | |||
564 | return NULL; | ||
565 | } | ||
566 | |||
567 | void cs_kill(CSSession *cs) | ||
568 | { | ||
569 | if (!cs) return; | ||
570 | |||
571 | /* queue_message will not be called since it's unregistered before cs_kill is called */ | ||
572 | pthread_mutex_destroy(cs->queue_mutex); | ||
573 | |||
574 | |||
575 | if ( cs->audio_encoder ) | ||
576 | opus_encoder_destroy(cs->audio_encoder); | ||
577 | |||
578 | if ( cs->audio_decoder ) | ||
579 | opus_decoder_destroy(cs->audio_decoder); | ||
580 | |||
581 | if ( cs->capabilities & cs_VideoDecoding ) | ||
582 | vpx_codec_destroy(&cs->v_decoder); | ||
583 | |||
584 | if ( cs->capabilities & cs_VideoEncoding ) | ||
585 | vpx_codec_destroy(&cs->v_encoder); | ||
586 | |||
587 | jbuf_free(cs->j_buf); | ||
588 | buffer_free(cs->vbuf_raw); | ||
589 | free(cs->frame_buf); | ||
590 | free(cs->split_video_frame); | ||
591 | |||
592 | LOGGER_DEBUG("Terminated codec state: %p", cs); | ||
593 | free(cs); | ||
594 | } | ||
595 | |||
596 | |||
597 | |||
598 | |||
599 | /* Called from RTP */ | ||
600 | void queue_message(RTPSession *session, RTPMessage *msg) | ||
601 | { | ||
602 | /* This function is unregistered during call termination befor destroing | ||
603 | * Codec session so no need to check for validity of cs | ||
604 | */ | ||
605 | CSSession *cs = session->cs; | ||
606 | |||
607 | if (!cs) return; | ||
608 | |||
609 | /* Audio */ | ||
610 | if (session->payload_type == msi_TypeAudio % 128) { | ||
611 | pthread_mutex_lock(cs->queue_mutex); | ||
612 | int ret = jbuf_write(cs->j_buf, msg); | ||
613 | pthread_mutex_unlock(cs->queue_mutex); | ||
614 | |||
615 | if (ret == -1) { | ||
616 | rtp_free_msg(NULL, msg); | ||
617 | } | ||
618 | } | ||
619 | /* Video */ | ||
620 | else { | ||
621 | uint8_t *packet = msg->data; | ||
622 | uint32_t packet_size = msg->length; | ||
623 | |||
624 | if (packet_size < VIDEOFRAME_HEADER_SIZE) | ||
625 | goto end; | ||
626 | |||
627 | uint8_t diff = packet[0] - cs->frameid_in; | ||
628 | |||
629 | if (diff != 0) { | ||
630 | if (diff < 225) { /* New frame */ | ||
631 | /* Flush last frames' data and get ready for this frame */ | ||
632 | Payload *p = malloc(sizeof(Payload) + cs->frame_size); | ||
633 | |||
634 | if (p) { | ||
635 | pthread_mutex_lock(cs->queue_mutex); | ||
636 | |||
637 | if (buffer_full(cs->vbuf_raw)) { | ||
638 | LOGGER_DEBUG("Dropped video frame"); | ||
639 | Payload *tp; | ||
640 | buffer_read(cs->vbuf_raw, &tp); | ||
641 | free(tp); | ||
642 | } else { | ||
643 | p->size = cs->frame_size; | ||
644 | memcpy(p->data, cs->frame_buf, cs->frame_size); | ||
645 | } | ||
646 | |||
647 | buffer_write(cs->vbuf_raw, p); | ||
648 | pthread_mutex_unlock(cs->queue_mutex); | ||
649 | } else { | ||
650 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
651 | goto end; | ||
652 | } | ||
653 | |||
654 | cs->last_timestamp = msg->header->timestamp; | ||
655 | cs->frameid_in = packet[0]; | ||
656 | memset(cs->frame_buf, 0, cs->frame_size); | ||
657 | cs->frame_size = 0; | ||
658 | |||
659 | } else { /* Old frame; drop */ | ||
660 | LOGGER_DEBUG("Old packet: %u", packet[0]); | ||
661 | goto end; | ||
662 | } | ||
663 | } | ||
664 | |||
665 | uint8_t piece_number = packet[1]; | ||
666 | |||
667 | uint32_t length_before_piece = ((piece_number - 1) * cs->video_frame_piece_size); | ||
668 | uint32_t framebuf_new_length = length_before_piece + (packet_size - VIDEOFRAME_HEADER_SIZE); | ||
669 | |||
670 | if (framebuf_new_length > cs->max_video_frame_size) { | ||
671 | goto end; | ||
672 | } | ||
673 | |||
674 | /* Otherwise it's part of the frame so just process */ | ||
675 | /* LOGGER_DEBUG("Video Packet: %u %u", packet[0], packet[1]); */ | ||
676 | |||
677 | memcpy(cs->frame_buf + length_before_piece, | ||
678 | packet + VIDEOFRAME_HEADER_SIZE, | ||
679 | packet_size - VIDEOFRAME_HEADER_SIZE); | ||
680 | |||
681 | if (framebuf_new_length > cs->frame_size) { | ||
682 | cs->frame_size = framebuf_new_length; | ||
683 | } | ||
684 | |||
685 | end: | ||
686 | rtp_free_msg(NULL, msg); | ||
687 | } | ||
688 | } | ||
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/msi.c b/toxav/msi.c index dcb7b62a..d3559160 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 | * |
@@ -33,163 +33,336 @@ | |||
33 | #include <string.h> | 33 | #include <string.h> |
34 | #include <stdlib.h> | 34 | #include <stdlib.h> |
35 | #include <stdbool.h> | 35 | #include <stdbool.h> |
36 | #include <assert.h> | ||
36 | 37 | ||
37 | #define MSI_MAXMSG_SIZE 256 | 38 | #define MSI_MAXMSG_SIZE 256 |
38 | 39 | ||
39 | /* Define default timeout for a request. | ||
40 | * There is no behavior specified by the msi on what will | ||
41 | * client do on timeout, but to call timeout callback. | ||
42 | */ | ||
43 | #define m_deftout 10000 /* in milliseconds */ | ||
44 | |||
45 | /** | 40 | /** |
46 | * Protocol: | 41 | * Protocol: |
47 | * | 42 | * |
48 | * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| | 43 | * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| |
49 | */ | 44 | */ |
50 | 45 | ||
51 | typedef uint8_t MSIRawCSettingsType[23]; | ||
52 | |||
53 | typedef enum { | 46 | typedef enum { |
54 | IDRequest = 1, | 47 | IDRequest = 1, |
55 | IDResponse, | 48 | IDError, |
56 | IDReason, | 49 | IDCapabilities, |
57 | IDCallId, | 50 | IDVFPSZ, |
58 | IDCSettings, | ||
59 | 51 | ||
60 | } MSIHeaderID; | 52 | } MSIHeaderID; |
61 | 53 | ||
62 | typedef enum { | ||
63 | TypeRequest, | ||
64 | TypeResponse, | ||
65 | |||
66 | } MSIMessageType; | ||
67 | 54 | ||
68 | typedef enum { | 55 | typedef enum { |
69 | invite, | 56 | requ_push, |
70 | start, | 57 | requ_pop, |
71 | cancel, | ||
72 | reject, | ||
73 | end, | ||
74 | |||
75 | } MSIRequest; | 58 | } MSIRequest; |
76 | 59 | ||
77 | typedef enum { | ||
78 | ringing, | ||
79 | starting, | ||
80 | ending, | ||
81 | error | ||
82 | |||
83 | } MSIResponse; | ||
84 | |||
85 | 60 | ||
86 | #define GENERIC_HEADER(header, val_type) \ | 61 | #define GENERIC_HEADER(header, val_type) \ |
87 | typedef struct _MSIHeader##header { \ | 62 | typedef struct { \ |
88 | val_type value; \ | 63 | val_type value; \ |
89 | _Bool exists; \ | 64 | bool exists; \ |
90 | } MSIHeader##header; | 65 | } MSIHeader##header |
91 | |||
92 | |||
93 | GENERIC_HEADER ( Request, MSIRequest ) | ||
94 | GENERIC_HEADER ( Response, MSIResponse ) | ||
95 | GENERIC_HEADER ( CallId, MSICallIDType ) | ||
96 | GENERIC_HEADER ( Reason, MSIReasonStrType ) | ||
97 | GENERIC_HEADER ( CSettings, MSIRawCSettingsType ) | ||
98 | |||
99 | 66 | ||
100 | typedef struct _MSIMessage { | ||
101 | 67 | ||
102 | MSIHeaderRequest request; | 68 | GENERIC_HEADER ( Request, MSIRequest ); |
103 | MSIHeaderResponse response; | 69 | GENERIC_HEADER ( Error, MSIError ); |
104 | MSIHeaderReason reason; | 70 | GENERIC_HEADER ( Capabilities, uint8_t ); |
105 | MSIHeaderCallId callid; | 71 | GENERIC_HEADER ( VFPSZ, uint16_t ); |
106 | MSIHeaderCSettings csettings; | ||
107 | 72 | ||
108 | int friend_id; | ||
109 | 73 | ||
74 | typedef struct { | ||
75 | MSIHeaderRequest request; | ||
76 | MSIHeaderError error; | ||
77 | MSIHeaderCapabilities capabilities; | ||
78 | MSIHeaderVFPSZ vfpsz; /* Video frame piece size. NOTE: Value must be in network b-order TODO: get rid of this eventually */ | ||
110 | } MSIMessage; | 79 | } MSIMessage; |
111 | 80 | ||
112 | 81 | ||
113 | static void invoke_callback(MSISession *s, int32_t c, MSICallbackID i) | 82 | void msg_init (MSIMessage *dest, MSIRequest request); |
114 | { | 83 | int msg_parse_in ( MSIMessage *dest, const uint8_t *data, uint16_t length ); |
115 | if ( s->callbacks[i].first ) { | 84 | uint8_t *msg_parse_header_out ( MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length ); |
116 | LOGGER_DEBUG("Invoking callback function: %d", i); | 85 | int send_message ( Messenger* m, uint32_t friend_number, const MSIMessage *msg ); |
86 | int send_error ( Messenger* m, uint32_t friend_number, MSIError error ); | ||
87 | static int invoke_callback(MSICall* call, MSICallbackID cb); | ||
88 | static MSICall *get_call ( MSISession *session, uint32_t friend_number ); | ||
89 | MSICall *new_call ( MSISession *session, uint32_t friend_number ); | ||
90 | void kill_call ( MSICall *call ); | ||
91 | void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data); | ||
92 | void handle_push ( MSICall *call, const MSIMessage *msg ); | ||
93 | void handle_pop ( MSICall *call, const MSIMessage *msg ); | ||
94 | void handle_msi_packet ( Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object ); | ||
117 | 95 | ||
118 | s->callbacks[i].first( s->agent_handler, c, s->callbacks[i].second ); | ||
119 | } | ||
120 | } | ||
121 | 96 | ||
122 | /** | 97 | /** |
123 | * Parse raw 'data' received from socket into MSIMessage struct. | 98 | * Public functions |
124 | * Every message has to have end value of 'end_byte' or _undefined_ behavior | ||
125 | * occures. The best practice is to check the end of the message at the handle_packet. | ||
126 | */ | 99 | */ |
127 | static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t length ) | 100 | void msi_register_callback ( MSISession* session, msi_action_cb* callback, MSICallbackID id) |
128 | { | 101 | { |
129 | 102 | pthread_mutex_lock(session->mutex); | |
130 | #define FAIL_CONSTRAINT(constraint, wanted) if ((constraint -= wanted) < 1) { LOGGER_ERROR("Read over length!"); return -1; } | 103 | session->callbacks[id] = callback; |
131 | #define FAIL_SIZE(byte, valid) if ( byte != valid ) { LOGGER_ERROR("Invalid data size!"); return -1; } | 104 | pthread_mutex_unlock(session->mutex); |
132 | #define FAIL_LIMITS(byte, high) if ( byte > high ) { LOGGER_ERROR("Failed limit!"); return -1; } | 105 | } |
133 | 106 | MSISession *msi_new ( Messenger *m ) | |
134 | if ( msg == NULL ) { | 107 | { |
135 | LOGGER_ERROR("Could not parse message: no storage!"); | 108 | if (m == NULL) { |
109 | LOGGER_ERROR("Could not init session on empty messenger!"); | ||
110 | return NULL; | ||
111 | } | ||
112 | |||
113 | MSISession *retu = calloc ( sizeof ( MSISession ), 1 ); | ||
114 | |||
115 | if (retu == NULL) { | ||
116 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
117 | return NULL; | ||
118 | } | ||
119 | |||
120 | if (create_recursive_mutex(retu->mutex) != 0) { | ||
121 | LOGGER_ERROR("Failed to init mutex! Program might misbehave"); | ||
122 | free(retu); | ||
123 | return NULL; | ||
124 | } | ||
125 | |||
126 | retu->messenger = m; | ||
127 | |||
128 | m_callback_msi_packet(m, handle_msi_packet, retu ); | ||
129 | |||
130 | /* This is called when remote terminates session */ | ||
131 | m_callback_connectionstatus_internal_av(m, on_peer_status, retu); | ||
132 | |||
133 | LOGGER_DEBUG("New msi session: %p ", retu); | ||
134 | return retu; | ||
135 | } | ||
136 | int msi_kill ( MSISession *session ) | ||
137 | { | ||
138 | if (session == NULL) { | ||
139 | LOGGER_ERROR("Tried to terminate non-existing session"); | ||
140 | return -1; | ||
141 | } | ||
142 | |||
143 | m_callback_msi_packet((struct Messenger *) session->messenger, NULL, NULL); | ||
144 | pthread_mutex_lock(session->mutex); | ||
145 | |||
146 | if (session->calls) { | ||
147 | MSIMessage msg; | ||
148 | msg_init(&msg, requ_pop); | ||
149 | |||
150 | MSICall* it = get_call(session, session->calls_head); | ||
151 | for (; it; it = it->next) { | ||
152 | send_message(session->messenger, it->friend_number, &msg); | ||
153 | kill_call(it); /* This will eventually free session->calls */ | ||
154 | } | ||
155 | } | ||
156 | |||
157 | pthread_mutex_unlock(session->mutex); | ||
158 | pthread_mutex_destroy(session->mutex); | ||
159 | |||
160 | LOGGER_DEBUG("Terminated session: %p", session); | ||
161 | free ( session ); | ||
162 | return 0; | ||
163 | } | ||
164 | int msi_invite ( MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities ) | ||
165 | { | ||
166 | LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_number); | ||
167 | |||
168 | pthread_mutex_lock(session->mutex); | ||
169 | if (get_call(session, friend_number) != NULL) { | ||
170 | LOGGER_ERROR("Already in a call"); | ||
171 | pthread_mutex_unlock(session->mutex); | ||
172 | return -1; | ||
173 | } | ||
174 | |||
175 | (*call) = new_call ( session, friend_number ); | ||
176 | |||
177 | if ( *call == NULL ) { | ||
178 | pthread_mutex_unlock(session->mutex); | ||
179 | return -1; | ||
180 | } | ||
181 | |||
182 | (*call)->self_capabilities = capabilities; | ||
183 | |||
184 | MSIMessage msg; | ||
185 | msg_init(&msg, requ_push); | ||
186 | |||
187 | msg.capabilities.exists = true; | ||
188 | msg.capabilities.value = capabilities; | ||
189 | |||
190 | msg.vfpsz.exists = true; | ||
191 | msg.vfpsz.value = VIDEOFRAME_PIECE_SIZE; | ||
192 | |||
193 | send_message ( (*call)->session->messenger, (*call)->friend_number, &msg ); | ||
194 | |||
195 | (*call)->state = msi_CallRequesting; | ||
196 | |||
197 | LOGGER_DEBUG("Invite sent"); | ||
198 | pthread_mutex_unlock(session->mutex); | ||
199 | return 0; | ||
200 | } | ||
201 | int msi_hangup ( MSICall* call ) | ||
202 | { | ||
203 | LOGGER_DEBUG("Session: %p Hanging up call with friend: %u", call->session, call->friend_number); | ||
204 | |||
205 | MSISession* session = call->session; | ||
206 | pthread_mutex_lock(session->mutex); | ||
207 | |||
208 | if ( call->state == msi_CallInactive ) { | ||
209 | LOGGER_ERROR("Call is in invalid state!"); | ||
210 | pthread_mutex_unlock(session->mutex); | ||
136 | return -1; | 211 | return -1; |
137 | } | 212 | } |
213 | |||
214 | MSIMessage msg; | ||
215 | msg_init(&msg, requ_pop); | ||
216 | |||
217 | send_message ( session->messenger, call->friend_number, &msg ); | ||
218 | |||
219 | kill_call(call); | ||
220 | pthread_mutex_unlock(session->mutex); | ||
221 | return 0; | ||
222 | } | ||
223 | int msi_answer ( MSICall* call, uint8_t capabilities ) | ||
224 | { | ||
225 | LOGGER_DEBUG("Session: %p Answering call from: %u", call->session, call->friend_number); | ||
226 | |||
227 | MSISession* session = call->session; | ||
228 | pthread_mutex_lock(session->mutex); | ||
229 | |||
230 | if ( call->state != msi_CallRequested ) { | ||
231 | /* Though sending in invalid state will not cause anything wierd | ||
232 | * Its better to not do it like a maniac */ | ||
233 | LOGGER_ERROR("Call is in invalid state!"); | ||
234 | pthread_mutex_unlock(session->mutex); | ||
235 | return -1; | ||
236 | } | ||
237 | |||
238 | call->self_capabilities = capabilities; | ||
239 | |||
240 | MSIMessage msg; | ||
241 | msg_init(&msg, requ_push); | ||
242 | |||
243 | msg.capabilities.exists = true; | ||
244 | msg.capabilities.value = capabilities; | ||
245 | |||
246 | msg.vfpsz.exists = true; | ||
247 | msg.vfpsz.value = VIDEOFRAME_PIECE_SIZE; | ||
248 | |||
249 | send_message ( session->messenger, call->friend_number, &msg ); | ||
250 | |||
251 | call->state = msi_CallActive; | ||
252 | pthread_mutex_unlock(session->mutex); | ||
253 | |||
254 | return 0; | ||
255 | } | ||
256 | int msi_change_capabilities( MSICall* call, uint8_t capabilities ) | ||
257 | { | ||
258 | LOGGER_DEBUG("Session: %p Trying to change capabilities to friend %u", call->session, call->friend_number); | ||
259 | |||
260 | MSISession* session = call->session; | ||
261 | pthread_mutex_lock(session->mutex); | ||
262 | |||
263 | if ( call->state != msi_CallActive ) { | ||
264 | /* Sending capabilities change can cause error on other side if | ||
265 | * the call is not active since we don't send header 'vfpsz'. | ||
266 | * If we were to send 'vfpsz' while call is active it would be | ||
267 | * ignored. However, if call is not active peer will expect | ||
268 | * the said header on 'push' so that it could handle the call | ||
269 | * like new. TODO: explain this better | ||
270 | */ | ||
271 | LOGGER_ERROR("Call is in invalid state!"); | ||
272 | pthread_mutex_unlock(session->mutex); | ||
273 | return -1; | ||
274 | } | ||
275 | |||
276 | call->self_capabilities = capabilities; | ||
277 | |||
278 | MSIMessage msg; | ||
279 | msg_init(&msg, requ_push); | ||
280 | |||
281 | msg.capabilities.exists = true; | ||
282 | msg.capabilities.value = capabilities; | ||
283 | |||
284 | send_message ( call->session->messenger, call->friend_number, &msg ); | ||
285 | |||
286 | pthread_mutex_unlock(session->mutex); | ||
287 | return 0; | ||
288 | } | ||
289 | |||
138 | 290 | ||
139 | if ( data[length - 1] ) { /* End byte must have value 0 */ | 291 | /** |
292 | * Private functions | ||
293 | */ | ||
294 | void msg_init(MSIMessage* dest, MSIRequest request) | ||
295 | { | ||
296 | memset(dest, 0, sizeof(*dest)); | ||
297 | dest->request.exists = true; | ||
298 | dest->request.value = request; | ||
299 | } | ||
300 | int msg_parse_in ( MSIMessage *dest, const uint8_t *data, uint16_t length ) | ||
301 | { | ||
302 | /* Parse raw data received from socket into MSIMessage struct */ | ||
303 | |||
304 | #define CHECK_SIZE(bytes, constraint, size) \ | ||
305 | if ((constraint -= (2 + size)) < 1) { LOGGER_ERROR("Read over length!"); return -1; } \ | ||
306 | if ( bytes[1] != size ) { LOGGER_ERROR("Invalid data size!"); return -1; } | ||
307 | |||
308 | #define CHECK_ENUM_HIGH(bytes, enum_high) /* Assumes size == 1 */ \ | ||
309 | if ( bytes[2] > enum_high ) { LOGGER_ERROR("Failed enum high limit!"); return -1; } | ||
310 | |||
311 | #define SET_UINT8(bytes, header) do { \ | ||
312 | header.value = bytes[2]; \ | ||
313 | header.exists = true; \ | ||
314 | bytes += 3; \ | ||
315 | } while(0) | ||
316 | |||
317 | #define SET_UINT16(bytes, header) do { \ | ||
318 | memcpy(&header.value, bytes + 2, 2);\ | ||
319 | header.exists = true; \ | ||
320 | bytes += 4; \ | ||
321 | } while(0) | ||
322 | |||
323 | |||
324 | assert(dest); | ||
325 | |||
326 | if ( length == 0 || data[length - 1] ) { /* End byte must have value 0 */ | ||
140 | LOGGER_ERROR("Invalid end byte"); | 327 | LOGGER_ERROR("Invalid end byte"); |
141 | return -1; | 328 | return -1; |
142 | } | 329 | } |
143 | 330 | ||
331 | memset(dest, 0, sizeof(*dest)); | ||
332 | |||
144 | const uint8_t *it = data; | 333 | const uint8_t *it = data; |
145 | int size_constraint = length; | 334 | int size_constraint = length; |
146 | 335 | ||
147 | while ( *it ) {/* until end byte is hit */ | 336 | while ( *it ) {/* until end byte is hit */ |
148 | switch (*it) { | 337 | switch (*it) { |
149 | case IDRequest: | 338 | case IDRequest: |
150 | FAIL_CONSTRAINT(size_constraint, 3); | 339 | CHECK_SIZE(it, size_constraint, 1); |
151 | FAIL_SIZE(it[1], 1); | 340 | CHECK_ENUM_HIGH(it, requ_pop); |
152 | // FAIL_LIMITS(it[2], invite, end); | 341 | SET_UINT8(it, dest->request); |
153 | FAIL_LIMITS(it[2], end); | ||
154 | msg->request.value = it[2]; | ||
155 | it += 3; | ||
156 | msg->request.exists = 1; | ||
157 | break; | 342 | break; |
158 | 343 | ||
159 | case IDResponse: | 344 | case IDError: |
160 | FAIL_CONSTRAINT(size_constraint, 3); | 345 | CHECK_SIZE(it, size_constraint, 1); |
161 | FAIL_SIZE(it[1], 1); | 346 | CHECK_ENUM_HIGH(it, msi_EUndisclosed); |
162 | // FAIL_LIMITS(it[2], ringing, error); | 347 | SET_UINT8(it, dest->error); |
163 | FAIL_LIMITS(it[2], error); | ||
164 | msg->response.value = it[2]; | ||
165 | it += 3; | ||
166 | msg->response.exists = 1; | ||
167 | break; | ||
168 | |||
169 | case IDCallId: | ||
170 | FAIL_CONSTRAINT(size_constraint, sizeof(MSICallIDType) + 2); | ||
171 | FAIL_SIZE(it[1], sizeof(MSICallIDType)); | ||
172 | memcpy(msg->callid.value, it + 2, sizeof(MSICallIDType)); | ||
173 | it += sizeof(MSICallIDType) + 2; | ||
174 | msg->callid.exists = 1; | ||
175 | break; | 348 | break; |
176 | 349 | ||
177 | case IDReason: | 350 | case IDCapabilities: |
178 | FAIL_CONSTRAINT(size_constraint, sizeof(MSIReasonStrType) + 2); | 351 | CHECK_SIZE(it, size_constraint, 1); |
179 | FAIL_SIZE(it[1], sizeof(MSIReasonStrType)); | 352 | SET_UINT8(it, dest->capabilities); |
180 | memcpy(msg->reason.value, it + 2, sizeof(MSIReasonStrType)); | ||
181 | it += sizeof(MSIReasonStrType) + 2; | ||
182 | msg->reason.exists = 1; | ||
183 | break; | 353 | break; |
184 | 354 | ||
185 | case IDCSettings: | 355 | case IDVFPSZ: |
186 | FAIL_CONSTRAINT(size_constraint, sizeof(MSIRawCSettingsType) + 2); | 356 | CHECK_SIZE(it, size_constraint, 2); |
187 | FAIL_SIZE(it[1], sizeof(MSIRawCSettingsType)); | 357 | SET_UINT16(it, dest->vfpsz); |
188 | memcpy(msg->csettings.value, it + 2, sizeof(MSIRawCSettingsType)); | 358 | dest->vfpsz.value = ntohs(dest->vfpsz.value); |
189 | it += sizeof(MSIRawCSettingsType) + 2; | 359 | |
190 | msg->csettings.exists = 1; | 360 | if (dest->vfpsz.value > 1200) { |
361 | LOGGER_ERROR("Invalid vfpsz param"); | ||
362 | return -1; | ||
363 | } | ||
191 | break; | 364 | break; |
192 | 365 | ||
193 | default: | 366 | default: |
194 | LOGGER_ERROR("Invalid id byte"); | 367 | LOGGER_ERROR("Invalid id byte"); |
195 | return -1; | 368 | return -1; |
@@ -197,80 +370,25 @@ static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t lengt | |||
197 | } | 370 | } |
198 | } | 371 | } |
199 | 372 | ||
200 | return 0; | 373 | if (dest->request.exists == false) { |
201 | } | 374 | LOGGER_ERROR("Invalid request field!"); |
202 | 375 | return -1; | |
203 | /** | ||
204 | * Create the message. | ||
205 | */ | ||
206 | MSIMessage *msi_new_message ( MSIMessageType type, const uint8_t type_value ) | ||
207 | { | ||
208 | MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); | ||
209 | |||
210 | if ( retu == NULL ) { | ||
211 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
212 | return NULL; | ||
213 | } | ||
214 | |||
215 | if ( type == TypeRequest ) { | ||
216 | retu->request.exists = 1; | ||
217 | retu->request.value = type_value; | ||
218 | |||
219 | } else { | ||
220 | retu->response.exists = 1; | ||
221 | retu->response.value = type_value; | ||
222 | } | ||
223 | |||
224 | return retu; | ||
225 | } | ||
226 | |||
227 | |||
228 | /** | ||
229 | * Parse data from handle_packet. | ||
230 | */ | ||
231 | MSIMessage *parse_recv ( const uint8_t *data, uint16_t length ) | ||
232 | { | ||
233 | if ( data == NULL ) { | ||
234 | LOGGER_WARNING("Tried to parse empty message!"); | ||
235 | return NULL; | ||
236 | } | ||
237 | |||
238 | MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); | ||
239 | |||
240 | if ( retu == NULL ) { | ||
241 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
242 | return NULL; | ||
243 | } | ||
244 | |||
245 | if ( parse_raw_data ( retu, data, length ) == -1 ) { | ||
246 | |||
247 | free ( retu ); | ||
248 | return NULL; | ||
249 | } | 376 | } |
377 | |||
378 | return 0; | ||
250 | 379 | ||
251 | return retu; | 380 | #undef CHECK_SIZE |
381 | #undef CHECK_ENUM_HIGH | ||
382 | #undef SET_UINT8 | ||
383 | #undef SET_UINT16 | ||
252 | } | 384 | } |
253 | 385 | uint8_t *msg_parse_header_out ( MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length ) | |
254 | |||
255 | /** | ||
256 | * Speaks for itself. | ||
257 | */ | ||
258 | uint8_t *format_output ( uint8_t *dest, | ||
259 | MSIHeaderID id, | ||
260 | const void *value, | ||
261 | uint8_t value_len, | ||
262 | uint16_t *length ) | ||
263 | { | 386 | { |
264 | if ( dest == NULL ) { | 387 | /* Parse a single header for sending */ |
265 | LOGGER_ERROR("No destination space!"); | 388 | assert(dest); |
266 | return NULL; | 389 | assert(value); |
267 | } | 390 | assert(value_len); |
268 | 391 | ||
269 | if (value == NULL || value_len == 0) { | ||
270 | LOGGER_ERROR("Empty header value"); | ||
271 | return NULL; | ||
272 | } | ||
273 | |||
274 | *dest = id; | 392 | *dest = id; |
275 | dest ++; | 393 | dest ++; |
276 | *dest = value_len; | 394 | *dest = value_len; |
@@ -282,521 +400,211 @@ uint8_t *format_output ( uint8_t *dest, | |||
282 | 400 | ||
283 | return dest + value_len; /* Set to next position ready to be written */ | 401 | return dest + value_len; /* Set to next position ready to be written */ |
284 | } | 402 | } |
285 | 403 | int send_message ( Messenger* m, uint32_t friend_number, const MSIMessage *msg ) | |
286 | |||
287 | /** | ||
288 | * Parse MSIMessage to send. | ||
289 | */ | ||
290 | uint16_t parse_send ( MSIMessage *msg, uint8_t *dest ) | ||
291 | { | 404 | { |
292 | if (msg == NULL) { | 405 | /* Parse and send message */ |
293 | LOGGER_ERROR("No message!"); | 406 | assert(m); |
294 | return 0; | 407 | |
295 | } | 408 | uint8_t parsed [MSI_MAXMSG_SIZE]; |
296 | |||
297 | if (dest == NULL ) { | ||
298 | LOGGER_ERROR("No destination!"); | ||
299 | return 0; | ||
300 | } | ||
301 | 409 | ||
302 | uint8_t *it = dest; | 410 | uint8_t *it = parsed; |
303 | uint16_t size = 0; | 411 | uint16_t size = 0; |
304 | 412 | ||
305 | if (msg->request.exists) { | 413 | if (msg->request.exists) { |
306 | uint8_t cast = msg->request.value; | 414 | uint8_t cast = msg->request.value; |
307 | it = format_output(it, IDRequest, &cast, 1, &size); | 415 | it = msg_parse_header_out(IDRequest, it, &cast, |
308 | } | 416 | sizeof(cast), &size); |
309 | 417 | } else { | |
310 | if (msg->response.exists) { | 418 | LOGGER_DEBUG("Must have request field"); |
311 | uint8_t cast = msg->response.value; | ||
312 | it = format_output(it, IDResponse, &cast, 1, &size); | ||
313 | } | ||
314 | |||
315 | if (msg->callid.exists) { | ||
316 | it = format_output(it, IDCallId, &msg->callid.value, sizeof(msg->callid.value), &size); | ||
317 | } | ||
318 | |||
319 | if (msg->reason.exists) { | ||
320 | it = format_output(it, IDReason, &msg->reason.value, sizeof(msg->reason.value), &size); | ||
321 | } | ||
322 | |||
323 | if (msg->csettings.exists) { | ||
324 | it = format_output(it, IDCSettings, &msg->csettings.value, sizeof(msg->csettings.value), &size); | ||
325 | } | ||
326 | |||
327 | *it = 0; | ||
328 | size ++; | ||
329 | |||
330 | return size; | ||
331 | } | ||
332 | |||
333 | void msi_msg_set_reason ( MSIMessage *msg, const MSIReasonStrType value ) | ||
334 | { | ||
335 | if ( !msg ) return; | ||
336 | |||
337 | msg->reason.exists = 1; | ||
338 | memcpy(msg->reason.value, value, sizeof(MSIReasonStrType)); | ||
339 | } | ||
340 | |||
341 | void msi_msg_set_callid ( MSIMessage *msg, const MSICallIDType value ) | ||
342 | { | ||
343 | if ( !msg ) return; | ||
344 | |||
345 | msg->callid.exists = 1; | ||
346 | memcpy(msg->callid.value, value, sizeof(MSICallIDType)); | ||
347 | } | ||
348 | |||
349 | void msi_msg_set_csettings ( MSIMessage *msg, const MSICSettings *value ) | ||
350 | { | ||
351 | if ( !msg ) return; | ||
352 | |||
353 | msg->csettings.exists = 1; | ||
354 | |||
355 | msg->csettings.value[0] = value->call_type; | ||
356 | uint8_t *iter = msg->csettings.value + 1; | ||
357 | |||
358 | /* Video bitrate */ | ||
359 | uint32_t lval = htonl(value->video_bitrate); | ||
360 | memcpy(iter, &lval, 4); | ||
361 | iter += 4; | ||
362 | |||
363 | /* Video max width */ | ||
364 | uint16_t sval = htons(value->max_video_width); | ||
365 | memcpy(iter, &sval, 2); | ||
366 | iter += 2; | ||
367 | |||
368 | /* Video max height */ | ||
369 | sval = htons(value->max_video_height); | ||
370 | memcpy(iter, &sval, 2); | ||
371 | iter += 2; | ||
372 | |||
373 | /* Audio bitrate */ | ||
374 | lval = htonl(value->audio_bitrate); | ||
375 | memcpy(iter, &lval, 4); | ||
376 | iter += 4; | ||
377 | |||
378 | /* Audio frame duration */ | ||
379 | sval = htons(value->audio_frame_duration); | ||
380 | memcpy(iter, &sval, 2); | ||
381 | iter += 2; | ||
382 | |||
383 | /* Audio sample rate */ | ||
384 | lval = htonl(value->audio_sample_rate); | ||
385 | memcpy(iter, &lval, 4); | ||
386 | iter += 4; | ||
387 | |||
388 | /* Audio channels */ | ||
389 | lval = htonl(value->audio_channels); | ||
390 | memcpy(iter, &lval, 4); | ||
391 | } | ||
392 | |||
393 | void msi_msg_get_csettings ( MSIMessage *msg, MSICSettings *dest ) | ||
394 | { | ||
395 | if ( !msg || !dest || !msg->csettings.exists ) return; | ||
396 | |||
397 | dest->call_type = msg->csettings.value[0]; | ||
398 | uint8_t *iter = msg->csettings.value + 1; | ||
399 | |||
400 | memcpy(&dest->video_bitrate, iter, 4); | ||
401 | iter += 4; | ||
402 | dest->video_bitrate = ntohl(dest->video_bitrate); | ||
403 | |||
404 | memcpy(&dest->max_video_width, iter, 2); | ||
405 | iter += 2; | ||
406 | dest->max_video_width = ntohs(dest->max_video_width); | ||
407 | |||
408 | memcpy(&dest->max_video_height, iter, 2); | ||
409 | iter += 2; | ||
410 | dest->max_video_height = ntohs(dest->max_video_height); | ||
411 | |||
412 | memcpy(&dest->audio_bitrate, iter, 4); | ||
413 | iter += 4; | ||
414 | dest->audio_bitrate = ntohl(dest->audio_bitrate); | ||
415 | |||
416 | memcpy(&dest->audio_frame_duration, iter, 2); | ||
417 | iter += 2; | ||
418 | dest->audio_frame_duration = ntohs(dest->audio_frame_duration); | ||
419 | |||
420 | memcpy(&dest->audio_sample_rate, iter, 4); | ||
421 | iter += 4; | ||
422 | dest->audio_sample_rate = ntohl(dest->audio_sample_rate); | ||
423 | |||
424 | memcpy(&dest->audio_channels, iter, 4); | ||
425 | dest->audio_channels = ntohl(dest->audio_channels); | ||
426 | } | ||
427 | |||
428 | typedef struct _Timer { | ||
429 | void (*func)(struct _Timer *); | ||
430 | uint64_t timeout; | ||
431 | MSISession *session; | ||
432 | int call_idx; | ||
433 | int id; | ||
434 | |||
435 | } Timer; | ||
436 | |||
437 | typedef struct _TimerHandler { | ||
438 | Timer **timers; | ||
439 | |||
440 | uint32_t max_capacity; | ||
441 | uint32_t size; | ||
442 | } TimerHandler; | ||
443 | |||
444 | |||
445 | static int timer_alloc (MSISession *session , void (*func)(Timer *), int call_idx, uint32_t timeout) | ||
446 | { | ||
447 | static int timer_id; | ||
448 | TimerHandler *timer_handler = session->timer_handler; | ||
449 | |||
450 | uint32_t i = 0; | ||
451 | |||
452 | for (; i < timer_handler->max_capacity && timer_handler->timers[i]; i ++); | ||
453 | |||
454 | if (i == timer_handler->max_capacity) { | ||
455 | LOGGER_WARNING("Maximum capacity reached!"); | ||
456 | return -1; | ||
457 | } | ||
458 | |||
459 | Timer *timer = timer_handler->timers[i] = calloc(sizeof(Timer), 1); | ||
460 | |||
461 | if (timer == NULL) { | ||
462 | LOGGER_ERROR("Failed to allocate timer!"); | ||
463 | return -1; | ||
464 | } | ||
465 | |||
466 | timer_handler->size ++; | ||
467 | |||
468 | timer->func = func; | ||
469 | timer->session = session; | ||
470 | timer->call_idx = call_idx; | ||
471 | timer->timeout = timeout + current_time_monotonic(); /* In ms */ | ||
472 | ++timer_id; | ||
473 | timer->id = timer_id; | ||
474 | |||
475 | /* reorder */ | ||
476 | if (i) { | ||
477 | int64_t j = i - 1; | ||
478 | |||
479 | for (; j >= 0 && timeout < timer_handler->timers[j]->timeout; j--) { | ||
480 | Timer *tmp = timer_handler->timers[j]; | ||
481 | timer_handler->timers[j] = timer; | ||
482 | timer_handler->timers[j + 1] = tmp; | ||
483 | } | ||
484 | } | ||
485 | |||
486 | LOGGER_DEBUG("Allocated timer index: %ull timeout: %ull, current size: %ull", i, timeout, timer_handler->size); | ||
487 | return timer->id; | ||
488 | } | ||
489 | |||
490 | static int timer_release ( TimerHandler *timers_container, int id) | ||
491 | { | ||
492 | Timer **timed_events = timers_container->timers; | ||
493 | |||
494 | uint32_t i; | ||
495 | int rc = -1; | ||
496 | |||
497 | for (i = 0; i < timers_container->max_capacity; ++i) { | ||
498 | if (timed_events[i] && timed_events[i]->id == id) { | ||
499 | rc = i; | ||
500 | break; | ||
501 | } | ||
502 | } | ||
503 | |||
504 | if (rc == -1) { | ||
505 | LOGGER_WARNING("No event with id: %d", id); | ||
506 | return -1; | 419 | return -1; |
507 | } | 420 | } |
508 | 421 | ||
509 | free(timed_events[rc]); | 422 | if (msg->error.exists) { |
510 | 423 | uint8_t cast = msg->error.value; | |
511 | timed_events[rc] = NULL; | 424 | it = msg_parse_header_out(IDError, it, &cast, |
512 | 425 | sizeof(cast), &size); | |
513 | i = rc + 1; | 426 | } |
514 | 427 | ||
515 | for (; i < timers_container->max_capacity && timed_events[i]; i ++) { | 428 | if (msg->capabilities.exists) { |
516 | timed_events[i - 1] = timed_events[i]; | 429 | it = msg_parse_header_out(IDCapabilities, it, &msg->capabilities.value, |
517 | timed_events[i] = NULL; | 430 | sizeof(msg->capabilities.value), &size); |
518 | } | 431 | } |
519 | 432 | ||
520 | timers_container->size--; | 433 | if (msg->vfpsz.exists) { |
521 | 434 | uint16_t nb_vfpsz = htons(msg->vfpsz.value); | |
522 | LOGGER_DEBUG("Popped id: %d, current size: %ull ", id, timers_container->size); | 435 | it = msg_parse_header_out(IDVFPSZ, it, &nb_vfpsz, |
523 | return 0; | 436 | sizeof(nb_vfpsz), &size); |
524 | } | 437 | } |
525 | 438 | ||
526 | /** | 439 | if ( it == parsed ) { |
527 | * Generate _random_ alphanumerical string. | 440 | LOGGER_WARNING("Parsing message failed; empty message"); |
528 | */ | ||
529 | static void t_randomstr ( uint8_t *str, uint32_t size ) | ||
530 | { | ||
531 | if (str == NULL) { | ||
532 | LOGGER_DEBUG("Empty destination!"); | ||
533 | return; | ||
534 | } | ||
535 | |||
536 | static const uint8_t _bytes[] = | ||
537 | "0123456789" | ||
538 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
539 | "abcdefghijklmnopqrstuvwxyz"; | ||
540 | |||
541 | uint32_t _it = 0; | ||
542 | |||
543 | for ( ; _it < size; _it++ ) { | ||
544 | str[_it] = _bytes[ random_int() % 61 ]; | ||
545 | } | ||
546 | } | ||
547 | |||
548 | /* TODO: it would be nice to actually have some sane error codes */ | ||
549 | typedef enum { | ||
550 | error_none, | ||
551 | error_deadcall, /* has call id but it's from old call */ | ||
552 | error_id_mismatch, /* non-existing call */ | ||
553 | |||
554 | error_no_callid, /* not having call id */ | ||
555 | error_no_call, /* no call in session */ | ||
556 | error_no_crypto_key, /* no crypto key */ | ||
557 | |||
558 | error_busy | ||
559 | |||
560 | } MSICallError; /* Error codes */ | ||
561 | |||
562 | |||
563 | /** | ||
564 | * Stringify error code. | ||
565 | */ | ||
566 | static const uint8_t *stringify_error ( MSICallError error_code ) | ||
567 | { | ||
568 | static const uint8_t *strings[] = { | ||
569 | ( uint8_t *) "", | ||
570 | ( uint8_t *) "Using dead call", | ||
571 | ( uint8_t *) "Call id not set to any call", | ||
572 | ( uint8_t *) "Call id not available", | ||
573 | ( uint8_t *) "No active call in session", | ||
574 | ( uint8_t *) "No Crypto-key set", | ||
575 | ( uint8_t *) "Callee busy" | ||
576 | }; | ||
577 | |||
578 | return strings[error_code]; | ||
579 | } | ||
580 | |||
581 | static int send_message ( MSISession *session, MSICall *call, MSIMessage *msg, uint32_t to ) | ||
582 | { | ||
583 | msi_msg_set_callid ( msg, call->id ); | ||
584 | |||
585 | uint8_t msg_string_final [MSI_MAXMSG_SIZE]; | ||
586 | uint16_t length = parse_send ( msg, msg_string_final ); | ||
587 | |||
588 | if (!length) { | ||
589 | LOGGER_WARNING("Parsing message failed; nothing sent!"); | ||
590 | return -1; | 441 | return -1; |
591 | } | 442 | } |
592 | 443 | ||
593 | if ( m_msi_packet(session->messenger_handle, to, msg_string_final, length) ) { | 444 | *it = 0; |
445 | size ++; | ||
446 | |||
447 | if ( m_msi_packet(m, friend_number, parsed, size) ) { | ||
594 | LOGGER_DEBUG("Sent message"); | 448 | LOGGER_DEBUG("Sent message"); |
595 | return 0; | 449 | return 0; |
596 | } | 450 | } |
597 | 451 | ||
598 | return -1; | 452 | return -1; |
599 | } | 453 | } |
600 | 454 | int send_error ( Messenger* m, uint32_t friend_number, MSIError error ) | |
601 | static int send_reponse ( MSISession *session, MSICall *call, MSIResponse response, uint32_t to ) | ||
602 | { | 455 | { |
603 | MSIMessage *msg = msi_new_message ( TypeResponse, response ); | 456 | /* Send error message */ |
604 | int ret = send_message ( session, call, msg, to ); | 457 | assert(m); |
605 | free ( msg ); | 458 | |
606 | return ret; | 459 | LOGGER_DEBUG("Sending error: %d to friend: %d", error, friend_number); |
607 | } | ||
608 | |||
609 | static int send_error ( MSISession *session, MSICall *call, MSICallError errid, uint32_t to ) | ||
610 | { | ||
611 | if (!call) { | ||
612 | LOGGER_WARNING("Cannot handle error on 'null' call"); | ||
613 | return -1; | ||
614 | } | ||
615 | |||
616 | LOGGER_DEBUG("Sending error: %d on call: %s", errid, call->id); | ||
617 | |||
618 | MSIMessage *msg_error = msi_new_message ( TypeResponse, error ); | ||
619 | |||
620 | msi_msg_set_reason ( msg_error, stringify_error(errid) ); | ||
621 | send_message ( session, call, msg_error, to ); | ||
622 | free ( msg_error ); | ||
623 | 460 | ||
461 | MSIMessage msg; | ||
462 | msg_init(&msg, requ_pop); | ||
463 | |||
464 | msg.error.exists = true; | ||
465 | msg.error.value = error; | ||
466 | |||
467 | send_message ( m, friend_number, &msg ); | ||
624 | return 0; | 468 | return 0; |
625 | } | 469 | } |
626 | 470 | int invoke_callback(MSICall* call, MSICallbackID cb) | |
627 | /** | ||
628 | * Determine 'bigger' call id | ||
629 | */ | ||
630 | static int call_id_bigger( const uint8_t *first, const uint8_t *second) | ||
631 | { | ||
632 | return (memcmp(first, second, sizeof(MSICallIDType)) < 0); | ||
633 | } | ||
634 | |||
635 | |||
636 | /** | ||
637 | * Set/change peer csettings | ||
638 | */ | ||
639 | static int flush_peer_csettings ( MSICall *call, MSIMessage *msg, int peer_id ) | ||
640 | { | 471 | { |
641 | if ( msg->csettings.exists ) { | 472 | assert(call); |
642 | msi_msg_get_csettings(msg, &call->csettings_peer[peer_id]); | 473 | |
643 | 474 | if ( call->session->callbacks[cb] ) { | |
644 | LOGGER_DEBUG("Peer: %d \n" | 475 | LOGGER_DEBUG("Invoking callback function: %d", cb); |
645 | "Type: %u \n" | 476 | if ( call->session->callbacks[cb] ( call->session->av, call ) != 0 ) { |
646 | "Video bitrate: %u \n" | 477 | LOGGER_WARNING("Callback state handling failed, sending error"); |
647 | "Video height: %u \n" | 478 | goto FAILURE; |
648 | "Video width: %u \n" | 479 | } |
649 | "Audio bitrate: %u \n" | 480 | |
650 | "Audio framedur: %u \n" | ||
651 | "Audio sample rate: %u \n" | ||
652 | "Audio channels: %u \n", peer_id, | ||
653 | call->csettings_peer[peer_id].call_type, | ||
654 | call->csettings_peer[peer_id].video_bitrate, | ||
655 | call->csettings_peer[peer_id].max_video_height, | ||
656 | call->csettings_peer[peer_id].max_video_width, | ||
657 | call->csettings_peer[peer_id].audio_bitrate, | ||
658 | call->csettings_peer[peer_id].audio_frame_duration, | ||
659 | call->csettings_peer[peer_id].audio_sample_rate, | ||
660 | call->csettings_peer[peer_id].audio_channels ); | ||
661 | |||
662 | return 0; | 481 | return 0; |
663 | } | 482 | } |
664 | 483 | ||
665 | LOGGER_WARNING("No csettings header!"); | 484 | FAILURE: |
485 | /* If no callback present or error happened while handling, | ||
486 | * an error message will be sent to friend | ||
487 | */ | ||
488 | |||
489 | if (call->error == msi_ENone) | ||
490 | call->error = msi_EHandle; | ||
666 | return -1; | 491 | return -1; |
667 | } | 492 | } |
668 | 493 | static MSICall *get_call ( MSISession *session, uint32_t friend_number ) | |
669 | |||
670 | /** | ||
671 | * Add peer to peer list. | ||
672 | */ | ||
673 | static void add_peer( MSICall *call, int peer_id ) | ||
674 | { | 494 | { |
675 | uint32_t *peers = !call->peers ? peers = calloc(sizeof(uint32_t), 1) : | 495 | assert(session); |
676 | realloc( call->peers, sizeof(uint32_t) * call->peer_count); | 496 | |
677 | 497 | if (session->calls == NULL || session->calls_tail < friend_number) | |
678 | if (!peers) { | 498 | return NULL; |
679 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | 499 | |
680 | return; | 500 | return session->calls[friend_number]; |
681 | } | ||
682 | |||
683 | call->peer_count ++; | ||
684 | call->peers = peers; | ||
685 | call->peers[call->peer_count - 1] = peer_id; | ||
686 | |||
687 | LOGGER_DEBUG("Added peer: %d", peer_id); | ||
688 | } | ||
689 | |||
690 | |||
691 | static MSICall *find_call ( MSISession *session, uint8_t *call_id ) | ||
692 | { | ||
693 | if ( call_id == NULL ) return NULL; | ||
694 | |||
695 | int32_t i = 0; | ||
696 | |||
697 | for (; i < session->max_calls; i ++ ) | ||
698 | if ( session->calls[i] && memcmp(session->calls[i]->id, call_id, sizeof(session->calls[i]->id)) == 0 ) { | ||
699 | return session->calls[i]; | ||
700 | } | ||
701 | |||
702 | return NULL; | ||
703 | } | 501 | } |
704 | 502 | MSICall *new_call ( MSISession *session, uint32_t friend_number ) | |
705 | static MSICall *init_call ( MSISession *session, int peers, int ringing_timeout ) | ||
706 | { | 503 | { |
707 | 504 | assert(session); | |
708 | if (peers == 0) { | 505 | |
709 | LOGGER_ERROR("No peers!"); | 506 | MSICall *rc = calloc(sizeof(MSICall), 1); |
507 | |||
508 | if (rc == NULL) | ||
710 | return NULL; | 509 | return NULL; |
711 | } | 510 | |
712 | 511 | rc->session = session; | |
713 | int32_t call_idx = 0; | 512 | rc->friend_number = friend_number; |
714 | 513 | ||
715 | for (; call_idx < session->max_calls; call_idx ++) { | 514 | if (session->calls == NULL) { /* Creating */ |
716 | if ( !session->calls[call_idx] ) { | 515 | session->calls = calloc (sizeof(MSICall*), friend_number + 1); |
717 | 516 | ||
718 | if (!(session->calls[call_idx] = calloc ( sizeof ( MSICall ), 1 ))) { | 517 | if (session->calls == NULL) { |
719 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | 518 | free(rc); |
720 | return NULL; | 519 | return NULL; |
721 | } | ||
722 | |||
723 | break; | ||
724 | } | 520 | } |
725 | } | 521 | |
726 | 522 | session->calls_tail = session->calls_head = friend_number; | |
727 | if ( call_idx == session->max_calls ) { | 523 | |
728 | LOGGER_WARNING("Reached maximum amount of calls!"); | 524 | } else if (session->calls_tail < friend_number) { /* Appending */ |
729 | return NULL; | 525 | void* tmp = realloc(session->calls, sizeof(MSICall*) * friend_number + 1); |
730 | } | 526 | |
731 | 527 | if (tmp == NULL) { | |
732 | 528 | free(rc); | |
733 | MSICall *call = session->calls[call_idx]; | 529 | return NULL; |
734 | 530 | } | |
735 | call->call_idx = call_idx; | 531 | |
736 | 532 | session->calls = tmp; | |
737 | if ( !(call->csettings_peer = calloc ( sizeof ( MSICSettings ), peers )) ) { | 533 | |
738 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | 534 | /* Set fields in between to null */ |
739 | free(call); | 535 | uint32_t i = session->calls_tail + 1; |
740 | return NULL; | 536 | for (; i < friend_number; i ++) |
741 | } | 537 | session->calls[i] = NULL; |
742 | 538 | ||
743 | call->session = session; | 539 | rc->prev = session->calls[session->calls_tail]; |
744 | 540 | session->calls[session->calls_tail]->next = rc; | |
745 | call->request_timer_id = 0; | 541 | |
746 | call->ringing_timer_id = 0; | 542 | session->calls_tail = friend_number; |
747 | 543 | ||
748 | call->ringing_tout_ms = ringing_timeout; | 544 | } else if (session->calls_head > friend_number) { /* Inserting at front */ |
749 | 545 | rc->next = session->calls[session->calls_head]; | |
750 | LOGGER_DEBUG("Started new call with index: %u", call_idx); | 546 | session->calls[session->calls_head]->prev = rc; |
751 | return call; | 547 | session->calls_head = friend_number; |
752 | } | 548 | } |
753 | 549 | ||
754 | static int terminate_call ( MSISession *session, MSICall *call ) | 550 | session->calls[friend_number] = rc; |
755 | { | 551 | return rc; |
756 | if ( !call ) { | 552 | } |
757 | LOGGER_WARNING("Tried to terminate non-existing call!"); | 553 | void kill_call ( MSICall *call ) |
758 | return -1; | 554 | { |
759 | } | 555 | /* Assume that session mutex is locked */ |
760 | 556 | if ( call == NULL ) | |
761 | /* Check event loop and cancel timed events if there are any | 557 | return; |
762 | */ | 558 | |
763 | timer_release ( session->timer_handler, call->request_timer_id); | 559 | LOGGER_DEBUG("Killing call: %p", call); |
764 | timer_release ( session->timer_handler, call->ringing_timer_id); | 560 | |
765 | 561 | MSISession* session = call->session; | |
766 | session->calls[call->call_idx] = NULL; | 562 | |
767 | 563 | MSICall* prev = call->prev; | |
768 | LOGGER_DEBUG("Terminated call id: %d", call->call_idx); | 564 | MSICall* next = call->next; |
769 | 565 | ||
770 | free ( call->csettings_peer ); | 566 | if (prev) |
771 | free ( call->peers ); | 567 | prev->next = next; |
772 | free ( call ); | 568 | else if (next) |
773 | 569 | session->calls_head = next->friend_number; | |
774 | return 0; | 570 | else goto CLEAR_CONTAINER; |
775 | } | 571 | |
776 | 572 | if (next) | |
777 | static void handle_remote_connection_change(Messenger *messenger, uint32_t friend_num, uint8_t status, void *session_p) | 573 | next->prev = prev; |
778 | { | 574 | else if (prev) |
779 | (void)messenger; | 575 | session->calls_tail = prev->friend_number; |
780 | MSISession *session = session_p; | 576 | else goto CLEAR_CONTAINER; |
577 | |||
578 | session->calls[call->friend_number] = NULL; | ||
579 | free(call); | ||
580 | return; | ||
581 | |||
582 | CLEAR_CONTAINER: | ||
583 | session->calls_head = session->calls_tail = 0; | ||
584 | free(session->calls); | ||
585 | free(call); | ||
586 | session->calls = NULL; | ||
587 | } | ||
588 | void on_peer_status(Messenger* m, uint32_t friend_number, uint8_t status, void* data) | ||
589 | { | ||
590 | (void)m; | ||
591 | MSISession *session = data; | ||
781 | 592 | ||
782 | switch ( status ) { | 593 | switch ( status ) { |
783 | case 0: { /* Went offline */ | 594 | case 0: { /* Friend is now offline */ |
784 | int32_t j = 0; | 595 | LOGGER_DEBUG("Friend %d is now offline", friend_number); |
785 | 596 | ||
786 | for ( ; j < session->max_calls; j ++ ) { | 597 | pthread_mutex_lock(session->mutex); |
787 | 598 | MSICall* call = get_call(session, friend_number); | |
788 | if ( !session->calls[j] ) continue; | 599 | |
789 | 600 | if (call == NULL) { | |
790 | uint16_t i = 0; | 601 | pthread_mutex_unlock(session->mutex); |
791 | 602 | return; | |
792 | for ( ; i < session->calls[j]->peer_count; i ++ ) | ||
793 | if ( session->calls[j]->peers[i] == (uint32_t)friend_num ) { | ||
794 | invoke_callback(session, j, msi_OnPeerTimeout); | ||
795 | terminate_call(session, session->calls[j]); | ||
796 | LOGGER_DEBUG("Remote: %d timed out!", friend_num); | ||
797 | return; /* TODO: On group calls change behaviour */ | ||
798 | } | ||
799 | } | 603 | } |
604 | |||
605 | invoke_callback(call, msi_OnPeerTimeout); /* Failure is ignored */ | ||
606 | kill_call(call); | ||
607 | pthread_mutex_unlock(session->mutex); | ||
800 | } | 608 | } |
801 | break; | 609 | break; |
802 | 610 | ||
@@ -804,811 +612,186 @@ static void handle_remote_connection_change(Messenger *messenger, uint32_t frien | |||
804 | break; | 612 | break; |
805 | } | 613 | } |
806 | } | 614 | } |
807 | 615 | void handle_push ( MSICall *call, const MSIMessage *msg ) | |
808 | /** | ||
809 | * Function called at request timeout | ||
810 | */ | ||
811 | static void handle_timeout ( Timer *timer ) | ||
812 | { | 616 | { |
813 | /* TODO: Cancel might not arrive there; set up | 617 | assert(call); |
814 | * timers on these cancels and terminate call on | 618 | |
815 | * their timeout | 619 | LOGGER_DEBUG("Session: %p Handling 'push' friend: %d", call->session, call->friend_number); |
816 | */ | ||
817 | MSICall *call = timer->session->calls[timer->call_idx]; | ||
818 | |||
819 | |||
820 | if (call) { | ||
821 | LOGGER_DEBUG("[Call: %d] Request timed out!", call->call_idx); | ||
822 | 620 | ||
823 | invoke_callback(timer->session, timer->call_idx, msi_OnRequestTimeout); | 621 | if (!msg->capabilities.exists) { |
824 | msi_cancel(timer->session, timer->call_idx, call->peers [0], "Request timed out"); | 622 | LOGGER_WARNING("Session: %p Invalid capabilities on 'push'"); |
623 | call->error = msi_EInvalidMessage; | ||
624 | goto FAILURE; | ||
825 | } | 625 | } |
826 | } | 626 | |
827 | 627 | if (call->state != msi_CallActive) { | |
828 | 628 | if (!msg->vfpsz.exists) { | |
829 | /********** Request handlers **********/ | 629 | LOGGER_WARNING("Session: %p Invalid vfpsz on 'push'"); |
830 | static int handle_recv_invite ( MSISession *session, MSICall *call, MSIMessage *msg ) | 630 | call->error = msi_EInvalidMessage; |
831 | { | 631 | goto FAILURE; |
832 | LOGGER_DEBUG("Session: %p Handling 'invite' on call: %d", session, call ? call->call_idx : -1); | 632 | } |
833 | 633 | ||
834 | 634 | call->peer_vfpsz = msg->vfpsz.value; | |
835 | if (!msg->csettings.exists) {/**/ | 635 | } |
836 | LOGGER_WARNING("Peer sent invalid codec settings!"); | 636 | |
837 | send_error ( session, call, error_no_callid, msg->friend_id ); | 637 | |
838 | return 0; | 638 | switch (call->state) { |
839 | } | 639 | case msi_CallInactive: { |
840 | 640 | LOGGER_INFO("Friend is calling us"); | |
841 | if ( call ) { | 641 | |
842 | if ( call->peers[0] == (uint32_t)msg->friend_id ) { | 642 | /* Call requested */ |
843 | if (call->state == msi_CallInviting) { | 643 | call->peer_capabilities = msg->capabilities.value; |
844 | /* The glare case. A calls B when at the same time | 644 | call->state = msi_CallRequested; |
845 | * B calls A. Who has advantage is set bey calculating | 645 | |
846 | * 'bigger' Call id and then that call id is being used in | 646 | if ( invoke_callback(call, msi_OnInvite) == -1 ) |
847 | * future. User with 'bigger' Call id has the advantage | 647 | goto FAILURE; |
848 | * as in he will wait the response from the other. | 648 | |
649 | } break; | ||
650 | |||
651 | case msi_CallActive: { | ||
652 | if (msg->vfpsz.exists) { | ||
653 | /* If peer sended video frame piece size | ||
654 | * while the call is already active it's probable | ||
655 | * that he is trying to re-call us while the call | ||
656 | * is not terminated on our side. We can assume that | ||
657 | * in this case we can automatically answer the re-call. | ||
849 | */ | 658 | */ |
850 | LOGGER_DEBUG("Glare case; Peer: %d", call->peers[0]); | 659 | if (call->peer_vfpsz != msg->vfpsz.value) { |
851 | 660 | LOGGER_WARNING("Friend sent invalid parameters for re-call"); | |
852 | if ( call_id_bigger (call->id, msg->callid.value) == 1 ) { /* Peer has advantage */ | 661 | call->error = msi_EInvalidParam; |
853 | 662 | invoke_callback(call, msi_OnError); | |
854 | /* Terminate call; peer will timeout(call) if call initialization fails */ | 663 | goto FAILURE; |
855 | terminate_call(session, call); | ||
856 | |||
857 | call = init_call ( session, 1, 0 ); | ||
858 | |||
859 | if ( !call ) { | ||
860 | LOGGER_ERROR("Starting call"); | ||
861 | return 0; | ||
862 | } | ||
863 | |||
864 | } else { | ||
865 | return 0; /* Wait for ringing from peer */ | ||
866 | } | ||
867 | } else if (call->state == msi_CallActive) { | ||
868 | /* Request for media change; call callback and send starting response */ | ||
869 | if (flush_peer_csettings(call, msg, 0) != 0) { /**/ | ||
870 | LOGGER_WARNING("Peer sent invalid csetting!"); | ||
871 | send_error ( session, call, error_no_callid, msg->friend_id ); | ||
872 | return 0; | ||
873 | } | 664 | } |
874 | 665 | ||
875 | LOGGER_DEBUG("Set new call type: %s", call->csettings_peer[0].call_type == msi_TypeAudio ? "audio" : "video"); | 666 | LOGGER_INFO("Friend is recalling us"); |
876 | send_reponse(session, call, starting, msg->friend_id); | 667 | |
877 | invoke_callback(session, call->call_idx, msi_OnPeerCSChange); | 668 | MSIMessage msg; |
878 | return 1; | 669 | msg_init(&msg, requ_push); |
670 | |||
671 | msg.capabilities.exists = true; | ||
672 | msg.capabilities.value = call->self_capabilities; | ||
673 | |||
674 | msg.vfpsz.exists = true; | ||
675 | msg.vfpsz.value = VIDEOFRAME_PIECE_SIZE; | ||
676 | |||
677 | send_message ( call->session->messenger, call->friend_number, &msg ); | ||
678 | |||
679 | /* If peer changed capabilities during re-call they will | ||
680 | * be handled accordingly during the next step | ||
681 | */ | ||
879 | } | 682 | } |
880 | } else { | 683 | |
881 | send_error ( session, call, error_busy, msg->friend_id ); /* TODO: Ugh*/ | 684 | /* Only act if capabilities changed */ |
882 | terminate_call(session, call); | 685 | if ( call->peer_capabilities != msg->capabilities.value) { |
883 | return 0; | 686 | LOGGER_INFO("Friend is changing capabilities to: %u", msg->capabilities.value); |
884 | } | 687 | |
885 | } else { | 688 | call->peer_capabilities = msg->capabilities.value; |
886 | call = init_call ( session, 1, 0 ); | 689 | if ( invoke_callback(call, msi_OnCapabilities) == -1 ) |
887 | 690 | goto FAILURE; | |
888 | if ( !call ) { | 691 | } |
889 | LOGGER_ERROR("Starting call"); | 692 | } break; |
890 | return 0; | 693 | |
891 | } | 694 | case msi_CallRequesting: { |
892 | } | 695 | LOGGER_INFO("Friend answered our call"); |
893 | 696 | ||
894 | if ( !msg->callid.exists ) { | 697 | /* Call started */ |
895 | send_error ( session, call, error_no_callid, msg->friend_id ); | 698 | call->peer_capabilities = msg->capabilities.value; |
896 | terminate_call(session, call); | 699 | call->state = msi_CallActive; |
897 | return 0; | 700 | |
898 | } | 701 | if ( invoke_callback(call, msi_OnStart) == -1 ) |
899 | 702 | goto FAILURE; | |
900 | memcpy ( call->id, msg->callid.value, sizeof(msg->callid.value) ); | 703 | |
901 | call->state = msi_CallStarting; | 704 | } break; |
902 | 705 | ||
903 | add_peer( call, msg->friend_id); | 706 | case msi_CallRequested: { |
904 | flush_peer_csettings ( call, msg, 0 ); | 707 | /* Consecutive pushes during initialization state are ignored */ |
905 | send_reponse(session, call, ringing, msg->friend_id); | 708 | LOGGER_WARNING("Consecutive push"); |
906 | invoke_callback(session, call->call_idx, msi_OnInvite); | 709 | } break; |
907 | 710 | } | |
908 | return 1; | 711 | |
909 | } | 712 | return; |
910 | 713 | ||
911 | static int handle_recv_start ( MSISession *session, MSICall *call, MSIMessage *msg ) | 714 | FAILURE: |
912 | { | 715 | send_error(call->session->messenger, call->friend_number, call->error); |
913 | if ( !call ) { | 716 | kill_call(call); |
914 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | 717 | } |
915 | return 0; | 718 | void handle_pop ( MSICall *call, const MSIMessage *msg ) |
916 | } | 719 | { |
917 | 720 | assert(call); | |
918 | (void)msg; | 721 | |
919 | 722 | LOGGER_DEBUG("Session: %p Handling 'pop', friend id: %d", call->session, call->friend_number); | |
920 | LOGGER_DEBUG("Session: %p Handling 'start' on call: %d, friend id: %d", session, call->call_idx, msg->friend_id ); | 723 | |
921 | 724 | /* callback errors are ignored */ | |
922 | call->state = msi_CallActive; | 725 | |
923 | invoke_callback(session, call->call_idx, msi_OnStart); | 726 | if (msg->error.exists) { |
924 | return 1; | 727 | LOGGER_WARNING("Friend detected an error: %d", msg->error.value); |
925 | } | 728 | call->error = msg->error.value; |
926 | 729 | invoke_callback(call, msi_OnError); | |
927 | static int handle_recv_reject ( MSISession *session, MSICall *call, MSIMessage *msg ) | 730 | |
928 | { | 731 | } else switch (call->state) { |
929 | if ( !call ) { | 732 | case msi_CallInactive: { |
930 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | 733 | LOGGER_ERROR("Handling what should be impossible case"); |
931 | return 0; | 734 | abort(); |
932 | } | 735 | } break; |
933 | 736 | ||
934 | LOGGER_DEBUG("Session: %p Handling 'reject' on call: %u", session, call->call_idx); | 737 | case msi_CallActive: { |
935 | 738 | /* Hangup */ | |
936 | invoke_callback(session, call->call_idx, msi_OnReject); | 739 | LOGGER_INFO("Friend hung up on us"); |
937 | 740 | invoke_callback(call, msi_OnEnd); | |
938 | send_reponse(session, call, ending, msg->friend_id); | 741 | } break; |
939 | terminate_call(session, call); | 742 | |
940 | 743 | case msi_CallRequesting: { | |
941 | return 1; | 744 | /* Reject */ |
942 | } | 745 | LOGGER_INFO("Friend rejected our call"); |
943 | 746 | invoke_callback(call, msi_OnEnd); | |
944 | static int handle_recv_cancel ( MSISession *session, MSICall *call, MSIMessage *msg ) | 747 | } break; |
945 | { | 748 | |
946 | if ( !call ) { | 749 | case msi_CallRequested: { |
947 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | 750 | /* Cancel */ |
948 | return 0; | 751 | LOGGER_INFO("Friend canceled call invite"); |
949 | } | 752 | invoke_callback(call, msi_OnEnd); |
950 | 753 | } break; | |
951 | (void)msg; | 754 | } |
952 | 755 | ||
953 | LOGGER_DEBUG("Session: %p Handling 'cancel' on call: %u", session, call->call_idx); | 756 | kill_call ( call ); |
954 | 757 | } | |
955 | invoke_callback(session, call->call_idx, msi_OnCancel); | 758 | void handle_msi_packet ( Messenger* m, uint32_t friend_number, const uint8_t* data, uint16_t length, void* object ) |
956 | terminate_call ( session, call ); | ||
957 | |||
958 | return 1; | ||
959 | } | ||
960 | |||
961 | static int handle_recv_end ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
962 | { | ||
963 | if ( !call ) { | ||
964 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
965 | return 0; | ||
966 | } | ||
967 | |||
968 | LOGGER_DEBUG("Session: %p Handling 'end' on call: %d", session, call->call_idx); | ||
969 | |||
970 | invoke_callback(session, call->call_idx, msi_OnEnd); | ||
971 | send_reponse(session, call, ending, msg->friend_id); | ||
972 | terminate_call ( session, call ); | ||
973 | |||
974 | return 1; | ||
975 | } | ||
976 | |||
977 | /********** Response handlers **********/ | ||
978 | static int handle_recv_ringing ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
979 | { | ||
980 | if ( !call ) { | ||
981 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
982 | return 0; | ||
983 | } | ||
984 | |||
985 | (void)msg; | ||
986 | |||
987 | if ( call->ringing_timer_id ) { | ||
988 | LOGGER_WARNING("Call already ringing"); | ||
989 | return 0; | ||
990 | } | ||
991 | |||
992 | LOGGER_DEBUG("Session: %p Handling 'ringing' on call: %d", session, call->call_idx ); | ||
993 | |||
994 | call->ringing_timer_id = timer_alloc | ||
995 | ( session, handle_timeout, call->call_idx, call->ringing_tout_ms ); | ||
996 | invoke_callback(session, call->call_idx, msi_OnRinging); | ||
997 | return 1; | ||
998 | } | ||
999 | static int handle_recv_starting ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
1000 | { | ||
1001 | if ( !call ) { | ||
1002 | LOGGER_WARNING("Session: %p Handling 'starting' on non-existing call"); | ||
1003 | return 0; | ||
1004 | } | ||
1005 | |||
1006 | if ( call->state == msi_CallActive ) { /* Change media */ | ||
1007 | |||
1008 | LOGGER_DEBUG("Session: %p Changing media on call: %d", session, call->call_idx ); | ||
1009 | |||
1010 | invoke_callback(session, call->call_idx, msi_OnSelfCSChange); | ||
1011 | |||
1012 | } else if ( call->state == msi_CallInviting ) { | ||
1013 | LOGGER_DEBUG("Session: %p Handling 'starting' on call: %d", session, call->call_idx ); | ||
1014 | |||
1015 | call->state = msi_CallActive; | ||
1016 | |||
1017 | MSIMessage *msg_start = msi_new_message ( TypeRequest, start ); | ||
1018 | send_message ( session, call, msg_start, msg->friend_id ); | ||
1019 | free ( msg_start ); | ||
1020 | |||
1021 | |||
1022 | flush_peer_csettings ( call, msg, 0 ); | ||
1023 | |||
1024 | /* This is here in case of glare */ | ||
1025 | timer_release(session->timer_handler, call->ringing_timer_id); | ||
1026 | invoke_callback(session, call->call_idx, msi_OnStart); | ||
1027 | } else { | ||
1028 | LOGGER_ERROR("Invalid call state"); | ||
1029 | terminate_call(session, call ); | ||
1030 | return 0; | ||
1031 | } | ||
1032 | |||
1033 | return 1; | ||
1034 | } | ||
1035 | static int handle_recv_ending ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
1036 | { | ||
1037 | if ( !call ) { | ||
1038 | LOGGER_WARNING("Session: %p Handling 'start' on no call"); | ||
1039 | return 0; | ||
1040 | } | ||
1041 | |||
1042 | (void)msg; | ||
1043 | |||
1044 | LOGGER_DEBUG("Session: %p Handling 'ending' on call: %d", session, call->call_idx ); | ||
1045 | |||
1046 | invoke_callback(session, call->call_idx, msi_OnEnd); | ||
1047 | terminate_call ( session, call ); | ||
1048 | |||
1049 | return 1; | ||
1050 | } | ||
1051 | static int handle_recv_error ( MSISession *session, MSICall *call, MSIMessage *msg ) | ||
1052 | { | ||
1053 | if ( !call ) { | ||
1054 | LOGGER_WARNING("Handling 'error' on non-existing call!"); | ||
1055 | return -1; | ||
1056 | } | ||
1057 | |||
1058 | LOGGER_DEBUG("Session: %p Handling 'error' on call: %d", session, call->call_idx ); | ||
1059 | |||
1060 | invoke_callback(session, call->call_idx, msi_OnEnd); | ||
1061 | |||
1062 | /* Handle error accordingly */ | ||
1063 | if ( msg->reason.exists ) { | ||
1064 | /* TODO */ | ||
1065 | } | ||
1066 | |||
1067 | terminate_call ( session, call ); | ||
1068 | |||
1069 | return 1; | ||
1070 | } | ||
1071 | |||
1072 | /** | ||
1073 | * BASIC call flow: | ||
1074 | * | ||
1075 | * ALICE BOB | ||
1076 | * | invite --> | | ||
1077 | * | | | ||
1078 | * | <-- ringing | | ||
1079 | * | | | ||
1080 | * | <-- starting | | ||
1081 | * | | | ||
1082 | * | start --> | | ||
1083 | * | | | ||
1084 | * | <-- MEDIA TRANS --> | | ||
1085 | * | | | ||
1086 | * | end --> | | ||
1087 | * | | | ||
1088 | * | <-- ending | | ||
1089 | * | ||
1090 | * Alice calls Bob by sending invite packet. | ||
1091 | * Bob recvs the packet and sends an ringing packet; | ||
1092 | * which notifies Alice that her invite is acknowledged. | ||
1093 | * Ringing screen shown on both sides. | ||
1094 | * Bob accepts the invite for a call by sending starting packet. | ||
1095 | * Alice recvs the starting packet and sends the started packet to | ||
1096 | * inform Bob that she recved the starting packet. | ||
1097 | * Now the media transmission is established ( i.e. RTP transmission ). | ||
1098 | * Alice hangs up and sends end packet. | ||
1099 | * Bob recves the end packet and sends ending packet | ||
1100 | * as the acknowledgement that the call is ending. | ||
1101 | * | ||
1102 | * | ||
1103 | */ | ||
1104 | static void msi_handle_packet ( Messenger *messenger, uint32_t source, const uint8_t *data, uint16_t length, | ||
1105 | void *object ) | ||
1106 | { | 759 | { |
1107 | LOGGER_DEBUG("Got msi message"); | 760 | LOGGER_DEBUG("Got msi message"); |
1108 | /* Unused */ | 761 | |
1109 | (void)messenger; | ||
1110 | |||
1111 | MSISession *session = object; | 762 | MSISession *session = object; |
1112 | MSIMessage *msg; | 763 | MSIMessage msg; |
1113 | 764 | ||
1114 | if ( !length ) { | 765 | if ( msg_parse_in ( &msg, data, length ) == -1 ) { |
1115 | LOGGER_WARNING("Length param negative"); | ||
1116 | return; | ||
1117 | } | ||
1118 | |||
1119 | msg = parse_recv ( data, length ); | ||
1120 | |||
1121 | if ( !msg ) { | ||
1122 | LOGGER_WARNING("Error parsing message"); | 766 | LOGGER_WARNING("Error parsing message"); |
767 | send_error(m, friend_number, msi_EInvalidMessage); | ||
1123 | return; | 768 | return; |
1124 | } else { | 769 | } else { |
1125 | LOGGER_DEBUG("Successfully parsed message"); | 770 | LOGGER_DEBUG("Successfully parsed message"); |
1126 | } | 771 | } |
1127 | 772 | ||
1128 | msg->friend_id = source; | ||
1129 | |||
1130 | pthread_mutex_lock(session->mutex); | ||
1131 | |||
1132 | /* Find what call */ | ||
1133 | MSICall *call = msg->callid.exists ? find_call(session, msg->callid.value ) : NULL; | ||
1134 | |||
1135 | /* Now handle message */ | ||
1136 | |||
1137 | if ( msg->request.exists ) { /* Handle request */ | ||
1138 | |||
1139 | switch (msg->request.value) { | ||
1140 | case invite: | ||
1141 | handle_recv_invite ( session, call, msg ); | ||
1142 | break; | ||
1143 | |||
1144 | case start: | ||
1145 | handle_recv_start ( session, call, msg ); | ||
1146 | break; | ||
1147 | |||
1148 | case cancel: | ||
1149 | handle_recv_cancel ( session, call, msg ); | ||
1150 | break; | ||
1151 | |||
1152 | case reject: | ||
1153 | handle_recv_reject ( session, call, msg ); | ||
1154 | break; | ||
1155 | |||
1156 | case end: | ||
1157 | handle_recv_end ( session, call, msg ); | ||
1158 | break; | ||
1159 | } | ||
1160 | |||
1161 | } else if ( msg->response.exists ) { /* Handle response */ | ||
1162 | |||
1163 | /* Got response so cancel timer */ | ||
1164 | if ( call ) timer_release(session->timer_handler, call->request_timer_id); | ||
1165 | |||
1166 | switch (msg->response.value) { | ||
1167 | case ringing: | ||
1168 | handle_recv_ringing ( session, call, msg ); | ||
1169 | break; | ||
1170 | |||
1171 | case starting: | ||
1172 | handle_recv_starting ( session, call, msg ); | ||
1173 | break; | ||
1174 | |||
1175 | case ending: | ||
1176 | handle_recv_ending ( session, call, msg ); | ||
1177 | break; | ||
1178 | |||
1179 | case error: | ||
1180 | handle_recv_error ( session, call, msg ); | ||
1181 | break; | ||
1182 | } | ||
1183 | |||
1184 | } else { | ||
1185 | LOGGER_WARNING("Invalid message: no resp nor requ headers"); | ||
1186 | } | ||
1187 | |||
1188 | free ( msg ); | ||
1189 | |||
1190 | pthread_mutex_unlock(session->mutex); | ||
1191 | } | ||
1192 | |||
1193 | |||
1194 | |||
1195 | /********** User functions **********/ | ||
1196 | void msi_register_callback ( MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata ) | ||
1197 | { | ||
1198 | session->callbacks[id].first = callback; | ||
1199 | session->callbacks[id].second = userdata; | ||
1200 | } | ||
1201 | |||
1202 | |||
1203 | MSISession *msi_new ( Messenger *messenger, int32_t max_calls ) | ||
1204 | { | ||
1205 | if (messenger == NULL) { | ||
1206 | LOGGER_ERROR("Could not init session on empty messenger!"); | ||
1207 | return NULL; | ||
1208 | } | ||
1209 | |||
1210 | if ( !max_calls ) { | ||
1211 | LOGGER_WARNING("Invalid max call treshold!"); | ||
1212 | return NULL; | ||
1213 | } | ||
1214 | |||
1215 | MSISession *retu = calloc ( sizeof ( MSISession ), 1 ); | ||
1216 | |||
1217 | if (retu == NULL) { | ||
1218 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1219 | return NULL; | ||
1220 | } | ||
1221 | |||
1222 | if (!(retu->calls = calloc( sizeof (MSICall *), max_calls ))) { | ||
1223 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1224 | goto error; | ||
1225 | } | ||
1226 | |||
1227 | retu->timer_handler = calloc(1, sizeof(TimerHandler)); | ||
1228 | |||
1229 | if (retu->timer_handler == NULL) { | ||
1230 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1231 | goto error; | ||
1232 | } | ||
1233 | |||
1234 | /* Allocate space for timers */ | ||
1235 | ((TimerHandler *)retu->timer_handler)->max_capacity = max_calls * 10; | ||
1236 | |||
1237 | if (!(((TimerHandler *)retu->timer_handler)->timers = calloc(max_calls * 10, sizeof(Timer *)))) { | ||
1238 | LOGGER_ERROR("Allocation failed! Program might misbehave!"); | ||
1239 | goto error; | ||
1240 | } | ||
1241 | |||
1242 | if (create_recursive_mutex(retu->mutex) != 0) { | ||
1243 | LOGGER_ERROR("Failed to init mutex! Program might misbehave"); | ||
1244 | goto error; | ||
1245 | } | ||
1246 | |||
1247 | retu->messenger_handle = messenger; | ||
1248 | retu->agent_handler = NULL; | ||
1249 | retu->max_calls = max_calls; | ||
1250 | retu->frequ = 10000; /* default value? */ | ||
1251 | retu->call_timeout = 30000; /* default value? */ | ||
1252 | |||
1253 | m_callback_msi_packet(messenger, msi_handle_packet, retu ); | ||
1254 | |||
1255 | /* This is called when remote terminates session */ | ||
1256 | m_callback_connectionstatus_internal_av(messenger, handle_remote_connection_change, retu); | ||
1257 | |||
1258 | LOGGER_DEBUG("New msi session: %p max calls: %u", retu, max_calls); | ||
1259 | return retu; | ||
1260 | |||
1261 | error: | ||
1262 | |||
1263 | if (retu->timer_handler) { | ||
1264 | free(((TimerHandler *)retu->timer_handler)->timers); | ||
1265 | free(retu->timer_handler); | ||
1266 | } | ||
1267 | |||
1268 | free(retu->calls); | ||
1269 | free(retu); | ||
1270 | return NULL; | ||
1271 | } | ||
1272 | |||
1273 | |||
1274 | int msi_kill ( MSISession *session ) | ||
1275 | { | ||
1276 | if (session == NULL) { | ||
1277 | LOGGER_ERROR("Tried to terminate non-existing session"); | ||
1278 | return -1; | ||
1279 | } | ||
1280 | |||
1281 | m_callback_msi_packet((struct Messenger *) session->messenger_handle, NULL, NULL); | ||
1282 | pthread_mutex_lock(session->mutex); | 773 | pthread_mutex_lock(session->mutex); |
1283 | 774 | MSICall *call = get_call(session, friend_number); | |
1284 | /* Cancel active calls */ | 775 | |
1285 | int32_t idx = 0; | 776 | if (call == NULL) { |
1286 | 777 | if (msg.request.value != requ_push) { | |
1287 | for (; idx < session->max_calls; idx ++) if ( session->calls[idx] ) { | 778 | send_error(m, friend_number, msi_EStrayMessage); |
1288 | /* Cancel all? */ | 779 | pthread_mutex_unlock(session->mutex); |
1289 | uint16_t _it = 0; | 780 | return; |
1290 | /*for ( ; _it < session->calls[idx]->peer_count; _it++ ) | ||
1291 | * FIXME: will not work on multiple peers, must cancel call for all peers | ||
1292 | */ | ||
1293 | MSICallState state = session->calls[idx]->state; | ||
1294 | |||
1295 | if (state == msi_CallInviting) { | ||
1296 | msi_cancel( session, idx, session->calls[idx]->peers [_it], "MSI session terminated!" ); | ||
1297 | } else { | ||
1298 | msi_stopcall(session, idx); | ||
1299 | } | ||
1300 | } | 781 | } |
1301 | 782 | ||
1302 | free(((TimerHandler *)session->timer_handler)->timers); | 783 | call = new_call(session, friend_number); |
1303 | free(session->timer_handler); | 784 | if (call == NULL) { |
1304 | 785 | send_error(m, friend_number, msi_ESystem); | |
1305 | free ( session->calls ); | ||
1306 | pthread_mutex_unlock(session->mutex); | ||
1307 | pthread_mutex_destroy(session->mutex); | ||
1308 | |||
1309 | LOGGER_DEBUG("Terminated session: %p", session); | ||
1310 | free ( session ); | ||
1311 | return 0; | ||
1312 | } | ||
1313 | |||
1314 | int msi_invite ( MSISession *session, | ||
1315 | int32_t *call_index, | ||
1316 | const MSICSettings *csettings, | ||
1317 | uint32_t rngsec, | ||
1318 | uint32_t friend_id ) | ||
1319 | { | ||
1320 | pthread_mutex_lock(session->mutex); | ||
1321 | |||
1322 | LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_id); | ||
1323 | |||
1324 | |||
1325 | int i = 0; | ||
1326 | |||
1327 | for (; i < session->max_calls; i ++) | ||
1328 | if (session->calls[i] && session->calls[i]->peers[0] == friend_id) { | ||
1329 | LOGGER_ERROR("Already in a call with friend %d", friend_id); | ||
1330 | pthread_mutex_unlock(session->mutex); | 786 | pthread_mutex_unlock(session->mutex); |
1331 | return msi_ErrorAlreadyInCallWithPeer; | 787 | return; |
1332 | } | 788 | } |
1333 | |||
1334 | |||
1335 | MSICall *call = init_call ( session, 1, rngsec ); /* Just one peer for now */ | ||
1336 | |||
1337 | if ( !call ) { | ||
1338 | pthread_mutex_unlock(session->mutex); | ||
1339 | LOGGER_ERROR("Cannot handle more calls"); | ||
1340 | return msi_ErrorReachedCallLimit; | ||
1341 | } | ||
1342 | |||
1343 | *call_index = call->call_idx; | ||
1344 | |||
1345 | t_randomstr ( call->id, sizeof(call->id) ); | ||
1346 | |||
1347 | add_peer ( call, friend_id ); | ||
1348 | |||
1349 | call->csettings_local = *csettings; | ||
1350 | |||
1351 | MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); | ||
1352 | |||
1353 | msi_msg_set_csettings(msg_invite, csettings); | ||
1354 | send_message ( session, call, msg_invite, friend_id ); | ||
1355 | free( msg_invite ); | ||
1356 | |||
1357 | call->state = msi_CallInviting; | ||
1358 | |||
1359 | call->request_timer_id = timer_alloc ( session, handle_timeout, call->call_idx, m_deftout ); | ||
1360 | |||
1361 | LOGGER_DEBUG("Invite sent"); | ||
1362 | |||
1363 | pthread_mutex_unlock(session->mutex); | ||
1364 | |||
1365 | return 0; | ||
1366 | } | ||
1367 | |||
1368 | int msi_hangup ( MSISession *session, int32_t call_index ) | ||
1369 | { | ||
1370 | pthread_mutex_lock(session->mutex); | ||
1371 | LOGGER_DEBUG("Session: %p Hanging up call: %u", session, call_index); | ||
1372 | |||
1373 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1374 | LOGGER_ERROR("Invalid call index!"); | ||
1375 | pthread_mutex_unlock(session->mutex); | ||
1376 | return msi_ErrorNoCall; | ||
1377 | } | ||
1378 | |||
1379 | if ( session->calls[call_index]->state != msi_CallActive ) { | ||
1380 | LOGGER_ERROR("Call is not active!"); | ||
1381 | pthread_mutex_unlock(session->mutex); | ||
1382 | return msi_ErrorInvalidState; | ||
1383 | } | 789 | } |
1384 | 790 | ||
1385 | MSIMessage *msg_end = msi_new_message ( TypeRequest, end ); | 791 | if (msg.request.value == requ_push) |
1386 | 792 | handle_push(call, &msg); | |
1387 | /* hangup for each peer */ | 793 | else |
1388 | int it = 0; | 794 | handle_pop(call, &msg); /* always kills the call */ |
1389 | 795 | ||
1390 | for ( ; it < session->calls[call_index]->peer_count; it ++ ) | ||
1391 | send_message ( session, session->calls[call_index], msg_end, session->calls[call_index]->peers[it] ); | ||
1392 | |||
1393 | session->calls[call_index]->state = msi_CallOver; | ||
1394 | |||
1395 | free ( msg_end ); | ||
1396 | |||
1397 | session->calls[call_index]->request_timer_id = | ||
1398 | timer_alloc ( session, handle_timeout, call_index, m_deftout ); | ||
1399 | |||
1400 | pthread_mutex_unlock(session->mutex); | ||
1401 | return 0; | ||
1402 | } | ||
1403 | |||
1404 | int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ) | ||
1405 | { | ||
1406 | pthread_mutex_lock(session->mutex); | ||
1407 | LOGGER_DEBUG("Session: %p Answering call: %u", session, call_index); | ||
1408 | |||
1409 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1410 | LOGGER_ERROR("Invalid call index!"); | ||
1411 | pthread_mutex_unlock(session->mutex); | ||
1412 | return msi_ErrorNoCall; | ||
1413 | } | ||
1414 | |||
1415 | if ( session->calls[call_index]->state != msi_CallStarting ) { | ||
1416 | LOGGER_ERROR("Call is in invalid state!"); | ||
1417 | pthread_mutex_unlock(session->mutex); | ||
1418 | return msi_ErrorInvalidState; | ||
1419 | } | ||
1420 | |||
1421 | MSIMessage *msg_starting = msi_new_message ( TypeResponse, starting ); | ||
1422 | |||
1423 | session->calls[call_index]->csettings_local = *csettings; | ||
1424 | |||
1425 | msi_msg_set_csettings(msg_starting, csettings); | ||
1426 | |||
1427 | send_message ( session, session->calls[call_index], msg_starting, session->calls[call_index]->peers[0] ); | ||
1428 | free ( msg_starting ); | ||
1429 | |||
1430 | session->calls[call_index]->state = msi_CallActive; | ||
1431 | |||
1432 | pthread_mutex_unlock(session->mutex); | ||
1433 | return 0; | ||
1434 | } | ||
1435 | |||
1436 | int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ) | ||
1437 | { | ||
1438 | pthread_mutex_lock(session->mutex); | ||
1439 | LOGGER_DEBUG("Session: %p Canceling call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); | ||
1440 | |||
1441 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1442 | LOGGER_ERROR("Invalid call index!"); | ||
1443 | pthread_mutex_unlock(session->mutex); | ||
1444 | return msi_ErrorNoCall; | ||
1445 | } | ||
1446 | |||
1447 | if ( session->calls[call_index]->state != msi_CallInviting ) { | ||
1448 | LOGGER_ERROR("Call is in invalid state: %u", session->calls[call_index]->state); | ||
1449 | pthread_mutex_unlock(session->mutex); | ||
1450 | return msi_ErrorInvalidState; | ||
1451 | } | ||
1452 | |||
1453 | MSIMessage *msg_cancel = msi_new_message ( TypeRequest, cancel ); | ||
1454 | |||
1455 | /* FIXME */ | ||
1456 | #if 0 | ||
1457 | |||
1458 | if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { | ||
1459 | MSIReasonStrType reason_cast; | ||
1460 | memset(reason_cast, '\0', sizeof(MSIReasonStrType)); | ||
1461 | memcpy(reason_cast, reason, strlen(reason)); | ||
1462 | msi_msg_set_reason(msg_cancel, reason_cast); | ||
1463 | } | ||
1464 | |||
1465 | #else | ||
1466 | (void)reason; | ||
1467 | |||
1468 | #endif | ||
1469 | |||
1470 | send_message ( session, session->calls[call_index], msg_cancel, peer ); | ||
1471 | free ( msg_cancel ); | ||
1472 | |||
1473 | terminate_call ( session, session->calls[call_index] ); | ||
1474 | pthread_mutex_unlock(session->mutex); | ||
1475 | |||
1476 | return 0; | ||
1477 | } | ||
1478 | |||
1479 | int msi_reject ( MSISession *session, int32_t call_index, const char *reason ) | ||
1480 | { | ||
1481 | pthread_mutex_lock(session->mutex); | ||
1482 | LOGGER_DEBUG("Session: %p Rejecting call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); | ||
1483 | |||
1484 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1485 | LOGGER_ERROR("Invalid call index!"); | ||
1486 | pthread_mutex_unlock(session->mutex); | ||
1487 | return msi_ErrorNoCall; | ||
1488 | } | ||
1489 | |||
1490 | if ( session->calls[call_index]->state != msi_CallStarting ) { | ||
1491 | LOGGER_ERROR("Call is in invalid state!"); | ||
1492 | pthread_mutex_unlock(session->mutex); | ||
1493 | return msi_ErrorInvalidState; | ||
1494 | } | ||
1495 | |||
1496 | MSIMessage *msg_reject = msi_new_message ( TypeRequest, reject ); | ||
1497 | |||
1498 | /* FIXME */ | ||
1499 | #if 0 | ||
1500 | |||
1501 | if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { | ||
1502 | MSIReasonStrType reason_cast; | ||
1503 | memset(reason_cast, '\0', sizeof(MSIReasonStrType)); | ||
1504 | memcpy(reason_cast, reason, strlen(reason)); | ||
1505 | msi_msg_set_reason(msg_reject, reason_cast); | ||
1506 | } | ||
1507 | |||
1508 | #else | ||
1509 | (void)reason; | ||
1510 | |||
1511 | #endif | ||
1512 | |||
1513 | send_message ( session, session->calls[call_index], msg_reject, | ||
1514 | session->calls[call_index]->peers[session->calls[call_index]->peer_count - 1] ); | ||
1515 | free ( msg_reject ); | ||
1516 | |||
1517 | session->calls[call_index]->state = msi_CallOver; | ||
1518 | session->calls[call_index]->request_timer_id = | ||
1519 | timer_alloc ( session, handle_timeout, call_index, m_deftout ); | ||
1520 | |||
1521 | pthread_mutex_unlock(session->mutex); | ||
1522 | return 0; | ||
1523 | } | ||
1524 | |||
1525 | int msi_stopcall ( MSISession *session, int32_t call_index ) | ||
1526 | { | ||
1527 | pthread_mutex_lock(session->mutex); | ||
1528 | LOGGER_DEBUG("Session: %p Stopping call index: %u", session, call_index); | ||
1529 | |||
1530 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1531 | pthread_mutex_unlock(session->mutex); | ||
1532 | return msi_ErrorNoCall; | ||
1533 | } | ||
1534 | |||
1535 | /* just terminate it */ | ||
1536 | |||
1537 | terminate_call ( session, session->calls[call_index] ); | ||
1538 | |||
1539 | pthread_mutex_unlock(session->mutex); | ||
1540 | return 0; | ||
1541 | } | ||
1542 | |||
1543 | int msi_change_csettings(MSISession *session, int32_t call_index, const MSICSettings *csettings) | ||
1544 | { | ||
1545 | pthread_mutex_lock(session->mutex); | ||
1546 | |||
1547 | LOGGER_DEBUG("Changing media on call: %d", call_index); | ||
1548 | |||
1549 | if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { | ||
1550 | LOGGER_ERROR("Invalid call index!"); | ||
1551 | pthread_mutex_unlock(session->mutex); | ||
1552 | return msi_ErrorNoCall; | ||
1553 | } | ||
1554 | |||
1555 | MSICall *call = session->calls[call_index]; | ||
1556 | |||
1557 | if ( call->state != msi_CallActive ) { | ||
1558 | LOGGER_ERROR("Call is not active!"); | ||
1559 | pthread_mutex_unlock(session->mutex); | ||
1560 | return msi_ErrorInvalidState; | ||
1561 | } | ||
1562 | |||
1563 | MSICSettings *local = &call->csettings_local; | ||
1564 | |||
1565 | if ( | ||
1566 | local->call_type == csettings->call_type && | ||
1567 | local->video_bitrate == csettings->video_bitrate && | ||
1568 | local->max_video_width == csettings->max_video_width && | ||
1569 | local->max_video_height == csettings->max_video_height && | ||
1570 | local->audio_bitrate == csettings->audio_bitrate && | ||
1571 | local->audio_frame_duration == csettings->audio_frame_duration && | ||
1572 | local->audio_sample_rate == csettings->audio_sample_rate && | ||
1573 | local->audio_channels == csettings->audio_channels ) { | ||
1574 | LOGGER_ERROR("Call is already set accordingly!"); | ||
1575 | pthread_mutex_unlock(session->mutex); | ||
1576 | return -1; | ||
1577 | } | ||
1578 | |||
1579 | *local = *csettings; | ||
1580 | |||
1581 | MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); | ||
1582 | |||
1583 | msi_msg_set_csettings ( msg_invite, local ); | ||
1584 | send_message ( session, call, msg_invite, call->peers[0] ); | ||
1585 | free ( msg_invite ); | ||
1586 | |||
1587 | LOGGER_DEBUG("Request for media change sent"); | ||
1588 | |||
1589 | pthread_mutex_unlock(session->mutex); | ||
1590 | |||
1591 | return 0; | ||
1592 | } | ||
1593 | |||
1594 | void msi_do(MSISession *session) | ||
1595 | { | ||
1596 | pthread_mutex_lock(session->mutex); | ||
1597 | |||
1598 | TimerHandler *timer = session->timer_handler; | ||
1599 | |||
1600 | uint64_t time = current_time_monotonic(); | ||
1601 | |||
1602 | while ( timer->timers[0] && timer->timers[0]->timeout < time ) { | ||
1603 | LOGGER_DEBUG("Executing timer assigned at: %d", timer->timers[0]->timeout); | ||
1604 | |||
1605 | int id = timer->timers[0]->id; | ||
1606 | timer->timers[0]->func(timer->timers[0]); | ||
1607 | |||
1608 | /* In case function has released timer */ | ||
1609 | if (timer->timers[0] && timer->timers[0]->id == id) | ||
1610 | timer_release(timer, id); | ||
1611 | } | ||
1612 | |||
1613 | pthread_mutex_unlock(session->mutex); | 796 | pthread_mutex_unlock(session->mutex); |
1614 | } | 797 | } |
diff --git a/toxav/msi.h b/toxav/msi.h index 660df05e..59f32c1d 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,187 +19,136 @@ | |||
19 | * | 19 | * |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #ifndef __TOXMSI | 22 | #ifndef MSI_H |
23 | #define __TOXMSI | 23 | #define MSI_H |
24 | 24 | ||
25 | #include <inttypes.h> | 25 | #include <inttypes.h> |
26 | #include <pthread.h> | 26 | #include <pthread.h> |
27 | 27 | ||
28 | #include "codec.h" | 28 | #include "audio.h" |
29 | #include "video.h" | ||
29 | #include "../toxcore/Messenger.h" | 30 | #include "../toxcore/Messenger.h" |
30 | 31 | ||
31 | typedef uint8_t MSICallIDType[12]; | 32 | /** Preconfigured value for video splitting */ |
32 | typedef uint8_t MSIReasonStrType[255]; | 33 | #define VIDEOFRAME_PIECE_SIZE 500 |
33 | typedef void ( *MSICallbackType ) ( void *agent, int32_t call_idx, void *arg ); | ||
34 | 34 | ||
35 | /** | 35 | /** |
36 | * Call type identifier. Also used as rtp callback prefix. | 36 | * Error codes. |
37 | */ | 37 | */ |
38 | typedef enum { | 38 | typedef enum { |
39 | msi_TypeAudio = 192, | 39 | msi_ENone, |
40 | msi_TypeVideo | 40 | msi_EInvalidMessage, |
41 | } MSICallType; | 41 | msi_EInvalidParam, |
42 | 42 | msi_EInvalidState, | |
43 | msi_EStrayMessage, | ||
44 | msi_ESystem, | ||
45 | msi_EHandle, | ||
46 | msi_EUndisclosed, /* NOTE: must be last enum otherwise parsing will not work */ | ||
47 | } MSIError; | ||
43 | 48 | ||
44 | /** | 49 | /** |
45 | * Call state identifiers. | 50 | * Supported capabilities |
46 | */ | 51 | */ |
47 | typedef enum { | 52 | typedef enum { |
48 | msi_CallInviting, /* when sending call invite */ | 53 | msi_CapSAudio = 4, /* sending audio */ |
49 | msi_CallStarting, /* when getting call invite */ | 54 | msi_CapSVideo = 8, /* sending video */ |
50 | msi_CallActive, | 55 | msi_CapRAudio = 16, /* receiving audio */ |
51 | msi_CallHold, | 56 | msi_CapRVideo = 32, /* receiving video */ |
52 | msi_CallOver | 57 | } MSICapabilities; |
53 | |||
54 | } MSICallState; | ||
55 | 58 | ||
56 | 59 | ||
57 | /** | 60 | /** |
58 | * Encoding settings. | 61 | * Call state identifiers. |
59 | */ | 62 | */ |
60 | typedef struct _MSICodecSettings { | 63 | typedef enum { |
61 | MSICallType call_type; | 64 | msi_CallInactive, /* Default */ |
62 | 65 | msi_CallActive, | |
63 | uint32_t video_bitrate; /* In kbits/s */ | 66 | msi_CallRequesting, /* when sending call invite */ |
64 | uint16_t max_video_width; /* In px */ | 67 | msi_CallRequested, /* when getting call invite */ |
65 | uint16_t max_video_height; /* In px */ | 68 | } MSICallState; |
66 | |||
67 | uint32_t audio_bitrate; /* In bits/s */ | ||
68 | uint16_t audio_frame_duration; /* In ms */ | ||
69 | uint32_t audio_sample_rate; /* In Hz */ | ||
70 | uint32_t audio_channels; | ||
71 | } MSICSettings; | ||
72 | |||
73 | 69 | ||
74 | /** | 70 | /** |
75 | * Callbacks ids that handle the states | 71 | * Callbacks ids that handle the states |
76 | */ | 72 | */ |
77 | typedef enum { | 73 | typedef enum { |
78 | msi_OnInvite, /* Incoming call */ | 74 | msi_OnInvite, /* Incoming call */ |
79 | msi_OnRinging, /* When peer is ready to accept/reject the call */ | ||
80 | msi_OnStart, /* Call (RTP transmission) started */ | 75 | msi_OnStart, /* Call (RTP transmission) started */ |
81 | msi_OnCancel, /* The side that initiated call canceled invite */ | ||
82 | msi_OnReject, /* The side that was invited rejected the call */ | ||
83 | msi_OnEnd, /* Call that was active ended */ | 76 | msi_OnEnd, /* Call that was active ended */ |
84 | msi_OnRequestTimeout, /* When the requested action didn't get response in specified time */ | 77 | msi_OnError, /* On protocol error */ |
85 | msi_OnPeerTimeout, /* Peer timed out; stop the call */ | 78 | msi_OnPeerTimeout, /* Peer timed out; stop the call */ |
86 | msi_OnPeerCSChange, /* Peer requested Csettings change */ | 79 | msi_OnCapabilities, /* Peer requested capabilities change */ |
87 | msi_OnSelfCSChange /* Csettings change confirmation */ | ||
88 | } MSICallbackID; | 80 | } MSICallbackID; |
89 | 81 | ||
90 | /** | 82 | /** |
91 | * Errors | 83 | * The call struct. Please do not modify outside msi.c |
92 | */ | ||
93 | typedef enum { | ||
94 | msi_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ | ||
95 | msi_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ | ||
96 | msi_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ | ||
97 | msi_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ | ||
98 | } MSIError; | ||
99 | |||
100 | /** | ||
101 | * The call struct. | ||
102 | */ | 84 | */ |
103 | typedef struct _MSICall { /* Call info structure */ | 85 | typedef struct MSICall_s { |
104 | struct _MSISession *session; /* Session pointer */ | 86 | struct MSISession_s *session; /* Session pointer */ |
105 | |||
106 | MSICallState state; | ||
107 | |||
108 | MSICSettings csettings_local; /* Local call settings */ | ||
109 | MSICSettings *csettings_peer; /* Peers call settings */ | ||
110 | |||
111 | MSICallIDType id; /* Random value identifying the call */ | ||
112 | |||
113 | int ringing_tout_ms; /* Ringing timeout in ms */ | ||
114 | |||
115 | int request_timer_id; /* Timer id for outgoing request/action */ | ||
116 | int ringing_timer_id; /* Timer id for ringing timeout */ | ||
117 | |||
118 | uint32_t *peers; | ||
119 | uint16_t peer_count; | ||
120 | 87 | ||
121 | int32_t call_idx; /* Index of this call in MSISession */ | 88 | MSICallState state; |
89 | uint8_t peer_capabilities; /* Peer capabilities */ | ||
90 | uint8_t self_capabilities; /* Self capabilities */ | ||
91 | uint16_t peer_vfpsz; /* Video frame piece size */ | ||
92 | uint32_t friend_number; /* Index of this call in MSISession */ | ||
93 | MSIError error; /* Last error */ | ||
94 | |||
95 | void* av_call; /* Pointer to av call handler */ | ||
96 | |||
97 | struct MSICall_s* next; | ||
98 | struct MSICall_s* prev; | ||
122 | } MSICall; | 99 | } MSICall; |
123 | 100 | ||
124 | 101 | ||
125 | /** | 102 | /** |
126 | * Control session struct | 103 | * Expected return on success is 0, if any other number is |
104 | * returned the call is considered errored and will be handled | ||
105 | * as such which means it will be terminated without any notice. | ||
127 | */ | 106 | */ |
128 | typedef struct _MSISession { | 107 | typedef int msi_action_cb ( void *av, MSICall* call); |
129 | 108 | ||
109 | /** | ||
110 | * Control session struct. Please do not modify outside msi.c | ||
111 | */ | ||
112 | typedef struct MSISession_s { | ||
130 | /* Call handlers */ | 113 | /* Call handlers */ |
131 | MSICall **calls; | 114 | MSICall **calls; |
132 | int32_t max_calls; | 115 | uint32_t calls_tail; |
133 | 116 | uint32_t calls_head; | |
134 | void *agent_handler; | 117 | |
135 | Messenger *messenger_handle; | 118 | void *av; |
136 | 119 | Messenger *messenger; | |
137 | uint32_t frequ; | ||
138 | uint32_t call_timeout; /* Time of the timeout for some action to end; 0 if infinite */ | ||
139 | 120 | ||
140 | pthread_mutex_t mutex[1]; | 121 | pthread_mutex_t mutex[1]; |
141 | 122 | msi_action_cb* callbacks[7]; | |
142 | void *timer_handler; | ||
143 | PAIR(MSICallbackType, void *) callbacks[10]; | ||
144 | } MSISession; | 123 | } MSISession; |
145 | 124 | ||
146 | /** | 125 | /** |
147 | * Start the control session. | 126 | * Start the control session. |
148 | */ | 127 | */ |
149 | MSISession *msi_new ( Messenger *messenger, int32_t max_calls ); | 128 | MSISession *msi_new ( Messenger *m ); |
150 | |||
151 | /** | 129 | /** |
152 | * Terminate control session. | 130 | * Terminate control session. NOTE: all calls will be freed |
153 | */ | 131 | */ |
154 | int msi_kill ( MSISession *session ); | 132 | int msi_kill ( MSISession *session ); |
155 | |||
156 | /** | 133 | /** |
157 | * Callback setter. | 134 | * Callback setter. |
158 | */ | 135 | */ |
159 | void msi_register_callback(MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata); | 136 | void msi_register_callback(MSISession *session, msi_action_cb* callback, MSICallbackID id); |
160 | |||
161 | /** | 137 | /** |
162 | * Send invite request to friend_id. | 138 | * Send invite request to friend_number. |
163 | */ | 139 | */ |
164 | int msi_invite ( MSISession *session, | 140 | int msi_invite ( MSISession* session, MSICall** call, uint32_t friend_number, uint8_t capabilities ); |
165 | int32_t *call_index, | ||
166 | const MSICSettings *csettings, | ||
167 | uint32_t rngsec, | ||
168 | uint32_t friend_id ); | ||
169 | |||
170 | /** | 141 | /** |
171 | * Hangup active call. | 142 | * Hangup call. NOTE: 'call' will be freed |
172 | */ | 143 | */ |
173 | int msi_hangup ( MSISession *session, int32_t call_index ); | 144 | int msi_hangup ( MSICall* call ); |
174 | |||
175 | /** | 145 | /** |
176 | * Answer active call request. | 146 | * Answer call request. |
177 | */ | 147 | */ |
178 | int msi_answer ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); | 148 | int msi_answer ( MSICall* call, uint8_t capabilities ); |
179 | |||
180 | /** | ||
181 | * Cancel request. | ||
182 | */ | ||
183 | int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ); | ||
184 | |||
185 | /** | ||
186 | * Reject incoming call. | ||
187 | */ | ||
188 | int msi_reject ( MSISession *session, int32_t call_index, const char *reason ); | ||
189 | |||
190 | /** | ||
191 | * Terminate the call. | ||
192 | */ | ||
193 | int msi_stopcall ( MSISession *session, int32_t call_index ); | ||
194 | |||
195 | /** | ||
196 | * Change codec settings of the current call. | ||
197 | */ | ||
198 | int msi_change_csettings ( MSISession *session, int32_t call_index, const MSICSettings *csettings ); | ||
199 | |||
200 | /** | 149 | /** |
201 | * Main msi loop | 150 | * Change capabilities of the call. |
202 | */ | 151 | */ |
203 | void msi_do( MSISession *session ); | 152 | int msi_change_capabilities ( MSICall* call, uint8_t capabilities ); |
204 | 153 | ||
205 | #endif /* __TOXMSI */ | 154 | #endif /* MSI_H */ |
diff --git a/toxav/rtp.c b/toxav/rtp.c index ccac7564..7b3a5ed0 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 | * |
@@ -25,19 +25,21 @@ | |||
25 | 25 | ||
26 | #include "../toxcore/logger.h" | 26 | #include "../toxcore/logger.h" |
27 | #include "../toxcore/util.h" | 27 | #include "../toxcore/util.h" |
28 | #include "../toxcore/Messenger.h" | ||
28 | 29 | ||
29 | #include "rtp.h" | 30 | #include "rtp.h" |
30 | #include <stdlib.h> | 31 | #include <stdlib.h> |
31 | void queue_message(RTPSession *_session, RTPMessage *_msg); | 32 | #include <assert.h> |
32 | 33 | ||
33 | #define size_32 4 | 34 | #define size_32 4 |
35 | #define RTCP_REPORT_INTERVAL_MS 500 | ||
34 | 36 | ||
35 | #define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0) | 37 | #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) | 38 | #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) | 39 | #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) | 40 | #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) | 41 | #define ADD_SETTING_MARKER(_h, _v) do { ( _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) | 42 | #define ADD_SETTING_PAYLOAD(_h, _v) do { ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0) |
41 | 43 | ||
42 | #define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) | 44 | #define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) |
43 | #define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) | 45 | #define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) |
@@ -46,24 +48,260 @@ void queue_message(RTPSession *_session, RTPMessage *_msg); | |||
46 | #define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) | 48 | #define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) |
47 | #define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) | 49 | #define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) |
48 | 50 | ||
49 | /** | 51 | |
50 | * Checks if message came in late. | 52 | typedef struct { |
51 | */ | 53 | uint64_t timestamp; /* in ms */ |
52 | static int check_late_message (RTPSession *session, RTPMessage *msg) | 54 | |
55 | uint32_t received_packets; | ||
56 | uint32_t expected_packets; | ||
57 | /* ... other stuff in the future */ | ||
58 | } RTCPReport; | ||
59 | |||
60 | typedef struct RTCPSession_s { | ||
61 | RTPSession *rtp_session; | ||
62 | |||
63 | uint8_t prefix; | ||
64 | uint64_t last_sent_report_ts; | ||
65 | uint32_t last_received_packets; | ||
66 | uint32_t last_expected_packets; | ||
67 | |||
68 | RingBuffer* pl_stats; /* Packet loss stats over time */ | ||
69 | } RTCPSession; | ||
70 | |||
71 | |||
72 | RTPHeader *parse_header_in ( const uint8_t *payload, int length ); | ||
73 | RTPExtHeader *parse_ext_header_in ( const uint8_t *payload, uint16_t length ); | ||
74 | uint8_t *parse_header_out ( const RTPHeader* header, uint8_t* payload ); | ||
75 | uint8_t *parse_ext_header_out ( const RTPExtHeader* header, uint8_t* payload ); | ||
76 | int handle_rtp_packet ( Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object ); | ||
77 | int handle_rtcp_packet ( Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object ); | ||
78 | void send_rtcp_report ( RTCPSession* session, Messenger* m, uint32_t friendnumber ); | ||
79 | |||
80 | |||
81 | RTPSession *rtp_new ( int payload_type, Messenger *m, int friend_num, void* cs, int (*mcb) (void*, RTPMessage*) ) | ||
53 | { | 82 | { |
54 | /* | 83 | assert(mcb); |
55 | * Check Sequence number. If this new msg has lesser number then the session->rsequnum | 84 | assert(cs); |
56 | * it shows that the message came in late. Also check timestamp to be 100% certain. | 85 | assert(m); |
57 | * | 86 | |
58 | */ | 87 | RTPSession *retu = calloc(1, sizeof(RTPSession)); |
59 | return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1; | 88 | |
89 | if ( !retu ) { | ||
90 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
91 | return NULL; | ||
92 | } | ||
93 | |||
94 | retu->version = RTP_VERSION; /* It's always 2 */ | ||
95 | retu->ssrc = random_int(); | ||
96 | retu->payload_type = payload_type % 128; | ||
97 | |||
98 | retu->m = m; | ||
99 | retu->friend_number = friend_num; | ||
100 | |||
101 | if ( !(retu->csrc = calloc(1, sizeof(uint32_t))) ) { | ||
102 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
103 | free(retu); | ||
104 | return NULL; | ||
105 | } | ||
106 | |||
107 | retu->csrc[0] = retu->ssrc; /* Set my ssrc to the list receive */ | ||
108 | |||
109 | /* Also set payload type as prefix */ | ||
110 | retu->prefix = payload_type; | ||
111 | |||
112 | retu->cs = cs; | ||
113 | retu->mcb = mcb; | ||
114 | |||
115 | /* Initialize rtcp session */ | ||
116 | if (!(retu->rtcp_session = calloc(1, sizeof(RTCPSession)))) { | ||
117 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
118 | free(retu->csrc); | ||
119 | free(retu); | ||
120 | return NULL; | ||
121 | } | ||
122 | |||
123 | retu->rtcp_session->prefix = payload_type + 2; | ||
124 | retu->rtcp_session->pl_stats = rb_new(4); | ||
125 | retu->rtcp_session->rtp_session = retu; | ||
126 | |||
127 | if (-1 == rtp_start_receiving(retu)) { | ||
128 | LOGGER_WARNING("Failed to start rtp receiving mode"); | ||
129 | free(retu->rtcp_session); | ||
130 | free(retu->csrc); | ||
131 | free(retu); | ||
132 | return NULL; | ||
133 | } | ||
134 | |||
135 | return retu; | ||
60 | } | 136 | } |
137 | void rtp_kill ( RTPSession *session ) | ||
138 | { | ||
139 | if ( !session ) return; | ||
61 | 140 | ||
141 | rtp_stop_receiving (session); | ||
62 | 142 | ||
63 | /** | 143 | free ( session->ext_header ); |
64 | * Extracts header from payload. | 144 | free ( session->csrc ); |
65 | */ | 145 | |
66 | RTPHeader *extract_header ( const uint8_t *payload, int length ) | 146 | void* t; |
147 | while (!rb_empty(session->rtcp_session->pl_stats)) { | ||
148 | rb_read(session->rtcp_session->pl_stats, (void**) &t); | ||
149 | free(t); | ||
150 | } | ||
151 | rb_free(session->rtcp_session->pl_stats); | ||
152 | |||
153 | LOGGER_DEBUG("Terminated RTP session: %p", session); | ||
154 | |||
155 | /* And finally free session */ | ||
156 | free ( session ); | ||
157 | } | ||
158 | int rtp_do(RTPSession *session) | ||
159 | { | ||
160 | if (!session || !session->rtcp_session) | ||
161 | return rtp_StateNormal; | ||
162 | |||
163 | if (current_time_monotonic() - session->rtcp_session->last_sent_report_ts >= RTCP_REPORT_INTERVAL_MS) { | ||
164 | send_rtcp_report(session->rtcp_session, session->m, session->friend_number); | ||
165 | } | ||
166 | |||
167 | if (rb_full(session->rtcp_session->pl_stats)) { | ||
168 | RTCPReport* reports[4]; | ||
169 | |||
170 | int i = 0; | ||
171 | for (; i < 4; i++) | ||
172 | rb_read(session->rtcp_session->pl_stats, (void**) reports + i); | ||
173 | |||
174 | /* Check for timed out reports (> 6 sec) */ | ||
175 | uint64_t now = current_time_monotonic(); | ||
176 | for (i = 0; i < 4 && (now - reports[i]->timestamp) < 6000; i ++); | ||
177 | for (; i < 4; i ++) { | ||
178 | rb_write(session->rtcp_session->pl_stats, reports[i]); | ||
179 | reports[i] = NULL; | ||
180 | } | ||
181 | if (!rb_empty(session->rtcp_session->pl_stats)) { | ||
182 | for (i = 0; reports[i] != NULL; i ++) | ||
183 | free(reports[i]); | ||
184 | return rtp_StateNormal; /* As some reports are timed out, we need more */ | ||
185 | } | ||
186 | |||
187 | /* We have 4 on-time reports so we can proceed */ | ||
188 | uint32_t quality = 100; | ||
189 | for (i = 0; i < 4; i++) { | ||
190 | uint32_t current = reports[i]->received_packets * 100 / reports[i]->expected_packets; | ||
191 | quality = MIN(quality, current); | ||
192 | free(reports[i]); | ||
193 | } | ||
194 | |||
195 | if (quality <= 90) { | ||
196 | LOGGER_WARNING("Stream quality: BAD (%d)", quality); | ||
197 | return rtp_StateBad; | ||
198 | } else if (quality >= 99) { | ||
199 | LOGGER_DEBUG("Stream quality: GOOD (%d)", quality); | ||
200 | return rtp_StateGood; | ||
201 | } else { | ||
202 | LOGGER_DEBUG("Stream quality: NORMAL (%d)", quality); | ||
203 | } | ||
204 | } | ||
205 | return rtp_StateNormal; | ||
206 | } | ||
207 | int rtp_start_receiving(RTPSession* session) | ||
208 | { | ||
209 | if (session == NULL) | ||
210 | return -1; | ||
211 | |||
212 | if (m_callback_rtp_packet(session->m, session->friend_number, session->prefix, | ||
213 | handle_rtp_packet, session) == -1) { | ||
214 | LOGGER_WARNING("Failed to register rtp receive handler"); | ||
215 | return -1; | ||
216 | } | ||
217 | if (m_callback_rtp_packet(session->m, session->friend_number, session->rtcp_session->prefix, | ||
218 | handle_rtcp_packet, session->rtcp_session) == -1) { | ||
219 | LOGGER_WARNING("Failed to register rtcp receive handler"); | ||
220 | m_callback_rtp_packet(session->m, session->friend_number, session->prefix, NULL, NULL); | ||
221 | return -1; | ||
222 | } | ||
223 | |||
224 | return 0; | ||
225 | } | ||
226 | int rtp_stop_receiving(RTPSession* session) | ||
227 | { | ||
228 | if (session == NULL) | ||
229 | return -1; | ||
230 | |||
231 | m_callback_rtp_packet(session->m, session->friend_number, session->prefix, NULL, NULL); | ||
232 | m_callback_rtp_packet(session->m, session->friend_number, session->rtcp_session->prefix, NULL, NULL); /* RTCP */ | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | int rtp_send_data ( RTPSession *session, const uint8_t *data, uint16_t length, bool dummy ) | ||
237 | { | ||
238 | if ( !session ) { | ||
239 | LOGGER_WARNING("No session!"); | ||
240 | return -1; | ||
241 | } | ||
242 | |||
243 | uint8_t parsed[MAX_RTP_SIZE]; | ||
244 | uint8_t *it; | ||
245 | |||
246 | RTPHeader header[1]; | ||
247 | memset(header, 0, sizeof(header)); | ||
248 | |||
249 | ADD_FLAG_VERSION ( header, session->version ); | ||
250 | ADD_FLAG_PADDING ( header, session->padding ); | ||
251 | ADD_FLAG_EXTENSION ( header, session->extension ); | ||
252 | ADD_FLAG_CSRCC ( header, session->cc ); | ||
253 | ADD_SETTING_MARKER ( header, session->marker ); | ||
254 | |||
255 | if (dummy) | ||
256 | ADD_SETTING_PAYLOAD ( header, (session->payload_type + 2) % 128 ); | ||
257 | else | ||
258 | ADD_SETTING_PAYLOAD ( header, session->payload_type ); | ||
259 | |||
260 | header->sequnum = session->sequnum; | ||
261 | header->timestamp = current_time_monotonic(); | ||
262 | header->ssrc = session->ssrc; | ||
263 | |||
264 | int i; | ||
265 | for ( i = 0; i < session->cc; i++ ) | ||
266 | header->csrc[i] = session->csrc[i]; | ||
267 | |||
268 | header->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); | ||
269 | |||
270 | uint32_t parsed_len = length + header->length + 1; | ||
271 | assert(parsed_len + (session->ext_header ? session->ext_header->length * size_32 : 0) < MAX_RTP_SIZE ); | ||
272 | |||
273 | parsed[0] = session->prefix; | ||
274 | it = parse_header_out ( header, parsed + 1 ); | ||
275 | |||
276 | if ( session->ext_header ) { | ||
277 | parsed_len += ( 4 /* Minimum ext header len */ + session->ext_header->length * size_32 ); | ||
278 | it = parse_ext_header_out ( session->ext_header, it ); | ||
279 | } | ||
280 | |||
281 | memcpy(it, data, length); | ||
282 | |||
283 | if ( -1 == send_custom_lossy_packet(session->m, session->friend_number, parsed, parsed_len) ) { | ||
284 | LOGGER_WARNING("Failed to send full packet (len: %d)! std error: %s", length, strerror(errno)); | ||
285 | return -1; | ||
286 | } | ||
287 | |||
288 | session->sequnum ++; | ||
289 | return 0; | ||
290 | } | ||
291 | void rtp_free_msg ( RTPMessage *msg ) | ||
292 | { | ||
293 | if ( msg->ext_header ) { | ||
294 | free ( msg->ext_header->table ); | ||
295 | free ( msg->ext_header ); | ||
296 | } | ||
297 | |||
298 | free ( msg->header ); | ||
299 | free ( msg ); | ||
300 | } | ||
301 | |||
302 | |||
303 | |||
304 | RTPHeader *parse_header_in ( const uint8_t *payload, int length ) | ||
67 | { | 305 | { |
68 | if ( !payload || !length ) { | 306 | if ( !payload || !length ) { |
69 | LOGGER_WARNING("No payload to extract!"); | 307 | LOGGER_WARNING("No payload to extract!"); |
@@ -84,12 +322,7 @@ RTPHeader *extract_header ( const uint8_t *payload, int length ) | |||
84 | 322 | ||
85 | retu->flags = *it; | 323 | retu->flags = *it; |
86 | ++it; | 324 | ++it; |
87 | 325 | ||
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 ) { | 326 | if ( GET_FLAG_VERSION(retu) != RTP_VERSION ) { |
94 | /* Deallocate */ | 327 | /* Deallocate */ |
95 | LOGGER_WARNING("Invalid version!"); | 328 | LOGGER_WARNING("Invalid version!"); |
@@ -97,35 +330,28 @@ RTPHeader *extract_header ( const uint8_t *payload, int length ) | |||
97 | return NULL; | 330 | return NULL; |
98 | } | 331 | } |
99 | 332 | ||
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 ); | 333 | uint8_t cc = GET_FLAG_CSRCC ( retu ); |
105 | int total = 12 /* Minimum header len */ + ( cc * 4 ); | 334 | int total = 12 /* Minimum header len */ + ( cc * 4 ); |
106 | 335 | ||
107 | if ( length < total ) { | 336 | if ( length < total ) { |
108 | /* Deallocate */ | ||
109 | LOGGER_WARNING("Length invalid!"); | 337 | LOGGER_WARNING("Length invalid!"); |
110 | free(retu); | 338 | free(retu); |
111 | return NULL; | 339 | return NULL; |
112 | } | 340 | } |
113 | 341 | ||
114 | memset(retu->csrc, 0, 16 * sizeof (uint32_t)); | ||
115 | |||
116 | retu->marker_payloadt = *it; | 342 | retu->marker_payloadt = *it; |
117 | ++it; | 343 | ++it; |
118 | retu->length = total; | 344 | retu->length = total; |
119 | 345 | ||
120 | 346 | ||
121 | memcpy(&retu->timestamp, it, sizeof(retu->timestamp)); | 347 | memcpy(&retu->timestamp, it, sizeof(retu->timestamp)); |
122 | retu->timestamp = ntohl(retu->timestamp); | ||
123 | it += 4; | 348 | it += 4; |
124 | memcpy(&retu->ssrc, it, sizeof(retu->ssrc)); | 349 | memcpy(&retu->ssrc, it, sizeof(retu->ssrc)); |
350 | |||
351 | retu->timestamp = ntohl(retu->timestamp); | ||
125 | retu->ssrc = ntohl(retu->ssrc); | 352 | retu->ssrc = ntohl(retu->ssrc); |
126 | 353 | ||
127 | uint8_t x; | 354 | uint8_t x; |
128 | |||
129 | for ( x = 0; x < cc; x++ ) { | 355 | for ( x = 0; x < cc; x++ ) { |
130 | it += 4; | 356 | it += 4; |
131 | memcpy(&retu->csrc[x], it, sizeof(retu->csrc[x])); | 357 | memcpy(&retu->csrc[x], it, sizeof(retu->csrc[x])); |
@@ -134,11 +360,7 @@ RTPHeader *extract_header ( const uint8_t *payload, int length ) | |||
134 | 360 | ||
135 | return retu; | 361 | return retu; |
136 | } | 362 | } |
137 | 363 | RTPExtHeader *parse_ext_header_in ( const uint8_t *payload, uint16_t length ) | |
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 | { | 364 | { |
143 | const uint8_t *it = payload; | 365 | const uint8_t *it = payload; |
144 | 366 | ||
@@ -149,44 +371,37 @@ RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length ) | |||
149 | return NULL; | 371 | return NULL; |
150 | } | 372 | } |
151 | 373 | ||
152 | uint16_t ext_length; | 374 | memcpy(&retu->length, it, sizeof(retu->length)); |
153 | memcpy(&ext_length, it, sizeof(ext_length)); | 375 | retu->length = ntohs(retu->length); |
154 | ext_length = ntohs(ext_length); | ||
155 | it += 2; | 376 | it += 2; |
156 | 377 | ||
157 | 378 | if ( length < ( retu->length * sizeof(uint32_t) ) ) { | |
158 | if ( length < ( ext_length * sizeof(uint32_t) ) ) { | ||
159 | LOGGER_WARNING("Length invalid!"); | 379 | LOGGER_WARNING("Length invalid!"); |
160 | free(retu); | 380 | free(retu); |
161 | return NULL; | 381 | return NULL; |
162 | } | 382 | } |
163 | 383 | ||
164 | retu->length = ext_length; | ||
165 | memcpy(&retu->type, it, sizeof(retu->type)); | 384 | memcpy(&retu->type, it, sizeof(retu->type)); |
166 | retu->type = ntohs(retu->type); | 385 | retu->type = ntohs(retu->type); |
386 | |||
167 | it += 2; | 387 | it += 2; |
168 | 388 | ||
169 | if ( !(retu->table = calloc(ext_length, sizeof (uint32_t))) ) { | 389 | if ( !(retu->table = calloc(retu->length, sizeof (uint32_t))) ) { |
170 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 390 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); |
171 | free(retu); | 391 | free(retu); |
172 | return NULL; | 392 | return NULL; |
173 | } | 393 | } |
174 | 394 | ||
175 | uint16_t x; | 395 | uint16_t x; |
176 | 396 | for ( x = 0; x < retu->length; x++ ) { | |
177 | for ( x = 0; x < ext_length; x++ ) { | ||
178 | it += 4; | 397 | it += 4; |
179 | memcpy(&(retu->table[x]), it, sizeof(retu->table[x])); | 398 | memcpy(retu->table + x, it, sizeof(*retu->table)); |
180 | retu->table[x] = ntohl(retu->table[x]); | 399 | retu->table[x] = ntohl(retu->table[x]); |
181 | } | 400 | } |
182 | 401 | ||
183 | return retu; | 402 | return retu; |
184 | } | 403 | } |
185 | 404 | uint8_t *parse_header_out ( const RTPHeader *header, uint8_t *payload ) | |
186 | /** | ||
187 | * Adds header to payload. Make sure _payload_ has enough space. | ||
188 | */ | ||
189 | uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) | ||
190 | { | 405 | { |
191 | uint8_t cc = GET_FLAG_CSRCC ( header ); | 406 | uint8_t cc = GET_FLAG_CSRCC ( header ); |
192 | uint8_t *it = payload; | 407 | uint8_t *it = payload; |
@@ -206,7 +421,6 @@ uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) | |||
206 | *it = header->marker_payloadt; | 421 | *it = header->marker_payloadt; |
207 | ++it; | 422 | ++it; |
208 | 423 | ||
209 | |||
210 | timestamp = htonl(header->timestamp); | 424 | timestamp = htonl(header->timestamp); |
211 | memcpy(it, ×tamp, sizeof(timestamp)); | 425 | memcpy(it, ×tamp, sizeof(timestamp)); |
212 | it += 4; | 426 | it += 4; |
@@ -223,11 +437,7 @@ uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) | |||
223 | 437 | ||
224 | return it + 4; | 438 | return it + 4; |
225 | } | 439 | } |
226 | 440 | uint8_t *parse_ext_header_out ( const RTPExtHeader *header, uint8_t *payload ) | |
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 | { | 441 | { |
232 | uint8_t *it = payload; | 442 | uint8_t *it = payload; |
233 | uint16_t length; | 443 | uint16_t length; |
@@ -242,9 +452,7 @@ uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) | |||
242 | it -= 2; /* Return to 0 position */ | 452 | it -= 2; /* Return to 0 position */ |
243 | 453 | ||
244 | if ( header->table ) { | 454 | if ( header->table ) { |
245 | |||
246 | uint16_t x; | 455 | uint16_t x; |
247 | |||
248 | for ( x = 0; x < header->length; x++ ) { | 456 | for ( x = 0; x < header->length; x++ ) { |
249 | it += 4; | 457 | it += 4; |
250 | entry = htonl(header->table[x]); | 458 | entry = htonl(header->table[x]); |
@@ -254,275 +462,150 @@ uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) | |||
254 | 462 | ||
255 | return it + 4; | 463 | return it + 4; |
256 | } | 464 | } |
257 | 465 | int handle_rtp_packet ( Messenger* m, uint32_t friendnumber, const uint8_t* data, uint16_t length, void* object ) | |
258 | /** | ||
259 | * Builds header from control session values. | ||
260 | */ | ||
261 | RTPHeader *build_header ( RTPSession *session ) | ||
262 | { | 466 | { |
263 | RTPHeader *retu = calloc ( 1, sizeof (RTPHeader) ); | 467 | (void) m; |
468 | (void) friendnumber; | ||
469 | |||
470 | RTPSession *session = object; | ||
264 | 471 | ||
265 | if ( !retu ) { | 472 | if ( !session || length < 13 || length > MAX_RTP_SIZE ) { |
266 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 473 | LOGGER_WARNING("No session or invalid length of received buffer!"); |
267 | return NULL; | 474 | return -1; |
268 | } | 475 | } |
476 | |||
477 | RTPHeader* header = parse_header_in ( data + 1, length ); | ||
269 | 478 | ||
270 | ADD_FLAG_VERSION ( retu, session->version ); | 479 | if ( !header ) { |
271 | ADD_FLAG_PADDING ( retu, session->padding ); | 480 | LOGGER_WARNING("Could not parse message: Header failed to extract!"); |
272 | ADD_FLAG_EXTENSION ( retu, session->extension ); | 481 | return -1; |
273 | ADD_FLAG_CSRCC ( retu, session->cc ); | ||
274 | ADD_SETTING_MARKER ( retu, session->marker ); | ||
275 | ADD_SETTING_PAYLOAD ( retu, session->payload_type ); | ||
276 | |||
277 | retu->sequnum = session->sequnum; | ||
278 | retu->timestamp = current_time_monotonic(); /* milliseconds */ | ||
279 | retu->ssrc = session->ssrc; | ||
280 | |||
281 | int i; | ||
282 | |||
283 | for ( i = 0; i < session->cc; i++ ) | ||
284 | retu->csrc[i] = session->csrc[i]; | ||
285 | |||
286 | retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); | ||
287 | |||
288 | return retu; | ||
289 | } | ||
290 | |||
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 | |||
302 | if ( !retu->header ) { | ||
303 | LOGGER_WARNING("Header failed to extract!"); | ||
304 | free(retu); | ||
305 | return NULL; | ||
306 | } | 482 | } |
307 | 483 | ||
308 | uint16_t from_pos = retu->header->length; | 484 | RTPExtHeader* ext_header = NULL; |
309 | retu->length = length - from_pos; | 485 | |
310 | 486 | uint16_t from_pos = header->length + 1; | |
311 | 487 | uint16_t msg_length = length - from_pos; | |
312 | 488 | ||
313 | if ( GET_FLAG_EXTENSION ( retu->header ) ) { | 489 | if ( GET_FLAG_EXTENSION ( header ) ) { |
314 | retu->ext_header = extract_ext_header ( data + from_pos, length ); | 490 | ext_header = parse_ext_header_in ( data + from_pos, length ); |
315 | 491 | ||
316 | if ( retu->ext_header ) { | 492 | if ( ext_header ) { |
317 | retu->length -= ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | 493 | msg_length -= ( 4 /* Minimum ext header len */ + ext_header->length * size_32 ); |
318 | from_pos += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | 494 | from_pos += ( 4 /* Minimum ext header len */ + ext_header->length * size_32 ); |
319 | } else { /* Error */ | 495 | } else { /* Error */ |
320 | LOGGER_WARNING("Ext Header failed to extract!"); | 496 | LOGGER_WARNING("Could not parse message: Ext Header failed to extract!"); |
321 | rtp_free_msg(NULL, retu); | 497 | free(header); |
322 | return NULL; | 498 | return -1; |
323 | } | 499 | } |
324 | } else { | ||
325 | retu->ext_header = NULL; | ||
326 | } | ||
327 | |||
328 | if ( length - from_pos <= MAX_RTP_SIZE ) | ||
329 | memcpy ( retu->data, data + from_pos, length - from_pos ); | ||
330 | else { | ||
331 | LOGGER_WARNING("Invalid length!"); | ||
332 | rtp_free_msg(NULL, retu); | ||
333 | return NULL; | ||
334 | } | 500 | } |
335 | 501 | ||
336 | retu->next = NULL; | 502 | if (msg_length > MAX_RTP_SIZE) { |
337 | 503 | LOGGER_WARNING("Could not parse message: Invalid length!"); | |
338 | return retu; | 504 | free(header); |
339 | } | 505 | free(ext_header); |
340 | |||
341 | /** | ||
342 | * Callback for networking core. | ||
343 | */ | ||
344 | int rtp_handle_packet ( Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object ) | ||
345 | { | ||
346 | RTPSession *session = object; | ||
347 | RTPMessage *msg; | ||
348 | |||
349 | if ( !session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */ | ||
350 | LOGGER_WARNING("No session or invalid length of received buffer!"); | ||
351 | return -1; | 506 | return -1; |
352 | } | 507 | } |
353 | 508 | ||
354 | msg = msg_parse ( data + 1, length - 1 ); | ||
355 | |||
356 | if ( !msg ) { | ||
357 | LOGGER_WARNING("Could not parse message!"); | ||
358 | return -1; | ||
359 | } | ||
360 | |||
361 | /* Check if message came in late */ | 509 | /* Check if message came in late */ |
362 | if ( check_late_message(session, msg) < 0 ) { /* Not late */ | 510 | if ( header->sequnum > session->rsequnum || header->timestamp > session->rtimestamp ) { |
363 | session->rsequnum = msg->header->sequnum; | 511 | /* Not late */ |
364 | session->timestamp = msg->header->timestamp; | 512 | if (header->sequnum > session->rsequnum) |
513 | session->rtcp_session->last_expected_packets += header->sequnum - session->rsequnum; | ||
514 | else if (header->sequnum < session->rsequnum) | ||
515 | session->rtcp_session->last_expected_packets += (header->sequnum + 65535) - session->rsequnum; | ||
516 | else /* Usual case when transmission starts */ | ||
517 | session->rtcp_session->last_expected_packets ++; | ||
518 | |||
519 | session->rsequnum = header->sequnum; | ||
520 | session->rtimestamp = header->timestamp; | ||
365 | } | 521 | } |
366 | 522 | ||
367 | queue_message(session, msg); | 523 | session->rtcp_session->last_received_packets ++; |
368 | 524 | ||
369 | return 0; | 525 | /* Check if the message is dummy. We don't keep dummy messages */ |
370 | } | 526 | if (GET_SETTING_PAYLOAD(header) == (session->payload_type + 2) % 128) { |
371 | 527 | LOGGER_DEBUG("Received dummy rtp message"); | |
372 | /** | 528 | free(header); |
373 | * Allocate message and store data there | 529 | free(ext_header); |
374 | */ | 530 | return 0; |
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 | } | 531 | } |
381 | 532 | ||
382 | uint8_t *from_pos; | 533 | /* Otherwise we will store the message if we have an appropriate handler */ |
383 | RTPMessage *retu = calloc(1, sizeof (RTPMessage)); | 534 | if (!session->mcb) { |
384 | 535 | LOGGER_DEBUG("No handler for the message of %d payload", GET_SETTING_PAYLOAD(header)); | |
385 | if ( !retu ) { | 536 | free(header); |
386 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 537 | free(ext_header); |
387 | return NULL; | 538 | return 0; |
388 | } | 539 | } |
389 | 540 | ||
390 | /* Sets header values and copies the extension header in retu */ | 541 | RTPMessage *msg = calloc(1, sizeof (RTPMessage) + msg_length); |
391 | retu->header = build_header ( session ); /* It allocates memory and all */ | 542 | |
392 | retu->ext_header = session->ext_header; | 543 | if ( !msg ) { |
393 | 544 | LOGGER_WARNING("Could not parse message: Allocation failed!"); | |
394 | 545 | free(header); | |
395 | uint32_t total_length = length + retu->header->length + 1; | 546 | free(ext_header); |
396 | 547 | return -1; | |
397 | retu->data[0] = session->prefix; | ||
398 | |||
399 | if ( retu->ext_header ) { | ||
400 | total_length += ( 4 /* Minimum ext header len */ + retu->ext_header->length * size_32 ); | ||
401 | |||
402 | from_pos = add_header ( retu->header, retu->data + 1 ); | ||
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 | } | 548 | } |
407 | 549 | ||
408 | /* | 550 | msg->header = header; |
409 | * Parses the extension header into the message | 551 | msg->ext_header = ext_header; |
410 | * Of course if any | 552 | msg->length = msg_length; |
411 | */ | 553 | |
412 | 554 | memcpy ( msg->data, data + from_pos, msg_length ); | |
413 | /* Appends data on to retu->data */ | 555 | |
414 | memcpy ( from_pos, data, length ); | 556 | return session->mcb (session->cs, msg); |
415 | |||
416 | retu->length = total_length; | ||
417 | |||
418 | retu->next = NULL; | ||
419 | |||
420 | return retu; | ||
421 | } | 557 | } |
422 | 558 | int handle_rtcp_packet ( Messenger* m, uint32_t friendnumber, const uint8_t* data, uint16_t length, void* object ) | |
423 | |||
424 | |||
425 | int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ) | ||
426 | { | 559 | { |
427 | RTPMessage *msg = rtp_new_message (session, data, length); | 560 | (void) m; |
428 | 561 | (void) friendnumber; | |
429 | if ( !msg ) return -1; | 562 | |
430 | 563 | if (length < 9) | |
431 | int ret = send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length); | 564 | return -1; |
432 | 565 | ||
433 | if ( 0 != ret) { | 566 | RTCPSession* session = object; |
434 | LOGGER_WARNING("Failed to send full packet (len: %d)! error: %i", length, ret); | 567 | RTCPReport* report = malloc(sizeof(RTCPReport)); |
435 | rtp_free_msg ( session, msg ); | 568 | |
436 | return rtp_ErrorSending; | 569 | memcpy(&report->received_packets, data + 1, 4); |
570 | memcpy(&report->expected_packets, data + 5, 4); | ||
571 | |||
572 | report->received_packets = ntohl(report->received_packets); | ||
573 | report->expected_packets = ntohl(report->expected_packets); | ||
574 | |||
575 | if (report->expected_packets == 0 || report->received_packets > report->expected_packets) { | ||
576 | LOGGER_WARNING("Malformed rtcp report! %d %d", report->expected_packets, report->received_packets); | ||
577 | free(report); | ||
578 | return 0; | ||
437 | } | 579 | } |
438 | 580 | ||
439 | /* Set sequ number */ | 581 | report->timestamp = current_time_monotonic(); |
440 | session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1; | 582 | |
441 | rtp_free_msg ( session, msg ); | 583 | free(rb_write(session->pl_stats, report)); |
442 | 584 | ||
585 | LOGGER_DEBUG("Got rtcp report: ex: %d rc: %d", report->expected_packets, report->received_packets); | ||
443 | return 0; | 586 | return 0; |
444 | } | 587 | } |
445 | 588 | void send_rtcp_report(RTCPSession* session, Messenger* m, uint32_t friendnumber) | |
446 | void rtp_free_msg ( RTPSession *session, RTPMessage *msg ) | ||
447 | { | ||
448 | if ( !session ) { | ||
449 | if ( msg->ext_header ) { | ||
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 | |||
460 | free ( msg->header ); | ||
461 | free ( msg ); | ||
462 | } | ||
463 | |||
464 | RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ) | ||
465 | { | 589 | { |
466 | RTPSession *retu = calloc(1, sizeof(RTPSession)); | 590 | if (session->last_expected_packets == 0) |
467 | 591 | return; | |
468 | if ( !retu ) { | 592 | |
469 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | 593 | uint8_t parsed[9]; |
470 | return NULL; | 594 | parsed[0] = session->prefix; |
471 | } | 595 | |
472 | 596 | uint32_t received_packets = htonl(session->last_received_packets); | |
473 | if ( -1 == m_callback_rtp_packet(messenger, friend_num, payload_type, rtp_handle_packet, retu)) { | 597 | uint32_t expected_packets = htonl(session->last_expected_packets); |
474 | LOGGER_ERROR("Error setting custom register handler for rtp session"); | 598 | |
475 | free(retu); | 599 | memcpy(parsed + 1, &received_packets, 4); |
476 | return NULL; | 600 | memcpy(parsed + 5, &expected_packets, 4); |
477 | } | 601 | |
478 | 602 | if (-1 == send_custom_lossy_packet(m, friendnumber, parsed, sizeof(parsed))) | |
479 | LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num); | 603 | LOGGER_WARNING("Failed to send full packet (len: %d)! std error: %s", sizeof(parsed), strerror(errno)); |
480 | 604 | else { | |
481 | retu->version = RTP_VERSION; /* It's always 2 */ | 605 | LOGGER_DEBUG("Sent rtcp report: ex: %d rc: %d", session->last_expected_packets, session->last_received_packets); |
482 | retu->padding = 0; /* If some additional data is needed about the packet */ | 606 | |
483 | retu->extension = 0; /* If extension to header is needed */ | 607 | session->last_received_packets = 0; |
484 | retu->cc = 1; /* Amount of contributors */ | 608 | session->last_expected_packets = 0; |
485 | retu->csrc = NULL; /* Container */ | 609 | session->last_sent_report_ts = current_time_monotonic(); |
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 | |||
496 | |||
497 | if ( !(retu->csrc = calloc(1, sizeof (uint32_t))) ) { | ||
498 | LOGGER_WARNING("Alloc failed! Program might misbehave!"); | ||
499 | free(retu); | ||
500 | return NULL; | ||
501 | } | 610 | } |
502 | 611 | } \ No newline at end of file | |
503 | retu->csrc[0] = retu->ssrc; /* Set my ssrc to the list receive */ | ||
504 | |||
505 | /* Also set payload type as prefix */ | ||
506 | retu->prefix = payload_type; | ||
507 | |||
508 | /* | ||
509 | * | ||
510 | */ | ||
511 | return retu; | ||
512 | } | ||
513 | |||
514 | void rtp_kill ( RTPSession *session, Messenger *messenger ) | ||
515 | { | ||
516 | if ( !session ) return; | ||
517 | |||
518 | m_callback_rtp_packet(messenger, session->dest, session->prefix, NULL, NULL); | ||
519 | |||
520 | free ( session->ext_header ); | ||
521 | free ( session->csrc ); | ||
522 | |||
523 | LOGGER_DEBUG("Terminated RTP session: %p", session); | ||
524 | |||
525 | /* And finally free session */ | ||
526 | free ( session ); | ||
527 | |||
528 | } | ||
diff --git a/toxav/rtp.h b/toxav/rtp.h index c98840ac..b18d6f8d 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,25 +19,45 @@ | |||
19 | * | 19 | * |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #ifndef __TOXRTP | 22 | #ifndef RTP_H |
23 | #define __TOXRTP | 23 | #define RTP_H |
24 | 24 | ||
25 | #define RTP_VERSION 2 | 25 | #define RTP_VERSION 2 |
26 | #include <inttypes.h> | ||
27 | // #include <pthread.h> | ||
28 | 26 | ||
29 | #include "../toxcore/Messenger.h" | 27 | #include "../toxcore/Messenger.h" |
30 | 28 | ||
31 | #define MAX_SEQU_NUM 65535 | 29 | #define LOGGED_LOCK(mutex) do { \ |
32 | #define MAX_RTP_SIZE 65535 | 30 | /*LOGGER_DEBUG("Locking mutex: %p", mutex);*/\ |
31 | pthread_mutex_lock(mutex);\ | ||
32 | /*LOGGER_DEBUG("Locked mutex: %p", mutex);*/\ | ||
33 | } while(0) | ||
34 | |||
35 | #define LOGGED_UNLOCK(mutex) do { \ | ||
36 | /*LOGGER_DEBUG("Unlocking mutex: %p", mutex);*/\ | ||
37 | pthread_mutex_unlock(mutex);\ | ||
38 | /*LOGGER_DEBUG("Unlocked mutex: %p", mutex);*/\ | ||
39 | } while(0) | ||
40 | |||
41 | #define MAX_RTP_SIZE 1500 | ||
33 | 42 | ||
34 | typedef enum { | ||
35 | rtp_ErrorSending = -40 | ||
36 | } RTPError; | ||
37 | /** | 43 | /** |
38 | * Standard rtp header | 44 | * Payload type identifier. Also used as rtp callback prefix. (Not dummies) |
45 | */ | ||
46 | enum { | ||
47 | rtp_TypeAudio = 192, | ||
48 | rtp_TypeVideo, | ||
49 | }; | ||
50 | |||
51 | enum { | ||
52 | rtp_StateBad = -1, | ||
53 | rtp_StateNormal, | ||
54 | rtp_StateGood, | ||
55 | }; | ||
56 | |||
57 | /** | ||
58 | * Standard rtp header. | ||
39 | */ | 59 | */ |
40 | typedef struct _RTPHeader { | 60 | typedef struct { |
41 | uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */ | 61 | uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */ |
42 | uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */ | 62 | uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */ |
43 | uint16_t sequnum; /* Sequence Number */ | 63 | uint16_t sequnum; /* Sequence Number */ |
@@ -45,83 +65,89 @@ typedef struct _RTPHeader { | |||
45 | uint32_t ssrc; /* SSRC */ | 65 | uint32_t ssrc; /* SSRC */ |
46 | uint32_t csrc[16]; /* CSRC's table */ | 66 | uint32_t csrc[16]; /* CSRC's table */ |
47 | uint32_t length; /* Length of the header in payload string. */ | 67 | uint32_t length; /* Length of the header in payload string. */ |
48 | |||
49 | } RTPHeader; | 68 | } RTPHeader; |
50 | 69 | /** | |
51 | /** | ||
52 | * Standard rtp extension header. | 70 | * Standard rtp extension header. |
53 | */ | 71 | */ |
54 | typedef struct _RTPExtHeader { | 72 | typedef struct { |
55 | uint16_t type; /* Extension profile */ | 73 | uint16_t type; /* Extension profile */ |
56 | uint16_t length; /* Number of extensions */ | 74 | uint16_t length; /* Number of extensions */ |
57 | uint32_t *table; /* Extension's table */ | 75 | uint32_t *table; /* Extension's table */ |
58 | |||
59 | } RTPExtHeader; | 76 | } RTPExtHeader; |
60 | 77 | ||
61 | /** | 78 | /** |
62 | * Standard rtp message. | 79 | * Standard rtp message. |
63 | */ | 80 | */ |
64 | typedef struct _RTPMessage { | 81 | typedef struct RTPMessage_s { |
65 | RTPHeader *header; | 82 | RTPHeader *header; |
66 | RTPExtHeader *ext_header; | 83 | RTPExtHeader *ext_header; |
67 | 84 | ||
68 | uint8_t data[MAX_RTP_SIZE]; | ||
69 | uint32_t length; | 85 | uint32_t length; |
70 | 86 | uint8_t data[]; | |
71 | struct _RTPMessage *next; | ||
72 | } RTPMessage; | 87 | } RTPMessage; |
73 | 88 | ||
74 | /** | 89 | /** |
75 | * RTP control session. | 90 | * RTP control session. |
76 | */ | 91 | */ |
77 | typedef struct _RTPSession { | 92 | typedef struct { |
78 | uint8_t version; | 93 | uint8_t version; |
79 | uint8_t padding; | 94 | uint8_t padding; |
80 | uint8_t extension; | 95 | uint8_t extension; |
81 | uint8_t cc; | 96 | uint8_t cc; |
82 | uint8_t marker; | 97 | uint8_t marker; |
83 | uint8_t payload_type; | 98 | uint8_t payload_type; |
84 | uint16_t sequnum; /* Set when sending */ | 99 | uint16_t sequnum; /* Sending sequence number */ |
85 | uint16_t rsequnum; /* Check when recving msg */ | 100 | uint16_t rsequnum; /* Receiving sequence number */ |
86 | uint32_t timestamp; | 101 | uint32_t rtimestamp; |
87 | uint32_t ssrc; | 102 | uint32_t ssrc; |
88 | uint32_t *csrc; | 103 | uint32_t *csrc; |
89 | 104 | ||
90 | /* If some additional data must be sent via message | 105 | /* If some additional data must be sent via message |
91 | * apply it here. Only by allocating this member you will be | 106 | * apply it here. Only by allocating this member you will be |
92 | * automatically placing it within a message. | 107 | * automatically placing it within a message. |
93 | */ | 108 | */ |
94 | RTPExtHeader *ext_header; | 109 | RTPExtHeader *ext_header; |
95 | 110 | ||
96 | /* Msg prefix for core to know when recving */ | 111 | /* Msg prefix for core to know when recving */ |
97 | uint8_t prefix; | 112 | uint8_t prefix; |
98 | |||
99 | int dest; | ||
100 | 113 | ||
101 | struct _CSSession *cs; | 114 | Messenger *m; |
115 | int friend_number; | ||
116 | struct RTCPSession_s *rtcp_session; | ||
102 | 117 | ||
118 | void *cs; | ||
119 | int (*mcb) (void*, RTPMessage* msg); | ||
120 | |||
103 | } RTPSession; | 121 | } RTPSession; |
104 | 122 | ||
105 | /** | 123 | /** |
106 | * Must be called before calling any other rtp function. | 124 | * Must be called before calling any other rtp function. |
107 | */ | 125 | */ |
108 | RTPSession *rtp_new ( int payload_type, Messenger *messenger, int friend_num ); | 126 | RTPSession *rtp_new ( int payload_type, Messenger *m, int friend_num, void* cs, int (*mcb) (void*, RTPMessage*) ); |
109 | |||
110 | /** | 127 | /** |
111 | * Terminate the session. | 128 | * Terminate the session. |
112 | */ | 129 | */ |
113 | void rtp_kill ( RTPSession *session, Messenger *messenger ); | 130 | void rtp_kill ( RTPSession* session ); |
114 | |||
115 | /** | 131 | /** |
116 | * Sends msg to _RTPSession::dest | 132 | * Do periodical rtp work. |
117 | */ | 133 | */ |
118 | int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ); | 134 | int rtp_do(RTPSession *session); |
119 | 135 | /** | |
136 | * By default rtp is in receiving state | ||
137 | */ | ||
138 | int rtp_start_receiving (RTPSession *session); | ||
139 | /** | ||
140 | * Pause rtp receiving mode. | ||
141 | */ | ||
142 | int rtp_stop_receiving (RTPSession *session); | ||
143 | /** | ||
144 | * Sends msg to RTPSession::dest | ||
145 | */ | ||
146 | int rtp_send_data ( RTPSession* session, const uint8_t* data, uint16_t length, bool dummy ); | ||
120 | /** | 147 | /** |
121 | * Dealloc msg. | 148 | * Dealloc msg. |
122 | */ | 149 | */ |
123 | void rtp_free_msg ( RTPSession *session, RTPMessage *msg ); | 150 | void rtp_free_msg ( RTPMessage *msg ); |
124 | |||
125 | 151 | ||
126 | 152 | ||
127 | #endif /* __TOXRTP */ | 153 | #endif /* RTP_H */ |
diff --git a/toxav/toxav.c b/toxav/toxav.c index a51ec5e3..35523319 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,1325 @@ 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 | #define BITRATE_CHANGE_TESTING_TIME_MS 4000 | |
45 | /* true if invalid call index */ | 39 | |
46 | #define CALL_INVALID_INDEX(idx, max) (idx < 0 || idx >= max) | 40 | typedef struct ToxAvBitrateAdapter_s { |
47 | 41 | bool active; | |
48 | const ToxAvCSettings av_DefaultSettings = { | 42 | uint64_t end_time; |
49 | av_TypeAudio, | 43 | uint64_t next_send; |
50 | 44 | uint64_t next_send_interval; | |
51 | 500, | 45 | uint32_t bit_rate; |
52 | 1280, | 46 | } ToxAvBitrateAdapter; |
53 | 720, | 47 | |
54 | 48 | typedef struct ToxAVCall_s { | |
55 | 32000, | 49 | ToxAV* av; |
56 | 20, | 50 | |
57 | 48000, | 51 | pthread_mutex_t mutex_audio[1]; |
58 | 1 | 52 | PAIR(RTPSession *, ACSession *) audio; |
59 | }; | 53 | |
60 | 54 | pthread_mutex_t mutex_video[1]; | |
61 | static const uint32_t jbuf_capacity = 6; | 55 | PAIR(RTPSession *, VCSession *) video; |
62 | static const uint8_t audio_index = 0, video_index = 1; | 56 | |
63 | |||
64 | typedef struct _ToxAvCall { | ||
65 | pthread_mutex_t mutex[1]; | 57 | pthread_mutex_t mutex[1]; |
66 | pthread_mutex_t mutex_encoding_audio[1]; | 58 | |
67 | pthread_mutex_t mutex_encoding_video[1]; | 59 | bool active; |
68 | pthread_mutex_t mutex_do[1]; | 60 | MSICall* msi_call; |
69 | RTPSession *crtps[2]; /** Audio is first and video is second */ | 61 | uint32_t friend_number; |
70 | CSSession *cs; | 62 | |
71 | _Bool active; | 63 | uint32_t audio_bit_rate; /* Sending audio bit rate */ |
72 | } ToxAvCall; | 64 | uint32_t video_bit_rate; /* Sending video bit rate */ |
73 | 65 | ||
74 | struct _ToxAv { | 66 | ToxAvBitrateAdapter aba; |
75 | Messenger *messenger; | 67 | ToxAvBitrateAdapter vba; |
76 | MSISession *msi_session; /** Main msi session */ | 68 | |
77 | ToxAvCall *calls; /** Per-call params */ | 69 | /** Required for monitoring changes in states */ |
78 | uint32_t max_calls; | 70 | uint8_t previous_self_capabilities; |
79 | 71 | ||
80 | PAIR(ToxAvAudioCallback, void *) acb; | 72 | /** Quality control */ |
81 | PAIR(ToxAvVideoCallback, void *) vcb; | 73 | uint64_t time_audio_good; |
82 | 74 | uint32_t last_bad_audio_bit_rate; | |
83 | /* Decode time measure */ | 75 | uint64_t time_video_good; |
84 | int32_t dectmsscount; /** Measure count */ | 76 | uint32_t last_bad_video_bit_rate; |
85 | int32_t dectmsstotal; /** Last cycle total */ | 77 | |
86 | int32_t avgdectms; /** Average decoding time in ms */ | 78 | struct ToxAVCall_s *prev; |
79 | struct ToxAVCall_s *next; | ||
80 | } ToxAVCall; | ||
81 | |||
82 | struct ToxAV { | ||
83 | Messenger* m; | ||
84 | MSISession* msi; | ||
85 | |||
86 | /* Two-way storage: first is array of calls and second is list of calls with head and tail */ | ||
87 | ToxAVCall** calls; | ||
88 | uint32_t calls_tail; | ||
89 | uint32_t calls_head; | ||
90 | pthread_mutex_t mutex[1]; | ||
91 | |||
92 | PAIR(toxav_call_cb *, void*) ccb; /* Call callback */ | ||
93 | PAIR(toxav_call_state_cb *, void *) scb; /* Call state callback */ | ||
94 | PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */ | ||
95 | PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ | ||
96 | PAIR(toxav_audio_bit_rate_status_cb *, void *) abcb; /* Audio bit rate control callback */ | ||
97 | PAIR(toxav_video_bit_rate_status_cb *, void *) vbcb; /* Video bit rate control callback */ | ||
98 | |||
99 | /** Decode time measures */ | ||
100 | int32_t dmssc; /** Measure count */ | ||
101 | int32_t dmsst; /** Last cycle total */ | ||
102 | int32_t dmssa; /** Average decoding time in ms */ | ||
103 | |||
104 | uint32_t interval; /** Calculated interval */ | ||
87 | }; | 105 | }; |
88 | 106 | ||
89 | static const MSICSettings *msicsettings_cast (const ToxAvCSettings *from) | 107 | |
108 | int callback_invite(void* toxav_inst, MSICall* call); | ||
109 | int callback_start(void* toxav_inst, MSICall* call); | ||
110 | int callback_end(void* toxav_inst, MSICall* call); | ||
111 | int callback_error(void* toxav_inst, MSICall* call); | ||
112 | int callback_capabilites(void* toxav_inst, MSICall* call); | ||
113 | |||
114 | bool audio_bit_rate_invalid(uint32_t bit_rate); | ||
115 | bool video_bit_rate_invalid(uint32_t bit_rate); | ||
116 | bool invoke_call_state(ToxAV* av, uint32_t friend_number, uint32_t state); | ||
117 | ToxAVCall* call_new(ToxAV* av, uint32_t friend_number, TOXAV_ERR_CALL* error); | ||
118 | ToxAVCall* call_get(ToxAV* av, uint32_t friend_number); | ||
119 | ToxAVCall* call_remove(ToxAVCall* call); | ||
120 | bool call_prepare_transmission(ToxAVCall* call); | ||
121 | void call_kill_transmission(ToxAVCall* call); | ||
122 | void ba_set(ToxAvBitrateAdapter* ba, uint32_t bit_rate); | ||
123 | bool ba_shoud_send_dummy(ToxAvBitrateAdapter* ba); | ||
124 | |||
125 | uint32_t toxav_version_major(void) | ||
90 | { | 126 | { |
91 | assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); | 127 | return 0; |
92 | return (const MSICSettings *) from; | ||
93 | } | 128 | } |
94 | 129 | uint32_t toxav_version_minor(void) | |
95 | static const ToxAvCSettings *toxavcsettings_cast (const MSICSettings *from) | ||
96 | { | 130 | { |
97 | assert(sizeof(MSICSettings) == sizeof(ToxAvCSettings)); | 131 | return 0; |
98 | return (const ToxAvCSettings *) from; | ||
99 | |||
100 | } | 132 | } |
101 | 133 | uint32_t toxav_version_patch(void) | |
102 | ToxAv *toxav_new( Tox *messenger, int32_t max_calls) | ||
103 | { | 134 | { |
104 | ToxAv *av = calloc ( sizeof(ToxAv), 1); | 135 | return 0; |
136 | } | ||
137 | bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) | ||
138 | { | ||
139 | (void)major; | ||
140 | (void)minor; | ||
141 | (void)patch; | ||
142 | |||
143 | return 1; | ||
144 | } | ||
105 | 145 | ||
146 | ToxAV* toxav_new(Tox* tox, TOXAV_ERR_NEW* error) | ||
147 | { | ||
148 | TOXAV_ERR_NEW rc = TOXAV_ERR_NEW_OK; | ||
149 | ToxAV *av = NULL; | ||
150 | |||
151 | if (tox == NULL) { | ||
152 | rc = TOXAV_ERR_NEW_NULL; | ||
153 | goto END; | ||
154 | } | ||
155 | |||
156 | if (((Messenger*)tox)->msi_packet) { | ||
157 | rc = TOXAV_ERR_NEW_MULTIPLE; | ||
158 | goto END; | ||
159 | } | ||
160 | |||
161 | av = calloc (sizeof(ToxAV), 1); | ||
162 | |||
106 | if (av == NULL) { | 163 | if (av == NULL) { |
107 | LOGGER_WARNING("Allocation failed!"); | 164 | LOGGER_WARNING("Allocation failed!"); |
108 | return NULL; | 165 | rc = TOXAV_ERR_NEW_MALLOC; |
166 | goto END; | ||
109 | } | 167 | } |
110 | 168 | ||
111 | av->messenger = (Messenger *)messenger; | 169 | if (create_recursive_mutex(av->mutex) != 0) { |
112 | av->msi_session = msi_new(av->messenger, max_calls); | 170 | LOGGER_WARNING("Mutex creation failed!"); |
113 | av->msi_session->agent_handler = av; | 171 | rc = TOXAV_ERR_NEW_MALLOC; |
114 | av->calls = calloc(sizeof(ToxAvCall), max_calls); | 172 | goto END; |
115 | av->max_calls = max_calls; | ||
116 | |||
117 | unsigned int i; | ||
118 | |||
119 | for (i = 0; i < max_calls; ++i) { | ||
120 | if (create_recursive_mutex(av->calls[i].mutex) != 0 ) { | ||
121 | LOGGER_WARNING("Failed to init call(%u) mutex!", i); | ||
122 | msi_kill(av->msi_session); | ||
123 | |||
124 | free(av->calls); | ||
125 | free(av); | ||
126 | return NULL; | ||
127 | } | ||
128 | } | 173 | } |
129 | 174 | ||
175 | av->m = (Messenger *)tox; | ||
176 | av->msi = msi_new(av->m); | ||
177 | |||
178 | if (av->msi == NULL) { | ||
179 | pthread_mutex_destroy(av->mutex); | ||
180 | rc = TOXAV_ERR_NEW_MALLOC; | ||
181 | goto END; | ||
182 | } | ||
183 | |||
184 | av->interval = 200; | ||
185 | av->msi->av = av; | ||
186 | |||
187 | msi_register_callback(av->msi, callback_invite, msi_OnInvite); | ||
188 | msi_register_callback(av->msi, callback_start, msi_OnStart); | ||
189 | msi_register_callback(av->msi, callback_end, msi_OnEnd); | ||
190 | msi_register_callback(av->msi, callback_error, msi_OnError); | ||
191 | msi_register_callback(av->msi, callback_error, msi_OnPeerTimeout); | ||
192 | msi_register_callback(av->msi, callback_capabilites, msi_OnCapabilities); | ||
193 | |||
194 | END: | ||
195 | if (error) | ||
196 | *error = rc; | ||
197 | |||
198 | if (rc != TOXAV_ERR_NEW_OK) { | ||
199 | free(av); | ||
200 | av = NULL; | ||
201 | } | ||
202 | |||
130 | return av; | 203 | return av; |
131 | } | 204 | } |
132 | 205 | ||
133 | void toxav_kill ( ToxAv *av ) | 206 | void toxav_kill(ToxAV* av) |
134 | { | 207 | { |
135 | uint32_t i; | 208 | if (av == NULL) |
136 | 209 | return; | |
137 | for (i = 0; i < av->max_calls; i ++) { | 210 | pthread_mutex_lock(av->mutex); |
138 | if ( av->calls[i].crtps[audio_index] ) | 211 | |
139 | rtp_kill(av->calls[i].crtps[audio_index], av->msi_session->messenger_handle); | 212 | msi_kill(av->msi); |
140 | 213 | ||
141 | 214 | /* Msi kill will hang up all calls so just clean these calls */ | |
142 | if ( av->calls[i].crtps[video_index] ) | 215 | if (av->calls) { |
143 | rtp_kill(av->calls[i].crtps[video_index], av->msi_session->messenger_handle); | 216 | ToxAVCall* it = call_get(av, av->calls_head); |
144 | 217 | while (it) { | |
145 | if ( av->calls[i].cs ) | 218 | call_kill_transmission(it); |
146 | cs_kill(av->calls[i].cs); | 219 | it = call_remove(it); /* This will eventually free av->calls */ |
147 | 220 | } | |
148 | pthread_mutex_destroy(av->calls[i].mutex); | ||
149 | } | 221 | } |
150 | 222 | ||
151 | msi_kill(av->msi_session); | 223 | pthread_mutex_unlock(av->mutex); |
152 | 224 | pthread_mutex_destroy(av->mutex); | |
153 | free(av->calls); | ||
154 | free(av); | 225 | free(av); |
155 | } | 226 | } |
156 | 227 | ||
157 | uint32_t toxav_do_interval(ToxAv *av) | 228 | Tox* toxav_get_tox(const ToxAV* av) |
158 | { | 229 | { |
159 | int i = 0; | 230 | return (Tox*) av->m; |
160 | uint32_t rc = 200 + av->avgdectms; /* Return 200 if no call is active */ | ||
161 | |||
162 | for (; i < av->max_calls; i ++) { | ||
163 | pthread_mutex_lock(av->calls[i].mutex); | ||
164 | |||
165 | if (av->calls[i].active) { | ||
166 | /* This should work. Video payload will always come in greater intervals */ | ||
167 | rc = MIN(av->calls[i].cs->audio_decoder_frame_duration, rc); | ||
168 | } | ||
169 | |||
170 | pthread_mutex_unlock(av->calls[i].mutex); | ||
171 | } | ||
172 | |||
173 | return rc < av->avgdectms ? 0 : rc - av->avgdectms; | ||
174 | } | 231 | } |
175 | 232 | ||
176 | void toxav_do(ToxAv *av) | 233 | uint32_t toxav_iteration_interval(const ToxAV* av) |
177 | { | 234 | { |
178 | msi_do(av->msi_session); | 235 | /* If no call is active interval is 200 */ |
236 | return av->calls ? av->interval : 200; | ||
237 | } | ||
179 | 238 | ||
239 | void toxav_iterate(ToxAV* av) | ||
240 | { | ||
241 | pthread_mutex_lock(av->mutex); | ||
242 | if (av->calls == NULL) { | ||
243 | pthread_mutex_unlock(av->mutex); | ||
244 | return; | ||
245 | } | ||
246 | |||
180 | uint64_t start = current_time_monotonic(); | 247 | uint64_t start = current_time_monotonic(); |
181 | 248 | int32_t rc = 500; | |
182 | uint32_t i = 0; | 249 | |
183 | 250 | ToxAVCall* i = av->calls[av->calls_head]; | |
184 | for (; i < av->max_calls; i ++) { | 251 | for (; i; i = i->next) { |
185 | pthread_mutex_lock(av->calls[i].mutex); | 252 | if (i->active) { |
186 | 253 | pthread_mutex_lock(i->mutex); | |
187 | if (av->calls[i].active) { | 254 | pthread_mutex_unlock(av->mutex); |
188 | pthread_mutex_lock(av->calls[i].mutex_do); | 255 | |
189 | pthread_mutex_unlock(av->calls[i].mutex); | 256 | ac_do(i->audio.second); |
190 | cs_do(av->calls[i].cs); | 257 | if (rtp_do(i->audio.first) < 0) { |
191 | pthread_mutex_unlock(av->calls[i].mutex_do); | 258 | /* Bad transmission */ |
192 | } else { | 259 | |
193 | pthread_mutex_unlock(av->calls[i].mutex); | 260 | uint32_t bb = i->audio_bit_rate; |
261 | |||
262 | if (i->aba.active) { | ||
263 | bb = i->aba.bit_rate; | ||
264 | /* Stop sending dummy packets */ | ||
265 | memset(&i->aba, 0, sizeof(i->aba)); | ||
266 | } | ||
267 | |||
268 | /* Notify app */ | ||
269 | if (av->abcb.first) | ||
270 | av->abcb.first (av, i->friend_number, false, bb, av->abcb.second); | ||
271 | } else if (i->aba.active && i->aba.end_time < current_time_monotonic()) { | ||
272 | |||
273 | i->audio_bit_rate = i->aba.bit_rate; | ||
274 | |||
275 | /* Notify user about the new bit rate */ | ||
276 | if (av->abcb.first) | ||
277 | av->abcb.first (av, i->friend_number, true, i->aba.bit_rate, av->abcb.second); | ||
278 | |||
279 | /* Stop sending dummy packets */ | ||
280 | memset(&i->aba, 0, sizeof(i->aba)); | ||
281 | } | ||
282 | |||
283 | vc_do(i->video.second); | ||
284 | if (rtp_do(i->video.first) < 0) { | ||
285 | /* Bad transmission */ | ||
286 | uint32_t bb = i->video_bit_rate; | ||
287 | |||
288 | if (i->vba.active) { | ||
289 | bb = i->vba.bit_rate; | ||
290 | /* Stop sending dummy packets */ | ||
291 | memset(&i->vba, 0, sizeof(i->vba)); | ||
292 | } | ||
293 | |||
294 | /* Notify app */ | ||
295 | if (av->vbcb.first) | ||
296 | av->vbcb.first (av, i->friend_number, false, bb, av->vbcb.second); | ||
297 | |||
298 | } else if (i->vba.active && i->vba.end_time < current_time_monotonic()) { | ||
299 | |||
300 | i->video_bit_rate = i->vba.bit_rate; | ||
301 | |||
302 | /* Notify user about the new bit rate */ | ||
303 | if (av->vbcb.first) | ||
304 | av->vbcb.first (av, i->friend_number, true, i->vba.bit_rate, av->vbcb.second); | ||
305 | |||
306 | /* Stop sending dummy packets */ | ||
307 | memset(&i->vba, 0, sizeof(i->vba)); | ||
308 | } | ||
309 | |||
310 | if (i->msi_call->self_capabilities & msi_CapRAudio && | ||
311 | i->msi_call->peer_capabilities & msi_CapSAudio) | ||
312 | rc = MIN(i->audio.second->last_packet_frame_duration, rc); | ||
313 | |||
314 | if (i->msi_call->self_capabilities & msi_CapRVideo && | ||
315 | i->msi_call->peer_capabilities & msi_CapSVideo) | ||
316 | rc = MIN(i->video.second->lcfd, (uint32_t) rc); | ||
317 | |||
318 | uint32_t fid = i->friend_number; | ||
319 | |||
320 | pthread_mutex_unlock(i->mutex); | ||
321 | pthread_mutex_lock(av->mutex); | ||
322 | |||
323 | /* In case this call is popped from container stop iteration */ | ||
324 | if (call_get(av, fid) != i) | ||
325 | break; | ||
194 | } | 326 | } |
195 | } | 327 | } |
196 | 328 | pthread_mutex_unlock(av->mutex); | |
197 | uint64_t end = current_time_monotonic(); | 329 | |
198 | 330 | av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa); | |
199 | /* TODO maybe use variable for sizes */ | 331 | av->dmsst += current_time_monotonic() - start; |
200 | av->dectmsstotal += end - start; | 332 | |
201 | 333 | if (++av->dmssc == 3) { | |
202 | if (++av->dectmsscount == 3) { | 334 | av->dmssa = av->dmsst / 3 + 2 /* NOTE Magic Offset for precission */; |
203 | av->avgdectms = av->dectmsstotal / 3 + 2 /* NOTE Magic Offset */; | 335 | av->dmssc = 0; |
204 | av->dectmsscount = 0; | 336 | av->dmsst = 0; |
205 | av->dectmsstotal = 0; | ||
206 | } | 337 | } |
207 | } | 338 | } |
208 | 339 | ||
209 | void toxav_register_callstate_callback ( ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata ) | 340 | bool toxav_call(ToxAV* av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL* error) |
210 | { | ||
211 | msi_register_callback(av->msi_session, (MSICallbackType)cb, (MSICallbackID) id, userdata); | ||
212 | } | ||
213 | |||
214 | void toxav_register_audio_callback(ToxAv *av, ToxAvAudioCallback cb, void *userdata) | ||
215 | { | ||
216 | av->acb.first = cb; | ||
217 | av->acb.second = userdata; | ||
218 | } | ||
219 | |||
220 | void toxav_register_video_callback(ToxAv *av, ToxAvVideoCallback cb, void *userdata) | ||
221 | { | ||
222 | av->vcb.first = cb; | ||
223 | av->vcb.second = userdata; | ||
224 | } | ||
225 | |||
226 | int toxav_call (ToxAv *av, | ||
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 | |||
235 | int toxav_hangup ( ToxAv *av, int32_t call_index ) | ||
236 | { | 341 | { |
237 | return msi_hangup(av->msi_session, call_index); | 342 | if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) |
343 | ||(video_bit_rate && video_bit_rate_invalid(video_bit_rate)) | ||
344 | ) { | ||
345 | if (error) | ||
346 | *error = TOXAV_ERR_CALL_INVALID_BIT_RATE; | ||
347 | return false; | ||
348 | } | ||
349 | |||
350 | pthread_mutex_lock(av->mutex); | ||
351 | ToxAVCall* call = call_new(av, friend_number, error); | ||
352 | if (call == NULL) { | ||
353 | pthread_mutex_unlock(av->mutex); | ||
354 | return false; | ||
355 | } | ||
356 | |||
357 | call->audio_bit_rate = audio_bit_rate; | ||
358 | call->video_bit_rate = video_bit_rate; | ||
359 | |||
360 | call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; | ||
361 | |||
362 | call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; | ||
363 | call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; | ||
364 | |||
365 | if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) { | ||
366 | call_remove(call); | ||
367 | if (error) | ||
368 | *error = TOXAV_ERR_CALL_MALLOC; | ||
369 | pthread_mutex_unlock(av->mutex); | ||
370 | return false; | ||
371 | } | ||
372 | |||
373 | call->msi_call->av_call = call; | ||
374 | pthread_mutex_unlock(av->mutex); | ||
375 | |||
376 | return true; | ||
238 | } | 377 | } |
239 | 378 | ||
240 | int toxav_answer ( ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ) | 379 | void toxav_callback_call(ToxAV* av, toxav_call_cb* function, void* user_data) |
241 | { | 380 | { |
242 | return msi_answer(av->msi_session, call_index, msicsettings_cast(csettings)); | 381 | pthread_mutex_lock(av->mutex); |
382 | av->ccb.first = function; | ||
383 | av->ccb.second = user_data; | ||
384 | pthread_mutex_unlock(av->mutex); | ||
243 | } | 385 | } |
244 | 386 | ||
245 | int toxav_reject ( ToxAv *av, int32_t call_index, const char *reason ) | 387 | bool toxav_answer(ToxAV* av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER* error) |
246 | { | 388 | { |
247 | return msi_reject(av->msi_session, call_index, reason); | 389 | pthread_mutex_lock(av->mutex); |
390 | |||
391 | TOXAV_ERR_ANSWER rc = TOXAV_ERR_ANSWER_OK; | ||
392 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
393 | rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND; | ||
394 | goto END; | ||
395 | } | ||
396 | |||
397 | if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) | ||
398 | ||(video_bit_rate && video_bit_rate_invalid(video_bit_rate)) | ||
399 | ) { | ||
400 | rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE; | ||
401 | goto END; | ||
402 | } | ||
403 | |||
404 | ToxAVCall* call = call_get(av, friend_number); | ||
405 | if (call == NULL) { | ||
406 | rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; | ||
407 | goto END; | ||
408 | } | ||
409 | |||
410 | if (!call_prepare_transmission(call)) { | ||
411 | rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION; | ||
412 | goto END; | ||
413 | } | ||
414 | |||
415 | call->audio_bit_rate = audio_bit_rate; | ||
416 | call->video_bit_rate = video_bit_rate; | ||
417 | |||
418 | call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo; | ||
419 | |||
420 | call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0; | ||
421 | call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0; | ||
422 | |||
423 | if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0) | ||
424 | rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; /* the only reason for msi_answer to fail */ | ||
425 | |||
426 | |||
427 | END: | ||
428 | pthread_mutex_unlock(av->mutex); | ||
429 | |||
430 | if (error) | ||
431 | *error = rc; | ||
432 | |||
433 | return rc == TOXAV_ERR_ANSWER_OK; | ||
248 | } | 434 | } |
249 | 435 | ||
250 | int toxav_cancel ( ToxAv *av, int32_t call_index, int peer_id, const char *reason ) | 436 | void toxav_callback_call_state(ToxAV* av, toxav_call_state_cb* function, void* user_data) |
251 | { | 437 | { |
252 | return msi_cancel(av->msi_session, call_index, peer_id, reason); | 438 | pthread_mutex_lock(av->mutex); |
439 | av->scb.first = function; | ||
440 | av->scb.second = user_data; | ||
441 | pthread_mutex_unlock(av->mutex); | ||
253 | } | 442 | } |
254 | 443 | ||
255 | int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings) | 444 | bool toxav_call_control(ToxAV* av, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL* error) |
256 | { | 445 | { |
257 | return msi_change_csettings(av->msi_session, call_index, msicsettings_cast(csettings)); | 446 | pthread_mutex_lock(av->mutex); |
447 | TOXAV_ERR_CALL_CONTROL rc = TOXAV_ERR_CALL_CONTROL_OK; | ||
448 | |||
449 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
450 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; | ||
451 | goto END; | ||
452 | } | ||
453 | |||
454 | ToxAVCall* call = call_get(av, friend_number); | ||
455 | if (call == NULL || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) { | ||
456 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
457 | goto END; | ||
458 | } | ||
459 | |||
460 | switch (control) { | ||
461 | case TOXAV_CALL_CONTROL_RESUME: { | ||
462 | /* Only act if paused and had media transfer active before */ | ||
463 | if (call->msi_call->self_capabilities == 0 && | ||
464 | call->previous_self_capabilities ) { | ||
465 | |||
466 | if (msi_change_capabilities(call->msi_call, | ||
467 | call->previous_self_capabilities) == -1) { | ||
468 | /* The only reason for this function to fail is invalid state | ||
469 | * ( not active ) */ | ||
470 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
471 | goto END; | ||
472 | } | ||
473 | |||
474 | rtp_start_receiving(call->audio.first); | ||
475 | rtp_start_receiving(call->video.first); | ||
476 | } else { | ||
477 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
478 | goto END; | ||
479 | } | ||
480 | } break; | ||
481 | |||
482 | case TOXAV_CALL_CONTROL_PAUSE: { | ||
483 | /* Only act if not already paused */ | ||
484 | if (call->msi_call->self_capabilities) { | ||
485 | call->previous_self_capabilities = call->msi_call->self_capabilities; | ||
486 | |||
487 | if (msi_change_capabilities(call->msi_call, 0) == -1 ) { | ||
488 | /* The only reason for this function to fail is invalid state | ||
489 | * ( not active ) */ | ||
490 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
491 | goto END; | ||
492 | } | ||
493 | |||
494 | rtp_stop_receiving(call->audio.first); | ||
495 | rtp_stop_receiving(call->video.first); | ||
496 | } else { | ||
497 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
498 | goto END; | ||
499 | } | ||
500 | } break; | ||
501 | |||
502 | case TOXAV_CALL_CONTROL_CANCEL: { | ||
503 | /* Hang up */ | ||
504 | msi_hangup(call->msi_call); | ||
505 | |||
506 | /* No mather the case, terminate the call */ | ||
507 | call_kill_transmission(call); | ||
508 | call_remove(call); | ||
509 | } break; | ||
510 | |||
511 | case TOXAV_CALL_CONTROL_MUTE_AUDIO: { | ||
512 | if (call->msi_call->self_capabilities & msi_CapRAudio) { | ||
513 | if (msi_change_capabilities(call->msi_call, call-> | ||
514 | msi_call->self_capabilities ^ msi_CapRAudio) == -1) { | ||
515 | /* The only reason for this function to fail is invalid state | ||
516 | * ( not active ) */ | ||
517 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
518 | goto END; | ||
519 | } | ||
520 | |||
521 | rtp_stop_receiving(call->audio.first); | ||
522 | } else { | ||
523 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
524 | goto END; | ||
525 | } | ||
526 | } break; | ||
527 | |||
528 | case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: { | ||
529 | if (call->msi_call->self_capabilities ^ msi_CapRAudio) { | ||
530 | if (msi_change_capabilities(call->msi_call, call-> | ||
531 | msi_call->self_capabilities | msi_CapRAudio) == -1) { | ||
532 | /* The only reason for this function to fail is invalid state | ||
533 | * ( not active ) */ | ||
534 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
535 | goto END; | ||
536 | } | ||
537 | |||
538 | rtp_start_receiving(call->audio.first); | ||
539 | } else { | ||
540 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
541 | goto END; | ||
542 | } | ||
543 | } break; | ||
544 | |||
545 | case TOXAV_CALL_CONTROL_HIDE_VIDEO: { | ||
546 | if (call->msi_call->self_capabilities & msi_CapRVideo) { | ||
547 | if (msi_change_capabilities(call->msi_call, call-> | ||
548 | msi_call->self_capabilities ^ msi_CapRVideo) == -1) { | ||
549 | /* The only reason for this function to fail is invalid state | ||
550 | * ( not active ) */ | ||
551 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
552 | goto END; | ||
553 | } | ||
554 | |||
555 | rtp_stop_receiving(call->video.first); | ||
556 | } else { | ||
557 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
558 | goto END; | ||
559 | } | ||
560 | } break; | ||
561 | |||
562 | case TOXAV_CALL_CONTROL_SHOW_VIDEO: { | ||
563 | if (call->msi_call->self_capabilities ^ msi_CapRVideo) { | ||
564 | if (msi_change_capabilities(call->msi_call, call-> | ||
565 | msi_call->self_capabilities | msi_CapRVideo) == -1) { | ||
566 | /* The only reason for this function to fail is invalid state | ||
567 | * ( not active ) */ | ||
568 | rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; | ||
569 | goto END; | ||
570 | } | ||
571 | |||
572 | rtp_start_receiving(call->audio.first); | ||
573 | } else { | ||
574 | rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; | ||
575 | goto END; | ||
576 | } | ||
577 | } break; | ||
578 | } | ||
579 | |||
580 | END: | ||
581 | pthread_mutex_unlock(av->mutex); | ||
582 | |||
583 | if (error) | ||
584 | *error = rc; | ||
585 | |||
586 | return rc == TOXAV_ERR_CALL_CONTROL_OK; | ||
258 | } | 587 | } |
259 | 588 | ||
260 | int toxav_stop_call ( ToxAv *av, int32_t call_index ) | 589 | void toxav_callback_audio_bit_rate_status(ToxAV* av, toxav_audio_bit_rate_status_cb* function, void* user_data) |
261 | { | 590 | { |
262 | return msi_stopcall(av->msi_session, call_index); | 591 | pthread_mutex_lock(av->mutex); |
592 | av->abcb.first = function; | ||
593 | av->abcb.second = user_data; | ||
594 | pthread_mutex_unlock(av->mutex); | ||
263 | } | 595 | } |
264 | 596 | ||
265 | int toxav_prepare_transmission ( ToxAv *av, int32_t call_index, int support_video ) | 597 | bool toxav_audio_bit_rate_set(ToxAV* av, uint32_t friend_number, uint32_t audio_bit_rate, bool force, TOXAV_ERR_SET_BIT_RATE* error) |
266 | { | 598 | { |
267 | if ( !av->msi_session || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || | 599 | TOXAV_ERR_SET_BIT_RATE rc = TOXAV_ERR_SET_BIT_RATE_OK; |
268 | !av->msi_session->calls[call_index] || !av->msi_session->calls[call_index]->csettings_peer) { | 600 | ToxAVCall* call; |
269 | LOGGER_ERROR("Error while starting RTP session: invalid call!\n"); | 601 | |
270 | return av_ErrorNoCall; | 602 | if (m_friend_exists(av->m, friend_number) == 0) { |
603 | rc = TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_FOUND; | ||
604 | goto END; | ||
271 | } | 605 | } |
272 | 606 | ||
273 | ToxAvCall *call = &av->calls[call_index]; | 607 | if (audio_bit_rate_invalid(audio_bit_rate)) { |
274 | 608 | rc = TOXAV_ERR_SET_BIT_RATE_INVALID; | |
275 | pthread_mutex_lock(call->mutex); | 609 | goto END; |
276 | |||
277 | if (call->active) { | ||
278 | pthread_mutex_unlock(call->mutex); | ||
279 | LOGGER_ERROR("Error while starting RTP session: call already active!\n"); | ||
280 | return av_ErrorAlreadyInCallWithPeer; | ||
281 | } | ||
282 | |||
283 | if (pthread_mutex_init(call->mutex_encoding_audio, NULL) != 0 | ||
284 | || pthread_mutex_init(call->mutex_encoding_video, NULL) != 0 || pthread_mutex_init(call->mutex_do, NULL) != 0) { | ||
285 | pthread_mutex_unlock(call->mutex); | ||
286 | LOGGER_ERROR("Error while starting RTP session: mutex initializing failed!\n"); | ||
287 | return av_ErrorUnknown; | ||
288 | } | 610 | } |
289 | 611 | ||
290 | const ToxAvCSettings *c_peer = toxavcsettings_cast | 612 | pthread_mutex_lock(av->mutex); |
291 | (&av->msi_session->calls[call_index]->csettings_peer[0]); | 613 | call = call_get(av, friend_number); |
292 | const ToxAvCSettings *c_self = toxavcsettings_cast | 614 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { |
293 | (&av->msi_session->calls[call_index]->csettings_local); | 615 | pthread_mutex_unlock(av->mutex); |
294 | 616 | rc = TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_IN_CALL; | |
295 | LOGGER_DEBUG( | 617 | goto END; |
296 | "Type: %u(s) %u(p)\n" | ||
297 | "Video bitrate: %u(s) %u(p)\n" | ||
298 | "Video height: %u(s) %u(p)\n" | ||
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 | |||
313 | if ( !(call->cs = cs_new(c_self, c_peer, jbuf_capacity, support_video)) ) { | ||
314 | LOGGER_ERROR("Error while starting Codec State!\n"); | ||
315 | pthread_mutex_unlock(call->mutex); | ||
316 | return av_ErrorInitializingCodecs; | ||
317 | } | 618 | } |
318 | 619 | ||
319 | call->cs->agent = av; | 620 | if (call->audio_bit_rate == audio_bit_rate || (call->aba.active && call->aba.bit_rate == audio_bit_rate)) { |
320 | call->cs->call_idx = call_index; | 621 | pthread_mutex_unlock(av->mutex); |
321 | 622 | goto END; | |
322 | call->cs->acb.first = av->acb.first; | ||
323 | call->cs->acb.second = av->acb.second; | ||
324 | |||
325 | call->cs->vcb.first = av->vcb.first; | ||
326 | call->cs->vcb.second = av->vcb.second; | ||
327 | |||
328 | |||
329 | call->crtps[audio_index] = | ||
330 | rtp_new(msi_TypeAudio, av->messenger, av->msi_session->calls[call_index]->peers[0]); | ||
331 | |||
332 | if ( !call->crtps[audio_index] ) { | ||
333 | LOGGER_ERROR("Error while starting audio RTP session!\n"); | ||
334 | goto error; | ||
335 | } | 623 | } |
336 | 624 | ||
337 | call->crtps[audio_index]->cs = call->cs; | 625 | |
338 | 626 | pthread_mutex_lock(call->mutex); | |
339 | if ( support_video ) { | 627 | |
340 | call->crtps[video_index] = | 628 | if (audio_bit_rate > call->audio_bit_rate && !force) |
341 | rtp_new(msi_TypeVideo, av->messenger, av->msi_session->calls[call_index]->peers[0]); | 629 | ba_set(&call->aba, audio_bit_rate); |
342 | 630 | else { | |
343 | if ( !call->crtps[video_index] ) { | 631 | /* Cancel any previous non forceful bitrate change request */ |
344 | LOGGER_ERROR("Error while starting video RTP session!\n"); | 632 | memset(&call->aba, 0, sizeof(call->aba)); |
345 | goto error; | 633 | call->audio_bit_rate = audio_bit_rate; |
346 | } | 634 | |
347 | 635 | if (!force && av->abcb.first) | |
348 | call->crtps[video_index]->cs = call->cs; | 636 | av->abcb.first (av, call->friend_number, true, audio_bit_rate, av->abcb.second); |
349 | } | 637 | } |
350 | 638 | ||
351 | call->active = 1; | ||
352 | pthread_mutex_unlock(call->mutex); | 639 | pthread_mutex_unlock(call->mutex); |
353 | return av_ErrorNone; | 640 | pthread_mutex_unlock(av->mutex); |
354 | error: | 641 | |
355 | rtp_kill(call->crtps[audio_index], av->messenger); | 642 | END: |
356 | call->crtps[audio_index] = NULL; | 643 | if (error) |
357 | rtp_kill(call->crtps[video_index], av->messenger); | 644 | *error = rc; |
358 | call->crtps[video_index] = NULL; | 645 | |
359 | cs_kill(call->cs); | 646 | return rc == TOXAV_ERR_SET_BIT_RATE_OK; |
360 | call->cs = NULL; | 647 | } |
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 | 648 | ||
366 | pthread_mutex_unlock(call->mutex); | 649 | void toxav_callback_video_bit_rate_status(ToxAV* av, toxav_video_bit_rate_status_cb* function, void* user_data) |
367 | return av_ErrorCreatingRtpSessions; | 650 | { |
651 | pthread_mutex_lock(av->mutex); | ||
652 | av->vbcb.first = function; | ||
653 | av->vbcb.second = user_data; | ||
654 | pthread_mutex_unlock(av->mutex); | ||
368 | } | 655 | } |
369 | 656 | ||
370 | int toxav_kill_transmission ( ToxAv *av, int32_t call_index ) | 657 | bool toxav_video_bit_rate_set(ToxAV* av, uint32_t friend_number, uint32_t video_bit_rate, bool force, TOXAV_ERR_SET_BIT_RATE* error) |
371 | { | 658 | { |
372 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 659 | TOXAV_ERR_SET_BIT_RATE rc = TOXAV_ERR_SET_BIT_RATE_OK; |
373 | LOGGER_WARNING("Invalid call index: %d", call_index); | 660 | ToxAVCall* call; |
374 | return av_ErrorNoCall; | 661 | |
662 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
663 | rc = TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_FOUND; | ||
664 | goto END; | ||
375 | } | 665 | } |
376 | 666 | ||
377 | ToxAvCall *call = &av->calls[call_index]; | 667 | if (video_bit_rate_invalid(video_bit_rate)) { |
378 | 668 | rc = TOXAV_ERR_SET_BIT_RATE_INVALID; | |
669 | goto END; | ||
670 | } | ||
671 | |||
672 | pthread_mutex_lock(av->mutex); | ||
673 | call = call_get(av, friend_number); | ||
674 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { | ||
675 | pthread_mutex_unlock(av->mutex); | ||
676 | rc = TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_IN_CALL; | ||
677 | goto END; | ||
678 | } | ||
679 | |||
680 | if (call->video_bit_rate == video_bit_rate || (call->vba.active && call->vba.bit_rate == video_bit_rate)) { | ||
681 | pthread_mutex_unlock(av->mutex); | ||
682 | goto END; | ||
683 | } | ||
684 | |||
379 | pthread_mutex_lock(call->mutex); | 685 | pthread_mutex_lock(call->mutex); |
380 | 686 | ||
381 | if (!call->active) { | 687 | if (video_bit_rate > call->video_bit_rate && !force) |
382 | pthread_mutex_unlock(call->mutex); | 688 | ba_set(&call->vba, video_bit_rate); |
383 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 689 | else { |
384 | return av_ErrorInvalidState; | 690 | /* Cancel any previous non forceful bitrate change request */ |
691 | memset(&call->vba, 0, sizeof(call->vba)); | ||
692 | call->video_bit_rate = video_bit_rate; | ||
693 | |||
694 | if (!force && av->vbcb.first) | ||
695 | av->vbcb.first (av, call->friend_number, true, video_bit_rate, av->vbcb.second); | ||
385 | } | 696 | } |
386 | 697 | ||
387 | call->active = 0; | ||
388 | |||
389 | pthread_mutex_lock(call->mutex_encoding_audio); | ||
390 | pthread_mutex_unlock(call->mutex_encoding_audio); | ||
391 | pthread_mutex_lock(call->mutex_encoding_video); | ||
392 | pthread_mutex_unlock(call->mutex_encoding_video); | ||
393 | pthread_mutex_lock(call->mutex_do); | ||
394 | pthread_mutex_unlock(call->mutex_do); | ||
395 | |||
396 | rtp_kill(call->crtps[audio_index], av->messenger); | ||
397 | call->crtps[audio_index] = NULL; | ||
398 | rtp_kill(call->crtps[video_index], av->messenger); | ||
399 | call->crtps[video_index] = NULL; | ||
400 | cs_kill(call->cs); | ||
401 | call->cs = NULL; | ||
402 | |||
403 | pthread_mutex_destroy(call->mutex_encoding_audio); | ||
404 | pthread_mutex_destroy(call->mutex_encoding_video); | ||
405 | pthread_mutex_destroy(call->mutex_do); | ||
406 | |||
407 | pthread_mutex_unlock(call->mutex); | 698 | pthread_mutex_unlock(call->mutex); |
408 | 699 | pthread_mutex_unlock(av->mutex); | |
409 | return av_ErrorNone; | 700 | |
701 | END: | ||
702 | if (error) | ||
703 | *error = rc; | ||
704 | |||
705 | return rc == TOXAV_ERR_SET_BIT_RATE_OK; | ||
410 | } | 706 | } |
411 | 707 | ||
412 | static int toxav_send_rtp_payload(ToxAv *av, | 708 | bool toxav_audio_send_frame(ToxAV* av, uint32_t friend_number, const int16_t* pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME* error) |
413 | ToxAvCall *call, | ||
414 | ToxAvCallType type, | ||
415 | const uint8_t *payload, | ||
416 | unsigned int length) | ||
417 | { | 709 | { |
418 | if (call->crtps[type - av_TypeAudio]) { | 710 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; |
419 | 711 | ToxAVCall* call; | |
420 | /* Audio */ | 712 | |
421 | if (type == av_TypeAudio) | 713 | if (m_friend_exists(av->m, friend_number) == 0) { |
422 | return rtp_send_msg(call->crtps[audio_index], av->messenger, payload, length); | 714 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; |
423 | 715 | goto END; | |
424 | /* Video */ | 716 | } |
425 | int parts = cs_split_video_payload(call->cs, payload, length); | 717 | |
426 | 718 | pthread_mutex_lock(av->mutex); | |
427 | if (parts < 0) return parts; | 719 | call = call_get(av, friend_number); |
428 | 720 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { | |
429 | uint16_t part_size; | 721 | pthread_mutex_unlock(av->mutex); |
430 | const uint8_t *iter; | 722 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; |
431 | 723 | goto END; | |
432 | int i; | 724 | } |
433 | 725 | ||
434 | for (i = 0; i < parts; i++) { | 726 | pthread_mutex_lock(call->mutex_audio); |
435 | iter = cs_get_split_video_frame(call->cs, &part_size); | 727 | pthread_mutex_unlock(av->mutex); |
436 | 728 | ||
437 | if (rtp_send_msg(call->crtps[video_index], av->messenger, iter, part_size) < 0) | 729 | if ( pcm == NULL ) { |
438 | return av_ErrorSendingPayload; | 730 | pthread_mutex_unlock(call->mutex_audio); |
731 | rc = TOXAV_ERR_SEND_FRAME_NULL; | ||
732 | goto END; | ||
733 | } | ||
734 | |||
735 | if ( channels > 2 ) { | ||
736 | pthread_mutex_unlock(call->mutex_audio); | ||
737 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
738 | goto END; | ||
739 | } | ||
740 | |||
741 | { /* Encode and send */ | ||
742 | if (ac_reconfigure_encoder(call->audio.second, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { | ||
743 | pthread_mutex_unlock(call->mutex_audio); | ||
744 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
745 | goto END; | ||
439 | } | 746 | } |
440 | 747 | ||
441 | return av_ErrorNone; | 748 | uint8_t dest[sample_count + sizeof(sampling_rate)]; /* This is more than enough always */ |
442 | 749 | ||
443 | } else return av_ErrorNoRtpSession; | 750 | sampling_rate = htonl(sampling_rate); |
751 | memcpy(dest, &sampling_rate, sizeof(sampling_rate)); | ||
752 | int vrc = opus_encode(call->audio.second->encoder, pcm, sample_count, | ||
753 | dest + sizeof(sampling_rate), sizeof(dest) - sizeof(sampling_rate)); | ||
754 | |||
755 | if (vrc < 0) { | ||
756 | LOGGER_WARNING("Failed to encode frame %s", opus_strerror(vrc)); | ||
757 | pthread_mutex_unlock(call->mutex_audio); | ||
758 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
759 | goto END; | ||
760 | } | ||
761 | |||
762 | if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate), false) != 0) { | ||
763 | LOGGER_WARNING("Failed to send audio packet"); | ||
764 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
765 | } | ||
766 | |||
767 | |||
768 | /* For bit rate measurement; send dummy packet */ | ||
769 | if (ba_shoud_send_dummy(&call->aba)) { | ||
770 | sampling_rate = ntohl(sampling_rate); | ||
771 | if (ac_reconfigure_test_encoder(call->audio.second, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { | ||
772 | /* FIXME should the bit rate changing fail here? */ | ||
773 | pthread_mutex_unlock(call->mutex_audio); | ||
774 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
775 | goto END; | ||
776 | } | ||
777 | |||
778 | sampling_rate = htonl(sampling_rate); | ||
779 | memcpy(dest, &sampling_rate, sizeof(sampling_rate)); | ||
780 | vrc = opus_encode(call->audio.second->test_encoder, pcm, sample_count, | ||
781 | dest + sizeof(sampling_rate), sizeof(dest) - sizeof(sampling_rate)); | ||
782 | |||
783 | if (vrc < 0) { | ||
784 | LOGGER_WARNING("Failed to encode frame %s", opus_strerror(vrc)); | ||
785 | pthread_mutex_unlock(call->mutex_audio); | ||
786 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
787 | goto END; | ||
788 | } | ||
789 | |||
790 | if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate), true) != 0) { | ||
791 | LOGGER_WARNING("Failed to send audio packet"); | ||
792 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
793 | } | ||
794 | |||
795 | if (call->aba.end_time == (uint64_t) ~0) | ||
796 | call->aba.end_time = current_time_monotonic() + BITRATE_CHANGE_TESTING_TIME_MS; | ||
797 | } | ||
798 | } | ||
799 | |||
800 | |||
801 | pthread_mutex_unlock(call->mutex_audio); | ||
802 | |||
803 | END: | ||
804 | if (error) | ||
805 | *error = rc; | ||
806 | |||
807 | return rc == TOXAV_ERR_SEND_FRAME_OK; | ||
444 | } | 808 | } |
445 | 809 | ||
446 | int toxav_prepare_video_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input) | 810 | bool toxav_video_send_frame(ToxAV* av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t* y, const uint8_t* u, const uint8_t* v, TOXAV_ERR_SEND_FRAME* error) |
447 | { | 811 | { |
448 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 812 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; |
449 | LOGGER_WARNING("Invalid call index: %d", call_index); | 813 | ToxAVCall* call; |
450 | return av_ErrorNoCall; | 814 | |
815 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
816 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; | ||
817 | goto END; | ||
451 | } | 818 | } |
452 | 819 | ||
453 | 820 | pthread_mutex_lock(av->mutex); | |
454 | ToxAvCall *call = &av->calls[call_index]; | 821 | call = call_get(av, friend_number); |
455 | pthread_mutex_lock(call->mutex); | 822 | if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) { |
456 | 823 | pthread_mutex_unlock(av->mutex); | |
457 | if (!call->active) { | 824 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; |
458 | pthread_mutex_unlock(call->mutex); | 825 | goto END; |
459 | LOGGER_WARNING("Action on inactive call: %d", call_index); | ||
460 | return av_ErrorInvalidState; | ||
461 | } | 826 | } |
462 | 827 | ||
463 | if (!(call->cs->capabilities & cs_VideoEncoding)) { | 828 | pthread_mutex_lock(call->mutex_video); |
464 | pthread_mutex_unlock(call->mutex); | 829 | pthread_mutex_unlock(av->mutex); |
465 | LOGGER_WARNING("Call doesn't support encoding video: %d", call_index); | 830 | |
466 | return av_ErrorInvalidState; | 831 | if ( y == NULL || u == NULL || v == NULL ) { |
832 | pthread_mutex_unlock(call->mutex_video); | ||
833 | rc = TOXAV_ERR_SEND_FRAME_NULL; | ||
834 | goto END; | ||
467 | } | 835 | } |
468 | 836 | ||
469 | if (cs_set_video_encoder_resolution(call->cs, input->w, input->h) < 0) { | 837 | if ( vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height) != 0 ) { |
470 | pthread_mutex_unlock(call->mutex); | 838 | pthread_mutex_unlock(call->mutex_video); |
471 | return av_ErrorSettingVideoResolution; | 839 | rc = TOXAV_ERR_SEND_FRAME_INVALID; |
840 | goto END; | ||
472 | } | 841 | } |
473 | 842 | ||
474 | pthread_mutex_lock(call->mutex_encoding_video); | 843 | { /* Encode */ |
475 | pthread_mutex_unlock(call->mutex); | 844 | vpx_image_t img; |
476 | 845 | img.w = img.h = img.d_w = img.d_h = 0; | |
477 | int rc = vpx_codec_encode(&call->cs->v_encoder, input, call->cs->frame_counter, 1, 0, MAX_ENCODE_TIME_US); | 846 | vpx_img_alloc(&img, VPX_IMG_FMT_VPXI420, width, height, 1); |
478 | 847 | ||
479 | if ( rc != VPX_CODEC_OK) { | 848 | /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." |
480 | LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(rc)); | 849 | * http://fourcc.org/yuv.php#IYUV |
481 | pthread_mutex_unlock(call->mutex_encoding_video); | 850 | */ |
482 | return av_ErrorEncodingVideo; | 851 | memcpy(img.planes[VPX_PLANE_Y], y, width * height); |
852 | memcpy(img.planes[VPX_PLANE_U], u, (width/2) * (height/2)); | ||
853 | memcpy(img.planes[VPX_PLANE_V], v, (width/2) * (height/2)); | ||
854 | |||
855 | int vrc = vpx_codec_encode(call->video.second->encoder, &img, | ||
856 | call->video.second->frame_counter, 1, 0, MAX_ENCODE_TIME_US); | ||
857 | |||
858 | vpx_img_free(&img); | ||
859 | if ( vrc != VPX_CODEC_OK) { | ||
860 | pthread_mutex_unlock(call->mutex_video); | ||
861 | LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(vrc)); | ||
862 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
863 | goto END; | ||
864 | } | ||
483 | } | 865 | } |
484 | 866 | ||
485 | ++call->cs->frame_counter; | 867 | ++call->video.second->frame_counter; |
486 | 868 | ||
487 | vpx_codec_iter_t iter = NULL; | 869 | { /* Split and send */ |
488 | const vpx_codec_cx_pkt_t *pkt; | 870 | vpx_codec_iter_t iter = NULL; |
489 | int copied = 0; | 871 | const vpx_codec_cx_pkt_t *pkt; |
490 | 872 | ||
491 | while ( (pkt = vpx_codec_get_cx_data(&call->cs->v_encoder, &iter)) ) { | 873 | vc_init_video_splitter_cycle(call->video.second); |
492 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { | 874 | |
493 | if ( copied + pkt->data.frame.sz > dest_max ) { | 875 | while ( (pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter)) ) { |
494 | pthread_mutex_unlock(call->mutex_encoding_video); | 876 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { |
495 | return av_ErrorPacketTooLarge; | 877 | int parts = vc_update_video_splitter_cycle(call->video.second, pkt->data.frame.buf, |
878 | pkt->data.frame.sz); | ||
879 | |||
880 | if (parts < 0) /* Should never happen though */ | ||
881 | continue; | ||
882 | |||
883 | uint16_t part_size; | ||
884 | const uint8_t *iter; | ||
885 | |||
886 | int i; | ||
887 | for (i = 0; i < parts; i++) { | ||
888 | iter = vc_iterate_split_video_frame(call->video.second, &part_size); | ||
889 | |||
890 | if (rtp_send_data(call->video.first, iter, part_size, false) < 0) { | ||
891 | pthread_mutex_unlock(call->mutex_video); | ||
892 | LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno)); | ||
893 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
894 | goto END; | ||
895 | } | ||
896 | } | ||
496 | } | 897 | } |
497 | |||
498 | memcpy(dest + copied, pkt->data.frame.buf, pkt->data.frame.sz); | ||
499 | copied += pkt->data.frame.sz; | ||
500 | } | 898 | } |
501 | } | 899 | } |
502 | 900 | ||
503 | pthread_mutex_unlock(call->mutex_encoding_video); | 901 | if (ba_shoud_send_dummy(&call->vba)) { |
504 | return copied; | 902 | if ( vc_reconfigure_test_encoder(call->video.second, call->vba.bit_rate * 1000, width, height) != 0 ) { |
903 | pthread_mutex_unlock(call->mutex_video); | ||
904 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
905 | goto END; | ||
906 | } | ||
907 | |||
908 | /* FIXME use the same image as before */ | ||
909 | vpx_image_t img; | ||
910 | img.w = img.h = img.d_w = img.d_h = 0; | ||
911 | vpx_img_alloc(&img, VPX_IMG_FMT_VPXI420, width, height, 1); | ||
912 | |||
913 | /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." | ||
914 | * http://fourcc.org/yuv.php#IYUV | ||
915 | */ | ||
916 | memcpy(img.planes[VPX_PLANE_Y], y, width * height); | ||
917 | memcpy(img.planes[VPX_PLANE_U], u, (width/2) * (height/2)); | ||
918 | memcpy(img.planes[VPX_PLANE_V], v, (width/2) * (height/2)); | ||
919 | |||
920 | int vrc = vpx_codec_encode(call->video.second->test_encoder, &img, | ||
921 | call->video.second->test_frame_counter, 1, 0, MAX_ENCODE_TIME_US); | ||
922 | |||
923 | vpx_img_free(&img); | ||
924 | if ( vrc != VPX_CODEC_OK) { | ||
925 | pthread_mutex_unlock(call->mutex_video); | ||
926 | LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(vrc)); | ||
927 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | ||
928 | goto END; | ||
929 | } | ||
930 | |||
931 | call->video.second->test_frame_counter++; | ||
932 | |||
933 | vpx_codec_iter_t iter = NULL; | ||
934 | const vpx_codec_cx_pkt_t *pkt; | ||
935 | |||
936 | /* Send the encoded data as dummy packets */ | ||
937 | while ( (pkt = vpx_codec_get_cx_data(call->video.second->test_encoder, &iter)) ) { | ||
938 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { | ||
939 | |||
940 | int parts = pkt->data.frame.sz / 1300; | ||
941 | int i; | ||
942 | for (i = 0; i < parts; i++) { | ||
943 | if (rtp_send_data(call->video.first, pkt->data.frame.buf + i * 1300, 1300, true) < 0) { | ||
944 | pthread_mutex_unlock(call->mutex_video); | ||
945 | LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno)); | ||
946 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
947 | goto END; | ||
948 | } | ||
949 | } | ||
950 | |||
951 | if (pkt->data.frame.sz % 1300) { | ||
952 | if (rtp_send_data(call->video.first, pkt->data.frame.buf + parts * 1300, pkt->data.frame.sz % 1300, true) < 0) { | ||
953 | pthread_mutex_unlock(call->mutex_video); | ||
954 | LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno)); | ||
955 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
956 | goto END; | ||
957 | } | ||
958 | } | ||
959 | } | ||
960 | } | ||
961 | |||
962 | if (call->vba.end_time == (uint64_t) ~0) | ||
963 | call->vba.end_time = current_time_monotonic() + BITRATE_CHANGE_TESTING_TIME_MS; | ||
964 | } | ||
965 | |||
966 | pthread_mutex_unlock(call->mutex_video); | ||
967 | |||
968 | END: | ||
969 | if (error) | ||
970 | *error = rc; | ||
971 | |||
972 | return rc == TOXAV_ERR_SEND_FRAME_OK; | ||
505 | } | 973 | } |
506 | 974 | ||
507 | int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size) | 975 | void toxav_callback_audio_receive_frame(ToxAV* av, toxav_audio_receive_frame_cb* function, void* user_data) |
508 | { | 976 | { |
509 | 977 | pthread_mutex_lock(av->mutex); | |
510 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 978 | av->acb.first = function; |
511 | LOGGER_WARNING("Invalid call index: %d", call_index); | 979 | av->acb.second = user_data; |
512 | return av_ErrorNoCall; | 980 | pthread_mutex_unlock(av->mutex); |
513 | } | ||
514 | |||
515 | ToxAvCall *call = &av->calls[call_index]; | ||
516 | pthread_mutex_lock(call->mutex); | ||
517 | |||
518 | |||
519 | if (!call->active) { | ||
520 | pthread_mutex_unlock(call->mutex); | ||
521 | LOGGER_WARNING("Action on inactive call: %d", call_index); | ||
522 | return av_ErrorInvalidState; | ||
523 | } | ||
524 | |||
525 | int rc = toxav_send_rtp_payload(av, call, av_TypeVideo, frame, frame_size); | ||
526 | pthread_mutex_unlock(call->mutex); | ||
527 | |||
528 | return rc; | ||
529 | } | 981 | } |
530 | 982 | ||
531 | int toxav_prepare_audio_frame ( ToxAv *av, | 983 | void toxav_callback_video_receive_frame(ToxAV* av, toxav_video_receive_frame_cb* function, void* user_data) |
532 | int32_t call_index, | ||
533 | uint8_t *dest, | ||
534 | int dest_max, | ||
535 | const int16_t *frame, | ||
536 | int frame_size) | ||
537 | { | 984 | { |
538 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 985 | pthread_mutex_lock(av->mutex); |
539 | LOGGER_WARNING("Action on nonexisting call: %d", call_index); | 986 | av->vcb.first = function; |
540 | return av_ErrorNoCall; | 987 | av->vcb.second = user_data; |
541 | } | 988 | pthread_mutex_unlock(av->mutex); |
989 | } | ||
542 | 990 | ||
543 | ToxAvCall *call = &av->calls[call_index]; | ||
544 | pthread_mutex_lock(call->mutex); | ||
545 | 991 | ||
546 | if (!call->active) { | 992 | /******************************************************************************* |
547 | pthread_mutex_unlock(call->mutex); | 993 | * |
548 | LOGGER_WARNING("Action on inactive call: %d", call_index); | 994 | * :: Internal |
549 | return av_ErrorInvalidState; | 995 | * |
996 | ******************************************************************************/ | ||
997 | int callback_invite(void* toxav_inst, MSICall* call) | ||
998 | { | ||
999 | ToxAV* toxav = toxav_inst; | ||
1000 | pthread_mutex_lock(toxav->mutex); | ||
1001 | |||
1002 | ToxAVCall* av_call = call_new(toxav, call->friend_number, NULL); | ||
1003 | if (av_call == NULL) { | ||
1004 | LOGGER_WARNING("Failed to initialize call..."); | ||
1005 | pthread_mutex_unlock(toxav->mutex); | ||
1006 | return -1; | ||
550 | } | 1007 | } |
551 | 1008 | ||
552 | pthread_mutex_lock(call->mutex_encoding_audio); | 1009 | call->av_call = av_call; |
553 | pthread_mutex_unlock(call->mutex); | 1010 | av_call->msi_call = call; |
554 | int32_t rc = opus_encode(call->cs->audio_encoder, frame, frame_size, dest, dest_max); | 1011 | |
555 | pthread_mutex_unlock(call->mutex_encoding_audio); | 1012 | if (toxav->ccb.first) |
556 | 1013 | toxav->ccb.first(toxav, call->friend_number, call->peer_capabilities & msi_CapSAudio, | |
557 | if (rc < 0) { | 1014 | call->peer_capabilities & msi_CapSVideo, toxav->ccb.second); |
558 | LOGGER_ERROR("Failed to encode payload: %s\n", opus_strerror(rc)); | 1015 | else { |
559 | return av_ErrorEncodingAudio; | 1016 | /* No handler to capture the call request, send failure */ |
1017 | pthread_mutex_unlock(toxav->mutex); | ||
1018 | return -1; | ||
560 | } | 1019 | } |
561 | 1020 | ||
562 | return rc; | 1021 | pthread_mutex_unlock(toxav->mutex); |
1022 | return 0; | ||
563 | } | 1023 | } |
564 | 1024 | ||
565 | int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *data, unsigned int size) | 1025 | int callback_start(void* toxav_inst, MSICall* call) |
566 | { | 1026 | { |
567 | if (CALL_INVALID_INDEX(call_index, av->msi_session->max_calls)) { | 1027 | ToxAV* toxav = toxav_inst; |
568 | LOGGER_WARNING("Action on nonexisting call: %d", call_index); | 1028 | pthread_mutex_lock(toxav->mutex); |
569 | return av_ErrorNoCall; | 1029 | |
1030 | ToxAVCall* av_call = call_get(toxav, call->friend_number); | ||
1031 | |||
1032 | if (av_call == NULL) { | ||
1033 | /* Should this ever happen? */ | ||
1034 | pthread_mutex_unlock(toxav->mutex); | ||
1035 | return -1; | ||
570 | } | 1036 | } |
571 | 1037 | ||
572 | ToxAvCall *call = &av->calls[call_index]; | 1038 | if (!call_prepare_transmission(av_call)) { |
573 | pthread_mutex_lock(call->mutex); | 1039 | callback_error(toxav_inst, call); |
574 | 1040 | pthread_mutex_unlock(toxav->mutex); | |
575 | 1041 | return -1; | |
576 | if (!call->active) { | ||
577 | pthread_mutex_unlock(call->mutex); | ||
578 | LOGGER_WARNING("Action on inactive call: %d", call_index); | ||
579 | return av_ErrorInvalidState; | ||
580 | } | 1042 | } |
581 | 1043 | ||
582 | int rc = toxav_send_rtp_payload(av, call, av_TypeAudio, data, size); | 1044 | if (!invoke_call_state(toxav, call->friend_number, call->peer_capabilities)) { |
583 | pthread_mutex_unlock(call->mutex); | 1045 | callback_error(toxav_inst, call); |
584 | return rc; | 1046 | pthread_mutex_unlock(toxav->mutex); |
1047 | return -1; | ||
1048 | } | ||
1049 | |||
1050 | pthread_mutex_unlock(toxav->mutex); | ||
1051 | return 0; | ||
585 | } | 1052 | } |
586 | 1053 | ||
587 | int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ) | 1054 | int callback_end(void* toxav_inst, MSICall* call) |
588 | { | 1055 | { |
589 | if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || | 1056 | ToxAV* toxav = toxav_inst; |
590 | !av->msi_session->calls[call_index] || av->msi_session->calls[call_index]->peer_count <= peer ) | 1057 | pthread_mutex_lock(toxav->mutex); |
591 | return av_ErrorNoCall; | 1058 | |
592 | 1059 | invoke_call_state(toxav, call->friend_number, TOXAV_CALL_STATE_FINISHED); | |
593 | *dest = *toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[peer]); | 1060 | |
594 | return av_ErrorNone; | 1061 | call_kill_transmission(call->av_call); |
1062 | call_remove(call->av_call); | ||
1063 | |||
1064 | pthread_mutex_unlock(toxav->mutex); | ||
1065 | return 0; | ||
595 | } | 1066 | } |
596 | 1067 | ||
597 | int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ) | 1068 | int callback_error(void* toxav_inst, MSICall* call) |
598 | { | 1069 | { |
599 | if ( peer < 0 || CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] | 1070 | ToxAV* toxav = toxav_inst; |
600 | || av->msi_session->calls[call_index]->peer_count <= peer ) | 1071 | pthread_mutex_lock(toxav->mutex); |
601 | return av_ErrorNoCall; | 1072 | |
602 | 1073 | invoke_call_state(toxav, call->friend_number, TOXAV_CALL_STATE_ERROR); | |
603 | return av->msi_session->calls[call_index]->peers[peer]; | 1074 | |
1075 | call_kill_transmission(call->av_call); | ||
1076 | call_remove(call->av_call); | ||
1077 | |||
1078 | pthread_mutex_unlock(toxav->mutex); | ||
1079 | return 0; | ||
604 | } | 1080 | } |
605 | 1081 | ||
606 | ToxAvCallState toxav_get_call_state(ToxAv *av, int32_t call_index) | 1082 | int callback_capabilites(void* toxav_inst, MSICall* call) |
607 | { | 1083 | { |
608 | if ( CALL_INVALID_INDEX(call_index, av->msi_session->max_calls) || !av->msi_session->calls[call_index] ) | 1084 | ToxAV* toxav = toxav_inst; |
609 | return av_CallNonExistent; | 1085 | pthread_mutex_lock(toxav->mutex); |
610 | 1086 | ||
611 | return av->msi_session->calls[call_index]->state; | 1087 | invoke_call_state(toxav, call->friend_number, call->peer_capabilities); |
612 | 1088 | ||
1089 | pthread_mutex_unlock(toxav->mutex); | ||
1090 | return 0; | ||
613 | } | 1091 | } |
614 | 1092 | ||
615 | int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ) | 1093 | bool audio_bit_rate_invalid(uint32_t bit_rate) |
616 | { | 1094 | { |
617 | return av->calls[call_index].cs ? av->calls[call_index].cs->capabilities & (CSCapabilities) capability : 0; | 1095 | /* Opus RFC 6716 section-2.1.1 dictates the following: |
618 | /* 0 is error here */ | 1096 | * Opus supports all bit rates from 6 kbit/s to 510 kbit/s. |
1097 | */ | ||
1098 | return bit_rate < 6 || bit_rate > 510; | ||
619 | } | 1099 | } |
620 | 1100 | ||
621 | Tox *toxav_get_tox(ToxAv *av) | 1101 | bool video_bit_rate_invalid(uint32_t bit_rate) |
622 | { | 1102 | { |
623 | return (Tox *)av->messenger; | 1103 | (void) bit_rate; |
1104 | /* TODO: If anyone knows the answer to this one please fill it up */ | ||
1105 | return false; | ||
624 | } | 1106 | } |
625 | 1107 | ||
626 | int toxav_get_active_count(ToxAv *av) | 1108 | bool invoke_call_state(ToxAV* av, uint32_t friend_number, uint32_t state) |
627 | { | 1109 | { |
628 | if (!av) return -1; | 1110 | if (av->scb.first) |
629 | 1111 | av->scb.first(av, friend_number, state, av->scb.second); | |
630 | int rc = 0, i = 0; | 1112 | else |
631 | 1113 | return false; | |
632 | for (; i < av->max_calls; i++) { | 1114 | return true; |
633 | pthread_mutex_lock(av->calls[i].mutex); | 1115 | } |
634 | |||
635 | if (av->calls[i].active) rc++; | ||
636 | 1116 | ||
637 | pthread_mutex_unlock(av->calls[i].mutex); | 1117 | ToxAVCall* call_new(ToxAV* av, uint32_t friend_number, TOXAV_ERR_CALL* error) |
1118 | { | ||
1119 | /* Assumes mutex locked */ | ||
1120 | TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK; | ||
1121 | ToxAVCall* call = NULL; | ||
1122 | |||
1123 | if (m_friend_exists(av->m, friend_number) == 0) { | ||
1124 | rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND; | ||
1125 | goto END; | ||
1126 | } | ||
1127 | |||
1128 | if (m_get_friend_connectionstatus(av->m, friend_number) < 1) { | ||
1129 | rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED; | ||
1130 | goto END; | ||
1131 | } | ||
1132 | |||
1133 | if (call_get(av, friend_number) != NULL) { | ||
1134 | rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL; | ||
1135 | goto END; | ||
638 | } | 1136 | } |
1137 | |||
1138 | |||
1139 | call = calloc(sizeof(ToxAVCall), 1); | ||
1140 | |||
1141 | if (call == NULL) { | ||
1142 | rc = TOXAV_ERR_CALL_MALLOC; | ||
1143 | goto END; | ||
1144 | } | ||
1145 | |||
1146 | call->av = av; | ||
1147 | call->friend_number = friend_number; | ||
1148 | |||
1149 | if (av->calls == NULL) { /* Creating */ | ||
1150 | av->calls = calloc (sizeof(ToxAVCall*), friend_number + 1); | ||
1151 | |||
1152 | if (av->calls == NULL) { | ||
1153 | free(call); | ||
1154 | call = NULL; | ||
1155 | rc = TOXAV_ERR_CALL_MALLOC; | ||
1156 | goto END; | ||
1157 | } | ||
1158 | |||
1159 | av->calls_tail = av->calls_head = friend_number; | ||
1160 | |||
1161 | } else if (av->calls_tail < friend_number) { /* Appending */ | ||
1162 | void* tmp = realloc(av->calls, sizeof(ToxAVCall*) * friend_number + 1); | ||
1163 | |||
1164 | if (tmp == NULL) { | ||
1165 | free(call); | ||
1166 | call = NULL; | ||
1167 | rc = TOXAV_ERR_CALL_MALLOC; | ||
1168 | goto END; | ||
1169 | } | ||
1170 | |||
1171 | av->calls = tmp; | ||
1172 | |||
1173 | /* Set fields in between to null */ | ||
1174 | uint32_t i = av->calls_tail + 1; | ||
1175 | for (; i < friend_number; i ++) | ||
1176 | av->calls[i] = NULL; | ||
1177 | |||
1178 | call->prev = av->calls[av->calls_tail]; | ||
1179 | av->calls[av->calls_tail]->next = call; | ||
1180 | |||
1181 | av->calls_tail = friend_number; | ||
1182 | |||
1183 | } else if (av->calls_head > friend_number) { /* Inserting at front */ | ||
1184 | call->next = av->calls[av->calls_head]; | ||
1185 | av->calls[av->calls_head]->prev = call; | ||
1186 | av->calls_head = friend_number; | ||
1187 | } | ||
1188 | |||
1189 | av->calls[friend_number] = call; | ||
1190 | |||
1191 | END: | ||
1192 | if (error) | ||
1193 | *error = rc; | ||
1194 | |||
1195 | return call; | ||
1196 | } | ||
639 | 1197 | ||
640 | return rc; | 1198 | ToxAVCall* call_get(ToxAV* av, uint32_t friend_number) |
1199 | { | ||
1200 | /* Assumes mutex locked */ | ||
1201 | if (av->calls == NULL || av->calls_tail < friend_number) | ||
1202 | return NULL; | ||
1203 | |||
1204 | return av->calls[friend_number]; | ||
641 | } | 1205 | } |
642 | 1206 | ||
643 | /* Create a new toxav group. | 1207 | ToxAVCall* call_remove(ToxAVCall* call) |
644 | * | ||
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 | { | 1208 | { |
656 | Messenger *m = tox; | 1209 | if (call == NULL) |
657 | return add_av_groupchat(m->group_chat_object, audio_callback, userdata); | 1210 | return NULL; |
1211 | |||
1212 | uint32_t friend_number = call->friend_number; | ||
1213 | ToxAV* av = call->av; | ||
1214 | |||
1215 | ToxAVCall* prev = call->prev; | ||
1216 | ToxAVCall* next = call->next; | ||
1217 | |||
1218 | free(call); | ||
1219 | |||
1220 | if (prev) | ||
1221 | prev->next = next; | ||
1222 | else if (next) | ||
1223 | av->calls_head = next->friend_number; | ||
1224 | else goto CLEAR; | ||
1225 | |||
1226 | if (next) | ||
1227 | next->prev = prev; | ||
1228 | else if (prev) | ||
1229 | av->calls_tail = prev->friend_number; | ||
1230 | else goto CLEAR; | ||
1231 | |||
1232 | av->calls[friend_number] = NULL; | ||
1233 | return next; | ||
1234 | |||
1235 | CLEAR: | ||
1236 | av->calls_head = av->calls_tail = 0; | ||
1237 | free(av->calls); | ||
1238 | av->calls = NULL; | ||
1239 | |||
1240 | return NULL; | ||
658 | } | 1241 | } |
659 | 1242 | ||
660 | /* Join a AV group (you need to have been invited first.) | 1243 | bool call_prepare_transmission(ToxAVCall* call) |
661 | * | ||
662 | * returns group number on success | ||
663 | * returns -1 on failure. | ||
664 | * | ||
665 | * Audio data callback format (same as the one for toxav_add_av_groupchat()): | ||
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) | ||
667 | * | ||
668 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
669 | */ | ||
670 | int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, | ||
671 | void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), | ||
672 | void *userdata) | ||
673 | { | 1244 | { |
674 | Messenger *m = tox; | 1245 | /* Assumes mutex locked */ |
675 | return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata); | 1246 | |
1247 | if (call == NULL) | ||
1248 | return false; | ||
1249 | |||
1250 | ToxAV* av = call->av; | ||
1251 | |||
1252 | if (!av->acb.first && !av->vcb.first) | ||
1253 | /* It makes no sense to have CSession without callbacks */ | ||
1254 | return false; | ||
1255 | |||
1256 | if (call->active) { | ||
1257 | LOGGER_WARNING("Call already active!\n"); | ||
1258 | return true; | ||
1259 | } | ||
1260 | |||
1261 | if (create_recursive_mutex(call->mutex_audio) != 0) | ||
1262 | return false; | ||
1263 | |||
1264 | if (create_recursive_mutex(call->mutex_video) != 0) | ||
1265 | goto FAILURE_3; | ||
1266 | |||
1267 | if (create_recursive_mutex(call->mutex) != 0) | ||
1268 | goto FAILURE_2; | ||
1269 | |||
1270 | |||
1271 | { /* Prepare audio */ | ||
1272 | call->audio.second = ac_new(av, call->friend_number, av->acb.first, av->acb.second); | ||
1273 | call->audio.first = rtp_new(rtp_TypeAudio, av->m, call->friend_number, call->audio.second, ac_queue_message); | ||
1274 | |||
1275 | if ( !call->audio.first || !call->audio.second ) { | ||
1276 | LOGGER_ERROR("Error while starting audio!\n"); | ||
1277 | goto FAILURE; | ||
1278 | } | ||
1279 | } | ||
1280 | |||
1281 | { /* Prepare video */ | ||
1282 | call->video.second = vc_new(av, call->friend_number, av->vcb.first, av->vcb.second, call->msi_call->peer_vfpsz); | ||
1283 | call->video.first = rtp_new(rtp_TypeVideo, av->m, call->friend_number, call->video.second, vc_queue_message); | ||
1284 | |||
1285 | if ( !call->video.first || !call->video.second ) { | ||
1286 | LOGGER_ERROR("Error while starting video!\n"); | ||
1287 | goto FAILURE; | ||
1288 | } | ||
1289 | } | ||
1290 | |||
1291 | call->active = 1; | ||
1292 | return true; | ||
1293 | |||
1294 | FAILURE: | ||
1295 | rtp_kill(call->audio.first); | ||
1296 | ac_kill(call->audio.second); | ||
1297 | call->audio.first = NULL; | ||
1298 | call->audio.second = NULL; | ||
1299 | rtp_kill(call->video.first); | ||
1300 | vc_kill(call->video.second); | ||
1301 | call->video.first = NULL; | ||
1302 | call->video.second = NULL; | ||
1303 | pthread_mutex_destroy(call->mutex); | ||
1304 | FAILURE_2: | ||
1305 | pthread_mutex_destroy(call->mutex_video); | ||
1306 | FAILURE_3: | ||
1307 | pthread_mutex_destroy(call->mutex_audio); | ||
1308 | return false; | ||
676 | } | 1309 | } |
677 | 1310 | ||
678 | /* Send audio to the group chat. | 1311 | void call_kill_transmission(ToxAVCall* call) |
679 | * | ||
680 | * return 0 on success. | ||
681 | * return -1 on failure. | ||
682 | * | ||
683 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | ||
684 | * | ||
685 | * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) | ||
686 | * Valid number of channels are 1 or 2. | ||
687 | * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. | ||
688 | * | ||
689 | * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 | ||
690 | */ | ||
691 | int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, | ||
692 | unsigned int sample_rate) | ||
693 | { | 1312 | { |
694 | Messenger *m = tox; | 1313 | if (call == NULL || call->active == 0) |
695 | return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate); | 1314 | return; |
1315 | |||
1316 | call->active = 0; | ||
1317 | |||
1318 | pthread_mutex_lock(call->mutex_audio); | ||
1319 | pthread_mutex_unlock(call->mutex_audio); | ||
1320 | pthread_mutex_lock(call->mutex_video); | ||
1321 | pthread_mutex_unlock(call->mutex_video); | ||
1322 | pthread_mutex_lock(call->mutex); | ||
1323 | pthread_mutex_unlock(call->mutex); | ||
1324 | |||
1325 | rtp_kill(call->audio.first); | ||
1326 | ac_kill(call->audio.second); | ||
1327 | call->audio.first = NULL; | ||
1328 | call->audio.second = NULL; | ||
1329 | |||
1330 | rtp_kill(call->video.first); | ||
1331 | vc_kill(call->video.second); | ||
1332 | call->video.first = NULL; | ||
1333 | call->video.second = NULL; | ||
1334 | |||
1335 | pthread_mutex_destroy(call->mutex_audio); | ||
1336 | pthread_mutex_destroy(call->mutex_video); | ||
1337 | pthread_mutex_destroy(call->mutex); | ||
696 | } | 1338 | } |
697 | 1339 | ||
1340 | void ba_set(ToxAvBitrateAdapter* ba, uint32_t bit_rate) | ||
1341 | { | ||
1342 | ba->bit_rate = bit_rate; | ||
1343 | ba->next_send = current_time_monotonic(); | ||
1344 | ba->end_time = ~0; | ||
1345 | ba->next_send_interval = 1000; | ||
1346 | ba->active = true; | ||
1347 | } | ||
1348 | |||
1349 | bool ba_shoud_send_dummy(ToxAvBitrateAdapter* ba) | ||
1350 | { | ||
1351 | if (!ba->active || ba->next_send > current_time_monotonic()) | ||
1352 | return false; | ||
1353 | |||
1354 | ba->next_send_interval *= 0.8; | ||
1355 | ba->next_send = current_time_monotonic() + ba->next_send_interval; | ||
1356 | |||
1357 | return true; | ||
1358 | } \ No newline at end of file | ||
diff --git a/toxav/toxav.h b/toxav/toxav.h index 7285f45c..ffe9fbb9 100644 --- a/toxav/toxav.h +++ b/toxav/toxav.h | |||
@@ -1,329 +1,638 @@ | |||
1 | /** toxav.h | 1 | /* toxav.h |
2 | * | ||
3 | * Copyright (C) 2013-2015 Tox project All Rights Reserved. | ||
2 | * | 4 | * |
3 | * Copyright (C) 2013 Tox project All Rights Reserved. | 5 | * This file is part of Tox. |
4 | * | 6 | * |
5 | * This file is part of Tox. | 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. | ||
6 | * | 11 | * |
7 | * Tox is free software: you can redistribute it and/or modify | 12 | * Tox is distributed in the hope that it will be useful, |
8 | * it under the terms of the GNU General Public License as published by | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | * the Free Software Foundation, either version 3 of the License, or | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | * (at your option) any later version. | 15 | * GNU General Public License for more details. |
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 | * | 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 | */ | 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 `tox_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 synchronisation 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 | */ | ||
62 | /** | ||
63 | * External Tox type. | ||
64 | */ | ||
40 | #ifndef TOX_DEFINED | 65 | #ifndef TOX_DEFINED |
41 | #define TOX_DEFINED | 66 | #define TOX_DEFINED |
42 | typedef struct Tox Tox; | 67 | typedef struct Tox Tox; |
43 | #endif | 68 | #endif /* TOX_DEFINED */ |
44 | |||
45 | #define RTP_PAYLOAD_SIZE 65535 | ||
46 | |||
47 | |||
48 | /** | ||
49 | * Callbacks ids that handle the call states. | ||
50 | */ | ||
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 | 69 | ||
65 | /** | 70 | /** |
66 | * Call type identifier. | 71 | * ToxAV. |
67 | */ | 72 | */ |
68 | typedef enum { | ||
69 | av_TypeAudio = 192, | ||
70 | av_TypeVideo | ||
71 | } ToxAvCallType; | ||
72 | |||
73 | |||
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 | |||
83 | /** | 73 | /** |
84 | * Error indicators. Values under -20 are reserved for toxcore. | 74 | * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox |
85 | */ | 75 | * instance, and Tox instance can have only one ToxAV instance. One must make |
86 | typedef enum { | 76 | * sure to close ToxAV instance prior closing Tox instance otherwise undefined |
87 | av_ErrorNone = 0, | 77 | * behaviour occurs. Upon closing of ToxAV instance, all active calls will be |
88 | av_ErrorUnknown = -1, /* Unknown error */ | 78 | * forcibly terminated without notifying peers. |
89 | av_ErrorNoCall = -20, /* Trying to perform call action while not in a call */ | 79 | * |
90 | av_ErrorInvalidState = -21, /* Trying to perform call action while in invalid state*/ | 80 | */ |
91 | av_ErrorAlreadyInCallWithPeer = -22, /* Trying to call peer when already in a call with peer */ | 81 | #ifndef TOXAV_DEFINED |
92 | av_ErrorReachedCallLimit = -23, /* Cannot handle more calls */ | 82 | #define TOXAV_DEFINED |
93 | av_ErrorInitializingCodecs = -30, /* Failed creating CSSession */ | 83 | typedef struct ToxAV ToxAV; |
94 | av_ErrorSettingVideoResolution = -31, /* Error setting resolution */ | 84 | #endif /* TOXAV_DEFINED */ |
95 | av_ErrorSettingVideoBitrate = -32, /* Error setting bitrate */ | 85 | /******************************************************************************* |
96 | av_ErrorSplittingVideoPayload = -33, /* Error splitting video payload */ | 86 | * |
97 | av_ErrorEncodingVideo = -34, /* vpx_codec_encode failed */ | 87 | * :: API version |
98 | av_ErrorEncodingAudio = -35, /* opus_encode failed */ | 88 | * |
99 | av_ErrorSendingPayload = -40, /* Sending lossy packet failed */ | 89 | ******************************************************************************/ |
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 | |||
107 | /** | 90 | /** |
108 | * Locally supported capabilities. | 91 | * The major version number. Incremented when the API or ABI changes in an |
92 | * incompatible way. | ||
109 | */ | 93 | */ |
110 | typedef enum { | 94 | #define TOXAV_VERSION_MAJOR 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 | |||
118 | /** | 95 | /** |
119 | * Encoding settings. | 96 | * The minor version number. Incremented when functionality is added without |
97 | * breaking the API or ABI. Set to 0 when the major version number is | ||
98 | * incremented. | ||
120 | */ | 99 | */ |
121 | typedef struct _ToxAvCSettings { | 100 | #define TOXAV_VERSION_MINOR 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 | |||
136 | /** | 101 | /** |
137 | * Start new A/V session. There can only be one session at the time. | 102 | * The patch or revision number. Incremented when bugfixes are applied without |
103 | * changing any functionality or API or ABI. | ||
138 | */ | 104 | */ |
139 | ToxAv *toxav_new(Tox *messenger, int32_t max_calls); | 105 | #define TOXAV_VERSION_PATCH 0u |
140 | |||
141 | /** | 106 | /** |
142 | * Remove A/V session. | 107 | * A macro to check at preprocessing time whether the client code is compatible |
143 | */ | 108 | * with the installed version of ToxAV. |
144 | void toxav_kill(ToxAv *av); | 109 | */ |
145 | 110 | #define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ | |
111 | (TOXAV_VERSION_MAJOR == MAJOR && \ | ||
112 | (TOXAV_VERSION_MINOR > MINOR || \ | ||
113 | (TOXAV_VERSION_MINOR == MINOR && \ | ||
114 | TOXAV_VERSION_PATCH >= PATCH))) | ||
146 | /** | 115 | /** |
147 | * Returns the interval in milliseconds when the next toxav_do() should be called. | 116 | * A macro to make compilation fail if the client code is not compatible with |
148 | * If no call is active at the moment returns 200. | 117 | * the installed version of ToxAV. |
149 | */ | 118 | */ |
150 | uint32_t toxav_do_interval(ToxAv *av); | 119 | #define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ |
151 | 120 | typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] | |
152 | /** | 121 | /** |
153 | * Main loop for the session. Best called right after tox_do(); | 122 | * A convenience macro to call toxav_version_is_compatible with the currently |
123 | * compiling API version. | ||
154 | */ | 124 | */ |
155 | void toxav_do(ToxAv *av); | 125 | #define TOXAV_VERSION_IS_ABI_COMPATIBLE() \ |
156 | 126 | toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH) | |
157 | /** | 127 | /** |
158 | * Register callback for call state. | 128 | * Return the major version number of the library. Can be used to display the |
129 | * ToxAV library version or to check whether the client is compatible with the | ||
130 | * dynamically linked version of ToxAV. | ||
159 | */ | 131 | */ |
160 | void toxav_register_callstate_callback (ToxAv *av, ToxAVCallback cb, ToxAvCallbackID id, void *userdata); | 132 | uint32_t toxav_version_major(void); |
161 | |||
162 | /** | 133 | /** |
163 | * Register callback for audio data. | 134 | * Return the minor version number of the library. |
164 | */ | 135 | */ |
165 | void toxav_register_audio_callback (ToxAv *av, ToxAvAudioCallback cb, void *userdata); | 136 | uint32_t toxav_version_minor(void); |
166 | |||
167 | /** | 137 | /** |
168 | * Register callback for video data. | 138 | * Return the patch number of the library. |
169 | */ | 139 | */ |
170 | void toxav_register_video_callback (ToxAv *av, ToxAvVideoCallback cb, void *userdata); | 140 | uint32_t toxav_version_patch(void); |
171 | |||
172 | /** | 141 | /** |
173 | * Call user. Use its friend_id. | 142 | * Return whether the compiled library version is compatible with the passed |
143 | * version numbers. | ||
174 | */ | 144 | */ |
175 | int toxav_call(ToxAv *av, | 145 | bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); |
176 | int32_t *call_index, | 146 | /******************************************************************************* |
177 | int friend_id, | 147 | * |
178 | const ToxAvCSettings *csettings, | 148 | * :: Creation and destruction |
179 | int ringing_seconds); | 149 | * |
180 | 150 | ******************************************************************************/ | |
151 | typedef enum TOXAV_ERR_NEW { | ||
152 | /** | ||
153 | * The function returned successfully. | ||
154 | */ | ||
155 | TOXAV_ERR_NEW_OK, | ||
156 | /** | ||
157 | * One of the arguments to the function was NULL when it was not expected. | ||
158 | */ | ||
159 | TOXAV_ERR_NEW_NULL, | ||
160 | /** | ||
161 | * Memory allocation failure while trying to allocate structures required for | ||
162 | * the A/V session. | ||
163 | */ | ||
164 | TOXAV_ERR_NEW_MALLOC, | ||
165 | /** | ||
166 | * Attempted to create a second session for the same Tox instance. | ||
167 | */ | ||
168 | TOXAV_ERR_NEW_MULTIPLE, | ||
169 | } TOXAV_ERR_NEW; | ||
181 | /** | 170 | /** |
182 | * Hangup active call. | 171 | * Start new A/V session. There can only be only one session per Tox instance. |
183 | */ | 172 | */ |
184 | int toxav_hangup(ToxAv *av, int32_t call_index); | 173 | ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error); |
185 | |||
186 | /** | 174 | /** |
187 | * Answer incoming call. Pass the csettings that you will use. | 175 | * Releases all resources associated with the A/V session. |
176 | * | ||
177 | * If any calls were ongoing, these will be forcibly terminated without | ||
178 | * notifying peers. After calling this function, no other functions may be | ||
179 | * called and the av pointer becomes invalid. | ||
188 | */ | 180 | */ |
189 | int toxav_answer(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ); | 181 | void toxav_kill(ToxAV *toxAV); |
190 | |||
191 | /** | 182 | /** |
192 | * Reject incoming call. | 183 | * Returns the Tox instance the A/V object was created for. |
193 | */ | 184 | */ |
194 | int toxav_reject(ToxAv *av, int32_t call_index, const char *reason); | 185 | Tox *toxav_get_tox(const ToxAV *toxAV); |
195 | 186 | /******************************************************************************* | |
187 | * | ||
188 | * :: A/V event loop | ||
189 | * | ||
190 | ******************************************************************************/ | ||
196 | /** | 191 | /** |
197 | * Cancel outgoing request. | 192 | * Returns the interval in milliseconds when the next toxav_iterate call should |
193 | * be. If no call is active at the moment, this function returns 200. | ||
198 | */ | 194 | */ |
199 | int toxav_cancel(ToxAv *av, int32_t call_index, int peer_id, const char *reason); | 195 | uint32_t toxav_iteration_interval(const ToxAV *toxAV); |
200 | |||
201 | /** | 196 | /** |
202 | * Notify peer that we are changing codec settings. | 197 | * Main loop for the session. This function needs to be called in intervals of |
203 | */ | 198 | * toxav_iteration_interval() milliseconds. It is best called in the separate |
204 | int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings); | 199 | * thread from tox_iterate. |
205 | 200 | */ | |
201 | void toxav_iterate(ToxAV *toxAV); | ||
202 | /******************************************************************************* | ||
203 | * | ||
204 | * :: Call setup | ||
205 | * | ||
206 | ******************************************************************************/ | ||
207 | typedef enum TOXAV_ERR_CALL { | ||
208 | /** | ||
209 | * The function returned successfully. | ||
210 | */ | ||
211 | TOXAV_ERR_CALL_OK, | ||
212 | /** | ||
213 | * A resource allocation error occurred while trying to create the structures | ||
214 | * required for the call. | ||
215 | */ | ||
216 | TOXAV_ERR_CALL_MALLOC, | ||
217 | /** | ||
218 | * The friend number did not designate a valid friend. | ||
219 | */ | ||
220 | TOXAV_ERR_CALL_FRIEND_NOT_FOUND, | ||
221 | /** | ||
222 | * The friend was valid, but not currently connected. | ||
223 | */ | ||
224 | TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED, | ||
225 | /** | ||
226 | * Attempted to call a friend while already in an audio or video call with | ||
227 | * them. | ||
228 | */ | ||
229 | TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL, | ||
230 | /** | ||
231 | * Audio or video bit rate is invalid. | ||
232 | */ | ||
233 | TOXAV_ERR_CALL_INVALID_BIT_RATE, | ||
234 | } TOXAV_ERR_CALL; | ||
206 | /** | 235 | /** |
207 | * Terminate transmission. Note that transmission will be | 236 | * Call a friend. This will start ringing the friend. |
208 | * terminated without informing remote peer. Usually called when we can't inform peer. | 237 | * |
238 | * It is the client's responsibility to stop ringing after a certain timeout, | ||
239 | * if such behaviour is desired. If the client does not stop ringing, the | ||
240 | * library will not stop until the friend is disconnected. | ||
241 | * | ||
242 | * @param friend_number The friend number of the friend that should be called. | ||
243 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable | ||
244 | * audio sending. | ||
245 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
246 | * video sending. | ||
209 | */ | 247 | */ |
210 | int toxav_stop_call(ToxAv *av, int32_t call_index); | 248 | bool toxav_call(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); |
211 | |||
212 | /** | 249 | /** |
213 | * Allocates transmission data. Must be call before calling toxav_prepare_* and toxav_send_*. | 250 | * The function type for the call callback. |
214 | * Also, it must be called when call is started | 251 | * |
252 | * @param friend_number The friend number from which the call is incoming. | ||
253 | * @param audio_enabled True if friend is sending audio. | ||
254 | * @param video_enabled True if friend is sending video. | ||
215 | */ | 255 | */ |
216 | int toxav_prepare_transmission(ToxAv *av, int32_t call_index, int support_video); | 256 | typedef void toxav_call_cb(ToxAV *toxAV, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data); |
217 | |||
218 | /** | 257 | /** |
219 | * Clears transmission data. Call this at the end of the transmission. | 258 | * Set the callback for the `call` event. Pass NULL to unset. |
259 | * | ||
220 | */ | 260 | */ |
221 | int toxav_kill_transmission(ToxAv *av, int32_t call_index); | 261 | void toxav_callback_call(ToxAV *toxAV, toxav_call_cb *callback, void *user_data); |
222 | 262 | typedef enum TOXAV_ERR_ANSWER { | |
263 | /** | ||
264 | * The function returned successfully. | ||
265 | */ | ||
266 | TOXAV_ERR_ANSWER_OK, | ||
267 | /** | ||
268 | * Failed to initialize codecs for call session. Note that codec initiation | ||
269 | * will fail if there is no receive callback registered for either audio or | ||
270 | * video. | ||
271 | */ | ||
272 | TOXAV_ERR_ANSWER_CODEC_INITIALIZATION, | ||
273 | /** | ||
274 | * The friend number did not designate a valid friend. | ||
275 | */ | ||
276 | TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND, | ||
277 | /** | ||
278 | * The friend was valid, but they are not currently trying to initiate a call. | ||
279 | * This is also returned if this client is already in a call with the friend. | ||
280 | */ | ||
281 | TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING, | ||
282 | /** | ||
283 | * Audio or video bit rate is invalid. | ||
284 | */ | ||
285 | TOXAV_ERR_ANSWER_INVALID_BIT_RATE, | ||
286 | } TOXAV_ERR_ANSWER; | ||
223 | /** | 287 | /** |
224 | * Encode video frame. | 288 | * Accept an incoming call. |
225 | */ | 289 | * |
226 | int toxav_prepare_video_frame ( ToxAv *av, | 290 | * If answering fails for any reason, the call will still be pending and it is |
227 | int32_t call_index, | 291 | * possible to try and answer it later. |
228 | uint8_t *dest, | 292 | * |
229 | int dest_max, | 293 | * @param friend_number The friend number of the friend that is calling. |
230 | vpx_image_t *input); | 294 | * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable |
295 | * audio sending. | ||
296 | * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable | ||
297 | * video sending. | ||
298 | */ | ||
299 | bool toxav_answer(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); | ||
300 | /******************************************************************************* | ||
301 | * | ||
302 | * :: Call state graph | ||
303 | * | ||
304 | ******************************************************************************/ | ||
305 | enum TOXAV_CALL_STATE { | ||
306 | /** | ||
307 | * Set by the AV core if an error occurred on the remote end or if friend | ||
308 | * timed out. This is the final state after which no more state | ||
309 | * transitions can occur for the call. This call state will never be triggered | ||
310 | * in combination with other call states. | ||
311 | */ | ||
312 | TOXAV_CALL_STATE_ERROR = 1, | ||
313 | /** | ||
314 | * The call has finished. This is the final state after which no more state | ||
315 | * transitions can occur for the call. This call state will never be | ||
316 | * triggered in combination with other call states. | ||
317 | */ | ||
318 | TOXAV_CALL_STATE_FINISHED = 2, | ||
319 | /** | ||
320 | * The flag that marks that friend is sending audio. | ||
321 | */ | ||
322 | TOXAV_CALL_STATE_SENDING_A = 4, | ||
323 | /** | ||
324 | * The flag that marks that friend is sending video. | ||
325 | */ | ||
326 | TOXAV_CALL_STATE_SENDING_V = 8, | ||
327 | /** | ||
328 | * The flag that marks that friend is receiving audio. | ||
329 | */ | ||
330 | TOXAV_CALL_STATE_RECEIVING_A = 16, | ||
331 | /** | ||
332 | * The flag that marks that friend is receiving video. | ||
333 | */ | ||
334 | TOXAV_CALL_STATE_RECEIVING_V = 32, | ||
335 | }; | ||
231 | 336 | ||
232 | /** | ||
233 | * Send encoded video packet. | ||
234 | */ | ||
235 | int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, uint32_t frame_size); | ||
236 | 337 | ||
237 | /** | 338 | /** |
238 | * Encode audio frame. | 339 | * The function type for the call_state callback. |
340 | * | ||
341 | * @param friend_number The friend number for which the call state changed. | ||
342 | * @param state The new call state which is guaranteed to be different than | ||
343 | * the previous state. The state is set to 0 when the call is paused. | ||
239 | */ | 344 | */ |
240 | int toxav_prepare_audio_frame ( ToxAv *av, | 345 | typedef void toxav_call_state_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t state, void *user_data); |
241 | int32_t call_index, | ||
242 | uint8_t *dest, | ||
243 | int dest_max, | ||
244 | const int16_t *frame, | ||
245 | int frame_size); | ||
246 | |||
247 | /** | 346 | /** |
248 | * Send encoded audio frame. | 347 | * Set the callback for the `call_state` event. Pass NULL to unset. |
348 | * | ||
249 | */ | 349 | */ |
250 | int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int size); | 350 | void toxav_callback_call_state(ToxAV *toxAV, toxav_call_state_cb *callback, void *user_data); |
251 | 351 | /******************************************************************************* | |
352 | * | ||
353 | * :: Call control | ||
354 | * | ||
355 | ******************************************************************************/ | ||
356 | typedef enum TOXAV_CALL_CONTROL { | ||
357 | /** | ||
358 | * Resume a previously paused call. Only valid if the pause was caused by this | ||
359 | * client, if not, this control is ignored. Not valid before the call is accepted. | ||
360 | */ | ||
361 | TOXAV_CALL_CONTROL_RESUME, | ||
362 | /** | ||
363 | * Put a call on hold. Not valid before the call is accepted. | ||
364 | */ | ||
365 | TOXAV_CALL_CONTROL_PAUSE, | ||
366 | /** | ||
367 | * Reject a call if it was not answered, yet. Cancel a call after it was | ||
368 | * answered. | ||
369 | */ | ||
370 | TOXAV_CALL_CONTROL_CANCEL, | ||
371 | /** | ||
372 | * Request that the friend stops sending audio. Regardless of the friend's | ||
373 | * compliance, this will cause the audio_receive_frame event to stop being | ||
374 | * triggered on receiving an audio frame from the friend. | ||
375 | */ | ||
376 | TOXAV_CALL_CONTROL_MUTE_AUDIO, | ||
377 | /** | ||
378 | * Calling this control will notify client to start sending audio again. | ||
379 | */ | ||
380 | TOXAV_CALL_CONTROL_UNMUTE_AUDIO, | ||
381 | /** | ||
382 | * Request that the friend stops sending video. Regardless of the friend's | ||
383 | * compliance, this will cause the video_receive_frame event to stop being | ||
384 | * triggered on receiving an video frame from the friend. | ||
385 | */ | ||
386 | TOXAV_CALL_CONTROL_HIDE_VIDEO, | ||
387 | /** | ||
388 | * Calling this control will notify client to start sending video again. | ||
389 | */ | ||
390 | TOXAV_CALL_CONTROL_SHOW_VIDEO, | ||
391 | } TOXAV_CALL_CONTROL; | ||
392 | typedef enum TOXAV_ERR_CALL_CONTROL { | ||
393 | /** | ||
394 | * The function returned successfully. | ||
395 | */ | ||
396 | TOXAV_ERR_CALL_CONTROL_OK, | ||
397 | /** | ||
398 | * The friend_number passed did not designate a valid friend. | ||
399 | */ | ||
400 | TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND, | ||
401 | /** | ||
402 | * This client is currently not in a call with the friend. Before the call is | ||
403 | * answered, only CANCEL is a valid control. | ||
404 | */ | ||
405 | TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL, | ||
406 | /** | ||
407 | * Happens if user tried to pause an already paused call or if trying to | ||
408 | * resume a call that is not paused. | ||
409 | */ | ||
410 | TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION, | ||
411 | } TOXAV_ERR_CALL_CONTROL; | ||
252 | /** | 412 | /** |
253 | * Get codec settings from the peer. These were exchanged during call initialization | 413 | * Sends a call control command to a friend. |
254 | * or when peer send us new csettings. | 414 | * |
415 | * @param friend_number The friend number of the friend this client is in a call | ||
416 | * with. | ||
417 | * @param control The control command to send. | ||
418 | * | ||
419 | * @return true on success. | ||
255 | */ | 420 | */ |
256 | int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ); | 421 | bool toxav_call_control(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); |
257 | 422 | /******************************************************************************* | |
423 | * | ||
424 | * :: Controlling bit rates | ||
425 | * | ||
426 | ******************************************************************************/ | ||
427 | typedef enum TOXAV_ERR_SET_BIT_RATE { | ||
428 | /** | ||
429 | * The function returned successfully. | ||
430 | */ | ||
431 | TOXAV_ERR_SET_BIT_RATE_OK, | ||
432 | /** | ||
433 | * The bit rate passed was not one of the supported values. | ||
434 | */ | ||
435 | TOXAV_ERR_SET_BIT_RATE_INVALID, | ||
436 | /** | ||
437 | * The friend_number passed did not designate a valid friend. | ||
438 | */ | ||
439 | TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_FOUND, | ||
440 | /** | ||
441 | * This client is currently not in a call with the friend. | ||
442 | */ | ||
443 | TOXAV_ERR_SET_BIT_RATE_FRIEND_NOT_IN_CALL, | ||
444 | } TOXAV_ERR_SET_BIT_RATE; | ||
258 | /** | 445 | /** |
259 | * Get friend id of peer participating in conversation. | 446 | * The function type for the audio_bit_rate_status callback. |
260 | */ | 447 | * |
261 | int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ); | 448 | * @param friend_number The friend number of the friend for which to set the |
262 | 449 | * audio bit rate. | |
450 | * @param stable Is the stream stable enough to keep the bit rate. | ||
451 | * Upon successful, non forceful, bit rate change, this is set to | ||
452 | * true and 'bit_rate' is set to new bit rate. | ||
453 | * The stable is set to false with bit_rate set to the unstable | ||
454 | * bit rate when either current stream is unstable with said bit rate | ||
455 | * or the non forceful change failed. | ||
456 | * @param bit_rate The bit rate in Kb/sec. | ||
457 | */ | ||
458 | typedef void toxav_audio_bit_rate_status_cb(ToxAV *toxAV, uint32_t friend_number, bool stable, uint32_t bit_rate, void *user_data); | ||
263 | /** | 459 | /** |
264 | * Get current call state. | 460 | * Set the callback for the `audio_bit_rate_status` event. Pass NULL to unset. |
461 | * | ||
265 | */ | 462 | */ |
266 | ToxAvCallState toxav_get_call_state ( ToxAv *av, int32_t call_index ); | 463 | void toxav_callback_audio_bit_rate_status(ToxAV *toxAV, toxav_audio_bit_rate_status_cb *callback, void *user_data); |
267 | |||
268 | /** | 464 | /** |
269 | * Is certain capability supported. Used to determine if encoding/decoding is ready. | 465 | * Set the audio bit rate to be used in subsequent audio frames. If the passed |
270 | */ | 466 | * bit rate is the same as the current bit rate this function will return true |
271 | int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ); | 467 | * without calling a callback. If there is an active non forceful setup with the |
272 | 468 | * passed audio bit rate and the new set request is forceful, the bit rate is | |
469 | * forcefully set and the previous non forceful request is cancelled. The active | ||
470 | * non forceful setup will be canceled in favour of new non forceful setup. | ||
471 | * | ||
472 | * @param friend_number The friend number of the friend for which to set the | ||
473 | * audio bit rate. | ||
474 | * @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable | ||
475 | * audio sending. | ||
476 | * @param force True if the bit rate change is forceful. | ||
477 | * | ||
478 | */ | ||
479 | bool toxav_audio_bit_rate_set(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, bool force, TOXAV_ERR_SET_BIT_RATE *error); | ||
273 | /** | 480 | /** |
274 | * Returns tox reference. | 481 | * The function type for the video_bit_rate_status callback. |
275 | */ | 482 | * |
276 | Tox *toxav_get_tox (ToxAv *av); | 483 | * @param friend_number The friend number of the friend for which to set the |
277 | 484 | * video bit rate. | |
485 | * @param stable Is the stream stable enough to keep the bit rate. | ||
486 | * Upon successful, non forceful, bit rate change, this is set to | ||
487 | * true and 'bit_rate' is set to new bit rate. | ||
488 | * The stable is set to false with bit_rate set to the unstable | ||
489 | * bit rate when either current stream is unstable with said bit rate | ||
490 | * or the non forceful change failed. | ||
491 | * @param bit_rate The bit rate in Kb/sec. | ||
492 | */ | ||
493 | typedef void toxav_video_bit_rate_status_cb(ToxAV *toxAV, uint32_t friend_number, bool stable, uint32_t bit_rate, void *user_data); | ||
278 | /** | 494 | /** |
279 | * Returns number of active calls or -1 on error. | 495 | * Set the callback for the `video_bit_rate_status` event. Pass NULL to unset. |
496 | * | ||
280 | */ | 497 | */ |
281 | int toxav_get_active_count (ToxAv *av); | 498 | void toxav_callback_video_bit_rate_status(ToxAV *toxAV, toxav_video_bit_rate_status_cb *callback, void *user_data); |
282 | 499 | /** | |
283 | /* Create a new toxav group. | 500 | * Set the video bit rate to be used in subsequent video frames. If the passed |
501 | * bit rate is the same as the current bit rate this function will return true | ||
502 | * without calling a callback. If there is an active non forceful setup with the | ||
503 | * passed video bit rate and the new set request is forceful, the bit rate is | ||
504 | * forcefully set and the previous non forceful request is cancelled. The active | ||
505 | * non forceful setup will be canceled in favour of new non forceful setup. | ||
284 | * | 506 | * |
285 | * return group number on success. | 507 | * @param friend_number The friend number of the friend for which to set the |
286 | * return -1 on failure. | 508 | * video bit rate. |
509 | * @param audio_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable | ||
510 | * video sending. | ||
511 | * @param force True if the bit rate change is forceful. | ||
512 | * | ||
513 | */ | ||
514 | bool toxav_video_bit_rate_set(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, bool force, TOXAV_ERR_SET_BIT_RATE *error); | ||
515 | /******************************************************************************* | ||
516 | * | ||
517 | * :: A/V sending | ||
287 | * | 518 | * |
288 | * Audio data callback format: | 519 | ******************************************************************************/ |
289 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | 520 | typedef enum TOXAV_ERR_SEND_FRAME { |
521 | /** | ||
522 | * The function returned successfully. | ||
523 | */ | ||
524 | TOXAV_ERR_SEND_FRAME_OK, | ||
525 | /** | ||
526 | * In case of video, one of Y, U, or V was NULL. In case of audio, the samples | ||
527 | * data pointer was NULL. | ||
528 | */ | ||
529 | TOXAV_ERR_SEND_FRAME_NULL, | ||
530 | /** | ||
531 | * The friend_number passed did not designate a valid friend. | ||
532 | */ | ||
533 | TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND, | ||
534 | /** | ||
535 | * This client is currently not in a call with the friend. | ||
536 | */ | ||
537 | TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL, | ||
538 | /** | ||
539 | * One of the frame parameters was invalid. E.g. the resolution may be too | ||
540 | * small or too large, or the audio sampling rate may be unsupported. | ||
541 | */ | ||
542 | TOXAV_ERR_SEND_FRAME_INVALID, | ||
543 | /** | ||
544 | * Failed to push frame through rtp interface. | ||
545 | */ | ||
546 | TOXAV_ERR_SEND_FRAME_RTP_FAILED, | ||
547 | } TOXAV_ERR_SEND_FRAME; | ||
548 | /** | ||
549 | * Send an audio frame to a friend. | ||
290 | * | 550 | * |
291 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 551 | * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... |
292 | */ | 552 | * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... |
293 | int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, | 553 | * For mono audio, this has no meaning, every sample is subsequent. For stereo, |
294 | unsigned int, void *), void *userdata); | 554 | * this means the expected format is LRLRLR... with samples for left and right |
295 | 555 | * alternating. | |
296 | /* Join a AV group (you need to have been invited first.) | ||
297 | * | 556 | * |
298 | * returns group number on success | 557 | * @param friend_number The friend number of the friend to which to send an |
299 | * returns -1 on failure. | 558 | * audio frame. |
559 | * @param pcm An array of audio samples. The size of this array must be | ||
560 | * sample_count * channels. | ||
561 | * @param sample_count Number of samples in this frame. Valid numbers here are | ||
562 | * ((sample rate) * (audio length) / 1000), where audio length can be | ||
563 | * 2.5, 5, 10, 20, 40 or 60 millseconds. | ||
564 | * @param channels Number of audio channels. Supported values are 1 and 2. | ||
565 | * @param sampling_rate Audio sampling rate used in this frame. Valid sampling | ||
566 | * rates are 8000, 12000, 16000, 24000, or 48000. | ||
567 | */ | ||
568 | bool toxav_audio_send_frame(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error); | ||
569 | /** | ||
570 | * Send a video frame to a friend. | ||
300 | * | 571 | * |
301 | * Audio data callback format (same as the one for toxav_add_av_groupchat()): | 572 | * Y - plane should be of size: height * width |
302 | * audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata) | 573 | * U - plane should be of size: (height/2) * (width/2) |
574 | * V - plane should be of size: (height/2) * (width/2) | ||
303 | * | 575 | * |
304 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 576 | * @param friend_number The friend number of the friend to which to send a video |
305 | */ | 577 | * frame. |
306 | int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length, | 578 | * @param width Width of the frame in pixels. |
307 | void (*audio_callback)(Tox *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata); | 579 | * @param height Height of the frame in pixels. |
308 | 580 | * @param y Y (Luminance) plane data. | |
309 | /* Send audio to the group chat. | 581 | * @param u U (Chroma) plane data. |
582 | * @param v V (Chroma) plane data. | ||
583 | */ | ||
584 | bool toxav_video_send_frame(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error); | ||
585 | /******************************************************************************* | ||
586 | * | ||
587 | * :: A/V receiving | ||
588 | * | ||
589 | ******************************************************************************/ | ||
590 | /** | ||
591 | * The function type for the audio_receive_frame callback. | ||
592 | * | ||
593 | * @param friend_number The friend number of the friend who sent an audio frame. | ||
594 | * @param pcm An array of audio samples (sample_count * channels elements). | ||
595 | * @param sample_count The number of audio samples per channel in the PCM array. | ||
596 | * @param channels Number of audio channels. | ||
597 | * @param sampling_rate Sampling rate used in this frame. | ||
310 | * | 598 | * |
311 | * return 0 on success. | 599 | */ |
312 | * return -1 on failure. | 600 | typedef void toxav_audio_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, void *user_data); |
601 | /** | ||
602 | * Set the callback for the `audio_receive_frame` event. Pass NULL to unset. | ||
313 | * | 603 | * |
314 | * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). | 604 | */ |
605 | void toxav_callback_audio_receive_frame(ToxAV *toxAV, toxav_audio_receive_frame_cb *callback, void *user_data); | ||
606 | /** | ||
607 | * The function type for the video_receive_frame callback. | ||
315 | * | 608 | * |
316 | * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) | 609 | * @param friend_number The friend number of the friend who sent a video frame. |
317 | * Valid number of channels are 1 or 2. | 610 | * @param width Width of the frame in pixels. |
318 | * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. | 611 | * @param height Height of the frame in pixels. |
612 | * @param y | ||
613 | * @param u | ||
614 | * @param v Plane data. | ||
615 | * The size of plane data is derived from width and height where | ||
616 | * Y = MAX(width, abs(ystride)) * height, | ||
617 | * U = MAX(width/2, abs(ustride)) * (height/2) and | ||
618 | * V = MAX(width/2, abs(vstride)) * (height/2). | ||
619 | * @param ystride | ||
620 | * @param ustride | ||
621 | * @param vstride Strides data. Strides represent padding for each plane | ||
622 | * that may or may not be present. You must handle strides in | ||
623 | * your image processing code. Strides are negative if the | ||
624 | * image is bottom-up hence why you MUST abs() it when | ||
625 | * calculating plane buffer size. | ||
626 | */ | ||
627 | typedef void toxav_video_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, int32_t vstride, void *user_data); | ||
628 | /** | ||
629 | * Set the callback for the `video_receive_frame` event. Pass NULL to unset. | ||
319 | * | 630 | * |
320 | * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 | ||
321 | */ | 631 | */ |
322 | int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, | 632 | void toxav_callback_video_receive_frame(ToxAV *toxAV, toxav_video_receive_frame_cb *callback, void *user_data); |
323 | unsigned int sample_rate); | ||
324 | 633 | ||
325 | #ifdef __cplusplus | 634 | #ifdef __cplusplus |
326 | } | 635 | } |
327 | #endif | 636 | #endif |
328 | 637 | ||
329 | #endif /* __TOXAV */ | 638 | #endif /* TOXAV_H */ |
diff --git a/toxav/video.c b/toxav/video.c new file mode 100644 index 00000000..ee49c0a1 --- /dev/null +++ b/toxav/video.c | |||
@@ -0,0 +1,375 @@ | |||
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 | #include <stdlib.h> | ||
23 | #include <assert.h> | ||
24 | |||
25 | #include "video.h" | ||
26 | #include "msi.h" | ||
27 | #include "rtp.h" | ||
28 | |||
29 | #include "../toxcore/logger.h" | ||
30 | #include "../toxcore/network.h" | ||
31 | |||
32 | /* Good quality encode. */ | ||
33 | #define MAX_DECODE_TIME_US 0 | ||
34 | |||
35 | #define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */ | ||
36 | #define VIDEOFRAME_HEADER_SIZE 0x2 | ||
37 | |||
38 | #define VIDEO_DECODE_BUFFER_SIZE 20 | ||
39 | |||
40 | typedef struct { uint16_t size; uint8_t data[]; } Payload; | ||
41 | |||
42 | bool create_video_encoder (vpx_codec_ctx_t* dest, int32_t bit_rate); | ||
43 | |||
44 | |||
45 | VCSession* vc_new(ToxAV* av, uint32_t friend_number, toxav_video_receive_frame_cb* cb, void* cb_data, uint32_t mvfpsz) | ||
46 | { | ||
47 | VCSession *vc = calloc(sizeof(VCSession), 1); | ||
48 | |||
49 | if (!vc) { | ||
50 | LOGGER_WARNING("Allocation failed! Application might misbehave!"); | ||
51 | return NULL; | ||
52 | } | ||
53 | |||
54 | if (create_recursive_mutex(vc->queue_mutex) != 0) { | ||
55 | LOGGER_WARNING("Failed to create recursive mutex!"); | ||
56 | free(vc); | ||
57 | return NULL; | ||
58 | } | ||
59 | |||
60 | if ( !(vc->frame_buf = calloc(MAX_VIDEOFRAME_SIZE, 1)) ) | ||
61 | goto BASE_CLEANUP; | ||
62 | if ( !(vc->split_video_frame = calloc(VIDEOFRAME_PIECE_SIZE + VIDEOFRAME_HEADER_SIZE, 1)) ) | ||
63 | goto BASE_CLEANUP; | ||
64 | if ( !(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE)) ) | ||
65 | goto BASE_CLEANUP; | ||
66 | |||
67 | int rc = vpx_codec_dec_init_ver(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, | ||
68 | NULL, 0, VPX_DECODER_ABI_VERSION); | ||
69 | if ( rc != VPX_CODEC_OK) { | ||
70 | LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); | ||
71 | goto BASE_CLEANUP; | ||
72 | } | ||
73 | |||
74 | if (!create_video_encoder(vc->encoder, 500000)) { | ||
75 | vpx_codec_destroy(vc->decoder); | ||
76 | goto BASE_CLEANUP; | ||
77 | } | ||
78 | if (!create_video_encoder(vc->test_encoder, 500000)) { | ||
79 | vpx_codec_destroy(vc->encoder); | ||
80 | vpx_codec_destroy(vc->decoder); | ||
81 | goto BASE_CLEANUP; | ||
82 | } | ||
83 | |||
84 | vc->linfts = current_time_monotonic(); | ||
85 | vc->lcfd = 60; | ||
86 | vc->vcb.first = cb; | ||
87 | vc->vcb.second = cb_data; | ||
88 | vc->friend_number = friend_number; | ||
89 | vc->peer_video_frame_piece_size = mvfpsz; | ||
90 | vc->av = av; | ||
91 | |||
92 | return vc; | ||
93 | |||
94 | BASE_CLEANUP: | ||
95 | pthread_mutex_destroy(vc->queue_mutex); | ||
96 | rb_free(vc->vbuf_raw); | ||
97 | free(vc->split_video_frame); | ||
98 | free(vc->frame_buf); | ||
99 | free(vc); | ||
100 | return NULL; | ||
101 | } | ||
102 | void vc_kill(VCSession* vc) | ||
103 | { | ||
104 | if (!vc) | ||
105 | return; | ||
106 | |||
107 | vpx_codec_destroy(vc->encoder); | ||
108 | vpx_codec_destroy(vc->test_encoder); | ||
109 | vpx_codec_destroy(vc->decoder); | ||
110 | rb_free(vc->vbuf_raw); | ||
111 | free(vc->split_video_frame); | ||
112 | free(vc->frame_buf); | ||
113 | |||
114 | pthread_mutex_destroy(vc->queue_mutex); | ||
115 | |||
116 | LOGGER_DEBUG("Terminated video handler: %p", vc); | ||
117 | free(vc); | ||
118 | } | ||
119 | void vc_do(VCSession* vc) | ||
120 | { | ||
121 | if (!vc) | ||
122 | return; | ||
123 | |||
124 | Payload *p; | ||
125 | int rc; | ||
126 | |||
127 | pthread_mutex_lock(vc->queue_mutex); | ||
128 | if (rb_read(vc->vbuf_raw, (void**)&p)) { | ||
129 | pthread_mutex_unlock(vc->queue_mutex); | ||
130 | |||
131 | rc = vpx_codec_decode(vc->decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US); | ||
132 | free(p); | ||
133 | |||
134 | if (rc != VPX_CODEC_OK) { | ||
135 | LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc)); | ||
136 | } else { | ||
137 | vpx_codec_iter_t iter = NULL; | ||
138 | vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); | ||
139 | |||
140 | /* Play decoded images */ | ||
141 | for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) { | ||
142 | if (vc->vcb.first) | ||
143 | vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h, | ||
144 | (const uint8_t*)dest->planes[0], (const uint8_t*)dest->planes[1], (const uint8_t*)dest->planes[2], | ||
145 | dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second); | ||
146 | |||
147 | vpx_img_free(dest); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | return; | ||
152 | } | ||
153 | pthread_mutex_unlock(vc->queue_mutex); | ||
154 | } | ||
155 | void vc_init_video_splitter_cycle(VCSession* vc) | ||
156 | { | ||
157 | if (!vc) | ||
158 | return; | ||
159 | |||
160 | vc->split_video_frame[0] = vc->frameid_out++; | ||
161 | vc->split_video_frame[1] = 0; | ||
162 | } | ||
163 | int vc_update_video_splitter_cycle(VCSession* vc, const uint8_t* payload, uint16_t length) | ||
164 | { | ||
165 | if (!vc) | ||
166 | return 0; | ||
167 | |||
168 | vc->processing_video_frame = payload; | ||
169 | vc->processing_video_frame_size = length; | ||
170 | |||
171 | return ((length - 1) / VIDEOFRAME_PIECE_SIZE) + 1; | ||
172 | } | ||
173 | const uint8_t* vc_iterate_split_video_frame(VCSession* vc, uint16_t* size) | ||
174 | { | ||
175 | if (!vc || !size) | ||
176 | return NULL; | ||
177 | |||
178 | if (vc->processing_video_frame_size > VIDEOFRAME_PIECE_SIZE) { | ||
179 | memcpy(vc->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
180 | vc->processing_video_frame, | ||
181 | VIDEOFRAME_PIECE_SIZE); | ||
182 | |||
183 | vc->processing_video_frame += VIDEOFRAME_PIECE_SIZE; | ||
184 | vc->processing_video_frame_size -= VIDEOFRAME_PIECE_SIZE; | ||
185 | |||
186 | *size = VIDEOFRAME_PIECE_SIZE + VIDEOFRAME_HEADER_SIZE; | ||
187 | } else { | ||
188 | memcpy(vc->split_video_frame + VIDEOFRAME_HEADER_SIZE, | ||
189 | vc->processing_video_frame, | ||
190 | vc->processing_video_frame_size); | ||
191 | |||
192 | *size = vc->processing_video_frame_size + VIDEOFRAME_HEADER_SIZE; | ||
193 | } | ||
194 | |||
195 | vc->split_video_frame[1]++; | ||
196 | |||
197 | return vc->split_video_frame; | ||
198 | } | ||
199 | int vc_queue_message(void* vcp, struct RTPMessage_s *msg) | ||
200 | { | ||
201 | /* This function does the reconstruction of video packets. | ||
202 | * See more info about video splitting in docs | ||
203 | */ | ||
204 | if (!vcp || !msg) | ||
205 | return -1; | ||
206 | |||
207 | if ((msg->header->marker_payloadt & 0x7f) == (rtp_TypeVideo + 2) % 128) { | ||
208 | LOGGER_WARNING("Got dummy!"); | ||
209 | rtp_free_msg(msg); | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | if ((msg->header->marker_payloadt & 0x7f) != rtp_TypeVideo % 128) { | ||
214 | LOGGER_WARNING("Invalid payload type!"); | ||
215 | rtp_free_msg(msg); | ||
216 | return -1; | ||
217 | } | ||
218 | |||
219 | VCSession* vc = vcp; | ||
220 | |||
221 | uint8_t *packet = msg->data; | ||
222 | uint32_t packet_size = msg->length; | ||
223 | |||
224 | if (packet_size < VIDEOFRAME_HEADER_SIZE) | ||
225 | goto end; | ||
226 | |||
227 | uint8_t diff = packet[0] - vc->frameid_in; | ||
228 | |||
229 | if (diff != 0) { | ||
230 | if (diff < 225) { /* New frame */ | ||
231 | /* Flush last frames' data and get ready for this frame */ | ||
232 | Payload *p = malloc(sizeof(Payload) + vc->frame_size); | ||
233 | |||
234 | if (p) { | ||
235 | pthread_mutex_lock(vc->queue_mutex); | ||
236 | |||
237 | if (rb_full(vc->vbuf_raw)) { | ||
238 | LOGGER_DEBUG("Dropped video frame"); | ||
239 | Payload *tp; | ||
240 | rb_read(vc->vbuf_raw, (void**)&tp); | ||
241 | free(tp); | ||
242 | } else { | ||
243 | p->size = vc->frame_size; | ||
244 | memcpy(p->data, vc->frame_buf, vc->frame_size); | ||
245 | } | ||
246 | |||
247 | /* Calculate time took for peer to send us this frame */ | ||
248 | uint32_t t_lcfd = current_time_monotonic() - vc->linfts; | ||
249 | vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; | ||
250 | vc->linfts = current_time_monotonic(); | ||
251 | |||
252 | rb_write(vc->vbuf_raw, p); | ||
253 | pthread_mutex_unlock(vc->queue_mutex); | ||
254 | } else { | ||
255 | LOGGER_WARNING("Allocation failed! Program might misbehave!"); | ||
256 | goto end; | ||
257 | } | ||
258 | |||
259 | vc->frameid_in = packet[0]; | ||
260 | memset(vc->frame_buf, 0, vc->frame_size); | ||
261 | vc->frame_size = 0; | ||
262 | |||
263 | } else { /* Old frame; drop */ | ||
264 | LOGGER_DEBUG("Old packet: %u", packet[0]); | ||
265 | goto end; | ||
266 | } | ||
267 | } | ||
268 | |||
269 | uint8_t piece_number = packet[1]; | ||
270 | |||
271 | uint32_t length_before_piece = ((piece_number - 1) * vc->peer_video_frame_piece_size); | ||
272 | uint32_t framebuf_new_length = length_before_piece + (packet_size - VIDEOFRAME_HEADER_SIZE); | ||
273 | |||
274 | if (framebuf_new_length > MAX_VIDEOFRAME_SIZE) | ||
275 | goto end; | ||
276 | |||
277 | |||
278 | /* Otherwise it's part of the frame so just process */ | ||
279 | /* LOGGER_DEBUG("Video Packet: %u %u", packet[0], packet[1]); */ | ||
280 | |||
281 | memcpy(vc->frame_buf + length_before_piece, | ||
282 | packet + VIDEOFRAME_HEADER_SIZE, | ||
283 | packet_size - VIDEOFRAME_HEADER_SIZE); | ||
284 | |||
285 | if (framebuf_new_length > vc->frame_size) | ||
286 | vc->frame_size = framebuf_new_length; | ||
287 | |||
288 | end: | ||
289 | rtp_free_msg(msg); | ||
290 | return 0; | ||
291 | } | ||
292 | int vc_reconfigure_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height) | ||
293 | { | ||
294 | if (!vc) | ||
295 | return -1; | ||
296 | |||
297 | vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc; | ||
298 | if (cfg.rc_target_bitrate == (uint32_t) bit_rate && cfg.g_w == width && cfg.g_h == height) | ||
299 | return 0; /* Nothing changed */ | ||
300 | |||
301 | cfg.rc_target_bitrate = bit_rate; | ||
302 | cfg.g_w = width; | ||
303 | cfg.g_h = height; | ||
304 | |||
305 | int rc = vpx_codec_enc_config_set(vc->encoder, &cfg); | ||
306 | if ( rc != VPX_CODEC_OK) { | ||
307 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
308 | return -1; | ||
309 | } | ||
310 | |||
311 | return 0; | ||
312 | } | ||
313 | int vc_reconfigure_test_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height) | ||
314 | { | ||
315 | if (!vc) | ||
316 | return -1; | ||
317 | |||
318 | vpx_codec_enc_cfg_t cfg = *vc->test_encoder->config.enc; | ||
319 | if (cfg.rc_target_bitrate == (uint32_t) bit_rate && cfg.g_w == width && cfg.g_h == height) | ||
320 | return 0; /* Nothing changed */ | ||
321 | |||
322 | cfg.rc_target_bitrate = bit_rate; | ||
323 | cfg.g_w = width; | ||
324 | cfg.g_h = height; | ||
325 | |||
326 | int rc = vpx_codec_enc_config_set(vc->test_encoder, &cfg); | ||
327 | if ( rc != VPX_CODEC_OK) { | ||
328 | LOGGER_ERROR("Failed to set test encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
329 | return -1; | ||
330 | } | ||
331 | |||
332 | return 0; | ||
333 | } | ||
334 | |||
335 | |||
336 | |||
337 | bool create_video_encoder (vpx_codec_ctx_t* dest, int32_t bit_rate) | ||
338 | { | ||
339 | assert(dest); | ||
340 | |||
341 | vpx_codec_enc_cfg_t cfg; | ||
342 | int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
343 | |||
344 | if (rc != VPX_CODEC_OK) { | ||
345 | LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc)); | ||
346 | return false; | ||
347 | } | ||
348 | |||
349 | rc = vpx_codec_enc_init_ver(dest, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, | ||
350 | VPX_ENCODER_ABI_VERSION); | ||
351 | |||
352 | if ( rc != VPX_CODEC_OK) { | ||
353 | LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | ||
354 | return false; | ||
355 | } | ||
356 | |||
357 | cfg.rc_target_bitrate = bit_rate; | ||
358 | cfg.g_w = 800; | ||
359 | cfg.g_h = 600; | ||
360 | cfg.g_pass = VPX_RC_ONE_PASS; | ||
361 | cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; | ||
362 | cfg.g_lag_in_frames = 0; | ||
363 | cfg.kf_min_dist = 0; | ||
364 | cfg.kf_max_dist = 48; | ||
365 | cfg.kf_mode = VPX_KF_AUTO; | ||
366 | |||
367 | rc = vpx_codec_control(dest, VP8E_SET_CPUUSED, 8); | ||
368 | |||
369 | if ( rc != VPX_CODEC_OK) { | ||
370 | LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
371 | vpx_codec_destroy(dest); | ||
372 | } | ||
373 | |||
374 | return true; | ||
375 | } \ No newline at end of file | ||
diff --git a/toxav/video.h b/toxav/video.h new file mode 100644 index 00000000..96d3205d --- /dev/null +++ b/toxav/video.h | |||
@@ -0,0 +1,113 @@ | |||
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_s; | ||
40 | |||
41 | /* | ||
42 | * Base Video Codec session type. | ||
43 | */ | ||
44 | typedef struct VCSession_s { | ||
45 | |||
46 | /* encoding */ | ||
47 | vpx_codec_ctx_t encoder[1]; | ||
48 | vpx_codec_ctx_t test_encoder[1]; | ||
49 | uint32_t frame_counter; | ||
50 | uint32_t test_frame_counter; | ||
51 | |||
52 | /* decoding */ | ||
53 | vpx_codec_ctx_t decoder[1]; | ||
54 | void *vbuf_raw; /* Un-decoded data */ | ||
55 | |||
56 | /* Data handling */ | ||
57 | uint8_t *frame_buf; /* buffer for split video payloads */ | ||
58 | uint32_t frame_size; /* largest address written to in frame_buf for current input frame */ | ||
59 | uint8_t frameid_in, frameid_out; /* id of input and output video frame */ | ||
60 | uint64_t linfts; /* Last received frame time stamp */ | ||
61 | uint32_t lcfd; /* Last calculated frame duration for incoming video payload */ | ||
62 | |||
63 | /* Limits */ | ||
64 | uint32_t peer_video_frame_piece_size; | ||
65 | |||
66 | /* Splitting */ | ||
67 | uint8_t *split_video_frame; | ||
68 | const uint8_t *processing_video_frame; | ||
69 | uint16_t processing_video_frame_size; | ||
70 | |||
71 | ToxAV *av; | ||
72 | uint32_t friend_number; | ||
73 | |||
74 | PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */ | ||
75 | |||
76 | pthread_mutex_t queue_mutex[1]; | ||
77 | } VCSession; | ||
78 | |||
79 | /* | ||
80 | * Create new Video Codec session. | ||
81 | */ | ||
82 | VCSession* vc_new(ToxAV* av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data, uint32_t mvfpsz); | ||
83 | /* | ||
84 | * Kill the Video Codec session. | ||
85 | */ | ||
86 | void vc_kill(VCSession* vc); | ||
87 | /* | ||
88 | * Do periodic work. Work is consisted out of decoding only. | ||
89 | */ | ||
90 | void vc_do(VCSession* vc); | ||
91 | /* | ||
92 | * Set new video splitting cycle. This is requirement in order to send video packets. | ||
93 | */ | ||
94 | void vc_init_video_splitter_cycle(VCSession* vc); | ||
95 | /* | ||
96 | * Update the video splitter cycle with new data. | ||
97 | */ | ||
98 | int vc_update_video_splitter_cycle(VCSession* vc, const uint8_t* payload, uint16_t length); | ||
99 | /* | ||
100 | * Iterate over splitted cycle. | ||
101 | */ | ||
102 | const uint8_t *vc_iterate_split_video_frame(VCSession* vc, uint16_t *size); | ||
103 | /* | ||
104 | * Queue new rtp message. | ||
105 | */ | ||
106 | int vc_queue_message(void *vcp, struct RTPMessage_s *msg); | ||
107 | /* | ||
108 | * Set new values to the encoders. | ||
109 | */ | ||
110 | int vc_reconfigure_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height); | ||
111 | int vc_reconfigure_test_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height); | ||
112 | |||
113 | #endif /* VIDEO_H */ \ No newline at end of file | ||