Compare commits

..

5 Commits

Author SHA1 Message Date
Green Sky
18af7a7a38
Merge commit 'ac6c36994c569f801865f95b62f5a4334f32b8db'
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Has been cancelled
ContinuousDelivery / windows (push) Has been cancelled
ContinuousDelivery / windows-asan (push) Has been cancelled
ContinuousDelivery / dumpsyms (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
ContinuousIntegration / linux (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
2025-04-03 22:23:37 +02:00
Green Sky
ac6c36994c Squashed 'external/toxcore/c-toxcore/' changes from 501a32937f..465fbc8721
465fbc8721 Merge branch 'toxav_deadline' of github.com:Green-Sky/c-toxcore into tomato_testing_in_prod
6f16b5f4e5 chore(toxav): use realtime deadline for vp8 encoder Technically all this does is choose a quality based on frame duration, which we always set to 1, and as such is always realtime. (In same timebase as pts, which we use as a frame counter...)
REVERT: 501a32937f Merge branch 'toxav_deadline' of github.com:Green-Sky/c-toxcore into tomato_testing_in_prod
REVERT: 865261a67a chore(toxav): use realtime deadline for vp8 encoder Technically all this does is choose a quality based on frame duration, which we always set to 1, and as such is always realtime. (In same timebase as pts, which we use as a frame counter...)

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: 465fbc872113531ed5ef28273f7188d39fe44a47
2025-04-03 22:23:37 +02:00
Green Sky
ee085e348e
update toxcore to master + toxav patches Merge commit '30f60ab6678dd4e9e4cc52f72d363f9e6e30b3b2' 2025-04-03 22:13:17 +02:00
Green Sky
30f60ab667 Squashed 'external/toxcore/c-toxcore/' changes from 76bc4c496d..501a32937f
501a32937f Merge branch 'toxav_deadline' of github.com:Green-Sky/c-toxcore into tomato_testing_in_prod
0b49ba2d94 Merge branch 'toxav_video_bitrate' of github.com:Green-Sky/c-toxcore into tomato_testing_in_prod
9c0977d7c6 Merge branch 'toxav_remove_img_copy_encode' of github.com:Green-Sky/c-toxcore into tomato_testing_in_prod
4071d74cc9 fix(ngc): dont double every message, if we are not directly connected but we and the other peer would support direct.
1d4cc783b1 fix(bazel): one more fuzz target that needs netprof
066aafbfcd fix(bazel): make net_prof visible to its consumers
fa015c7e2e fix(toxav): remove extra copy of video frame on encode Tested and works, but there might be alignment issues and other stuff.
d34f7d1f5c fix(toxav): handle vpx_image_alloc failure
865261a67a chore(toxav): use realtime deadline for vp8 encoder Technically all this does is choose a quality based on frame duration, which we always set to 1, and as such is always realtime. (In same timebase as pts, which we use as a frame counter...)
dd12b9889a chore(toxav): tighten the video bitrate to the same as the vp8 encoder internally checks.
9dcc2f530d fix(bazel): missing dep for auto_tests
741ac5f5e6 fix(bazel): missing dep for fuzz target

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: 501a32937f4550d4340406a6a73da548849a53af
2025-04-03 22:13:17 +02:00
Green Sky
553e883ee1
make toxav respond to sink bitrate changes
and make debug taps disconnect on X
2025-04-03 21:57:14 +02:00
13 changed files with 185 additions and 94 deletions

View File

@ -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",

View File

@ -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,10 +1059,16 @@ 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;
// 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;
@ -1085,9 +1081,13 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u
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 unsigned long 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)

View File

@ -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) {

View File

@ -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",

View File

@ -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);

View File

@ -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];

View File

@ -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,16 +611,20 @@ 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 (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;
}
}
}
const int ret = send_packet_tcp_connection(chat->tcp_conn, gconn->tcp_connection_num, packet, length);
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));

View File

@ -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. */

View File

