diff --git a/auto_tests/BUILD.bazel b/auto_tests/BUILD.bazel index 24a3add..fb44e3e 100644 --- a/auto_tests/BUILD.bazel +++ b/auto_tests/BUILD.bazel @@ -71,6 +71,7 @@ extra_data = { "//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:net_crypto", + "//c-toxcore/toxcore:net_profile", "//c-toxcore/toxcore:network", "//c-toxcore/toxcore:onion", "//c-toxcore/toxcore:onion_announce", diff --git a/toxav/toxav.c b/toxav/toxav.c index 2f9639f..0a62edb 100644 --- a/toxav/toxav.c +++ b/toxav/toxav.c @@ -24,19 +24,8 @@ #include "../toxcore/tox_struct.h" // IWYU pragma: keep #include "../toxcore/util.h" -// TODO(zoff99): don't hardcode this, let the application choose it -// VPX Info: Time to spend encoding, in microseconds (it's a *soft* deadline) -#define WANTED_MAX_ENCODER_FPS 40 -#define MAX_ENCODE_TIME_US (1000000 / WANTED_MAX_ENCODER_FPS) // to allow x fps - #define VIDEO_SEND_X_KEYFRAMES_FIRST 7 // force the first n frames to be keyframes! -/* - * VPX_DL_REALTIME (1) deadline parameter analogous to VPx REALTIME mode. - * VPX_DL_GOOD_QUALITY (1000000) deadline parameter analogous to VPx GOOD QUALITY mode. - * VPX_DL_BEST_QUALITY (0) deadline parameter analogous to VPx BEST QUALITY mode. - */ - // iteration interval that is used when no call is active #define IDLE_ITERATION_INTERVAL_MS 200 @@ -1055,6 +1044,7 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u goto RETURN; } + // we start with I-frames (full frames) and then switch to normal mode later if (call->video_rtp->ssrc < VIDEO_SEND_X_KEYFRAMES_FIRST) { // Key frame flag for first frames vpx_encode_flags = VPX_EFLAG_FORCE_KF; @@ -1069,25 +1059,35 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u ++call->video_rtp->ssrc; } - // we start with I-frames (full frames) and then switch to normal mode later - { /* Encode */ vpx_image_t img; - img.w = 0; - img.h = 0; - img.d_w = 0; - img.d_h = 0; - vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); + // TODO(Green-Sky): figure out stride_align + // TODO(Green-Sky): check memory alignment? + if (vpx_img_wrap(&img, VPX_IMG_FMT_I420, width, height, 0, (uint8_t *)y) != nullptr) { + // vpx_img_wrap assumes contigues memory, so we fix that + img.planes[VPX_PLANE_U] = (uint8_t *)u; + img.planes[VPX_PLANE_V] = (uint8_t *)v; + } else { + // call to wrap failed, falling back to copy + img.w = 0; + img.h = 0; + img.d_w = 0; + img.d_h = 0; + vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); - /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." - * http://fourcc.org/yuv.php#IYUV - */ - memcpy(img.planes[VPX_PLANE_Y], y, width * height); - memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); - memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); + /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." + * http://fourcc.org/yuv.php#IYUV + */ + memcpy(img.planes[VPX_PLANE_Y], y, width * height); + memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); + memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); + } + + // TODO(zoff99): don't hardcode this, let the application choose it + const vpx_enc_deadline_t deadline = VPX_DL_REALTIME; const vpx_codec_err_t vrc = vpx_codec_encode(call->video->encoder, &img, - call->video->frame_counter, 1, vpx_encode_flags, MAX_ENCODE_TIME_US); + call->video->frame_counter, 1, vpx_encode_flags, deadline); vpx_img_free(&img); @@ -1304,14 +1304,11 @@ static bool audio_bit_rate_invalid(uint32_t bit_rate) static bool video_bit_rate_invalid(uint32_t bit_rate) { - /* https://www.webmproject.org/docs/webm-sdk/structvpx__codec__enc__cfg.html shows the following: - * unsigned int rc_target_bitrate - * the range of uint varies from platform to platform - * though, uint32_t should be large enough to store bitrates, - * we may want to prevent from passing overflowed bitrates to libvpx - * more in detail, it's the case where bit_rate is larger than uint, but smaller than uint32_t + /* Cap the target rate to 1000 Mbps to avoid some integer overflows in + * target bandwidth calculations. + * https://github.com/webmproject/libvpx/blob/027bbee30a0103b99d86327b48d29567fed11688/vp8/vp8_cx_iface.c#L350-L352 */ - return bit_rate > UINT32_MAX; + return bit_rate > 1000000; } static bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state) diff --git a/toxav/video.c b/toxav/video.c index 52d9829..9434feb 100644 --- a/toxav/video.c +++ b/toxav/video.c @@ -15,27 +15,6 @@ #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" -/** - * Soft deadline the decoder should attempt to meet, in "us" (microseconds). - * Set to zero for unlimited. - * - * By convention, the value 1 is used to mean "return as fast as possible." - */ -// TODO(zoff99): don't hardcode this, let the application choose it -#define WANTED_MAX_DECODER_FPS 40 - -/** - * VPX_DL_REALTIME (1) - * deadline parameter analogous to VPx REALTIME mode. - * - * VPX_DL_GOOD_QUALITY (1000000) - * deadline parameter analogous to VPx GOOD QUALITY mode. - * - * VPX_DL_BEST_QUALITY (0) - * deadline parameter analogous to VPx BEST QUALITY mode. - */ -#define MAX_DECODE_TIME_US (1000000 / WANTED_MAX_DECODER_FPS) // to allow x fps - /** * Codec control function to set encoder internal speed settings. Changes in * this value influences, among others, the encoder's selection of motion @@ -320,7 +299,7 @@ void vc_iterate(VCSession *vc) LOGGER_DEBUG(vc->log, "vc_iterate: rb_read p->len=%d p->header.xe=%d", (int)full_data_len, p->header.xe); LOGGER_DEBUG(vc->log, "vc_iterate: rb_read rb size=%d", (int)log_rb_size); - const vpx_codec_err_t rc = vpx_codec_decode(vc->decoder, p->data, full_data_len, nullptr, MAX_DECODE_TIME_US); + const vpx_codec_err_t rc = vpx_codec_decode(vc->decoder, p->data, full_data_len, nullptr, 0); free(p); if (rc != VPX_CODEC_OK) { diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index 2eae593..580d888 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel @@ -358,6 +358,10 @@ cc_library( name = "net_profile", srcs = ["net_profile.c"], hdrs = ["net_profile.h"], + visibility = [ + "//c-toxcore/auto_tests:__pkg__", + "//c-toxcore/testing/fuzzing:__pkg__", + ], deps = [ ":attributes", ":ccompat", @@ -555,6 +559,7 @@ cc_fuzz_test( deps = [ ":DHT", ":mem_test_util", + ":net_profile", "//c-toxcore/testing/fuzzing:fuzz_support", ], ) @@ -781,6 +786,7 @@ cc_fuzz_test( ":TCP_client", ":mem_test_util", ":net_crypto", + ":net_profile", ":network", "//c-toxcore/testing/fuzzing:fuzz_support", "//c-toxcore/testing/fuzzing:fuzz_tox", diff --git a/toxcore/group_chats.c b/toxcore/group_chats.c index 22f60bf..43ed7e8 100644 --- a/toxcore/group_chats.c +++ b/toxcore/group_chats.c @@ -1626,7 +1626,7 @@ int group_packet_wrap( * Returns true on success. */ non_null() -static bool send_lossy_group_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *data, +static bool send_lossy_group_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *data, uint16_t length, uint8_t packet_type) { assert(length <= MAX_GC_CUSTOM_LOSSY_PACKET_SIZE); @@ -2236,7 +2236,7 @@ static int handle_gc_invite_response_reject(const GC_Session *c, GC_Chat *chat, * Return true on success. */ non_null() -static bool send_gc_invite_response_reject(const GC_Chat *chat, const GC_Connection *gconn, uint8_t type) +static bool send_gc_invite_response_reject(const GC_Chat *chat, GC_Connection *gconn, uint8_t type) { if (type >= GJ_INVALID) { type = GJ_INVITE_FAILED; @@ -2353,7 +2353,7 @@ static bool send_gc_lossy_packet_all_peers(const GC_Chat *chat, const uint8_t *d uint32_t confirmed_peers = 0; for (uint32_t i = 1; i < chat->numpeers; ++i) { - const GC_Connection *gconn = get_gc_connection(chat, i); + GC_Connection *gconn = get_gc_connection(chat, i); assert(gconn != nullptr); @@ -7072,7 +7072,7 @@ static void do_peer_delete(const GC_Session *c, GC_Chat *chat, void *userdata) * Return true on success. */ non_null() -static bool ping_peer(const GC_Chat *chat, const GC_Connection *gconn) +static bool ping_peer(const GC_Chat *chat, GC_Connection *gconn) { const uint16_t buf_size = GC_PING_PACKET_MIN_DATA_SIZE + sizeof(IP_Port); uint8_t *data = (uint8_t *)mem_balloc(chat->mem, buf_size); diff --git a/toxcore/group_common.h b/toxcore/group_common.h index 9be3377..1921770 100644 --- a/toxcore/group_common.h +++ b/toxcore/group_common.h @@ -115,6 +115,7 @@ typedef struct GC_Connection { uint64_t last_sent_tcp_relays_time; /* the last time we attempted to send this peer our tcp relays */ uint16_t tcp_relay_share_index; uint64_t last_received_direct_time; /* the last time we received a direct UDP packet from this connection */ + uint64_t last_sent_direct_try_time; /* the last time we tried sending a direct UDP packet */ uint64_t last_sent_ip_time; /* the last time we sent our ip info to this peer in a ping packet */ Node_format connected_tcp_relays[MAX_FRIEND_TCP_CONNECTIONS]; diff --git a/toxcore/group_connection.c b/toxcore/group_connection.c index 79c9829..dd6d21f 100644 --- a/toxcore/group_connection.c +++ b/toxcore/group_connection.c @@ -29,6 +29,9 @@ /** Seconds since last direct UDP packet was received before the connection is considered dead */ #define GCC_UDP_DIRECT_TIMEOUT (GC_PING_TIMEOUT + 4) +/** Seconds since last direct UDP packet was sent before we can try again. Cheap NAT hole punch */ +#define GCC_UDP_DIRECT_RETRY 1 + /** Returns true if array entry does not contain an active packet. */ non_null() static bool array_entry_is_empty(const GC_Message_Array_Entry *array_entry) @@ -595,7 +598,7 @@ void gcc_resend_packets(const GC_Chat *chat, GC_Connection *gconn) } } -bool gcc_send_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *packet, uint16_t length) +bool gcc_send_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *packet, uint16_t length) { if (packet == nullptr || length == 0) { return false; @@ -608,8 +611,12 @@ bool gcc_send_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint return (uint16_t) sendpacket(chat->net, &gconn->addr.ip_port, packet, length) == length; } - if ((uint16_t) sendpacket(chat->net, &gconn->addr.ip_port, packet, length) == length) { - direct_send_attempt = true; + if (gcc_conn_should_try_direct(chat->mono_time, gconn)) { + gconn->last_sent_direct_try_time = mono_time_get(chat->mono_time); + + if ((uint16_t) sendpacket(chat->net, &gconn->addr.ip_port, packet, length) == length) { + direct_send_attempt = true; + } } } @@ -617,7 +624,7 @@ bool gcc_send_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint return ret == 0 || direct_send_attempt; } -int gcc_encrypt_and_send_lossless_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *data, +int gcc_encrypt_and_send_lossless_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *data, uint16_t length, uint64_t message_id, uint8_t packet_type) { const uint16_t packet_size = gc_get_wrapped_packet_size(length, NET_PACKET_GC_LOSSLESS); @@ -659,6 +666,11 @@ bool gcc_conn_is_direct(const Mono_Time *mono_time, const GC_Connection *gconn) return GCC_UDP_DIRECT_TIMEOUT + gconn->last_received_direct_time > mono_time_get(mono_time); } +bool gcc_conn_should_try_direct(const Mono_Time *mono_time, const GC_Connection *gconn) +{ + return mono_time_is_timeout(mono_time, gconn->last_sent_direct_try_time, GCC_UDP_DIRECT_RETRY); +} + bool gcc_direct_conn_is_possible(const GC_Chat *chat, const GC_Connection *gconn) { return !net_family_is_unspec(gconn->addr.ip_port.ip.family) && !net_family_is_unspec(net_family(chat->net)); diff --git a/toxcore/group_connection.h b/toxcore/group_connection.h index e165ac5..7122f7a 100644 --- a/toxcore/group_connection.h +++ b/toxcore/group_connection.h @@ -135,6 +135,10 @@ void gcc_make_session_shared_key(GC_Connection *gconn, const uint8_t *sender_pk) non_null() bool gcc_conn_is_direct(const Mono_Time *mono_time, const GC_Connection *gconn); +/** @brief Return true if we can try a direct connection with `gconn` again. */ +non_null() +bool gcc_conn_should_try_direct(const Mono_Time *mono_time, const GC_Connection *gconn); + /** @brief Return true if a direct UDP connection is possible with `gconn`. */ non_null() bool gcc_direct_conn_is_possible(const GC_Chat *chat, const GC_Connection *gconn); @@ -146,7 +150,7 @@ bool gcc_direct_conn_is_possible(const GC_Chat *chat, const GC_Connection *gconn * Return true on success. */ non_null() -bool gcc_send_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *packet, uint16_t length); +bool gcc_send_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *packet, uint16_t length); /** @brief Sends a lossless packet to `gconn` comprised of `data` of size `length`. * @@ -184,7 +188,7 @@ bool gcc_send_lossless_packet_fragments(const GC_Chat *chat, GC_Connection *gcon * Return -2 if the packet fails to send. */ non_null(1, 2) nullable(3) -int gcc_encrypt_and_send_lossless_packet(const GC_Chat *chat, const GC_Connection *gconn, const uint8_t *data, +int gcc_encrypt_and_send_lossless_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *data, uint16_t length, uint64_t message_id, uint8_t packet_type); /** @brief Called when a peer leaves the group. */