summaryrefslogtreecommitdiff
path: root/protocols/Tox/toxcore/toxav
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Tox/toxcore/toxav')
-rw-r--r--protocols/Tox/toxcore/toxav/Makefile.inc36
-rw-r--r--protocols/Tox/toxcore/toxav/codec.c357
-rw-r--r--protocols/Tox/toxcore/toxav/codec.h116
-rw-r--r--protocols/Tox/toxcore/toxav/msi.c1947
-rw-r--r--protocols/Tox/toxcore/toxav/msi.h267
-rw-r--r--protocols/Tox/toxcore/toxav/rtp.c600
-rw-r--r--protocols/Tox/toxcore/toxav/rtp.h196
-rw-r--r--protocols/Tox/toxcore/toxav/toxav.c1148
-rw-r--r--protocols/Tox/toxcore/toxav/toxav.h389
9 files changed, 5056 insertions, 0 deletions
diff --git a/protocols/Tox/toxcore/toxav/Makefile.inc b/protocols/Tox/toxcore/toxav/Makefile.inc
new file mode 100644
index 0000000000..de8ef8ff38
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/Makefile.inc
@@ -0,0 +1,36 @@
+if BUILD_AV
+
+lib_LTLIBRARIES += libtoxav.la
+libtoxav_la_include_HEADERS = ../toxav/toxav.h
+libtoxav_la_includedir = $(includedir)/tox
+
+libtoxav_la_SOURCES = ../toxav/rtp.h \
+ ../toxav/rtp.c \
+ ../toxav/msi.h \
+ ../toxav/msi.c \
+ ../toxav/codec.h \
+ ../toxav/codec.c \
+ ../toxav/toxav.h \
+ ../toxav/toxav.c
+
+
+libtoxav_la_CFLAGS = -I../toxcore \
+ -I../toxav \
+ $(LIBSODIUM_CFLAGS) \
+ $(NACL_CFLAGS) \
+ $(AV_CFLAGS) \
+ $(PTHREAD_CFLAGS)
+
+libtoxav_la_LDFLAGS = $(TOXAV_LT_LDFLAGS) \
+ $(LIBSODIUM_LDFLAGS) \
+ $(NACL_LDFLAGS) \
+ $(EXTRA_LT_LDFLAGS) \
+ $(WINSOCK2_LIBS)
+
+libtoxav_la_LIBADD = libtoxcore.la \
+ $(LIBSODIUM_LIBS) \
+ $(NACL_LIBS) \
+ $(PTHREAD_LIBS) \
+ $(AV_LIBS)
+
+endif \ No newline at end of file
diff --git a/protocols/Tox/toxcore/toxav/codec.c b/protocols/Tox/toxcore/toxav/codec.c
new file mode 100644
index 0000000000..10dc4f53ff
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/codec.c
@@ -0,0 +1,357 @@
+/** codec.c
+ *
+ * Audio and video codec intitialization, encoding/decoding and playback
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "../toxcore/logger.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+
+#include "rtp.h"
+#include "codec.h"
+
+JitterBuffer *create_queue(unsigned int capacity)
+{
+ unsigned int size = 1;
+
+ while (size <= capacity) {
+ size *= 2;
+ }
+
+ JitterBuffer *q;
+
+ if ( !(q = calloc(sizeof(JitterBuffer), 1)) ) return NULL;
+
+ if (!(q->queue = calloc(sizeof(RTPMessage *), size))) {
+ free(q);
+ return NULL;
+ }
+
+ q->size = size;
+ q->capacity = capacity;
+ return q;
+}
+
+static void clear_queue(JitterBuffer *q)
+{
+ for (; q->bottom != q->top; ++q->bottom) {
+ if (q->queue[q->bottom % q->size]) {
+ rtp_free_msg(NULL, q->queue[q->bottom % q->size]);
+ q->queue[q->bottom % q->size] = NULL;
+ }
+ }
+}
+
+void terminate_queue(JitterBuffer *q)
+{
+ if (!q) return;
+
+ clear_queue(q);
+ free(q->queue);
+ free(q);
+}
+
+void queue(JitterBuffer *q, RTPMessage *pk)
+{
+ uint16_t sequnum = pk->header->sequnum;
+
+ unsigned int num = sequnum % q->size;
+
+ if ((uint32_t)(sequnum - q->bottom) > q->size) {
+ clear_queue(q);
+ q->bottom = sequnum;
+ q->queue[num] = pk;
+ q->top = sequnum + 1;
+ return;
+ }
+
+ if (q->queue[num])
+ return;
+
+ q->queue[num] = pk;
+
+ if ((sequnum - q->bottom) >= (q->top - q->bottom))
+ q->top = sequnum + 1;
+}
+
+/* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */
+RTPMessage *dequeue(JitterBuffer *q, int *success)
+{
+ if (q->top == q->bottom) {
+ *success = 0;
+ return NULL;
+ }
+
+ unsigned int num = q->bottom % q->size;
+
+ if (q->queue[num]) {
+ RTPMessage *ret = q->queue[num];
+ q->queue[num] = NULL;
+ ++q->bottom;
+ *success = 1;
+ return ret;
+ }
+
+ if ((uint32_t)(q->top - q->bottom) > q->capacity) {
+ ++q->bottom;
+ *success = 2;
+ return NULL;
+ }
+
+ *success = 0;
+ return NULL;
+}
+
+
+int init_video_decoder(CodecState *cs)
+{
+ int rc = vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0, VPX_DECODER_ABI_VERSION);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ return 0;
+}
+
+int init_audio_decoder(CodecState *cs, uint32_t audio_channels)
+{
+ int rc;
+ cs->audio_decoder = opus_decoder_create(cs->audio_sample_rate, audio_channels, &rc );
+
+ if ( rc != OPUS_OK ) {
+ LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc));
+ return -1;
+ }
+
+ cs->audio_decoder_channels = audio_channels;
+ return 0;
+}
+
+int reconfigure_video_encoder_resolution(CodecState *cs, uint16_t width, uint16_t height)
+{
+ vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc;
+
+ if (cfg.g_w == width && cfg.g_h == height)
+ return 0;
+
+ if (width * height > cs->max_width * cs->max_height)
+ return -1;
+
+ LOGGER_DEBUG("New video resolution: %u %u", width, height);
+ cfg.g_w = width;
+ cfg.g_h = height;
+ int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ return 0;
+}
+
+int reconfigure_video_encoder_bitrate(CodecState *cs, uint32_t video_bitrate)
+{
+ vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc;
+
+ if (cfg.rc_target_bitrate == video_bitrate)
+ return 0;
+
+ LOGGER_DEBUG("New video bitrate: %u", video_bitrate);
+ cfg.rc_target_bitrate = video_bitrate;
+ int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ return 0;
+}
+
+int init_video_encoder(CodecState *cs, uint16_t max_width, uint16_t max_height, uint32_t video_bitrate)
+{
+ vpx_codec_enc_cfg_t cfg;
+ int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
+
+ if (rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ cfg.rc_target_bitrate = video_bitrate;
+ cfg.g_w = max_width;
+ cfg.g_h = max_height;
+ cfg.g_pass = VPX_RC_ONE_PASS;
+ cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS;
+ cfg.g_lag_in_frames = 0;
+ cfg.kf_min_dist = 0;
+ cfg.kf_max_dist = 300;
+ cfg.kf_mode = VPX_KF_AUTO;
+
+ cs->max_width = max_width;
+ cs->max_height = max_height;
+ cs->bitrate = video_bitrate;
+
+ rc = vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, VPX_ENCODER_ABI_VERSION);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ rc = vpx_codec_control(&cs->v_encoder, VP8E_SET_CPUUSED, 7);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
+ return -1;
+ }
+
+ return 0;
+}
+
+int init_audio_encoder(CodecState *cs, uint32_t audio_channels)
+{
+ int rc = OPUS_OK;
+ cs->audio_encoder = opus_encoder_create(cs->audio_sample_rate, audio_channels, OPUS_APPLICATION_AUDIO, &rc);
+
+ if ( rc != OPUS_OK ) {
+ LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc));
+ return -1;
+ }
+
+ rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_bitrate));
+
+ if ( rc != OPUS_OK ) {
+ LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
+ return -1;
+ }
+
+ rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10));
+
+ if ( rc != OPUS_OK ) {
+ LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
+ return -1;
+ }
+
+ cs->audio_encoder_channels = audio_channels;
+ return 0;
+}
+
+
+CodecState *codec_init_session ( uint32_t audio_bitrate,
+ uint16_t audio_frame_duration,
+ uint32_t audio_sample_rate,
+ uint32_t encoder_audio_channels,
+ uint32_t decoder_audio_channels,
+ uint32_t audio_VAD_tolerance_ms,
+ uint16_t max_video_width,
+ uint16_t max_video_height,
+ uint32_t video_bitrate )
+{
+ CodecState *retu = calloc(sizeof(CodecState), 1);
+
+ if (!retu) return NULL;
+
+ retu->audio_bitrate = audio_bitrate;
+ retu->audio_sample_rate = audio_sample_rate;
+
+ /* Encoders */
+ if (!max_video_width || !max_video_height) { /* Disable video */
+ /*video_width = 320;
+ video_height = 240; */
+ } else {
+ retu->capabilities |= ( 0 == init_video_encoder(retu, max_video_width, max_video_height,
+ video_bitrate) ) ? v_encoding : 0;
+ retu->capabilities |= ( 0 == init_video_decoder(retu) ) ? v_decoding : 0;
+ }
+
+ retu->capabilities |= ( 0 == init_audio_encoder(retu, encoder_audio_channels) ) ? a_encoding : 0;
+ retu->capabilities |= ( 0 == init_audio_decoder(retu, decoder_audio_channels) ) ? a_decoding : 0;
+
+ if ( retu->capabilities == 0 ) { /* everything failed */
+ free (retu);
+ return NULL;
+ }
+
+
+ retu->EVAD_tolerance = audio_VAD_tolerance_ms > audio_frame_duration ?
+ audio_VAD_tolerance_ms / audio_frame_duration : audio_frame_duration;
+
+ return retu;
+}
+
+void codec_terminate_session ( CodecState *cs )
+{
+ if (!cs) return;
+
+ if ( cs->audio_encoder )
+ opus_encoder_destroy(cs->audio_encoder);
+
+ if ( cs->audio_decoder )
+ opus_decoder_destroy(cs->audio_decoder);
+
+ if ( cs->capabilities & v_decoding )
+ vpx_codec_destroy(&cs->v_decoder);
+
+ if ( cs->capabilities & v_encoding )
+ vpx_codec_destroy(&cs->v_encoder);
+
+ LOGGER_DEBUG("Terminated codec state: %p", cs);
+ free(cs);
+}
+
+static float calculate_sum_sq (int16_t *n, uint16_t k)
+{
+ float result = 0;
+ uint16_t i = 0;
+
+ for ( ; i < k; i ++) result += (float) (n[i] * n[i]);
+
+ return result;
+}
+
+int energy_VAD(CodecState *cs, int16_t *PCM, uint16_t frame_size, float energy)
+{
+ float frame_energy = sqrt(calculate_sum_sq(PCM, frame_size)) / frame_size;
+
+ if ( frame_energy > energy) {
+ cs->EVAD_tolerance_cr = cs->EVAD_tolerance; /* Reset counter */
+ return 1;
+ }
+
+ if ( cs->EVAD_tolerance_cr ) {
+ cs->EVAD_tolerance_cr --;
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/protocols/Tox/toxcore/toxav/codec.h b/protocols/Tox/toxcore/toxav/codec.h
new file mode 100644
index 0000000000..db4fbea0dc
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/codec.h
@@ -0,0 +1,116 @@
+/** codec.h
+ *
+ * Audio and video codec intitialization, encoding/decoding and playback
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _CODEC_H_
+#define _CODEC_H_
+
+#include <stdio.h>
+#include <math.h>
+#include <pthread.h>
+
+#include <vpx/vpx_decoder.h>
+#include <vpx/vpx_encoder.h>
+#include <vpx/vp8dx.h>
+#include <vpx/vp8cx.h>
+#define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx())
+#define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx())
+
+/* Audio encoding/decoding */
+#include <opus.h>
+
+typedef enum _Capabilities {
+ none,
+ a_encoding = 1 << 0,
+ a_decoding = 1 << 1,
+ v_encoding = 1 << 2,
+ v_decoding = 1 << 3
+} Capabilities;
+
+extern const uint16_t min_jbuf_size;
+
+typedef struct _CodecState {
+
+ /* video encoding */
+ vpx_codec_ctx_t v_encoder;
+ uint32_t frame_counter;
+
+ /* video decoding */
+ vpx_codec_ctx_t v_decoder;
+ int bitrate;
+ int max_width;
+ int max_height;
+
+ /* audio encoding */
+ OpusEncoder *audio_encoder;
+ int audio_bitrate;
+ int audio_sample_rate;
+ int audio_encoder_channels;
+
+ /* audio decoding */
+ OpusDecoder *audio_decoder;
+ int audio_decoder_channels;
+
+ uint64_t capabilities; /* supports*/
+
+ /* Voice activity detection */
+ uint32_t EVAD_tolerance; /* In frames */
+ uint32_t EVAD_tolerance_cr;
+} CodecState;
+
+
+typedef struct _JitterBuffer {
+ RTPMessage **queue;
+ uint32_t size;
+ uint32_t capacity;
+ uint16_t bottom;
+ uint16_t top;
+} JitterBuffer;
+
+JitterBuffer *create_queue(unsigned int capacity);
+void terminate_queue(JitterBuffer *q);
+void queue(JitterBuffer *q, RTPMessage *pk);
+RTPMessage *dequeue(JitterBuffer *q, int *success);
+
+
+CodecState *codec_init_session ( uint32_t audio_bitrate,
+ uint16_t audio_frame_duration,
+ uint32_t audio_sample_rate,
+ uint32_t encoder_audio_channels,
+ uint32_t decoder_audio_channels,
+ uint32_t audio_VAD_tolerance_ms,
+ uint16_t max_video_width,
+ uint16_t max_video_height,
+ uint32_t video_bitrate );
+
+void codec_terminate_session(CodecState *cs);
+
+/* Reconfigure video encoder
+ return 0 on success.
+ return -1 on failure. */
+int reconfigure_video_encoder_resolution(CodecState *cs, uint16_t width, uint16_t height);
+int reconfigure_video_encoder_bitrate(CodecState *cs, uint32_t video_bitrate);
+
+/* Calculate energy and return 1 if has voice, 0 if not */
+int energy_VAD(CodecState *cs, int16_t *PCM, uint16_t frame_size, float energy);
+
+#endif /* _CODEC_H_ */
diff --git a/protocols/Tox/toxcore/toxav/msi.c b/protocols/Tox/toxcore/toxav/msi.c
new file mode 100644
index 0000000000..91742c3579
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/msi.c
@@ -0,0 +1,1947 @@
+/** msi.c
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "../toxcore/logger.h"
+#include "../toxcore/util.h"
+
+#include "msi.h"
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#define MSI_MAXMSG_SIZE 256
+
+/* Define default timeout for a request.
+ * There is no behavior specified by the msi on what will
+ * client do on timeout, but to call timeout callback.
+ */
+#define m_deftout 10000 /* in milliseconds */
+
+/**
+ * Protocol:
+ *
+ * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}|
+ */
+
+typedef uint8_t MSIRawCSettingsType[23];
+
+typedef enum {
+ IDRequest = 1,
+ IDResponse,
+ IDReason,
+ IDCallId,
+ IDCSettings,
+
+} MSIHeaderID;
+
+typedef enum {
+ TypeRequest,
+ TypeResponse,
+
+} MSIMessageType;
+
+typedef enum {
+ invite,
+ start,
+ cancel,
+ reject,
+ end,
+
+} MSIRequest;
+
+typedef enum {
+ ringing,
+ starting,
+ ending,
+ error
+
+} MSIResponse;
+
+
+#define GENERIC_HEADER(header, val_type) \
+typedef struct _MSIHeader##header { \
+val_type value; \
+_Bool exists; \
+} MSIHeader##header;
+
+
+GENERIC_HEADER ( Request, MSIRequest )
+GENERIC_HEADER ( Response, MSIResponse )
+GENERIC_HEADER ( CallId, MSICallIDType )
+GENERIC_HEADER ( Reason, MSIReasonStrType )
+GENERIC_HEADER ( CSettings, MSIRawCSettingsType )
+
+
+/**
+ * @brief This is the message structure. It contains all of the headers and
+ * destination/source of the message stored in friend_id.
+ *
+ */
+typedef struct _MSIMessage {
+
+ MSIHeaderRequest request;
+ MSIHeaderResponse response;
+ MSIHeaderReason reason;
+ MSIHeaderCallId callid;
+ MSIHeaderCSettings csettings;
+
+ int friend_id;
+
+} MSIMessage;
+
+
+inline__ void invoke_callback(MSISession *session, int32_t call_index, MSICallbackID id)
+{
+ if ( session->callbacks[id].function ) {
+ LOGGER_DEBUG("Invoking callback function: %d", id);
+ session->callbacks[id].function ( session->agent_handler, call_index, session->callbacks[id].data );
+ }
+}
+
+/**
+ * @brief Parse raw 'data' received from socket into MSIMessage struct.
+ * Every message has to have end value of 'end_byte' or _undefined_ behavior
+ * occures. The best practice is to check the end of the message at the handle_packet.
+ *
+ * @param msg Container.
+ * @param data The data.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t length )
+{
+
+#define FAIL_CONSTRAINT(constraint, wanted) if ((constraint -= wanted) < 1) { LOGGER_ERROR("Read over length!"); return -1; }
+#define FAIL_SIZE(byte, valid) if ( byte != valid ) { LOGGER_ERROR("Invalid data size!"); return -1; }
+#define FAIL_LIMITS(byte, high) if ( byte > high ) { LOGGER_ERROR("Failed limit!"); return -1; }
+
+ if ( msg == NULL ) {
+ LOGGER_ERROR("Could not parse message: no storage!");
+ return -1;
+ }
+
+ if ( data[length - 1] ) { /* End byte must have value 0 */
+ LOGGER_ERROR("Invalid end byte");
+ return -1;
+ }
+
+ const uint8_t *it = data;
+ int size_constraint = length;
+
+ while ( *it ) {/* until end byte is hit */
+ switch (*it) {
+ case IDRequest:
+ FAIL_CONSTRAINT(size_constraint, 3);
+ FAIL_SIZE(it[1], 1);
+// FAIL_LIMITS(it[2], invite, end);
+ FAIL_LIMITS(it[2], end);
+ msg->request.value = it[2];
+ it += 3;
+ msg->request.exists = 1;
+ break;
+
+ case IDResponse:
+ FAIL_CONSTRAINT(size_constraint, 3);
+ FAIL_SIZE(it[1], 1);
+// FAIL_LIMITS(it[2], ringing, error);
+ FAIL_LIMITS(it[2], error);
+ msg->response.value = it[2];
+ it += 3;
+ msg->response.exists = 1;
+ break;
+
+ case IDCallId:
+ FAIL_CONSTRAINT(size_constraint, sizeof(MSICallIDType) + 2);
+ FAIL_SIZE(it[1], sizeof(MSICallIDType));
+ memcpy(msg->callid.value, it + 2, sizeof(MSICallIDType));
+ it += sizeof(MSICallIDType) + 2;
+ msg->callid.exists = 1;
+ break;
+
+ case IDReason:
+ FAIL_CONSTRAINT(size_constraint, sizeof(MSIReasonStrType) + 2);
+ FAIL_SIZE(it[1], sizeof(MSIReasonStrType));
+ memcpy(msg->reason.value, it + 2, sizeof(MSIReasonStrType));
+ it += sizeof(MSIReasonStrType) + 2;
+ msg->reason.exists = 1;
+ break;
+
+ case IDCSettings:
+ FAIL_CONSTRAINT(size_constraint, sizeof(MSIRawCSettingsType) + 2);
+ FAIL_SIZE(it[1], sizeof(MSIRawCSettingsType));
+ memcpy(msg->csettings.value, it + 2, sizeof(MSIRawCSettingsType));
+ it += sizeof(MSIRawCSettingsType) + 2;
+ msg->csettings.exists = 1;
+ break;
+
+ default:
+ LOGGER_ERROR("Invalid id byte");
+ return -1;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Create the message.
+ *
+ * @param type Request or response.
+ * @param type_id Type of request/response.
+ * @return MSIMessage* Created message.
+ * @retval NULL Error occurred.
+ */
+MSIMessage *msi_new_message ( MSIMessageType type, const uint8_t type_value )
+{
+ MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 );
+
+ if ( retu == NULL ) {
+ LOGGER_WARNING("Allocation failed! Program might misbehave!");
+ return NULL;
+ }
+
+ if ( type == TypeRequest ) {
+ retu->request.exists = 1;
+ retu->request.value = type_value;
+
+ } else {
+ retu->response.exists = 1;
+ retu->response.value = type_value;
+ }
+
+ return retu;
+}
+
+
+/**
+ * @brief Parse data from handle_packet.
+ *
+ * @param data The data.
+ * @return MSIMessage* Parsed message.
+ * @retval NULL Error occurred.
+ */
+MSIMessage *parse_recv ( const uint8_t *data, uint16_t length )
+{
+ if ( data == NULL ) {
+ LOGGER_WARNING("Tried to parse empty message!");
+ return NULL;
+ }
+
+ MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 );
+
+ if ( retu == NULL ) {
+ LOGGER_WARNING("Allocation failed! Program might misbehave!");
+ return NULL;
+ }
+
+ if ( parse_raw_data ( retu, data, length ) == -1 ) {
+
+ free ( retu );
+ return NULL;
+ }
+
+ return retu;
+}
+
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param dest Container.
+ * @param header_field Field.
+ * @param header_value Field value.
+ * @param value_len Length of field value.
+ * @param length Pointer to container length.
+ * @return uint8_t* Iterated container.
+ */
+uint8_t *format_output ( uint8_t *dest, MSIHeaderID id, const void *value, uint8_t value_len, uint16_t *length )
+{
+ if ( dest == NULL ) {
+ LOGGER_ERROR("No destination space!");
+ return NULL;
+ }
+
+ if (value == NULL || value_len == 0) {
+ LOGGER_ERROR("Empty header value");
+ return NULL;
+ }
+
+ *dest = id;
+ dest ++;
+ *dest = value_len;
+ dest ++;
+
+ memcpy(dest, value, value_len);
+
+ *length += (2 + value_len);
+
+ return dest + value_len; /* Set to next position ready to be written */
+}
+
+
+/**
+ * @brief Parse MSIMessage to send.
+ *
+ * @param msg The message.
+ * @param dest Destination.
+ * @return uint16_t Its final size.
+ */
+uint16_t parse_send ( MSIMessage *msg, uint8_t *dest )
+{
+ if (msg == NULL) {
+ LOGGER_ERROR("No message!");
+ return 0;
+ }
+
+ if (dest == NULL ) {
+ LOGGER_ERROR("No destination!");
+ return 0;
+ }
+
+ uint8_t *it = dest;
+ uint16_t size = 0;
+
+ if (msg->request.exists) {
+ uint8_t cast = msg->request.value;
+ it = format_output(it, IDRequest, &cast, 1, &size);
+ }
+
+ if (msg->response.exists) {
+ uint8_t cast = msg->response.value;
+ it = format_output(it, IDResponse, &cast, 1, &size);
+ }
+
+ if (msg->callid.exists) {
+ it = format_output(it, IDCallId, &msg->callid.value, sizeof(msg->callid.value), &size);
+ }
+
+ if (msg->reason.exists) {
+ it = format_output(it, IDReason, &msg->reason.value, sizeof(msg->reason.value), &size);
+ }
+
+ if (msg->csettings.exists) {
+ it = format_output(it, IDCSettings, &msg->csettings.value, sizeof(msg->csettings.value), &size);
+ }
+
+ *it = 0;
+ size ++;
+
+ return size;
+}
+
+void msi_msg_set_reason ( MSIMessage *msg, const MSIReasonStrType value )
+{
+ if ( !msg ) return;
+
+ msg->reason.exists = 1;
+ memcpy(msg->reason.value, value, sizeof(MSIReasonStrType));
+}
+
+void msi_msg_set_callid ( MSIMessage *msg, const MSICallIDType value )
+{
+ if ( !msg ) return;
+
+ msg->callid.exists = 1;
+ memcpy(msg->callid.value, value, sizeof(MSICallIDType));
+}
+
+void msi_msg_set_csettings ( MSIMessage *msg, const MSICSettings *value )
+{
+ if ( !msg ) return;
+
+ msg->csettings.exists = 1;
+
+ msg->csettings.value[0] = value->call_type;
+ uint8_t *iter = msg->csettings.value + 1;
+
+ /* Video bitrate */
+ uint32_t lval = htonl(value->video_bitrate);
+ memcpy(iter, &lval, 4);
+ iter += 4;
+
+ /* Video max width */
+ uint16_t sval = htons(value->max_video_width);
+ memcpy(iter, &sval, 2);
+ iter += 2;
+
+ /* Video max height */
+ sval = htons(value->max_video_height);
+ memcpy(iter, &sval, 2);
+ iter += 2;
+
+ /* Audio bitrate */
+ lval = htonl(value->audio_bitrate);
+ memcpy(iter, &lval, 4);
+ iter += 4;
+
+ /* Audio frame duration */
+ sval = htons(value->audio_frame_duration);
+ memcpy(iter, &sval, 2);
+ iter += 2;
+
+ /* Audio sample rate */
+ lval = htonl(value->audio_sample_rate);
+ memcpy(iter, &lval, 4);
+ iter += 4;
+
+ /* Audio channels */
+ lval = htonl(value->audio_channels);
+ memcpy(iter, &lval, 4);
+}
+
+void msi_msg_get_csettings ( MSIMessage *msg, MSICSettings *dest )
+{
+ if ( !msg || !dest || !msg->csettings.exists ) return;
+
+ dest->call_type = msg->csettings.value[0];
+ uint8_t *iter = msg->csettings.value + 1;
+
+ memcpy(&dest->video_bitrate, iter, 4);
+ iter += 4;
+ dest->video_bitrate = ntohl(dest->video_bitrate);
+
+ memcpy(&dest->max_video_width, iter, 2);
+ iter += 2;
+ dest->max_video_width = ntohs(dest->max_video_width);
+
+ memcpy(&dest->max_video_height, iter, 2);
+ iter += 2;
+ dest->max_video_height = ntohs(dest->max_video_height);
+
+ memcpy(&dest->audio_bitrate, iter, 4);
+ iter += 4;
+ dest->audio_bitrate = ntohl(dest->audio_bitrate);
+
+ memcpy(&dest->audio_frame_duration, iter, 2);
+ iter += 2;
+ dest->audio_frame_duration = ntohs(dest->audio_frame_duration);
+
+ memcpy(&dest->audio_sample_rate, iter, 4);
+ iter += 4;
+ dest->audio_sample_rate = ntohl(dest->audio_sample_rate);
+
+ memcpy(&dest->audio_channels, iter, 4);
+ dest->audio_channels = ntohl(dest->audio_channels);
+}
+
+typedef struct _Timer {
+ void *(*func)(void *);
+ void *func_arg1;
+ int func_arg2;
+ uint64_t timeout;
+ int idx;
+
+} Timer;
+
+typedef struct _TimerHandler {
+ Timer **timers;
+ pthread_mutex_t mutex;
+
+ uint32_t max_capacity;
+ uint32_t size;
+ uint64_t resolution;
+
+ _Bool running;
+
+} TimerHandler;
+
+struct timer_function_args {
+ void *arg1;
+ int arg2;
+};
+
+/**
+ * @brief Allocate timer in array
+ *
+ * @param timers_container Handler
+ * @param func Function to be executed
+ * @param arg Its args
+ * @param timeout Timeout in ms
+ * @return int
+ */
+static int timer_alloc ( TimerHandler *timers_container, void *(func)(void *), void *arg1, int arg2, uint32_t timeout)
+{
+ static int timer_id;
+ pthread_mutex_lock(&timers_container->mutex);
+
+ uint32_t i = 0;
+
+ for (; i < timers_container->max_capacity && timers_container->timers[i]; i ++);
+
+ if (i == timers_container->max_capacity) {
+ LOGGER_WARNING("Maximum capacity reached!");
+ pthread_mutex_unlock(&timers_container->mutex);
+ return -1;
+ }
+
+ Timer *timer = timers_container->timers[i] = calloc(sizeof(Timer), 1);
+
+ if (timer == NULL) {
+ LOGGER_ERROR("Failed to allocate timer!");
+ pthread_mutex_unlock(&timers_container->mutex);
+ return -1;
+ }
+
+ timers_container->size ++;
+
+ timer->func = func;
+ timer->func_arg1 = arg1;
+ timer->func_arg2 = arg2;
+ timer->timeout = timeout + current_time_monotonic(); /* In ms */
+ ++timer_id;
+ timer->idx = timer_id;
+
+ /* reorder */
+ if (i) {
+ int64_t j = i - 1;
+
+ for (; j >= 0 && timeout < timers_container->timers[j]->timeout; j--) {
+ Timer *tmp = timers_container->timers[j];
+ timers_container->timers[j] = timer;
+ timers_container->timers[j + 1] = tmp;
+ }
+ }
+
+ pthread_mutex_unlock(&timers_container->mutex);
+
+ LOGGER_DEBUG("Allocated timer index: %ull timeout: %ull, current size: %ull", i, timeout, timers_container->size);
+ return timer->idx;
+}
+
+/**
+ * @brief Remove timer from array
+ *
+ * @param timers_container handler
+ * @param idx timer id
+ * @param lock_mutex (does the mutex need to be locked)
+ * @return int
+ */
+static int timer_release ( TimerHandler *timers_container, int idx , int lock_mutex)
+{
+ if (lock_mutex)
+ pthread_mutex_lock(&timers_container->mutex);
+
+ Timer **timed_events = timers_container->timers;
+
+ size_t i;
+ int rc = -1;
+
+ for (i = 0; i < timers_container->max_capacity; ++i) {
+ if (timed_events[i] && timed_events[i]->idx == idx) {
+ rc = i;
+ break;
+ }
+ }
+
+ if (rc == -1) {
+ LOGGER_WARNING("No event with id: %d", idx);
+
+ if (lock_mutex) pthread_mutex_unlock(&timers_container->mutex);
+
+ return -1;
+ }
+
+ free(timed_events[rc]);
+
+ timed_events[rc] = NULL;
+
+ i = rc + 1;
+
+ for (; i < timers_container->max_capacity && timed_events[i]; i ++) {
+ timed_events[i - 1] = timed_events[i];
+ timed_events[i] = NULL;
+ }
+
+ timers_container->size--;
+
+ LOGGER_DEBUG("Popped id: %d, current size: %ull ", idx, timers_container->size);
+
+ if (lock_mutex) pthread_mutex_unlock(&timers_container->mutex);
+
+ return 0;
+}
+
+/**
+ * @brief Main poll for timer execution
+ *
+ * @param arg ...
+ * @return void*
+ */
+static void *timer_poll( void *arg )
+{
+ TimerHandler *handler = arg;
+
+ while ( handler->running ) {
+
+ pthread_mutex_lock(&handler->mutex);
+
+ if ( handler->running ) {
+
+ uint64_t time = current_time_monotonic();
+
+ while ( handler->timers[0] && handler->timers[0]->timeout < time ) {
+ pthread_t tid;
+
+ struct timer_function_args *args = malloc(sizeof(struct timer_function_args));
+ args->arg1 = handler->timers[0]->func_arg1;
+ args->arg2 = handler->timers[0]->func_arg2;
+
+ if ( 0 != pthread_create(&tid, NULL, handler->timers[0]->func, args) ||
+ 0 != pthread_detach(tid) ) {
+ LOGGER_ERROR("Failed to execute timer at: %d!", handler->timers[0]->timeout);
+ free(args);
+ } else {
+ LOGGER_DEBUG("Exectued timer assigned at: %d", handler->timers[0]->timeout);
+ }
+
+ timer_release(handler, handler->timers[0]->idx, 0);
+ }
+
+ }
+
+ pthread_mutex_unlock(&handler->mutex);
+
+ usleep(handler->resolution);
+ }
+
+ pthread_exit(NULL);
+}
+
+/**
+ * @brief Start timer poll and return handler
+ *
+ * @param max_capacity capacity
+ * @param resolution ...
+ * @return TimerHandler*
+ */
+static TimerHandler *timer_init_session (int max_capacity, int resolution)
+{
+ TimerHandler *handler = calloc(1, sizeof(TimerHandler));
+
+ if (handler == NULL) {
+ LOGGER_ERROR("Failed to allocate memory, program might misbehave!");
+ return NULL;
+ }
+
+ handler->timers = calloc(max_capacity, sizeof(Timer *));
+
+ if (handler->timers == NULL) {
+ LOGGER_ERROR("Failed to allocate %d timed events!", max_capacity);
+ free(handler);
+ return NULL;
+ }
+
+ handler->max_capacity = max_capacity;
+ handler->running = 1;
+ handler->resolution = resolution;
+
+ pthread_mutex_init(&handler->mutex, NULL);
+
+
+ pthread_t _tid;
+
+ if ( 0 != pthread_create(&_tid, NULL, timer_poll, handler) || 0 != pthread_detach(_tid) ) {
+ LOGGER_ERROR("Failed to start timer poll thread!");
+ free(handler->timers);
+ free(handler);
+ return NULL;
+ }
+
+ return handler;
+}
+
+/**
+ * @brief Terminate timer session
+ *
+ * @param handler The timer handler
+ * @return void
+ */
+static void timer_terminate_session(TimerHandler *handler)
+{
+ pthread_mutex_lock(&handler->mutex);
+
+ handler->running = 0;
+
+ pthread_mutex_unlock(&handler->mutex);
+
+ size_t i = 0;
+
+ for (; i < handler->max_capacity; i ++)
+ free(handler->timers[i]);
+
+ free(handler->timers);
+
+ pthread_mutex_destroy( &handler->mutex );
+}
+
+/**
+ * @brief Generate _random_ alphanumerical string.
+ *
+ * @param str Destination.
+ * @param size Size of string.
+ * @return void
+ */
+static void t_randomstr ( uint8_t *str, uint32_t size )
+{
+ if (str == NULL) {
+ LOGGER_DEBUG("Empty destination!");
+ return;
+ }
+
+ static const uint8_t _bytes[] =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+
+ uint32_t _it = 0;
+
+ for ( ; _it < size; _it++ ) {
+ str[_it] = _bytes[ random_int() % 61 ];
+ }
+}
+
+
+typedef enum {
+ error_none,
+ error_deadcall, /* has call id but it's from old call */
+ error_id_mismatch, /* non-existing call */
+
+ error_no_callid, /* not having call id */
+ error_no_call, /* no call in session */
+ error_no_crypto_key, /* no crypto key */
+
+ error_busy
+
+} MSICallError; /* Error codes */
+
+
+/**
+ * @brief Stringify error code.
+ *
+ * @param error_code The code.
+ * @return const uint8_t* The string.
+ */
+static inline__ const uint8_t *stringify_error ( MSICallError error_code )
+{
+ static const uint8_t *strings[] = {
+ ( uint8_t *) "",
+ ( uint8_t *) "Using dead call",
+ ( uint8_t *) "Call id not set to any call",
+ ( uint8_t *) "Call id not available",
+ ( uint8_t *) "No active call in session",
+ ( uint8_t *) "No Crypto-key set",
+ ( uint8_t *) "Callee busy"
+ };
+
+ return strings[error_code];
+}
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param session Control session.
+ * @param msg The message.
+ * @param to Where to.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+static int send_message ( MSISession *session, MSICall *call, MSIMessage *msg, uint32_t to )
+{
+ msi_msg_set_callid ( msg, call->id );
+
+ uint8_t msg_string_final [MSI_MAXMSG_SIZE];
+ uint16_t length = parse_send ( msg, msg_string_final );
+
+ if (!length) {
+ LOGGER_WARNING("Parsing message failed; nothing sent!");
+ return -1;
+ }
+
+ if ( m_msi_packet(session->messenger_handle, to, msg_string_final, length) ) {
+ LOGGER_DEBUG("Sent message");
+ return 0;
+ }
+
+ return -1;
+}
+
+inline__ int send_reponse ( MSISession *session, MSICall *call, MSIResponse response, uint32_t to )
+{
+ MSIMessage *msg = msi_new_message ( TypeResponse, response );
+ int ret = send_message ( session, call, msg, to );
+ free ( msg );
+ return ret;
+}
+
+/**
+ * @brief Determine 'bigger' call id
+ *
+ * @param first duh
+ * @param second duh
+ * @return int
+ * @retval 0 it's first
+ * @retval 1 it's second
+ */
+static int call_id_bigger( const uint8_t *first, const uint8_t *second)
+{
+ return (memcmp(first, second, sizeof(MSICallIDType)) < 0);
+}
+
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param session Control session.
+ * @param msg The message.
+ * @param peer_id The peer.
+ * @return -1, 0
+ */
+static int flush_peer_csettings ( MSICall *call, MSIMessage *msg, int peer_id )
+{
+ if ( msg->csettings.exists ) {
+ msi_msg_get_csettings(msg, &call->csettings_peer[peer_id]);
+
+ LOGGER_DEBUG("Peer: %d \n"
+ "Type: %u \n"
+ "Video bitrate: %u \n"
+ "Video height: %u \n"
+ "Video width: %u \n"
+ "Audio bitrate: %u \n"
+ "Audio framedur: %u \n"
+ "Audio sample rate: %u \n"
+ "Audio channels: %u \n", peer_id,
+ call->csettings_peer[peer_id].call_type,
+ call->csettings_peer[peer_id].video_bitrate,
+ call->csettings_peer[peer_id].max_video_height,
+ call->csettings_peer[peer_id].max_video_width,
+ call->csettings_peer[peer_id].audio_bitrate,
+ call->csettings_peer[peer_id].audio_frame_duration,
+ call->csettings_peer[peer_id].audio_sample_rate,
+ call->csettings_peer[peer_id].audio_channels );
+
+ return 0;
+ }
+
+ LOGGER_WARNING("No csettings header!");
+ return -1;
+}
+
+static int terminate_call ( MSISession *session, MSICall *call );
+
+static void handle_remote_connection_change(Messenger *messenger, int friend_num, uint8_t status, void *session_p)
+{
+ (void)messenger;
+ MSISession *session = session_p;
+
+ switch ( status ) {
+ case 0: { /* Went offline */
+ int32_t j = 0;
+
+ for ( ; j < session->max_calls; j ++ ) {
+
+ if ( !session->calls[j] ) continue;
+
+ uint16_t i = 0;
+
+ for ( ; i < session->calls[j]->peer_count; i ++ )
+ if ( session->calls[j]->peers[i] == (uint32_t)friend_num ) {
+ invoke_callback(session, j, MSI_OnPeerTimeout);
+ terminate_call(session, session->calls[j]);
+ LOGGER_DEBUG("Remote: %d timed out!", friend_num);
+ return; /* TODO: On group calls change behaviour */
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static MSICall *find_call ( MSISession *session, uint8_t *call_id )
+{
+ if ( call_id == NULL ) return NULL;
+
+ int32_t i = 0;
+
+ for (; i < session->max_calls; i ++ )
+ if ( session->calls[i] && memcmp(session->calls[i]->id, call_id, sizeof(session->calls[i]->id)) == 0 ) {
+ return session->calls[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Sends error response to peer.
+ *
+ * @param session The session.
+ * @param errid The id.
+ * @param to Where to?
+ * @return int
+ * @retval -1/0 It's usually always success.
+ */
+static int send_error ( MSISession *session, MSICall *call, MSICallError errid, uint32_t to )
+{
+ if (!call) {
+ LOGGER_WARNING("Cannot handle error on 'null' call");
+ return -1;
+ }
+
+ LOGGER_DEBUG("Sending error: %d on call: %s", errid, call->id);
+
+ MSIMessage *msg_error = msi_new_message ( TypeResponse, error );
+
+ msi_msg_set_reason ( msg_error, stringify_error(errid) );
+ send_message ( session, call, msg_error, to );
+ free ( msg_error );
+
+ return 0;
+}
+
+
+
+/**
+ * @brief Add peer to peer list.
+ *
+ * @param call What call.
+ * @param peer_id Its id.
+ * @return void
+ */
+static void add_peer( MSICall *call, int peer_id )
+{
+ uint32_t *peers = !call->peers ? peers = calloc(sizeof(uint32_t), 1) :
+ realloc( call->peers, sizeof(uint32_t) * call->peer_count);
+
+ if (!peers) {
+ LOGGER_WARNING("Allocation failed! Program might misbehave!");
+ return;
+ }
+
+ call->peer_count ++;
+ call->peers = peers;
+ call->peers[call->peer_count - 1] = peer_id;
+
+ LOGGER_DEBUG("Added peer: %d", peer_id);
+}
+
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param session Control session.
+ * @param peers Amount of peers. (Currently it only supports 1)
+ * @param ringing_timeout Ringing timeout.
+ * @return MSICall* The created call.
+ */
+static MSICall *init_call ( MSISession *session, int peers, int ringing_timeout )
+{
+
+ if (peers == 0) {
+ LOGGER_ERROR("No peers!");
+ return NULL;
+ }
+
+ int32_t call_idx = 0;
+
+ for (; call_idx < session->max_calls; call_idx ++) {
+ if ( !session->calls[call_idx] ) {
+
+ if (!(session->calls[call_idx] = calloc ( sizeof ( MSICall ), 1 ))) {
+ LOGGER_WARNING("Allocation failed! Program might misbehave!");
+ return NULL;
+ }
+
+ break;
+ }
+ }
+
+ if ( call_idx == session->max_calls ) {
+ LOGGER_WARNING("Reached maximum amount of calls!");
+ return NULL;
+ }
+
+
+ MSICall *call = session->calls[call_idx];
+
+ call->call_idx = call_idx;
+
+ if ( !(call->csettings_peer = calloc ( sizeof ( MSICSettings ), peers )) ) {
+ LOGGER_WARNING("Allocation failed! Program might misbehave!");
+ free(call);
+ return NULL;
+ }
+
+ call->session = session;
+
+ call->request_timer_id = 0;
+ call->ringing_timer_id = 0;
+
+ call->ringing_tout_ms = ringing_timeout;
+
+ pthread_mutex_init ( &call->mutex, NULL );
+
+ LOGGER_DEBUG("Started new call with index: %u", call_idx);
+ return call;
+}
+
+
+/**
+ * @brief Terminate the call.
+ *
+ * @param session Control session.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+static int terminate_call ( MSISession *session, MSICall *call )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Tried to terminate non-existing call!");
+ return -1;
+ }
+
+ LOGGER_DEBUG("Terminated call id: %d", call->call_idx);
+ /* Check event loop and cancel timed events if there are any
+ * NOTE: This has to be done before possibly
+ * locking the mutex the second time
+ */
+ timer_release ( session->timer_handler, call->request_timer_id, 1);
+ timer_release ( session->timer_handler, call->ringing_timer_id, 1);
+
+ /* Get a handle */
+ pthread_mutex_lock ( &call->mutex );
+
+ session->calls[call->call_idx] = NULL;
+
+ free ( call->csettings_peer );
+ free ( call->peers);
+
+ /* Release handle */
+ pthread_mutex_unlock ( &call->mutex );
+
+ pthread_mutex_destroy ( &call->mutex );
+
+ free ( call );
+
+ return 0;
+}
+
+
+/**
+ * @brief Function called at request timeout. If not called in thread it might cause trouble
+ *
+ * @param arg Control session
+ * @return void*
+ */
+static void *handle_timeout ( void *arg )
+{
+ /* TODO: Cancel might not arrive there; set up
+ * timers on these cancels and terminate call on
+ * their timeout
+ */
+ struct timer_function_args *args = arg;
+ int call_index = args->arg2;
+ MSISession *session = args->arg1;
+ MSICall *call = session->calls[call_index];
+
+ if (call) {
+ LOGGER_DEBUG("[Call: %d] Request timed out!", call->call_idx);
+
+ invoke_callback(session, call_index, MSI_OnRequestTimeout);
+ }
+
+ if ( call && call->session ) {
+
+ /* TODO: Cancel all? */
+ /* uint16_t _it = 0;
+ * for ( ; _it < _session->call->peer_count; _it++ ) */
+ msi_cancel ( call->session, call->call_idx, call->peers [0], "Request timed out" );
+ /*terminate_call(call->session, call);*/
+ }
+
+ free(arg);
+ pthread_exit(NULL);
+}
+
+
+/********** Request handlers **********/
+static int handle_recv_invite ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ LOGGER_DEBUG("Session: %p Handling 'invite' on call: %d", session, call ? call->call_idx : -1);
+
+ pthread_mutex_lock(&session->mutex);
+
+ if (!msg->csettings.exists) {/**/
+ LOGGER_WARNING("Peer sent invalid codec settings!");
+ send_error ( session, call, error_no_callid, msg->friend_id );
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+
+ if ( call ) {
+ if ( call->peers[0] == (uint32_t)msg->friend_id ) {
+ if (call->state == call_inviting) {
+ /* The glare case. A calls B when at the same time
+ * B calls A. Who has advantage is set bey calculating
+ * 'bigger' Call id and then that call id is being used in
+ * future. User with 'bigger' Call id has the advantage
+ * as in he will wait the response from the other.
+ */
+ LOGGER_DEBUG("Glare case; Peer: %d", call->peers[0]);
+
+ if ( call_id_bigger (call->id, msg->callid.value) == 1 ) { /* Peer has advantage */
+
+ /* Terminate call; peer will timeout(call) if call initialization fails */
+ terminate_call(session, call);
+
+ call = init_call ( session, 1, 0 );
+
+ if ( !call ) {
+ pthread_mutex_unlock(&session->mutex);
+ LOGGER_ERROR("Starting call");
+ return 0;
+ }
+
+ } else {
+ pthread_mutex_unlock(&session->mutex);
+ return 0; /* Wait for ringing from peer */
+ }
+ } else if (call->state == call_active) {
+ /* Request for media change; call callback and send starting response */
+ if (flush_peer_csettings(call, msg, 0) != 0) { /**/
+ LOGGER_WARNING("Peer sent invalid csetting!");
+ send_error ( session, call, error_no_callid, msg->friend_id );
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+
+ LOGGER_DEBUG("Set new call type: %s", call->csettings_peer[0].call_type == type_audio ? "audio" : "video");
+ send_reponse(session, call, starting, msg->friend_id);
+ pthread_mutex_unlock(&session->mutex);
+ invoke_callback(session, call->call_idx, MSI_OnMediaChange);
+ return 1;
+ }
+ } else {
+ send_error ( session, call, error_busy, msg->friend_id ); /* TODO: Ugh*/
+ terminate_call(session, call);
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+ } else {
+ call = init_call ( session, 1, 0 );
+
+ if ( !call ) {
+ pthread_mutex_unlock(&session->mutex);
+ LOGGER_ERROR("Starting call");
+ return 0;
+ }
+ }
+
+ if ( !msg->callid.exists ) {
+ send_error ( session, call, error_no_callid, msg->friend_id );
+ terminate_call(session, call);
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+
+ memcpy ( call->id, msg->callid.value, sizeof(msg->callid.value) );
+ call->state = call_starting;
+
+ add_peer( call, msg->friend_id);
+
+ flush_peer_csettings ( call, msg, 0 );
+
+ send_reponse(session, call, ringing, msg->friend_id);
+
+ pthread_mutex_unlock(&session->mutex);
+
+ invoke_callback(session, call->call_idx, MSI_OnInvite);
+
+ return 1;
+}
+
+static int handle_recv_start ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ (void)msg;
+
+ LOGGER_DEBUG("Session: %p Handling 'start' on call: %d, friend id: %d", session, call->call_idx, msg->friend_id );
+
+ pthread_mutex_lock(&session->mutex);
+
+ call->state = call_active;
+
+ pthread_mutex_unlock(&session->mutex);
+
+ invoke_callback(session, call->call_idx, MSI_OnStart);
+ return 1;
+}
+
+static int handle_recv_reject ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ LOGGER_DEBUG("Session: %p Handling 'reject' on call: %u", session, call->call_idx);
+
+ invoke_callback(session, call->call_idx, MSI_OnReject);
+
+ pthread_mutex_lock(&session->mutex);
+
+ send_reponse(session, call, ending, msg->friend_id);
+ terminate_call(session, call);
+
+ pthread_mutex_unlock(&session->mutex);
+
+ return 1;
+}
+
+static int handle_recv_cancel ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ (void)msg;
+
+ LOGGER_DEBUG("Session: %p Handling 'cancel' on call: %u", session, call->call_idx);
+
+ invoke_callback(session, call->call_idx, MSI_OnCancel);
+
+ pthread_mutex_lock(&session->mutex);
+
+ terminate_call ( session, call );
+
+ pthread_mutex_unlock(&session->mutex);
+
+ return 1;
+}
+
+static int handle_recv_end ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ LOGGER_DEBUG("Session: %p Handling 'end' on call: %d", session, call->call_idx);
+
+ invoke_callback(session, call->call_idx, MSI_OnEnd);
+ pthread_mutex_lock(&session->mutex);
+
+ send_reponse(session, call, ending, msg->friend_id);
+ terminate_call ( session, call );
+
+ pthread_mutex_unlock(&session->mutex);
+
+
+ return 1;
+}
+
+/********** Response handlers **********/
+static int handle_recv_ringing ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ (void)msg;
+
+ pthread_mutex_lock(&session->mutex);
+
+ if ( call->ringing_timer_id ) {
+ LOGGER_WARNING("Call already ringing");
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+
+ LOGGER_DEBUG("Session: %p Handling 'ringing' on call: %d", session, call->call_idx );
+
+ call->ringing_timer_id = timer_alloc ( session->timer_handler, handle_timeout, session, call->call_idx,
+ call->ringing_tout_ms );
+
+ pthread_mutex_unlock(&session->mutex);
+
+ invoke_callback(session, call->call_idx, MSI_OnRinging);
+ return 1;
+}
+static int handle_recv_starting ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'starting' on non-existing call");
+ return 0;
+ }
+
+ pthread_mutex_lock(&session->mutex);
+
+ if ( call->state == call_active ) { /* Change media */
+
+ LOGGER_DEBUG("Session: %p Changing media on call: %d", session, call->call_idx );
+ pthread_mutex_unlock(&session->mutex);
+
+ invoke_callback(session, call->call_idx, MSI_OnMediaChange);
+
+ } else if ( call->state == call_inviting ) {
+ LOGGER_DEBUG("Session: %p Handling 'starting' on call: %d", session, call->call_idx );
+
+ call->state = call_active;
+
+ MSIMessage *msg_start = msi_new_message ( TypeRequest, start );
+ send_message ( session, call, msg_start, msg->friend_id );
+ free ( msg_start );
+
+
+ flush_peer_csettings ( call, msg, 0 );
+
+ /* This is here in case of glare */
+ timer_release ( session->timer_handler, call->ringing_timer_id, 1 );
+
+ pthread_mutex_unlock(&session->mutex);
+
+ invoke_callback(session, call->call_idx, MSI_OnStarting);
+ } else {
+ LOGGER_ERROR("Invalid call state");
+ terminate_call(session, call );
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+ }
+
+ return 1;
+}
+static int handle_recv_ending ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+ if ( !call ) {
+ LOGGER_WARNING("Session: %p Handling 'start' on no call");
+ return 0;
+ }
+
+ (void)msg;
+
+ LOGGER_DEBUG("Session: %p Handling 'ending' on call: %d", session, call->call_idx );
+
+ invoke_callback(session, call->call_idx, MSI_OnEnding);
+
+ /* Terminate call */
+ pthread_mutex_lock(&session->mutex);
+ terminate_call ( session, call );
+ pthread_mutex_unlock(&session->mutex);
+
+ return 1;
+}
+static int handle_recv_error ( MSISession *session, MSICall *call, MSIMessage *msg )
+{
+
+ if ( !call ) {
+ LOGGER_WARNING("Handling 'error' on non-existing call!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ LOGGER_DEBUG("Session: %p Handling 'error' on call: %d", session, call->call_idx );
+
+ invoke_callback(session, call->call_idx, MSI_OnEnding);
+
+ pthread_mutex_lock(&session->mutex);
+
+ /* Handle error accordingly */
+ if ( msg->reason.exists ) {
+ /* TODO */
+ }
+
+ terminate_call ( session, call );
+
+ pthread_mutex_unlock(&session->mutex);
+
+ return 1;
+}
+
+
+/**
+ * @brief BASIC call flow:
+ *
+ * ALICE BOB
+ * | invite --> |
+ * | |
+ * | <-- ringing |
+ * | |
+ * | <-- starting |
+ * | |
+ * | start --> |
+ * | |
+ * | <-- MEDIA TRANS --> |
+ * | |
+ * | end --> |
+ * | |
+ * | <-- ending |
+ *
+ * Alice calls Bob by sending invite packet.
+ * Bob recvs the packet and sends an ringing packet;
+ * which notifies Alice that her invite is acknowledged.
+ * Ringing screen shown on both sides.
+ * Bob accepts the invite for a call by sending starting packet.
+ * Alice recvs the starting packet and sends the started packet to
+ * inform Bob that she recved the starting packet.
+ * Now the media transmission is established ( i.e. RTP transmission ).
+ * Alice hangs up and sends end packet.
+ * Bob recves the end packet and sends ending packet
+ * as the acknowledgement that the call is ending.
+ *
+ *
+ */
+static void msi_handle_packet ( Messenger *messenger, int source, const uint8_t *data, uint16_t length, void *object )
+{
+ LOGGER_DEBUG("Got msi message");
+ /* Unused */
+ (void)messenger;
+
+ MSISession *session = object;
+ MSIMessage *msg;
+
+ if ( !length ) {
+ LOGGER_WARNING("Lenght param negative");
+ return;
+ }
+
+ msg = parse_recv ( data, length );
+
+ if ( !msg ) {
+ LOGGER_WARNING("Error parsing message");
+ return;
+ } else {
+ LOGGER_DEBUG("Successfully parsed message");
+ }
+
+ msg->friend_id = source;
+
+
+ /* Find what call */
+ MSICall *call = msg->callid.exists ? find_call(session, msg->callid.value ) : NULL;
+
+ /* Now handle message */
+
+ if ( msg->request.exists ) { /* Handle request */
+
+ switch (msg->request.value) {
+ case invite:
+ handle_recv_invite ( session, call, msg );
+ break;
+
+ case start:
+ handle_recv_start ( session, call, msg );
+ break;
+
+ case cancel:
+ handle_recv_cancel ( session, call, msg );
+ break;
+
+ case reject:
+ handle_recv_reject ( session, call, msg );
+ break;
+
+ case end:
+ handle_recv_end ( session, call, msg );
+ break;
+ }
+
+ } else if ( msg->response.exists ) { /* Handle response */
+
+ /* Got response so cancel timer */
+ if ( call ) timer_release ( session->timer_handler, call->request_timer_id, 1 );
+
+ switch (msg->response.value) {
+ case ringing:
+ handle_recv_ringing ( session, call, msg );
+ break;
+
+ case starting:
+ handle_recv_starting ( session, call, msg );
+ break;
+
+ case ending:
+ handle_recv_ending ( session, call, msg );
+ break;
+
+ case error:
+ handle_recv_error ( session, call, msg );
+ break;
+ }
+
+ } else {
+ LOGGER_WARNING("Invalid message: no resp nor requ headers");
+ }
+
+ free ( msg );
+}
+
+
+/**
+ * @brief Callback setter.
+ *
+ * @param callback The callback.
+ * @param id The id.
+ * @return void
+ */
+void msi_register_callback ( MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata )
+{
+ session->callbacks[id].function = callback;
+ session->callbacks[id].data = userdata;
+}
+
+
+/**
+ * @brief Start the control session.
+ *
+ * @param messenger Tox* object.
+ * @param max_calls Amount of calls possible
+ * @return MSISession* The created session.
+ * @retval NULL Error occurred.
+ */
+MSISession *msi_init_session ( Messenger *messenger, int32_t max_calls )
+{
+ if (messenger == NULL) {
+ LOGGER_ERROR("Could not init session on empty messenger!");
+ return NULL;
+ }
+
+ TimerHandler *handler = timer_init_session(max_calls * 10, 10000);
+
+ if ( !max_calls || !handler ) {
+ LOGGER_WARNING("Invalid max call treshold or timer handler initialization failed!");
+ return NULL;
+ }
+
+ MSISession *retu = calloc ( sizeof ( MSISession ), 1 );
+
+ if (retu == NULL) {
+ LOGGER_ERROR("Allocation failed! Program might misbehave!");
+ return NULL;
+ }
+
+ retu->messenger_handle = messenger;
+ retu->agent_handler = NULL;
+ retu->timer_handler = handler;
+
+ if (!(retu->calls = calloc( sizeof (MSICall *), max_calls ))) {
+ LOGGER_ERROR("Allocation failed! Program might misbehave!");
+ free(retu);
+ return NULL;
+ }
+
+ retu->max_calls = max_calls;
+
+ retu->frequ = 10000; /* default value? */
+ retu->call_timeout = 30000; /* default value? */
+
+
+ m_callback_msi_packet(messenger, msi_handle_packet, retu );
+
+ /* This is called when remote terminates session */
+ m_callback_connectionstatus_internal_av(messenger, handle_remote_connection_change, retu);
+
+ pthread_mutex_init(&retu->mutex, NULL);
+
+ LOGGER_DEBUG("New msi session: %p max calls: %u", retu, max_calls);
+ return retu;
+}
+
+
+/**
+ * @brief Terminate control session.
+ *
+ * @param session The session
+ * @return int
+ */
+int msi_terminate_session ( MSISession *session )
+{
+ if (session == NULL) {
+ LOGGER_ERROR("Tried to terminate non-existing session");
+ return -1;
+ }
+
+ pthread_mutex_lock(&session->mutex);
+ m_callback_msi_packet((struct Messenger *) session->messenger_handle, NULL, NULL);
+ pthread_mutex_unlock(&session->mutex);
+
+ int _status = 0;
+
+ /* If have calls, cancel them */
+ int32_t idx = 0;
+
+ for (; idx < session->max_calls; idx ++) if ( session->calls[idx] ) {
+ /* Cancel all? */
+ uint16_t _it = 0;
+ /*for ( ; _it < session->calls[idx]->peer_count; _it++ )
+ * FIXME: will not work on multiple peers, must cancel call for all peers
+ */
+ msi_cancel ( session, idx, session->calls[idx]->peers [_it], "MSI session terminated!" );
+ }
+
+ timer_terminate_session(session->timer_handler);
+
+ pthread_mutex_destroy(&session->mutex);
+
+ LOGGER_DEBUG("Terminated session: %p", session);
+ free ( session );
+ return _status;
+}
+
+
+/**
+ * @brief Send invite request to friend_id.
+ *
+ * @param session Control session.
+ * @param call_type Type of the call. Audio or Video(both audio and video)
+ * @param rngsec Ringing timeout.
+ * @param friend_id The friend.
+ * @return int
+ */
+int msi_invite ( MSISession *session, int32_t *call_index, MSICSettings csettings, uint32_t rngsec, uint32_t friend_id )
+{
+ pthread_mutex_lock(&session->mutex);
+
+ LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_id);
+
+
+ int i = 0;
+
+ for (; i < session->max_calls; i ++)
+ if (session->calls[i] && session->calls[i]->peers[0] == friend_id) {
+ LOGGER_ERROR("Already in a call with friend %d", friend_id);
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+
+ MSICall *call = init_call ( session, 1, rngsec ); /* Just one peer for now */
+
+ if ( !call ) {
+ pthread_mutex_unlock(&session->mutex);
+ LOGGER_ERROR("Cannot handle more calls");
+ return -1;
+ }
+
+ *call_index = call->call_idx;
+
+ t_randomstr ( call->id, sizeof(call->id) );
+
+ add_peer ( call, friend_id );
+
+ call->csettings_local = csettings;
+
+ MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite );
+
+ msi_msg_set_csettings(msg_invite, &csettings);
+ send_message ( session, call, msg_invite, friend_id );
+ free( msg_invite );
+
+ call->state = call_inviting;
+
+ call->request_timer_id = timer_alloc ( session->timer_handler, handle_timeout, session, call->call_idx, m_deftout );
+
+ LOGGER_DEBUG("Invite sent");
+
+ pthread_mutex_unlock(&session->mutex);
+
+ return 0;
+}
+
+
+/**
+ * @brief Hangup active call.
+ *
+ * @param session Control session.
+ * @param call_id To which call is this action handled.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+int msi_hangup ( MSISession *session, int32_t call_index )
+{
+ pthread_mutex_lock(&session->mutex);
+ LOGGER_DEBUG("Session: %p Hanging up call: %u", session, call_index);
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ LOGGER_ERROR("Invalid call index!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ if ( session->calls[call_index]->state != call_active ) {
+ LOGGER_ERROR("No call with such index or call is not active!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSIMessage *msg_end = msi_new_message ( TypeRequest, end );
+
+ /* hangup for each peer */
+ int it = 0;
+
+ for ( ; it < session->calls[call_index]->peer_count; it ++ )
+ send_message ( session, session->calls[call_index], msg_end, session->calls[call_index]->peers[it] );
+
+ session->calls[call_index]->state = call_hanged_up;
+
+ free ( msg_end );
+
+ session->calls[call_index]->request_timer_id =
+ timer_alloc ( session->timer_handler, handle_timeout, session, call_index, m_deftout );
+
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+}
+
+
+/**
+ * @brief Answer active call request.
+ *
+ * @param session Control session.
+ * @param call_id To which call is this action handled.
+ * @param call_type Answer with Audio or Video(both).
+ * @return int
+ */
+int msi_answer ( MSISession *session, int32_t call_index, MSICSettings csettings )
+{
+ pthread_mutex_lock(&session->mutex);
+ LOGGER_DEBUG("Session: %p Answering call: %u", session, call_index);
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ LOGGER_ERROR("Invalid call index!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSIMessage *msg_starting = msi_new_message ( TypeResponse, starting );
+
+ session->calls[call_index]->csettings_local = csettings;
+
+ msi_msg_set_csettings(msg_starting, &csettings);
+
+ send_message ( session, session->calls[call_index], msg_starting, session->calls[call_index]->peers[0] );
+ free ( msg_starting );
+
+ session->calls[call_index]->state = call_active;
+
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+}
+
+
+/**
+ * @brief Cancel request.
+ *
+ * @param session Control session.
+ * @param call_id To which call is this action handled.
+ * @param reason Set optional reason header. Pass NULL if none.
+ * @return int
+ */
+int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason )
+{
+ pthread_mutex_lock(&session->mutex);
+ LOGGER_DEBUG("Session: %p Canceling call: %u; reason: %s", session, call_index, reason ? reason : "Unknown");
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ LOGGER_ERROR("Invalid call index!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSIMessage *msg_cancel = msi_new_message ( TypeRequest, cancel );
+
+ /* FIXME */
+#if 0
+
+ if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) {
+ MSIReasonStrType reason_cast;
+ memset(reason_cast, '\0', sizeof(MSIReasonStrType));
+ memcpy(reason_cast, reason, strlen(reason));
+ msi_msg_set_reason(msg_cancel, reason_cast);
+ }
+
+#else
+ (void)reason;
+
+#endif
+
+ send_message ( session, session->calls[call_index], msg_cancel, peer );
+ free ( msg_cancel );
+
+ terminate_call ( session, session->calls[call_index] );
+ pthread_mutex_unlock(&session->mutex);
+
+ return 0;
+}
+
+
+/**
+ * @brief Reject request.
+ *
+ * @param session Control session.
+ * @param call_id To which call is this action handled.
+ * @return int
+ */
+int msi_reject ( MSISession *session, int32_t call_index, const char *reason )
+{
+ pthread_mutex_lock(&session->mutex);
+ LOGGER_DEBUG("Session: %p Rejecting call: %u; reason: %s", session, call_index, reason ? reason : "Unknown");
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ LOGGER_ERROR("Invalid call index!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSIMessage *msg_reject = msi_new_message ( TypeRequest, reject );
+
+ /* FIXME */
+#if 0
+
+ if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) {
+ MSIReasonStrType reason_cast;
+ memset(reason_cast, '\0', sizeof(MSIReasonStrType));
+ memcpy(reason_cast, reason, strlen(reason));
+ msi_msg_set_reason(msg_reject, reason_cast);
+ }
+
+#else
+ (void)reason;
+
+#endif
+
+ send_message ( session, session->calls[call_index], msg_reject,
+ session->calls[call_index]->peers[session->calls[call_index]->peer_count - 1] );
+ free ( msg_reject );
+
+ session->calls[call_index]->state = call_hanged_up;
+ session->calls[call_index]->request_timer_id =
+ timer_alloc ( session->timer_handler, handle_timeout, session, call_index, m_deftout );
+
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+}
+
+
+/**
+ * @brief Send invite request to friend_id.
+ *
+ * @param session Control session.
+ * @param call_index Call index.
+ * @param call_type Type of the call. Audio or Video(both audio and video)
+ * @param rngsec Ringing timeout.
+ * @param friend_id The friend.
+ * @return int
+ */
+int msi_change_csettings(MSISession *session, int32_t call_index, MSICSettings csettings)
+{
+ pthread_mutex_lock(&session->mutex);
+
+ LOGGER_DEBUG("Changing media on call: %d", call_index);
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ LOGGER_ERROR("Invalid call index!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSICall *call = session->calls[call_index];
+
+ if ( call->state != call_active ) {
+ LOGGER_ERROR("Call is not active!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ MSICSettings *local = &call->csettings_local;
+
+ if (
+ local->call_type == csettings.call_type &&
+ local->video_bitrate == csettings.video_bitrate &&
+ local->max_video_width == csettings.max_video_width &&
+ local->max_video_height == csettings.max_video_height &&
+ local->audio_bitrate == csettings.audio_bitrate &&
+ local->audio_frame_duration == csettings.audio_frame_duration &&
+ local->audio_sample_rate == csettings.audio_sample_rate &&
+ local->audio_channels == csettings.audio_channels ) {
+ LOGGER_ERROR("Call is already set accordingly!");
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ *local = csettings;
+
+ MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite );
+
+ msi_msg_set_csettings ( msg_invite, local );
+ send_message ( session, call, msg_invite, call->peers[0] );
+ free ( msg_invite );
+
+ LOGGER_DEBUG("Request for media change sent");
+
+ pthread_mutex_unlock(&session->mutex);
+
+ return 0;
+}
+
+
+/**
+ * @brief Terminate the current call.
+ *
+ * @param session Control session.
+ * @param call_id To which call is this action handled.
+ * @return int
+ */
+int msi_stopcall ( MSISession *session, int32_t call_index )
+{
+ pthread_mutex_lock(&session->mutex);
+ LOGGER_DEBUG("Session: %p Stopping call index: %u", session, call_index);
+
+ if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) {
+ pthread_mutex_unlock(&session->mutex);
+ return -1;
+ }
+
+ /* just terminate it */
+
+ terminate_call ( session, session->calls[call_index] );
+
+ pthread_mutex_unlock(&session->mutex);
+ return 0;
+}
diff --git a/protocols/Tox/toxcore/toxav/msi.h b/protocols/Tox/toxcore/toxav/msi.h
new file mode 100644
index 0000000000..64fa08819a
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/msi.h
@@ -0,0 +1,267 @@
+/** msi.h
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __TOXMSI
+#define __TOXMSI
+
+#include <inttypes.h>
+#include <pthread.h>
+
+#include "../toxcore/Messenger.h"
+
+typedef uint8_t MSICallIDType[12];
+typedef uint8_t MSIReasonStrType[255];
+typedef void ( *MSICallbackType ) ( void *agent, int32_t call_idx, void *arg );
+
+
+/**
+ * @brief Call type identifier. Also used as rtp callback prefix.
+ */
+typedef enum {
+ type_audio = 192,
+ type_video
+} MSICallType;
+
+
+/**
+ * @brief Call state identifiers.
+ */
+typedef enum {
+ call_inviting, /* when sending call invite */
+ call_starting, /* when getting call invite */
+ call_active,
+ call_hold,
+ call_hanged_up
+
+} MSICallState;
+
+
+/**
+ * @brief Encoding settings.
+ */
+typedef struct _MSICodecSettings {
+ MSICallType call_type;
+
+ uint32_t video_bitrate; /* In kbits/s */
+ uint16_t max_video_width; /* In px */
+ uint16_t max_video_height; /* In px */
+
+ uint32_t audio_bitrate; /* In bits/s */
+ uint16_t audio_frame_duration; /* In ms */
+ uint32_t audio_sample_rate; /* In Hz */
+ uint32_t audio_channels;
+} MSICSettings;
+
+
+/**
+ * @brief Callbacks ids that handle the states
+ */
+typedef enum {
+ /* Requests */
+ MSI_OnInvite,
+ MSI_OnStart,
+ MSI_OnCancel,
+ MSI_OnReject,
+ MSI_OnEnd,
+
+ /* Responses */
+ MSI_OnRinging,
+ MSI_OnStarting,
+ MSI_OnEnding,
+
+ /* Protocol */
+ MSI_OnRequestTimeout,
+ MSI_OnPeerTimeout,
+ MSI_OnMediaChange
+} MSICallbackID;
+
+/**
+ * @brief Callbacks container
+ */
+typedef struct _MSICallbackCont {
+ MSICallbackType function;
+ void *data;
+} MSICallbackCont;
+
+/**
+ * @brief The call struct.
+ *
+ */
+typedef struct _MSICall { /* Call info structure */
+ struct _MSISession *session; /* Session pointer */
+
+ MSICallState state;
+
+ MSICSettings csettings_local; /* Local call settings */
+ MSICSettings *csettings_peer; /* Peers call settings */
+
+ MSICallIDType id; /* Random value identifying the call */
+
+ int ringing_tout_ms; /* Ringing timeout in ms */
+
+ int request_timer_id; /* Timer id for outgoing request/action */
+ int ringing_timer_id; /* Timer id for ringing timeout */
+
+
+ pthread_mutex_t mutex; /* */
+ uint32_t *peers;
+ uint16_t peer_count;
+
+ int32_t call_idx; /* Index of this call in MSISession */
+} MSICall;
+
+
+/**
+ * @brief Control session struct
+ *
+ */
+typedef struct _MSISession {
+
+ /* Call handlers */
+ MSICall **calls;
+ int32_t max_calls;
+
+ void *agent_handler;
+ Messenger *messenger_handle;
+
+ uint32_t frequ;
+ uint32_t call_timeout; /* Time of the timeout for some action to end; 0 if infinite */
+
+ pthread_mutex_t mutex;
+
+ void *timer_handler;
+ MSICallbackCont callbacks[11]; /* Callbacks used by this session */
+} MSISession;
+
+/**
+ * @brief Callback setter.
+ *
+ * @param session The container.
+ * @param callback The callback.
+ * @param id The id.
+ * @return void
+ */
+void msi_register_callback(MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata);
+
+
+/**
+ * @brief Start the control session.
+ *
+ * @param messenger Tox* object.
+ * @param max_calls Amount of calls possible
+ * @return MSISession* The created session.
+ * @retval NULL Error occurred.
+ */
+MSISession *msi_init_session ( Messenger *messenger, int32_t max_calls );
+
+
+/**
+ * @brief Terminate control session.
+ *
+ * @param session The session
+ * @return int
+ */
+int msi_terminate_session ( MSISession *session );
+
+
+/**
+ * @brief Send invite request to friend_id.
+ *
+ * @param session Control session.
+ * @param call_index Set to new call index.
+ * @param call_type Type of the call. Audio or Video(both audio and video)
+ * @param rngsec Ringing timeout.
+ * @param friend_id The friend.
+ * @return int
+ */
+int msi_invite ( MSISession *session, int32_t *call_index, MSICSettings csettings, uint32_t rngsec,
+ uint32_t friend_id );
+
+
+/**
+ * @brief Hangup active call.
+ *
+ * @param session Control session.
+ * @param call_index To which call is this action handled.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+int msi_hangup ( MSISession *session, int32_t call_index );
+
+
+/**
+ * @brief Answer active call request.
+ *
+ * @param session Control session.
+ * @param call_index To which call is this action handled.
+ * @param call_type Answer with Audio or Video(both).
+ * @return int
+ */
+int msi_answer ( MSISession *session, int32_t call_index, MSICSettings csettings );
+
+
+/**
+ * @brief Cancel request.
+ *
+ * @param session Control session.
+ * @param call_index To which call is this action handled.
+ * @param peer To which peer.
+ * @param reason Set optional reason header. Pass NULL if none.
+ * @return int
+ */
+int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason );
+
+
+/**
+ * @brief Reject request.
+ *
+ * @param session Control session.
+ * @param call_index To which call is this action handled.
+ * @param reason Set optional reason header. Pass NULL if none.
+ * @return int
+ */
+int msi_reject ( MSISession *session, int32_t call_index, const char *reason );
+
+
+/**
+ * @brief Send invite request to friend_id.
+ *
+ * @param session Control session.
+ * @param call_index Call index.
+ * @param call_type Type of the call. Audio or Video(both audio and video)
+ * @param rngsec Ringing timeout.
+ * @param friend_id The friend.
+ * @return int
+ */
+int msi_change_csettings ( MSISession *session, int32_t call_index, MSICSettings csettings );
+
+
+/**
+ * @brief Terminate the current call.
+ *
+ * @param session Control session.
+ * @param call_index To which call is this action handled.
+ * @return int
+ */
+int msi_stopcall ( MSISession *session, int32_t call_index );
+
+#endif /* __TOXMSI */
diff --git a/protocols/Tox/toxcore/toxav/rtp.c b/protocols/Tox/toxcore/toxav/rtp.c
new file mode 100644
index 0000000000..de6c9c418c
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/rtp.c
@@ -0,0 +1,600 @@
+/** rtp.c
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "../toxcore/logger.h"
+#include "../toxcore/util.h"
+
+#include "rtp.h"
+#include <stdlib.h>
+void toxav_handle_packet(RTPSession *_session, RTPMessage *_msg);
+
+#define size_32 4
+
+#define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0)
+#define ADD_FLAG_PADDING(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xDF; ( _h->flags ) |= ( ( ( _v ) << 5 ) & 0x20 ); } while(0)
+#define ADD_FLAG_EXTENSION(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xEF;( _h->flags ) |= ( ( ( _v ) << 4 ) & 0x10 ); } while(0)
+#define ADD_FLAG_CSRCC(_h, _v) do { ( _h->flags ) &= 0xF0; ( _h->flags ) |= ( ( _v ) & 0x0F ); } while(0)
+#define ADD_SETTING_MARKER(_h, _v) do { if ( _v > 1 ) _v = 1; ( _h->marker_payloadt ) &= 0x7F; ( _h->marker_payloadt ) |= ( ( ( _v ) << 7 ) /*& 0x80 */ ); } while(0)
+#define ADD_SETTING_PAYLOAD(_h, _v) do { if ( _v > 127 ) _v = 127; ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0)
+
+#define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6)
+#define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5)
+#define GET_FLAG_EXTENSION(_h) (( _h->flags & 0x10 ) >> 4)
+#define GET_FLAG_CSRCC(_h) ( _h->flags & 0x0f )
+#define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7)
+#define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f)
+
+/**
+ * @brief Checks if message came in late.
+ *
+ * @param session Control session.
+ * @param msg The message.
+ * @return int
+ * @retval -1 The message came in order.
+ * @retval 0 The message came late.
+ */
+inline__ int check_late_message (RTPSession *session, RTPMessage *msg)
+{
+ /*
+ * Check Sequence number. If this new msg has lesser number then the session->rsequnum
+ * it shows that the message came in late. Also check timestamp to be 100% certain.
+ *
+ */
+ return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1;
+}
+
+
+/**
+ * @brief Extracts header from payload.
+ *
+ * @param payload The payload.
+ * @param length The size of payload.
+ * @return RTPHeader* Extracted header.
+ * @retval NULL Error occurred while extracting header.
+ */
+RTPHeader *extract_header ( const uint8_t *payload, int length )
+{
+ if ( !payload || !length ) {
+ LOGGER_WARNING("No payload to extract!");
+ return NULL;
+ }
+
+ RTPHeader *_retu = calloc(1, sizeof (RTPHeader));
+
+ if ( !_retu ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ return NULL;
+ }
+
+ bytes_to_U16(&_retu->sequnum, payload);
+
+ const uint8_t *_it = payload + 2;
+
+ _retu->flags = *_it;
+ ++_it;
+
+ /* This indicates if the first 2 bits are valid.
+ * Now it may happen that this is out of order but
+ * it cuts down chances of parsing some invalid value
+ */
+
+ if ( GET_FLAG_VERSION(_retu) != RTP_VERSION ) {
+ /* Deallocate */
+ LOGGER_WARNING("Invalid version!");
+ free(_retu);
+ return NULL;
+ }
+
+ /*
+ * Added a check for the size of the header little sooner so
+ * I don't need to parse the other stuff if it's bad
+ */
+ uint8_t _cc = GET_FLAG_CSRCC ( _retu );
+ int _length = 12 /* Minimum header len */ + ( _cc * 4 );
+
+ if ( length < _length ) {
+ /* Deallocate */
+ LOGGER_WARNING("Length invalid!");
+ free(_retu);
+ return NULL;
+ }
+
+ memset(_retu->csrc, 0, 16 * sizeof (uint32_t));
+
+ _retu->marker_payloadt = *_it;
+ ++_it;
+ _retu->length = _length;
+
+
+ bytes_to_U32(&_retu->timestamp, _it);
+ _it += 4;
+ bytes_to_U32(&_retu->ssrc, _it);
+
+ uint8_t _x;
+
+ for ( _x = 0; _x < _cc; _x++ ) {
+ _it += 4;
+ bytes_to_U32(&(_retu->csrc[_x]), _it);
+ }
+
+ return _retu;
+}
+
+/**
+ * @brief Extracts external header from payload. Must be called AFTER extract_header()!
+ *
+ * @param payload The ITERATED payload.
+ * @param length The size of payload.
+ * @return RTPExtHeader* Extracted extension header.
+ * @retval NULL Error occurred while extracting extension header.
+ */
+RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length )
+{
+ const uint8_t *_it = payload;
+
+ RTPExtHeader *_retu = calloc(1, sizeof (RTPExtHeader));
+
+ if ( !_retu ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ return NULL;
+ }
+
+ uint16_t _ext_length;
+ bytes_to_U16(&_ext_length, _it);
+ _it += 2;
+
+
+ if ( length < ( _ext_length * sizeof(uint32_t) ) ) {
+ LOGGER_WARNING("Length invalid!");
+ free(_retu);
+ return NULL;
+ }
+
+ _retu->length = _ext_length;
+ bytes_to_U16(&_retu->type, _it);
+ _it += 2;
+
+ if ( !(_retu->table = calloc(_ext_length, sizeof (uint32_t))) ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ free(_retu);
+ return NULL;
+ }
+
+ uint16_t _x;
+
+ for ( _x = 0; _x < _ext_length; _x++ ) {
+ _it += 4;
+ bytes_to_U32(&(_retu->table[_x]), _it);
+ }
+
+ return _retu;
+}
+
+/**
+ * @brief Adds header to payload. Make sure _payload_ has enough space.
+ *
+ * @param header The header.
+ * @param payload The payload.
+ * @return uint8_t* Iterated position.
+ */
+uint8_t *add_header ( RTPHeader *header, uint8_t *payload )
+{
+ uint8_t _cc = GET_FLAG_CSRCC ( header );
+
+ uint8_t *_it = payload;
+
+
+ /* Add sequence number first */
+ U16_to_bytes(_it, header->sequnum);
+ _it += 2;
+
+ *_it = header->flags;
+ ++_it;
+ *_it = header->marker_payloadt;
+ ++_it;
+
+
+ U32_to_bytes( _it, header->timestamp);
+ _it += 4;
+ U32_to_bytes( _it, header->ssrc);
+
+ uint8_t _x;
+
+ for ( _x = 0; _x < _cc; _x++ ) {
+ _it += 4;
+ U32_to_bytes( _it, header->csrc[_x]);
+ }
+
+ return _it + 4;
+}
+
+/**
+ * @brief Adds extension header to payload. Make sure _payload_ has enough space.
+ *
+ * @param header The header.
+ * @param payload The payload.
+ * @return uint8_t* Iterated position.
+ */
+uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload )
+{
+ uint8_t *_it = payload;
+
+ U16_to_bytes(_it, header->length);
+ _it += 2;
+ U16_to_bytes(_it, header->type);
+ _it -= 2; /* Return to 0 position */
+
+ if ( header->table ) {
+ uint16_t _x;
+
+ for ( _x = 0; _x < header->length; _x++ ) {
+ _it += 4;
+ U32_to_bytes(_it, header->table[_x]);
+ }
+ }
+
+ return _it + 4;
+}
+
+/**
+ * @brief Builds header from control session values.
+ *
+ * @param session Control session.
+ * @return RTPHeader* Created header.
+ */
+RTPHeader *build_header ( RTPSession *session )
+{
+ RTPHeader *_retu = calloc ( 1, sizeof (RTPHeader) );
+
+ if ( !_retu ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ return NULL;
+ }
+
+ ADD_FLAG_VERSION ( _retu, session->version );
+ ADD_FLAG_PADDING ( _retu, session->padding );
+ ADD_FLAG_EXTENSION ( _retu, session->extension );
+ ADD_FLAG_CSRCC ( _retu, session->cc );
+ ADD_SETTING_MARKER ( _retu, session->marker );
+ ADD_SETTING_PAYLOAD ( _retu, session->payload_type );
+
+ _retu->sequnum = session->sequnum;
+ _retu->timestamp = current_time_monotonic(); /* milliseconds */
+ _retu->ssrc = session->ssrc;
+
+ int i;
+
+ for ( i = 0; i < session->cc; i++ )
+ _retu->csrc[i] = session->csrc[i];
+
+ _retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 );
+
+ return _retu;
+}
+
+
+/**
+ * @brief Parses data into RTPMessage struct. Stores headers separately from the payload data
+ * and so the length variable is set accordingly. _sequnum_ argument is
+ * passed by the handle_packet() since it's parsed already.
+ *
+ * @param session Control session.
+ * @param sequnum Sequence number that's parsed from payload in handle_packet()
+ * @param data Payload data.
+ * @param length Payload size.
+ * @return RTPMessage*
+ * @retval NULL Error occurred.
+ */
+RTPMessage *msg_parse ( const uint8_t *data, int length )
+{
+ RTPMessage *_retu = calloc(1, sizeof (RTPMessage));
+
+ _retu->header = extract_header ( data, length ); /* It allocates memory and all */
+
+ if ( !_retu->header ) {
+ LOGGER_WARNING("Header failed to extract!");
+ free(_retu);
+ return NULL;
+ }
+
+ uint16_t _from_pos = _retu->header->length;
+ _retu->length = length - _from_pos;
+
+
+
+ if ( GET_FLAG_EXTENSION ( _retu->header ) ) {
+ _retu->ext_header = extract_ext_header ( data + _from_pos, length );
+
+ if ( _retu->ext_header ) {
+ _retu->length -= ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 );
+ _from_pos += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 );
+ } else { /* Error */
+ LOGGER_WARNING("Ext Header failed to extract!");
+ rtp_free_msg(NULL, _retu);
+ return NULL;
+ }
+ } else {
+ _retu->ext_header = NULL;
+ }
+
+ if ( length - _from_pos <= MAX_RTP_SIZE )
+ memcpy ( _retu->data, data + _from_pos, length - _from_pos );
+ else {
+ LOGGER_WARNING("Invalid length!");
+ rtp_free_msg(NULL, _retu);
+ return NULL;
+ }
+
+ _retu->next = NULL;
+
+ return _retu;
+}
+
+/**
+ * @brief Callback for networking core.
+ *
+ * @param object RTPSession object.
+ * @param ip_port Where the message comes from.
+ * @param data Message data.
+ * @param length Message length.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+int rtp_handle_packet ( void *object, const uint8_t *data, uint32_t length )
+{
+ RTPSession *_session = object;
+ RTPMessage *_msg;
+
+ if ( !_session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */
+ LOGGER_WARNING("No session or invalid length of received buffer!");
+ return -1;
+ }
+
+ _msg = msg_parse ( data + 1, length - 1 );
+
+ if ( !_msg ) {
+ LOGGER_WARNING("Could not parse message!");
+ return -1;
+ }
+
+ /* Check if message came in late */
+ if ( check_late_message(_session, _msg) < 0 ) { /* Not late */
+ _session->rsequnum = _msg->header->sequnum;
+ _session->timestamp = _msg->header->timestamp;
+ }
+
+ toxav_handle_packet(_session, _msg);
+
+ return 0;
+}
+
+
+
+/**
+ * @brief Stores headers and payload data in one container ( data )
+ * and the length is set accordingly. Returned message is used for sending _only_.
+ *
+ * @param session The control session.
+ * @param data Payload data to send ( This is what you pass ).
+ * @param length Size of the payload data.
+ * @return RTPMessage* Created message.
+ * @retval NULL Error occurred.
+ */
+RTPMessage *rtp_new_message ( RTPSession *session, const uint8_t *data, uint32_t length )
+{
+ if ( !session ) {
+ LOGGER_WARNING("No session!");
+ return NULL;
+ }
+
+ uint8_t *_from_pos;
+ RTPMessage *_retu = calloc(1, sizeof (RTPMessage));
+
+ if ( !_retu ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ return NULL;
+ }
+
+ /* Sets header values and copies the extension header in _retu */
+ _retu->header = build_header ( session ); /* It allocates memory and all */
+ _retu->ext_header = session->ext_header;
+
+
+ uint32_t _total_length = length + _retu->header->length + 1;
+
+ _retu->data[0] = session->prefix;
+
+ if ( _retu->ext_header ) {
+ _total_length += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 );
+
+ _from_pos = add_header ( _retu->header, _retu->data + 1 );
+ _from_pos = add_ext_header ( _retu->ext_header, _from_pos + 1 );
+ } else {
+ _from_pos = add_header ( _retu->header, _retu->data + 1 );
+ }
+
+ /*
+ * Parses the extension header into the message
+ * Of course if any
+ */
+
+ /* Appends _data on to _retu->_data */
+ memcpy ( _from_pos, data, length );
+
+ _retu->length = _total_length;
+
+ _retu->next = NULL;
+
+ return _retu;
+}
+
+
+/**
+ * @brief Sends data to _RTPSession::dest
+ *
+ * @param session The session.
+ * @param messenger Tox* object.
+ * @param data The payload.
+ * @param length Size of the payload.
+ * @return int
+ * @retval -1 On error.
+ * @retval 0 On success.
+ */
+int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length )
+{
+ RTPMessage *msg = rtp_new_message (session, data, length);
+
+ if ( !msg ) {
+ LOGGER_WARNING("No session!");
+ return -1;
+ }
+
+ if ( -1 == send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length) ) {
+ LOGGER_WARNING("Failed to send full packet! std error: %s", strerror(errno));
+ rtp_free_msg ( session, msg );
+ return -1;
+ }
+
+
+ /* Set sequ number */
+ session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1;
+ rtp_free_msg ( session, msg );
+
+ return 0;
+}
+
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param session The control session msg belongs to. You set it as NULL when freeing recved messages.
+ * Otherwise set it to session the message was created from.
+ * @param msg The message.
+ * @return void
+ */
+void rtp_free_msg ( RTPSession *session, RTPMessage *msg )
+{
+ if ( !session ) {
+ if ( msg->ext_header ) {
+ free ( msg->ext_header->table );
+ free ( msg->ext_header );
+ }
+ } else {
+ if ( msg->ext_header && session->ext_header != msg->ext_header ) {
+ free ( msg->ext_header->table );
+ free ( msg->ext_header );
+ }
+ }
+
+ free ( msg->header );
+ free ( msg );
+}
+
+/**
+ * @brief Must be called before calling any other rtp function. It's used
+ * to initialize RTP control session.
+ *
+ * @param payload_type Type of payload used to send. You can use values in toxmsi.h::MSICallType
+ * @param messenger Tox* object.
+ * @param friend_num Friend id.
+ * @return RTPSession* Created control session.
+ * @retval NULL Error occurred.
+ */
+RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num )
+{
+ RTPSession *_retu = calloc(1, sizeof(RTPSession));
+
+ if ( !_retu ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ return NULL;
+ }
+
+ if ( -1 == custom_lossy_packet_registerhandler(messenger, friend_num, payload_type, rtp_handle_packet, _retu)) {
+ LOGGER_ERROR("Error setting custom register handler for rtp session");
+ free(_retu);
+ return NULL;
+ }
+
+ LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num);
+
+ _retu->version = RTP_VERSION; /* It's always 2 */
+ _retu->padding = 0; /* If some additional data is needed about the packet */
+ _retu->extension = 0; /* If extension to header is needed */
+ _retu->cc = 1; /* Amount of contributors */
+ _retu->csrc = NULL; /* Container */
+ _retu->ssrc = random_int();
+ _retu->marker = 0;
+ _retu->payload_type = payload_type % 128;
+
+ _retu->dest = friend_num;
+
+ _retu->rsequnum = _retu->sequnum = 0;
+
+ _retu->ext_header = NULL; /* When needed allocate */
+
+
+ if ( !(_retu->csrc = calloc(1, sizeof (uint32_t))) ) {
+ LOGGER_WARNING("Alloc failed! Program might misbehave!");
+ free(_retu);
+ return NULL;
+ }
+
+ _retu->csrc[0] = _retu->ssrc; /* Set my ssrc to the list receive */
+
+ /* Also set payload type as prefix */
+ _retu->prefix = payload_type;
+
+ /*
+ *
+ */
+ return _retu;
+}
+
+
+/**
+ * @brief Terminate the session.
+ *
+ * @param session The session.
+ * @param messenger The messenger who owns the session
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+void rtp_terminate_session ( RTPSession *session, Messenger *messenger )
+{
+ if ( !session ) return;
+
+ custom_lossy_packet_registerhandler(messenger, session->dest, session->prefix, NULL, NULL);
+
+ free ( session->ext_header );
+ free ( session->csrc );
+
+ LOGGER_DEBUG("Terminated RTP session: %p", session);
+
+ /* And finally free session */
+ free ( session );
+
+}
diff --git a/protocols/Tox/toxcore/toxav/rtp.h b/protocols/Tox/toxcore/toxav/rtp.h
new file mode 100644
index 0000000000..d57c5ef720
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/rtp.h
@@ -0,0 +1,196 @@
+/** rtp.h
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __TOXRTP
+#define __TOXRTP
+
+#define RTP_VERSION 2
+#include <inttypes.h>
+#include <pthread.h>
+
+#include "../toxcore/util.h"
+#include "../toxcore/network.h"
+#include "../toxcore/net_crypto.h"
+#include "../toxcore/Messenger.h"
+
+#define MAX_SEQU_NUM 65535
+#define MAX_RTP_SIZE 65535
+
+/**
+ * @brief Standard rtp header
+ *
+ */
+
+typedef struct _RTPHeader {
+ uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */
+ uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */
+ uint16_t sequnum; /* Sequence Number */
+ uint32_t timestamp; /* Timestamp */
+ uint32_t ssrc; /* SSRC */
+ uint32_t csrc[16]; /* CSRC's table */
+ uint32_t length; /* Length of the header in payload string. */
+
+} RTPHeader;
+
+
+/**
+ * @brief Standard rtp extension header.
+ *
+ */
+typedef struct _RTPExtHeader {
+ uint16_t type; /* Extension profile */
+ uint16_t length; /* Number of extensions */
+ uint32_t *table; /* Extension's table */
+
+} RTPExtHeader;
+
+
+/**
+ * @brief Standard rtp message.
+ *
+ */
+typedef struct _RTPMessage {
+ RTPHeader *header;
+ RTPExtHeader *ext_header;
+
+ uint8_t data[MAX_RTP_SIZE];
+ uint32_t length;
+
+ struct _RTPMessage *next;
+} RTPMessage;
+
+
+/**
+ * @brief Our main session descriptor.
+ * It measures the session variables and controls
+ * the entire session. There are functions for manipulating
+ * the session so tend to use those instead of directly modifying
+ * session parameters.
+ *
+ */
+typedef struct _RTPSession {
+ uint8_t version;
+ uint8_t padding;
+ uint8_t extension;
+ uint8_t cc;
+ uint8_t marker;
+ uint8_t payload_type;
+ uint16_t sequnum; /* Set when sending */
+ uint16_t rsequnum; /* Check when recving msg */
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t *csrc;
+
+ /* If some additional data must be sent via message
+ * apply it here. Only by allocating this member you will be
+ * automatically placing it within a message.
+ */
+ RTPExtHeader *ext_header;
+
+ /* Msg prefix for core to know when recving */
+ uint8_t prefix;
+
+ int dest;
+ int32_t call_index;
+ struct _ToxAv *av;
+
+} RTPSession;
+
+
+/**
+ * @brief Release all messages held by session.
+ *
+ * @param session The session.
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+int rtp_release_session_recv ( RTPSession *session );
+
+
+/**
+ * @brief Call this to change queue limit
+ *
+ * @param session The session
+ * @param limit new limit
+ * @return void
+ */
+void rtp_queue_adjust_limit ( RTPSession *session, uint64_t limit );
+
+/**
+ * @brief Get's oldest message in the list.
+ *
+ * @param session Where the list is.
+ * @return RTPMessage* The message. You need to call rtp_msg_free() to free it.
+ * @retval NULL No messages in the list, or no list.
+ */
+RTPMessage *rtp_recv_msg ( RTPSession *session );
+
+
+/**
+ * @brief Sends msg to _RTPSession::dest
+ *
+ * @param session The session.
+ * @param msg The message
+ * @param messenger Tox* object.
+ * @return int
+ * @retval -1 On error.
+ * @retval 0 On success.
+ */
+int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length );
+
+
+/**
+ * @brief Speaks for it self.
+ *
+ * @param session The control session msg belongs to. It can be NULL.
+ * @param msg The message.
+ * @return void
+ */
+void rtp_free_msg ( RTPSession *session, RTPMessage *msg );
+
+/**
+ * @brief Must be called before calling any other rtp function. It's used
+ * to initialize RTP control session.
+ *
+ * @param payload_type Type of payload used to send. You can use values in toxmsi.h::MSICallType
+ * @param messenger Tox* object.
+ * @param friend_num Friend id.
+ * @return RTPSession* Created control session.
+ * @retval NULL Error occurred.
+ */
+RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num );
+
+
+/**
+ * @brief Terminate the session.
+ *
+ * @param session The session.
+ * @param messenger The messenger who owns the session
+ * @return int
+ * @retval -1 Error occurred.
+ * @retval 0 Success.
+ */
+void rtp_terminate_session ( RTPSession *session, Messenger *messenger );
+
+
+
+#endif /* __TOXRTP */
diff --git a/protocols/Tox/toxcore/toxav/toxav.c b/protocols/Tox/toxcore/toxav/toxav.c
new file mode 100644
index 0000000000..cd0ec70ed7
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/toxav.c
@@ -0,0 +1,1148 @@
+/** toxav.c
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+
+#define _GNU_SOURCE /* implicit declaration warning */
+
+#include "rtp.h"
+#include "codec.h"
+#include "msi.h"
+#include "toxav.h"
+
+#include "../toxcore/logger.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Assume 24 fps*/
+#define MAX_ENCODE_TIME_US ((1000 / 24) * 1000)
+#define MAX_DECODE_TIME_US 0
+
+#define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */
+#define VIDEOFRAME_PIECE_SIZE 0x500 /* 1.25 KiB*/
+#define VIDEOFRAME_HEADER_SIZE 0x2
+
+
+#define inline__ inline __attribute__((always_inline))
+
+/* call index invalid: true if invalid */
+#define cii(c_idx, session) (c_idx < 0 || c_idx >= session->max_calls)
+
+
+const ToxAvCSettings av_DefaultSettings = {
+ TypeAudio,
+
+ 500,
+ 1280,
+ 720,
+
+ 64000,
+ 20,
+ 48000,
+ 1
+};
+
+const uint32_t av_jbufdc = 3;
+const uint32_t av_VADd = 40;
+
+
+static const uint8_t audio_index = 0, video_index = 1;
+
+typedef struct {
+ uint32_t size;
+ uint8_t data[0];
+} DECODE_PACKET;
+
+#define VIDEO_DECODE_QUEUE_SIZE 2
+#define AUDIO_DECODE_QUEUE_SIZE 16
+
+typedef struct _CallSpecific {
+ RTPSession *crtps[2]; /** Audio is first and video is second */
+ CodecState *cs;/** Each call have its own encoders and decoders.
+ * You can, but don't have to, reuse encoders for
+ * multiple calls. If you choose to reuse encoders,
+ * make sure to also reuse encoded payload for every call.
+ * Decoders have to be unique for each call. FIXME: Now add refcounted encoders and
+ * reuse them really.
+ */
+ JitterBuffer *j_buf; /** Jitter buffer for audio */
+
+ uint32_t frame_limit; /* largest address written to in frame_buf for current input frame*/
+ uint8_t frame_id, frame_outid; /* id of input and output video frame */
+ void *frame_buf; /* buffer for split video payloads */
+
+ _Bool call_active;
+ pthread_mutex_t mutex;
+
+ /* used in the "decode on another thread" system */
+ volatile _Bool exit, decoding;
+ uint8_t video_decode_read, video_decode_write, audio_decode_read, audio_decode_write;
+ pthread_mutex_t decode_cond_mutex;
+ pthread_cond_t decode_cond;
+ DECODE_PACKET *volatile video_decode_queue[VIDEO_DECODE_QUEUE_SIZE];
+ DECODE_PACKET *volatile audio_decode_queue[AUDIO_DECODE_QUEUE_SIZE];
+} CallSpecific;
+
+struct _ToxAv {
+ Messenger *messenger;
+ MSISession *msi_session; /** Main msi session */
+ CallSpecific *calls; /** Per-call params */
+
+ void (*audio_callback)(ToxAv *, int32_t, int16_t *, int, void *);
+ void (*video_callback)(ToxAv *, int32_t, vpx_image_t *, void *);
+
+ void *audio_callback_userdata;
+ void *video_callback_userdata;
+
+ uint32_t max_calls;
+};
+
+static void *toxav_decoding(void *arg);
+
+static MSICSettings msicsettings_cast (const ToxAvCSettings *from)
+{
+ MSICSettings csettings;
+ csettings.call_type = from->call_type;
+
+ csettings.video_bitrate = from->video_bitrate;
+ csettings.max_video_width = from->max_video_width;
+ csettings.max_video_height = from->max_video_height;
+
+ csettings.audio_bitrate = from->audio_bitrate;
+ csettings.audio_frame_duration = from->audio_frame_duration;
+ csettings.audio_sample_rate = from->audio_sample_rate;
+ csettings.audio_channels = from->audio_channels;
+
+ return csettings;
+}
+
+static ToxAvCSettings toxavcsettings_cast (const MSICSettings *from)
+{
+ ToxAvCSettings csettings;
+ csettings.call_type = from->call_type;
+
+ csettings.video_bitrate = from->video_bitrate;
+ csettings.max_video_width = from->max_video_width;
+ csettings.max_video_height = from->max_video_height;
+
+ csettings.audio_bitrate = from->audio_bitrate;
+ csettings.audio_frame_duration = from->audio_frame_duration;
+ csettings.audio_sample_rate = from->audio_sample_rate;
+ csettings.audio_channels = from->audio_channels;
+
+ return csettings;
+}
+
+/**
+ * @brief Start new A/V session. There can only be one session at the time. If you register more
+ * it will result in undefined behaviour.
+ *
+ * @param messenger The messenger handle.
+ * @param userdata The agent handling A/V session (i.e. phone).
+ * @param video_width Width of video frame.
+ * @param video_height Height of video frame.
+ * @return ToxAv*
+ * @retval NULL On error.
+ */
+ToxAv *toxav_new( Tox *messenger, int32_t max_calls)
+{
+ ToxAv *av = calloc ( sizeof(ToxAv), 1);
+
+ if (av == NULL) {
+ LOGGER_WARNING("Allocation failed!");
+ return NULL;
+ }
+
+ av->messenger = (Messenger *)messenger;
+ av->msi_session = msi_init_session(av->messenger, max_calls);
+ av->msi_session->agent_handler = av;
+ av->calls = calloc(sizeof(CallSpecific), max_calls);
+ av->max_calls = max_calls;
+
+ return av;
+}
+
+/**
+ * @brief Remove A/V session.
+ *
+ * @param av Handler.
+ * @return void
+ */
+void toxav_kill ( ToxAv *av )
+{
+ uint32_t i;
+
+ for (i = 0; i < av->max_calls; i ++) {
+ if ( av->calls[i].crtps[audio_index] )
+ rtp_terminate_session(av->calls[i].crtps[audio_index], av->msi_session->messenger_handle);
+
+
+ if ( av->calls[i].crtps[video_index] )
+ rtp_terminate_session(av->calls[i].crtps[video_index], av->msi_session->messenger_handle);
+
+
+
+ if ( av->calls[i].j_buf ) terminate_queue(av->calls[i].j_buf);
+
+ if ( av->calls[i].cs ) codec_terminate_session(av->calls[i].cs);
+ }
+
+ msi_terminate_session(av->msi_session);
+
+ free(av->calls);
+ free(av);
+}
+
+/**
+ * @brief Register callback for call state.
+ *
+ * @param av Handler.
+ * @param callback The callback
+ * @param id One of the ToxAvCallbackID values
+ * @return void
+ */
+void toxav_register_callstate_callback ( ToxAv *av, ToxAVCallback callback, ToxAvCallbackID id, void *userdata )
+{
+ msi_register_callback(av->msi_session, (MSICallbackType)callback, (MSICallbackID) id, userdata);
+}
+
+/**
+ * @brief Register callback for recieving audio data
+ *
+ * @param callback The callback
+ * @return void
+ */
+void toxav_register_audio_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, int16_t *, int, void *),
+ void *user_data)
+{
+ av->audio_callback = callback;
+ av->audio_callback_userdata = user_data;
+}
+
+/**
+ * @brief Register callback for recieving video data
+ *
+ * @param callback The callback
+ * @return void
+ */
+void toxav_register_video_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, vpx_image_t *, void *),
+ void *user_data)
+{
+ av->video_callback = callback;
+ av->video_callback_userdata = user_data;
+}
+
+/**
+ * @brief Call user. Use its friend_id.
+ *
+ * @param av Handler.
+ * @param user The user.
+ * @param call_type Call type.
+ * @param ringing_seconds Ringing timeout.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_call (ToxAv *av, int32_t *call_index, int user, const ToxAvCSettings *csettings, int ringing_seconds )
+{
+ return msi_invite(av->msi_session, call_index, msicsettings_cast(csettings), ringing_seconds * 1000, user);
+}
+
+/**
+ * @brief Hangup active call.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_hangup ( ToxAv *av, int32_t call_index )
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ if ( av->msi_session->calls[call_index]->state != call_active ) {
+ return ErrorInvalidState;
+ }
+
+ return msi_hangup(av->msi_session, call_index);
+}
+
+/**
+ * @brief Answer incomming call.
+ *
+ * @param av Handler.
+ * @param call_type Answer with...
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_answer ( ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings )
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ if ( av->msi_session->calls[call_index]->state != call_starting ) {
+ return ErrorInvalidState;
+ }
+
+ return msi_answer(av->msi_session, call_index, msicsettings_cast(csettings));
+}
+
+/**
+ * @brief Reject incomming call.
+ *
+ * @param av Handler.
+ * @param reason Optional reason. Set NULL if none.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_reject ( ToxAv *av, int32_t call_index, const char *reason )
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ if ( av->msi_session->calls[call_index]->state != call_starting ) {
+ return ErrorInvalidState;
+ }
+
+ return msi_reject(av->msi_session, call_index, reason);
+}
+
+/**
+ * @brief Cancel outgoing request.
+ *
+ * @param av Handler.
+ * @param reason Optional reason.
+ * @param peer_id peer friend_id
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_cancel ( ToxAv *av, int32_t call_index, int peer_id, const char *reason )
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ if ( av->msi_session->calls[call_index]->state != call_inviting ) {
+ return ErrorInvalidState;
+ }
+
+ return msi_cancel(av->msi_session, call_index, peer_id, reason);
+}
+
+/**
+ * @brief Notify peer that we are changing call type
+ *
+ * @param av Handler.
+ * @return int
+ * @param call_type Change to...
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings)
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ return msi_change_csettings(av->msi_session, call_index, msicsettings_cast(csettings));
+}
+
+/**
+ * @brief Terminate transmission. Note that transmission will be terminated without informing remote peer.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_stop_call ( ToxAv *av, int32_t call_index )
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) {
+ return ErrorNoCall;
+ }
+
+ return msi_stopcall(av->msi_session, call_index);
+}
+
+/**
+ * @brief Must be call before any RTP transmission occurs.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_prepare_transmission ( ToxAv *av, int32_t call_index, uint32_t jbuf_capacity, uint32_t VAD_treshold,
+ int support_video )
+{
+ if ( !av->msi_session || cii(call_index, av->msi_session) ||
+ !av->msi_session->calls[call_index] || !av->msi_session->calls[call_index]->csettings_peer ||
+ av->calls[call_index].call_active) {
+ LOGGER_ERROR("Error while starting RTP session: invalid call!\n");
+ return ErrorInternal;
+ }
+
+ CallSpecific *call = &av->calls[call_index];
+
+ call->crtps[audio_index] =
+ rtp_init_session(type_audio, av->messenger, av->msi_session->calls[call_index]->peers[0]);
+
+
+ if ( !call->crtps[audio_index] ) {
+ LOGGER_ERROR("Error while starting audio RTP session!\n");
+ return ErrorInternal;
+ }
+
+ call->crtps[audio_index]->call_index = call_index;
+ call->crtps[audio_index]->av = av;
+
+ if ( support_video ) {
+ call->crtps[video_index] =
+ rtp_init_session(type_video, av->messenger, av->msi_session->calls[call_index]->peers[0]);
+
+ if ( !call->crtps[video_index] ) {
+ LOGGER_ERROR("Error while starting video RTP session!\n");
+ goto error;
+ }
+
+ call->crtps[video_index]->call_index = call_index;
+ call->crtps[video_index]->av = av;
+
+ call->frame_limit = 0;
+ call->frame_id = 0;
+ call->frame_outid = 0;
+
+ call->frame_buf = calloc(MAX_VIDEOFRAME_SIZE, 1);
+
+ if (!call->frame_buf) {
+ LOGGER_WARNING("Frame buffer allocation failed!");
+ goto error;
+ }
+
+ }
+
+ if ( !(call->j_buf = create_queue(jbuf_capacity)) ) {
+ LOGGER_WARNING("Jitter buffer creaton failed!");
+ goto error;
+ }
+
+ ToxAvCSettings csettings_peer = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[0]);
+ ToxAvCSettings csettings_local = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_local);
+ LOGGER_DEBUG(
+ "Type: %u \n"
+ "Video bitrate: %u \n"
+ "Video height: %u \n"
+ "Video width: %u \n"
+ "Audio bitrate: %u \n"
+ "Audio framedur: %u \n"
+ "Audio sample rate: %u \n"
+ "Audio channels: %u \n",
+ csettings_peer.call_type,
+ csettings_peer.video_bitrate,
+ csettings_peer.max_video_height,
+ csettings_peer.max_video_width,
+ csettings_peer.audio_bitrate,
+ csettings_peer.audio_frame_duration,
+ csettings_peer.audio_sample_rate,
+ csettings_peer.audio_channels );
+
+ if ( (call->cs = codec_init_session(csettings_local.audio_bitrate,
+ csettings_local.audio_frame_duration,
+ csettings_local.audio_sample_rate,
+ csettings_local.audio_channels,
+ csettings_peer.audio_channels,
+ VAD_treshold,
+ csettings_local.max_video_width,
+ csettings_local.max_video_height,
+ csettings_local.video_bitrate) )) {
+
+ if ( pthread_mutex_init(&call->mutex, NULL) != 0 ) goto error;
+
+ //todo: add error checks
+ pthread_mutex_init(&call->decode_cond_mutex, NULL);
+ pthread_cond_init(&call->decode_cond, NULL);
+
+ void **arg = malloc(2 * sizeof(void *));
+ arg[0] = av;
+ arg[1] = call;
+
+ pthread_t temp;
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 1 << 18);
+ pthread_create(&temp, &attr, toxav_decoding, arg);
+ pthread_attr_destroy(&attr);
+
+
+ LOGGER_WARNING("Got here");
+ call->call_active = 1;
+
+ return ErrorNone;
+ }
+
+error:
+ rtp_terminate_session(call->crtps[audio_index], av->messenger);
+ rtp_terminate_session(call->crtps[video_index], av->messenger);
+ free(call->frame_buf);
+ terminate_queue(call->j_buf);
+ codec_terminate_session(call->cs);
+
+ return ErrorInternal;
+}
+
+/**
+ * @brief Call this at the end of the transmission.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_kill_transmission ( ToxAv *av, int32_t call_index )
+{
+ if (cii(call_index, av->msi_session)) {
+ LOGGER_WARNING("Invalid call index: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ CallSpecific *call = &av->calls[call_index];
+
+ pthread_mutex_lock(&call->mutex);
+
+ if (!call->call_active) {
+ pthread_mutex_unlock(&call->mutex);
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+
+ call->call_active = 0;
+
+ rtp_terminate_session(call->crtps[audio_index], av->messenger);
+ call->crtps[audio_index] = NULL;
+ rtp_terminate_session(call->crtps[video_index], av->messenger);
+ call->crtps[video_index] = NULL;
+ terminate_queue(call->j_buf);
+ call->j_buf = NULL;
+
+ int i;
+ DECODE_PACKET *p;
+
+ call->exit = 1;
+ pthread_mutex_lock(&call->decode_cond_mutex);
+ pthread_cond_signal(&call->decode_cond);
+ pthread_cond_wait(&call->decode_cond, &call->decode_cond_mutex);
+ pthread_mutex_unlock(&call->decode_cond_mutex);
+ pthread_mutex_destroy(&call->decode_cond_mutex);
+ pthread_cond_destroy(&call->decode_cond);
+
+ for (i = 0; i != VIDEO_DECODE_QUEUE_SIZE; i++) {
+ p = call->video_decode_queue[i];
+ call->video_decode_queue[i] = NULL;
+
+ if (p) {
+ free(p);
+ }
+ }
+
+ for (i = 0; i != AUDIO_DECODE_QUEUE_SIZE; i++) {
+ p = call->audio_decode_queue[i];
+ call->audio_decode_queue[i] = NULL;
+
+ if (p) {
+ free(p);
+ }
+ }
+
+ codec_terminate_session(call->cs);
+ call->cs = NULL;
+
+ pthread_mutex_unlock(&call->mutex);
+ pthread_mutex_destroy(&call->mutex);
+
+ memset(call, 0, sizeof(CallSpecific));
+ return ErrorNone;
+}
+
+
+/**
+ * @brief Send RTP payload.
+ *
+ * @param av Handler.
+ * @param type Type of payload.
+ * @param payload The payload.
+ * @param length Size of it.
+ * @return int
+ * @retval 0 Success.
+ * @retval -1 Failure.
+ */
+static int toxav_send_rtp_payload(ToxAv *av, int32_t call_index, ToxAvCallType type, const uint8_t *payload,
+ unsigned int length)
+{
+ CallSpecific *call = &av->calls[call_index];
+
+ if (call->crtps[type - TypeAudio]) {
+
+ if (type == TypeAudio) {
+ return rtp_send_msg(call->crtps[type - TypeAudio], av->messenger, payload, length);
+ } else {
+ if (length == 0 || length > MAX_VIDEOFRAME_SIZE) {
+ LOGGER_ERROR("Invalid video frame size: %u\n", length);
+ return ErrorInternal;
+ }
+
+ /* number of pieces - 1*/
+ uint8_t numparts = (length - 1) / VIDEOFRAME_PIECE_SIZE;
+
+ uint8_t load[2 + VIDEOFRAME_PIECE_SIZE];
+ load[0] = call->frame_outid++;
+ load[1] = 0;
+
+ int i;
+
+ for (i = 0; i < numparts; i++) {
+ memcpy(load + VIDEOFRAME_HEADER_SIZE, payload, VIDEOFRAME_PIECE_SIZE);
+ payload += VIDEOFRAME_PIECE_SIZE;
+
+ if (rtp_send_msg(call->crtps[type - TypeAudio], av->messenger,
+ load, VIDEOFRAME_HEADER_SIZE + VIDEOFRAME_PIECE_SIZE) != 0) {
+
+ return ErrorInternal;
+ }
+
+ load[1]++;
+ }
+
+ /* remainder = length % VIDEOFRAME_PIECE_SIZE, VIDEOFRAME_PIECE_SIZE if = 0 */
+ length = ((length - 1) % VIDEOFRAME_PIECE_SIZE) + 1;
+ memcpy(load + VIDEOFRAME_HEADER_SIZE, payload, length);
+
+ return rtp_send_msg(call->crtps[type - TypeAudio], av->messenger, load, VIDEOFRAME_HEADER_SIZE + length);
+ }
+ } else {
+ return ErrorNoRtpSession;
+ }
+}
+
+/**
+ * @brief Encode and send video packet.
+ *
+ * @param av Handler.
+ * @param input The packet.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size)
+{
+
+ if (cii(call_index, av->msi_session)) {
+ LOGGER_WARNING("Invalid call index: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ CallSpecific *call = &av->calls[call_index];
+ pthread_mutex_lock(&call->mutex);
+
+
+ if (!call->call_active) {
+ pthread_mutex_unlock(&call->mutex);
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ int rc = toxav_send_rtp_payload(av, call_index, TypeVideo, frame, frame_size);
+ pthread_mutex_unlock(&call->mutex);
+
+ return rc;
+}
+
+/**
+ * @brief Encode video frame
+ *
+ * @param av Handler
+ * @param dest Where to
+ * @param dest_max Max size
+ * @param input What to encode
+ * @return int
+ * @retval ToxAvError On error.
+ * @retval >0 On success
+ */
+int toxav_prepare_video_frame(ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input)
+{
+ if (cii(call_index, av->msi_session)) {
+ LOGGER_WARNING("Invalid call index: %d", call_index);
+ return ErrorNoCall;
+ }
+
+
+ CallSpecific *call = &av->calls[call_index];
+ pthread_mutex_lock(&call->mutex);
+
+ if (!call->call_active) {
+ pthread_mutex_unlock(&call->mutex);
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ if (reconfigure_video_encoder_resolution(call->cs, input->d_w, input->d_h) != 0) {
+ pthread_mutex_unlock(&call->mutex);
+ return ErrorInternal;
+ }
+
+ int rc = vpx_codec_encode(&call->cs->v_encoder, input, call->cs->frame_counter, 1, 0, MAX_ENCODE_TIME_US);
+
+ if ( rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(rc));
+ pthread_mutex_unlock(&call->mutex);
+ return ErrorInternal;
+ }
+
+ ++call->cs->frame_counter;
+
+ vpx_codec_iter_t iter = NULL;
+ const vpx_codec_cx_pkt_t *pkt;
+ int copied = 0;
+
+ while ( (pkt = vpx_codec_get_cx_data(&call->cs->v_encoder, &iter)) ) {
+ if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
+ if ( copied + pkt->data.frame.sz > dest_max ) {
+ pthread_mutex_unlock(&call->mutex);
+ return ErrorPacketTooLarge;
+ }
+
+ memcpy(dest + copied, pkt->data.frame.buf, pkt->data.frame.sz);
+ copied += pkt->data.frame.sz;
+ }
+ }
+
+ pthread_mutex_unlock(&call->mutex);
+ return copied;
+}
+
+/**
+ * @brief Send audio frame.
+ *
+ * @param av Handler.
+ * @param data The audio data encoded with toxav_prepare_audio_frame().
+ * @param size Its size in number of bytes.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *data, unsigned int size)
+{
+ if (size > MAX_CRYPTO_DATA_SIZE)
+ return ErrorInternal;
+
+ if (cii(call_index, av->msi_session) || !av->calls[call_index].call_active) {
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ CallSpecific *call = &av->calls[call_index];
+ pthread_mutex_lock(&call->mutex);
+
+
+ if (!call->call_active) {
+ pthread_mutex_unlock(&call->mutex);
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ int rc = toxav_send_rtp_payload(av, call_index, TypeAudio, data, size);
+ pthread_mutex_unlock(&call->mutex);
+
+ return rc;
+}
+
+/**
+ * @brief Encode audio frame
+ *
+ * @param av Handler
+ * @param dest dest
+ * @param dest_max Max dest size
+ * @param frame The frame
+ * @param frame_size The frame size
+ * @return int
+ * @retval ToxAvError On error.
+ * @retval >0 On success
+ */
+int toxav_prepare_audio_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, const int16_t *frame,
+ int frame_size)
+{
+ if (cii(call_index, av->msi_session) || !av->calls[call_index].call_active) {
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ CallSpecific *call = &av->calls[call_index];
+ pthread_mutex_lock(&call->mutex);
+
+
+ if (!call->call_active) {
+ pthread_mutex_unlock(&call->mutex);
+ LOGGER_WARNING("Action on inactive call: %d", call_index);
+ return ErrorNoCall;
+ }
+
+ int32_t rc = opus_encode(call->cs->audio_encoder, frame, frame_size, dest, dest_max);
+ pthread_mutex_unlock(&call->mutex);
+
+ if (rc < 0) {
+ LOGGER_ERROR("Failed to encode payload: %s\n", opus_strerror(rc));
+ return ErrorInternal;
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Get peer transmission type. It can either be audio or video.
+ *
+ * @param av Handler.
+ * @param peer The peer
+ * @return int
+ * @retval ToxAvCallType On success.
+ * @retval ToxAvError On error.
+ */
+int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest )
+{
+ if ( peer < 0 || cii(call_index, av->msi_session) || !av->msi_session->calls[call_index]
+ || av->msi_session->calls[call_index]->peer_count <= peer )
+ return ErrorInternal;
+
+ *dest = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[peer]);
+ return ErrorNone;
+}
+
+/**
+ * @brief Get id of peer participating in conversation
+ *
+ * @param av Handler
+ * @param peer peer index
+ * @return int
+ * @retval ToxAvError No peer id
+ */
+int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer )
+{
+ if ( peer < 0 || cii(call_index, av->msi_session) || !av->msi_session->calls[call_index]
+ || av->msi_session->calls[call_index]->peer_count <= peer )
+ return ErrorInternal;
+
+ return av->msi_session->calls[call_index]->peers[peer];
+}
+
+/**
+ * @brief Get id of peer participating in conversation
+ *
+ * @param av Handler
+ * @param peer peer index
+ * @return int
+ * @retval ToxAvError No peer id
+ */
+ToxAvCallState toxav_get_call_state(ToxAv *av, int32_t call_index)
+{
+ if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] )
+ return av_CallNonExistant;
+
+ return av->msi_session->calls[call_index]->state;
+
+}
+
+/**
+ * @brief Is certain capability supported
+ *
+ * @param av Handler
+ * @return int
+ * @retval 1 Yes.
+ * @retval 0 No.
+ */
+inline__ int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability )
+{
+ return av->calls[call_index].cs ? av->calls[call_index].cs->capabilities & (Capabilities) capability : 0;
+ /* 0 is error here */
+}
+
+inline__ Tox *toxav_get_tox(ToxAv *av)
+{
+ return (Tox *)av->messenger;
+}
+
+int toxav_has_activity(ToxAv *av, int32_t call_index, int16_t *PCM, uint16_t frame_size, float ref_energy)
+{
+ if ( !av->calls[call_index].cs ) return ErrorInvalidCodecState;
+
+ return energy_VAD(av->calls[call_index].cs, PCM, frame_size, ref_energy);
+}
+
+
+static void decode_video(ToxAv *av, CallSpecific *call, DECODE_PACKET *p)
+{
+ int32_t call_index = call - av->calls;
+
+ int rc = vpx_codec_decode(&call->cs->v_decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US);
+
+ if (rc != VPX_CODEC_OK) {
+ LOGGER_ERROR("Error decoding video: %s\n", vpx_codec_err_to_string(rc));
+ }
+
+ vpx_codec_iter_t iter = NULL;
+ vpx_image_t *img;
+ img = vpx_codec_get_frame(&call->cs->v_decoder, &iter);
+
+ if (img && av->video_callback) {
+ av->video_callback(av, call_index, img, av->video_callback_userdata);
+ } else {
+ LOGGER_WARNING("Video packet dropped due to missing callback or no image!");
+ }
+
+ free(p);
+}
+
+static void decode_audio(ToxAv *av, CallSpecific *call, DECODE_PACKET *p)
+{
+ int32_t call_index = call - av->calls;
+
+ // ToxAvCSettings csettings;
+ // toxav_get_peer_csettings(av, call_index, 0, &csettings);
+
+ int frame_size = 10000; /* FIXME: not static? */
+ int16_t dest[frame_size];
+
+ int dec_size = opus_decode(call->cs->audio_decoder, p->data, p->size, dest, frame_size, (p->size == 0));
+ free(p);
+
+ if (dec_size < 0) {
+ LOGGER_WARNING("Decoding error: %s", opus_strerror(dec_size));
+ return;
+ }
+
+ if ( av->audio_callback )
+ av->audio_callback(av, call_index, dest, dec_size, av->audio_callback_userdata);
+ else
+ LOGGER_WARNING("Audio packet dropped due to missing callback!");
+}
+
+static void *toxav_decoding(void *arg)
+{
+ void **pp = arg;
+ ToxAv *av = pp[0];
+ CallSpecific *call = pp[1];
+ free(pp);
+
+ while (1) {
+ DECODE_PACKET *p;
+ _Bool video = 0;
+
+ pthread_mutex_lock(&call->decode_cond_mutex);
+
+ if (call->exit) {
+ break;
+ }
+
+ uint8_t r;
+
+ /* first check for available packets, otherwise wait for condition*/
+ r = call->audio_decode_read;
+ p = call->audio_decode_queue[r];
+
+ if (!p) {
+ r = call->video_decode_read;
+ p = call->video_decode_queue[r];
+
+ if (!p) {
+ pthread_cond_wait(&call->decode_cond, &call->decode_cond_mutex);
+ r = call->audio_decode_read;
+ p = call->audio_decode_queue[r];
+
+ if (!p) {
+ r = call->video_decode_read;
+ p = call->video_decode_queue[r];
+ video = 1;
+ }
+ } else {
+ video = 1;
+ }
+ }
+
+ if (video) {
+ if (p) {
+ call->video_decode_queue[r] = NULL;
+ call->video_decode_read = (r + 1) % VIDEO_DECODE_QUEUE_SIZE;
+ }
+ } else {
+ call->audio_decode_queue[r] = NULL;
+ call->audio_decode_read = (r + 1) % AUDIO_DECODE_QUEUE_SIZE;
+ }
+
+ pthread_mutex_unlock(&call->decode_cond_mutex);
+
+ if (p) {
+ if (video) {
+ decode_video(av, call, p);
+ } else {
+ decode_audio(av, call, p);
+ }
+ }
+ }
+
+ call->exit = 0;
+ pthread_cond_signal(&call->decode_cond);
+ pthread_mutex_unlock(&call->decode_cond_mutex);
+
+ return NULL;
+}
+
+void toxav_handle_packet(RTPSession *_session, RTPMessage *_msg)
+{
+ ToxAv *av = _session->av;
+ int32_t call_index = _session->call_index;
+ CallSpecific *call = &av->calls[call_index];
+
+ if (!call->call_active) return;
+
+ if (_session->payload_type == type_audio % 128) {
+ queue(call->j_buf, _msg);
+
+ int success = 0;
+
+ while ((_msg = dequeue(call->j_buf, &success)) || success == 2) {
+ DECODE_PACKET *p;
+
+ if (success == 2) {
+ p = malloc(sizeof(DECODE_PACKET));
+
+ if (p) {
+ p->size = 0;
+ }
+ } else {
+ p = malloc(sizeof(DECODE_PACKET) + _msg->length);
+
+ if (p) {
+ p->size = _msg->length;
+ memcpy(p->data, _msg->data, _msg->length);
+ }
+
+ rtp_free_msg(NULL, _msg);
+ }
+
+ if (p) {
+ /* do the decoding on another thread */
+ pthread_mutex_lock(&call->decode_cond_mutex);
+ uint8_t w = call->audio_decode_write;
+
+ if (call->audio_decode_queue[w] == NULL) {
+ call->audio_decode_queue[w] = p;
+ call->audio_decode_write = (w + 1) % AUDIO_DECODE_QUEUE_SIZE;
+ pthread_cond_signal(&call->decode_cond);
+ } else {
+ LOGGER_DEBUG("Dropped audio frame\n");
+ free(p);
+ }
+
+ pthread_mutex_unlock(&call->decode_cond_mutex);
+ } else {
+ //malloc failed
+ }
+ }
+
+ } else {
+ uint8_t *packet = _msg->data;
+ int recved_size = _msg->length;
+
+ if (recved_size < VIDEOFRAME_HEADER_SIZE) {
+ goto end;
+ }
+
+ uint8_t i = packet[0] - call->frame_id;
+
+ if (i == 0) {
+ /* piece of current frame */
+ } else if (i > 0 && i < 128) {
+ /* recieved a piece of a frame ahead, flush current frame and start reading this new frame */
+ DECODE_PACKET *p = malloc(sizeof(DECODE_PACKET) + call->frame_limit);
+
+ if (p) {
+ p->size = call->frame_limit;
+ memcpy(p->data, call->frame_buf, call->frame_limit);
+
+ /* do the decoding on another thread */
+ pthread_mutex_lock(&call->decode_cond_mutex);
+ uint8_t w = call->video_decode_write;
+
+ if (call->video_decode_queue[w] == NULL) {
+ call->video_decode_queue[w] = p;
+ call->video_decode_write = (w + 1) % VIDEO_DECODE_QUEUE_SIZE;
+ pthread_cond_signal(&call->decode_cond);
+ } else {
+ LOGGER_DEBUG("Dropped video frame\n");
+ free(p);
+ }
+
+ pthread_mutex_unlock(&call->decode_cond_mutex);
+ } else {
+ //malloc failed
+ }
+
+ call->frame_id = packet[0];
+ memset(call->frame_buf, 0, call->frame_limit);
+ call->frame_limit = 0;
+ } else {
+ /* old packet, dont read */
+ LOGGER_DEBUG("Old packet: %u\n", i);
+ goto end;
+ }
+
+ if (packet[1] > (MAX_VIDEOFRAME_SIZE - VIDEOFRAME_PIECE_SIZE + 1) /
+ VIDEOFRAME_PIECE_SIZE) { //TODO, fix this check? not sure
+ /* packet out of buffer range */
+ goto end;
+ }
+
+ LOGGER_DEBUG("Video Packet: %u %u\n", packet[0], packet[1]);
+ memcpy(call->frame_buf + packet[1] * VIDEOFRAME_PIECE_SIZE, packet + VIDEOFRAME_HEADER_SIZE,
+ recved_size - VIDEOFRAME_HEADER_SIZE);
+ uint32_t limit = packet[1] * VIDEOFRAME_PIECE_SIZE + recved_size - VIDEOFRAME_HEADER_SIZE;
+
+ if (limit > call->frame_limit) {
+ call->frame_limit = limit;
+ LOGGER_DEBUG("Limit: %u\n", call->frame_limit);
+ }
+
+end:
+ ;
+ rtp_free_msg(NULL, _msg);
+ }
+}
diff --git a/protocols/Tox/toxcore/toxav/toxav.h b/protocols/Tox/toxcore/toxav/toxav.h
new file mode 100644
index 0000000000..e31c7aad1f
--- /dev/null
+++ b/protocols/Tox/toxcore/toxav/toxav.h
@@ -0,0 +1,389 @@
+/** toxav.h
+ *
+ * Copyright (C) 2013 Tox project All Rights Reserved.
+ *
+ * This file is part of Tox.
+ *
+ * Tox is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Tox is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Tox. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifndef __TOXAV
+#define __TOXAV
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* vpx_image_t */
+#include <vpx/vpx_image.h>
+
+typedef void ( *ToxAVCallback ) ( void *agent, int32_t call_idx, void *arg );
+typedef struct _ToxAv ToxAv;
+
+#ifndef __TOX_DEFINED__
+#define __TOX_DEFINED__
+typedef struct Tox Tox;
+#endif
+
+#define RTP_PAYLOAD_SIZE 65535
+
+
+/**
+ * @brief Callbacks ids that handle the call states.
+ */
+typedef enum {
+ /* Requests */
+ av_OnInvite,
+ av_OnStart,
+ av_OnCancel,
+ av_OnReject,
+ av_OnEnd,
+
+ /* Responses */
+ av_OnRinging,
+ av_OnStarting,
+ av_OnEnding,
+
+ /* Protocol */
+ av_OnRequestTimeout,
+ av_OnPeerTimeout,
+ av_OnMediaChange
+} ToxAvCallbackID;
+
+
+/**
+ * @brief Call type identifier.
+ */
+typedef enum {
+ TypeAudio = 192,
+ TypeVideo
+} ToxAvCallType;
+
+
+typedef enum {
+ av_CallNonExistant = -1,
+ av_CallInviting, /* when sending call invite */
+ av_CallStarting, /* when getting call invite */
+ av_CallActive,
+ av_CallHold,
+ av_CallHanged_up
+} ToxAvCallState;
+
+/**
+ * @brief Error indicators.
+ */
+typedef enum {
+ ErrorNone = 0,
+ ErrorInternal = -1, /* Internal error */
+ ErrorAlreadyInCall = -2, /* Already has an active call */
+ ErrorNoCall = -3, /* Trying to perform call action while not in a call */
+ ErrorInvalidState = -4, /* Trying to perform call action while in invalid state*/
+ ErrorNoRtpSession = -5, /* Trying to perform rtp action on invalid session */
+ ErrorAudioPacketLost = -6, /* Indicating packet loss */
+ ErrorStartingAudioRtp = -7, /* Error in toxav_prepare_transmission() */
+ ErrorStartingVideoRtp = -8 , /* Error in toxav_prepare_transmission() */
+ ErrorTerminatingAudioRtp = -9, /* Returned in toxav_kill_transmission() */
+ ErrorTerminatingVideoRtp = -10, /* Returned in toxav_kill_transmission() */
+ ErrorPacketTooLarge = -11, /* Buffer exceeds size while encoding */
+ ErrorInvalidCodecState = -12, /* Codec state not initialized */
+
+} ToxAvError;
+
+
+/**
+ * @brief Locally supported capabilities.
+ */
+typedef enum {
+ AudioEncoding = 1 << 0,
+ AudioDecoding = 1 << 1,
+ VideoEncoding = 1 << 2,
+ VideoDecoding = 1 << 3
+} ToxAvCapabilities;
+
+
+/**
+ * @brief Encoding settings.
+ */
+typedef struct _ToxAvCodecSettings {
+ ToxAvCallType call_type;
+
+ uint32_t video_bitrate; /* In kbits/s */
+ uint16_t max_video_width; /* In px */
+ uint16_t max_video_height; /* In px */
+
+ uint32_t audio_bitrate; /* In bits/s */
+ uint16_t audio_frame_duration; /* In ms */
+ uint32_t audio_sample_rate; /* In Hz */
+ uint32_t audio_channels;
+} ToxAvCSettings;
+
+extern const ToxAvCSettings av_DefaultSettings;
+extern const uint32_t av_jbufdc; /* Jitter buffer default capacity */
+extern const uint32_t av_VADd; /* VAD default treshold */
+
+/**
+ * @brief Start new A/V session. There can only be one session at the time. If you register more
+ * it will result in undefined behaviour.
+ *
+ * @param messenger The messenger handle.
+ * @param userdata The agent handling A/V session (i.e. phone).
+ * @param video_width Width of video frame.
+ * @param video_height Height of video frame.
+ * @return ToxAv*
+ * @retval NULL On error.
+ */
+ToxAv *toxav_new(Tox *messenger, int32_t max_calls);
+
+/**
+ * @brief Remove A/V session.
+ *
+ * @param av Handler.
+ * @return void
+ */
+void toxav_kill(ToxAv *av);
+
+/**
+ * @brief Register callback for call state.
+ *
+ * @param av Handler.
+ * @param callback The callback
+ * @param id One of the ToxAvCallbackID values
+ * @return void
+ */
+void toxav_register_callstate_callback (ToxAv *av, ToxAVCallback callback, ToxAvCallbackID id, void *userdata);
+
+/**
+ * @brief Register callback for recieving audio data
+ *
+ * @param av Handler.
+ * @param callback The callback
+ * @return void
+ */
+void toxav_register_audio_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, int16_t *, int, void *),
+ void *user_data);
+
+/**
+ * @brief Register callback for recieving video data
+ *
+ * @param av Handler.
+ * @param callback The callback
+ * @return void
+ */
+void toxav_register_video_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, vpx_image_t *, void *),
+ void *user_data);
+
+/**
+ * @brief Call user. Use its friend_id.
+ *
+ * @param av Handler.
+ * @param user The user.
+ * @param call_type Call type.
+ * @param ringing_seconds Ringing timeout.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_call(ToxAv *av, int32_t *call_index, int user, const ToxAvCSettings *csettings, int ringing_seconds);
+
+/**
+ * @brief Hangup active call.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_hangup(ToxAv *av, int32_t call_index);
+
+/**
+ * @brief Answer incomming call.
+ *
+ * @param av Handler.
+ * @param call_type Answer with...
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_answer(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings );
+
+/**
+ * @brief Reject incomming call.
+ *
+ * @param av Handler.
+ * @param reason Optional reason. Set NULL if none.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_reject(ToxAv *av, int32_t call_index, const char *reason);
+
+/**
+ * @brief Cancel outgoing request.
+ *
+ * @param av Handler.
+ * @param reason Optional reason.
+ * @param peer_id peer friend_id
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_cancel(ToxAv *av, int32_t call_index, int peer_id, const char *reason);
+
+/**
+ * @brief Notify peer that we are changing call settings
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings);
+
+/**
+ * @brief Terminate transmission. Note that transmission will be terminated without informing remote peer.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_stop_call(ToxAv *av, int32_t call_index);
+
+/**
+ * @brief Must be call before any RTP transmission occurs.
+ *
+ * @param av Handler.
+ * @param support_video Is video supported ? 1 : 0
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_prepare_transmission(ToxAv *av, int32_t call_index, uint32_t jbuf_size, uint32_t VAD_treshold,
+ int support_video);
+
+/**
+ * @brief Call this at the end of the transmission.
+ *
+ * @param av Handler.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_kill_transmission(ToxAv *av, int32_t call_index);
+
+/**
+ * @brief Encode and send video packet.
+ *
+ * @param av Handler.
+ * @param frame The encoded frame.
+ * @param frame_size The size of the encoded frame.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size);
+
+/**
+ * @brief Send audio frame.
+ *
+ * @param av Handler.
+ * @param data The audio data encoded with toxav_prepare_audio_frame().
+ * @param size Its size in number of bytes.
+ * @return int
+ * @retval 0 Success.
+ * @retval ToxAvError On error.
+ */
+int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int size);
+
+/**
+ * @brief Encode video frame
+ *
+ * @param av Handler
+ * @param dest Where to
+ * @param dest_max Max size
+ * @param input What to encode
+ * @return int
+ * @retval ToxAvError On error.
+ * @retval >0 On success
+ */
+int toxav_prepare_video_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input );
+
+/**
+ * @brief Encode audio frame
+ *
+ * @param av Handler
+ * @param dest dest
+ * @param dest_max Max dest size
+ * @param frame The frame
+ * @param frame_size The frame size
+ * @return int
+ * @retval ToxAvError On error.
+ * @retval >0 On success
+ */
+int toxav_prepare_audio_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, const int16_t *frame,
+ int frame_size);
+
+/**
+ * @brief Get peer transmission type. It can either be audio or video.
+ *
+ * @param av Handler.
+ * @param peer The peer
+ * @return int
+ * @retval ToxAvCallType On success.
+ * @retval ToxAvError On error.
+ */
+int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest );
+
+/**
+ * @brief Get id of peer participating in conversation
+ *
+ * @param av Handler
+ * @param peer peer index
+ * @return int
+ * @retval ToxAvError No peer id
+ */
+int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer );
+
+/**
+ * @brief Get current call state
+ *
+ * @param av Handler
+ * @param call_index What call
+ * @return int
+ * @retval ToxAvCallState State id
+ */
+ToxAvCallState toxav_get_call_state ( ToxAv *av, int32_t call_index );
+/**
+ * @brief Is certain capability supported
+ *
+ * @param av Handler
+ * @return int
+ * @retval 1 Yes.
+ * @retval 0 No.
+ */
+int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability );
+
+
+Tox *toxav_get_tox(ToxAv *av);
+
+int toxav_has_activity ( ToxAv *av, int32_t call_index, int16_t *PCM, uint16_t frame_size, float ref_energy );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TOXAV */