summaryrefslogtreecommitdiff
path: root/protocols/Tox/toxcore/toxav/toxav.c
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Tox/toxcore/toxav/toxav.c')
-rw-r--r--protocols/Tox/toxcore/toxav/toxav.c1148
1 files changed, 1148 insertions, 0 deletions
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);
+ }
+}