@ -190,7 +190,8 @@ float DebugVideoTap::render(void) {
std::string window_title {"DebugVideoTap #"};
window_title += std::to_string(view._id);
ImGui::SetNextWindowSize({400, 420}, ImGuiCond_Appearing);
if (ImGui::Begin(window_title.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar)) {
bool window_open = true;
if (ImGui::Begin(window_title.c_str(), &window_open, ImGuiWindowFlags_NoScrollbar)) {
while (auto new_frame_opt = stream->pop()) {
// timing
if (view._v_last_ts == 0) {
@ -208,40 +209,25 @@ float DebugVideoTap::render(void) {
}
SDL_Surface* new_frame_surf = new_frame_opt.value().surface.get();
SDL_Surface* converted_surf = new_frame_surf;
//if (new_frame_surf->format != SDL_PIXELFORMAT_RGBA32) {
// // we need to convert
// //std::cerr << "DVT: need to convert\n";
// converted_surf = SDL_ConvertSurfaceAndColorspace(new_frame_surf, SDL_PIXELFORMAT_RGBA32, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
// assert(converted_surf->format == SDL_PIXELFORMAT_RGBA32);
//}
SDL_LockSurface(converted_surf);
if (view._tex == 0 || (int)view._tex_w != converted_surf->w || (int)view._tex_h != converted_surf->h) {
SDL_LockSurface(new_frame_surf);
if (view._tex == 0 || (int)view._tex_w != new_frame_surf->w || (int)view._tex_h != new_frame_surf->h) {
_tu.destroy(view._tex);
view._tex = _tu.upload(
static_cast<const uint8_t*>(converted_surf->pixels),
converted_surf->w,
converted_surf->h,
static_cast<const uint8_t*>(new_frame_surf->pixels),
new_frame_surf->w,
new_frame_surf->h,
TextureUploaderI::IYUV, // forced conversion
TextureUploaderI::LINEAR,
TextureUploaderI::STREAMING
);
view._tex_w = converted_surf->w;
view._tex_h = converted_surf->h;
view._tex_w = new_frame_surf->w;
view._tex_h = new_frame_surf->h;
} else {
//_tu.update(view._tex, static_cast<const uint8_t*>(converted_surf->pixels), converted_surf->w * converted_surf->h * 4);
_tu.update(view._tex, static_cast<const uint8_t*>(converted_surf->pixels), converted_surf->w * converted_surf->h * 3/2);
//_tu.updateRGBA(view._tex, static_cast<const uint8_t*>(converted_surf->pixels), converted_surf->w * converted_surf->h * 4);
_tu.update(view._tex, static_cast<const uint8_t*>(new_frame_surf->pixels), new_frame_surf->w * new_frame_surf->h * 3/2);
}
SDL_UnlockSurface(converted_surf);
//if (new_frame_surf != converted_surf) {
// // clean up temp
// SDL_DestroySurface(converted_surf);
//}
SDL_UnlockSurface(new_frame_surf);
}
ImGui::Checkbox("mirror ", &view._mirror);
@ -264,6 +250,15 @@ float DebugVideoTap::render(void) {
}
}
ImGui::End();
if (!window_open) {
_sm.forEachConnectedExt(_tap, [this, stream_ptr = stream.get()](ObjectHandle o, const void*, const void* writer) {
// very hacky
if (writer == stream_ptr) {
_sm.disconnect(o, _tap);
}
});
}
}
return min_interval;

View File

@ -86,7 +86,7 @@ bool StreamManager::connect(Object src, Object sink, bool threaded) {
bool StreamManager::disconnect(Object src, Object sink) {
auto res = std::find_if(
_connections.cbegin(), _connections.cend(),
[&](const auto& a) { return a->src == src && a->sink == sink; }
[src, sink](const auto& a) { return a->src == src && a->sink == sink; }
);
if (res == _connections.cend()) {
// not found

View File

@ -52,6 +52,14 @@ namespace Components {
template<typename FrameType>
using FrameStream2Sink = std::unique_ptr<FrameStream2SinkI<FrameType>>;
// supported sources/sinks have this component
// to signal the backend sender/encoder a generic bitrate
// TODO: split into advanced encoder bitrate / target / range
struct Bitrate {
// in kilo bits per second
int64_t rate{0};
};
} // Components
@ -66,6 +74,8 @@ class StreamManager : protected ObjectStoreEventI {
struct Data {
virtual ~Data(void) {}
virtual const void* getReaderStream(void) = 0;
virtual const void* getWriterStream(void) = 0;
};
std::unique_ptr<Data> data; // stores reader writer type erased
std::function<void(Connection&)> pump_fn; // TODO: make it return next interval?
@ -116,6 +126,29 @@ class StreamManager : protected ObjectStoreEventI {
bool disconnect(Object src, Object sink);
bool disconnectAll(Object o);
template<typename FN>
void forEachConnected(ObjectHandle o, FN&& fn) {
for (const auto& con : _connections) {
if (con->src == o) {
fn(con->sink);
} else if (con->sink == o) {
fn(con->src);
}
}
}
template<typename FN>
void forEachConnectedExt(ObjectHandle o, FN&& fn) {
for (const auto& con : _connections) {
if (con->src == o) {
// TODO: very ugly
fn(con->sink, con->data.get()->getReaderStream(), con->data.get()->getWriterStream());
} else if (con->sink == o) {
fn(con->src, con->data.get()->getReaderStream(), con->data.get()->getWriterStream());
}
}
}
// do we need the time delta?
float tick(float);
@ -183,9 +216,11 @@ bool StreamManager::connect(Object src, Object sink, bool threaded) {
auto& sink_stream = h_sink.get<Components::FrameStream2Sink<FrameType>>();
struct inlineData : public Connection::Data {
virtual ~inlineData(void) {}
~inlineData(void) {}
std::shared_ptr<FrameStream2I<FrameType>> reader;
std::shared_ptr<FrameStream2I<FrameType>> writer;
const void* getReaderStream(void) override { return reader.get(); }
const void* getWriterStream(void) override { return writer.get(); }
};
auto our_data = std::make_unique<inlineData>();

View File

@ -56,6 +56,16 @@ struct ToxAVCallAudioSink : public FrameStream2SinkI<AudioFrame2> {
}
}
bool setBitrate(uint64_t rate) {
// TODO: prevent disable active?
auto err = _toxav.toxavAudioSetBitRate(_fid, rate);
if (err == TOXAV_ERR_BIT_RATE_SET_OK) {
_audio_bitrate = rate;
return true;
}
return false;
}
// sink
std::shared_ptr<FrameStream2I<AudioFrame2>> subscribe(void) override {
if (_writer) {
@ -116,6 +126,16 @@ struct ToxAVCallVideoSink : public FrameStream2SinkI<SDLVideoFrame> {
}
}
bool setBitrate(uint64_t rate) {
// TODO: prevent disable active?
auto err = _toxav.toxavVideoSetBitRate(_fid, rate);
if (err == TOXAV_ERR_BIT_RATE_SET_OK) {
_video_bitrate = rate;
return true;
}
return false;
}
// sink
std::shared_ptr<FrameStream2I<SDLVideoFrame>> subscribe(void) override {
if (_writer) {
@ -187,6 +207,7 @@ void ToxAVVoIPModel::addAudioSink(ObjectHandle session, uint32_t friend_number)
auto new_asink = std::make_unique<ToxAVCallAudioSink>(_av, friend_number);
auto* new_asink_ptr = new_asink.get();
outgoing_audio.emplace<Components::Bitrate>(new_asink_ptr->_audio_bitrate);
outgoing_audio.emplace<ToxAVCallAudioSink*>(new_asink_ptr);
outgoing_audio.emplace<Components::FrameStream2Sink<AudioFrame2>>(std::move(new_asink));
outgoing_audio.emplace<Components::StreamSink>(Components::StreamSink::create<AudioFrame2>("ToxAV Friend Call Outgoing Audio"));
@ -240,6 +261,7 @@ void ToxAVVoIPModel::addVideoSink(ObjectHandle session, uint32_t friend_number)
auto new_vsink = std::make_unique<ToxAVCallVideoSink>(_av, friend_number);
auto* new_vsink_ptr = new_vsink.get();
outgoing_video.emplace<Components::Bitrate>(new_vsink_ptr->_video_bitrate);
outgoing_video.emplace<ToxAVCallVideoSink*>(new_vsink_ptr);
outgoing_video.emplace<Components::FrameStream2Sink<SDLVideoFrame>>(std::move(new_vsink));
outgoing_video.emplace<Components::StreamSink>(Components::StreamSink::create<SDLVideoFrame>("ToxAV Friend Call Outgoing Video"));
@ -523,7 +545,7 @@ void ToxAVVoIPModel::handleEvent(const Events::FriendCallState& e) {
}
ToxAVVoIPModel::ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, ContactStore4I& cs, ToxContactModel2& tcm) :
_os(os), _av(av), _av_sr(_av.newSubRef(this)), _cs(cs), _cs_sr(_cs.newSubRef(this)), _tcm(tcm)
_os(os), _os_sr(_os.newSubRef(this)), _av(av), _av_sr(_av.newSubRef(this)), _cs(cs), _cs_sr(_cs.newSubRef(this)), _tcm(tcm)
{
_av_sr
.subscribe(ToxAV_Event::friend_call)
@ -548,6 +570,10 @@ ToxAVVoIPModel::ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, ContactStore4I& cs,
.subscribe(ContactStore4_Event::contact_construct)
.subscribe(ContactStore4_Event::contact_update)
;
_os_sr
.subscribe(ObjectStore_Event::object_update)
;
}
ToxAVVoIPModel::~ToxAVVoIPModel(void) {
@ -873,3 +899,32 @@ bool ToxAVVoIPModel::onEvent(const ContactStore::Events::Contact4Destory&) {
return false;
}
bool ToxAVVoIPModel::onEvent(const ObjectStore::Events::ObjectConstruct&) {
return false;
}
bool ToxAVVoIPModel::onEvent(const ObjectStore::Events::ObjectUpdate& e) {
if (e.e.all_of<ToxAVCallVideoSink*>()) {
auto& rate = e.e.get_or_emplace<Components::Bitrate>().rate;
if (rate <= 0) {
rate = 0;
}
if (!e.e.get<ToxAVCallVideoSink*>()->setBitrate(rate)) {
rate = e.e.get<ToxAVCallVideoSink*>()->_video_bitrate;
}
} else if (e.e.all_of<ToxAVCallAudioSink*>()) {
auto& rate = e.e.get_or_emplace<Components::Bitrate>().rate;
if (rate <= 0) {
rate = 0;
}
if (!e.e.get<ToxAVCallAudioSink*>()->setBitrate(rate)) {
rate = e.e.get<ToxAVCallAudioSink*>()->_audio_bitrate;
}
}
return false;
}
bool ToxAVVoIPModel::onEvent(const ObjectStore::Events::ObjectDestory&) {
return false;
}

View File

@ -1,6 +1,6 @@
#pragma once
#include <solanaceae/object_store/fwd.hpp>
#include <solanaceae/object_store/object_store.hpp>
#include <solanaceae/contact/contact_store_i.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
#include "./frame_streams/voip_model.hpp"
@ -16,8 +16,9 @@
struct ToxAVCallAudioSink;
struct ToxAVCallVideoSink;
class ToxAVVoIPModel : protected ToxAVEventI, protected ContactStore4EventI, public VoIPModelI {
class ToxAVVoIPModel : protected ToxAVEventI, protected ContactStore4EventI, protected ObjectStoreEventI, public VoIPModelI {
ObjectStore2& _os;
ObjectStore2::SubscriptionReference _os_sr;
ToxAVI& _av;
ToxAVI::SubscriptionReference _av_sr;
ContactStore4I& _cs;
@ -93,5 +94,10 @@ class ToxAVVoIPModel : protected ToxAVEventI, protected ContactStore4EventI, pub
bool onEvent(const ContactStore::Events::Contact4Construct&) override;
bool onEvent(const ContactStore::Events::Contact4Update&) override;
bool onEvent(const ContactStore::Events::Contact4Destory&) override;
protected: // object events
bool onEvent(const ObjectStore::Events::ObjectConstruct&) override;
bool onEvent(const ObjectStore::Events::ObjectUpdate&) override;
bool onEvent(const ObjectStore::Events::ObjectDestory&) override;
};