wip toxav voip model (only asink and outgoing call and missing reframer)

This commit is contained in:
Green Sky 2024-09-29 18:23:17 +02:00
parent 0acabf70b7
commit 472615a31f
No known key found for this signature in database
10 changed files with 537 additions and 54 deletions

View File

@ -107,6 +107,9 @@ target_sources(tomato PUBLIC
./frame_streams/audio_stream2.hpp ./frame_streams/audio_stream2.hpp
./frame_streams/stream_manager.hpp ./frame_streams/stream_manager.hpp
./frame_streams/stream_manager.cpp ./frame_streams/stream_manager.cpp
./frame_streams/locked_frame_stream.hpp
./frame_streams/voip_model.hpp
./frame_streams/sdl/sdl_audio2_frame_stream2.hpp ./frame_streams/sdl/sdl_audio2_frame_stream2.hpp
./frame_streams/sdl/sdl_audio2_frame_stream2.cpp ./frame_streams/sdl/sdl_audio2_frame_stream2.cpp
@ -123,6 +126,9 @@ if (TOMATO_TOX_AV)
target_sources(tomato PUBLIC target_sources(tomato PUBLIC
./tox_av.hpp ./tox_av.hpp
./tox_av.cpp ./tox_av.cpp
./tox_av_voip_model.hpp
./tox_av_voip_model.cpp
) )
target_compile_definitions(tomato PUBLIC TOMATO_TOX_AV) target_compile_definitions(tomato PUBLIC TOMATO_TOX_AV)

View File

@ -9,6 +9,8 @@
#include <solanaceae/contact/components.hpp> #include <solanaceae/contact/components.hpp>
#include <solanaceae/util/utils.hpp> #include <solanaceae/util/utils.hpp>
#include "./frame_streams/voip_model.hpp"
// HACK: remove them // HACK: remove them
#include <solanaceae/tox_contacts/components.hpp> #include <solanaceae/tox_contacts/components.hpp>
@ -21,6 +23,7 @@
#include "./media_meta_info_loader.hpp" #include "./media_meta_info_loader.hpp"
#include "./sdl_clipboard_utils.hpp" #include "./sdl_clipboard_utils.hpp"
#include "entt/entity/entity.hpp"
#include <cctype> #include <cctype>
#include <ctime> #include <ctime>
@ -30,6 +33,7 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <string>
#include <variant> #include <variant>
namespace Components { namespace Components {
@ -257,6 +261,62 @@ float ChatGui4::render(float time_delta) {
if (ImGui::BeginChild(chat_label.c_str(), {0, 0}, ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar)) { if (ImGui::BeginChild(chat_label.c_str(), {0, 0}, ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar)) {
if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenuBar()) {
// check if contact has voip model
// use activesessioncomp instead?
if (_cr.all_of<VoIPModelI*>(*_selected_contact)) {
if (ImGui::BeginMenu("VoIP")) {
auto* voip_model = _cr.get<VoIPModelI*>(*_selected_contact);
std::vector<Object> contact_sessions;
for (const auto& [ov, o_vm, sc] : _os.registry().view<VoIPModelI*, Components::VoIP::SessionContact>().each()) {
if (o_vm != voip_model) {
continue;
}
if (sc.c != *_selected_contact) {
continue;
}
contact_sessions.push_back(ov);
}
static VoIPModelI::DefaultConfig g_default_connections{};
if (ImGui::BeginMenu("default connections")) {
ImGui::MenuItem("incoming audio", nullptr, &g_default_connections.incoming_audio);
ImGui::MenuItem("incoming video", nullptr, &g_default_connections.incoming_video);
ImGui::Separator();
ImGui::MenuItem("outgoing audio", nullptr, &g_default_connections.outgoing_audio);
ImGui::MenuItem("outgoing video", nullptr, &g_default_connections.outgoing_video);
ImGui::EndMenu();
}
// TODO: only list if >1
if (ImGui::BeginMenu("accept call", false)) {
// list incomming here?
ImGui::EndMenu();
}
// TODO: disable if already in call?
if (ImGui::Button("call")) {
auto new_session = voip_model->enter(*_selected_contact, g_default_connections);
}
// TODO: only list if >1
if (ImGui::BeginMenu("leave/reject call", !contact_sessions.empty())) {
// list
for (const auto ov : contact_sessions) {
std::string label = "end #";
label += std::to_string(entt::to_integral(entt::to_entity(ov)));
if (ImGui::MenuItem(label.c_str())) {
voip_model->leave({_os.registry(), ov});
}
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu("debug")) { if (ImGui::BeginMenu("debug")) {
ImGui::Checkbox("show extra info", &_show_chat_extra_info); ImGui::Checkbox("show extra info", &_show_chat_extra_info);
ImGui::Checkbox("show avatar transfers", &_show_chat_avatar_tf); ImGui::Checkbox("show avatar transfers", &_show_chat_avatar_tf);

View File

@ -0,0 +1,42 @@
#pragma once
#include "./frame_stream2.hpp"
#include <mutex>
#include <deque>
// threadsafe queue frame stream
// protected by a simple mutex lock
// prefer lockless queue implementations, when available
template<typename FrameType>
struct LockedFrameStream2 : public FrameStream2I<FrameType> {
std::mutex _lock;
std::deque<FrameType> _frames;
~LockedFrameStream2(void) {}
int32_t size(void) { return -1; }
std::optional<FrameType> pop(void) {
std::lock_guard lg{_lock};
if (_frames.empty()) {
return std::nullopt;
}
FrameType new_frame = std::move(_frames.front());
_frames.pop_front();
return std::move(new_frame);
}
bool push(const FrameType& value) {
std::lock_guard lg{_lock};
_frames.push_back(value);
return true;
}
};

View File

@ -10,9 +10,9 @@ namespace Components::VoIP {
struct TagVoIPSession {}; struct TagVoIPSession {};
// to talk to the model handling this session // to talk to the model handling this session
struct VoIPModel { //struct VoIPModel {
VoIPModelI* ptr {nullptr}; //VoIPModelI* ptr {nullptr};
}; //};
struct SessionState { struct SessionState {
// ???? // ????
@ -59,13 +59,13 @@ struct VoIPModelI {
bool outgoing_audio {true}; bool outgoing_audio {true};
bool outgoing_video {true}; bool outgoing_video {true};
}; };
virtual ObjectHandle enter(const Contact3 c, const DefaultConfig& defaults = {}); virtual ObjectHandle enter(const Contact3 c, const DefaultConfig& defaults = {true, true, true, true}) { return {}; }
// accept/join an invite to a session // accept/join an invite to a session
virtual bool accept(ObjectHandle session); virtual bool accept(ObjectHandle session) { return false; }
// leaves a call // leaves a call
// - VoIP session object // - VoIP session object
virtual bool leave(ObjectHandle session); virtual bool leave(ObjectHandle session) { return false; }
}; };

View File

@ -25,13 +25,14 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
tc(save_path, save_password), tc(save_path, save_password),
tpi(tc.getTox()), tpi(tc.getTox()),
ad(tc), ad(tc),
#if TOMATO_TOX_AV
tav(tc.getTox()),
#endif
tcm(cr, tc, tc), tcm(cr, tc, tc),
tmm(rmm, cr, tcm, tc, tc), tmm(rmm, cr, tcm, tc, tc),
ttm(rmm, cr, tcm, tc, tc, os), ttm(rmm, cr, tcm, tc, tc, os),
tffom(cr, rmm, tcm, tc, tc), tffom(cr, rmm, tcm, tc, tc),
#if TOMATO_TOX_AV
tav(tc.getTox()),
tavvoip(os, tav, cr, tcm),
#endif
theme(theme_), theme(theme_),
mmil(rmm), mmil(rmm),
tam(/*rmm, */ os, cr, conf), tam(/*rmm, */ os, cr, conf),
@ -80,7 +81,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
g_provideInstance<ToxPrivateI>("ToxPrivateI", "host", &tpi); g_provideInstance<ToxPrivateI>("ToxPrivateI", "host", &tpi);
g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc); g_provideInstance<ToxEventProviderI>("ToxEventProviderI", "host", &tc);
#if TOMATO_TOX_AV #if TOMATO_TOX_AV
g_provideInstance<ToxAV>("ToxAV", "host", &tav); g_provideInstance<ToxAVI>("ToxAVI", "host", &tav);
#endif #endif
g_provideInstance<ToxContactModel2>("ToxContactModel2", "host", &tcm); g_provideInstance<ToxContactModel2>("ToxContactModel2", "host", &tcm);
@ -528,6 +529,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
tav.toxavIterate(); tav.toxavIterate();
// HACK: pow by 1.18 to increase 200 -> ~500 // HACK: pow by 1.18 to increase 200 -> ~500
const float av_interval = std::pow(tav.toxavIterationInterval(), 1.18)/1000.f; const float av_interval = std::pow(tav.toxavIterationInterval(), 1.18)/1000.f;
tavvoip.tick();
#endif #endif
tcm.iterate(time_delta); // compute tcm.iterate(time_delta); // compute

View File

@ -39,6 +39,7 @@
#if TOMATO_TOX_AV #if TOMATO_TOX_AV
#include "./tox_av.hpp" #include "./tox_av.hpp"
#include "./tox_av_voip_model.hpp"
#endif #endif
#include <string> #include <string>
@ -67,13 +68,14 @@ struct MainScreen final : public Screen {
ToxClient tc; ToxClient tc;
ToxPrivateImpl tpi; ToxPrivateImpl tpi;
AutoDirty ad; AutoDirty ad;
#if TOMATO_TOX_AV
ToxAV tav;
#endif
ToxContactModel2 tcm; ToxContactModel2 tcm;
ToxMessageManager tmm; ToxMessageManager tmm;
ToxTransferManager ttm; ToxTransferManager ttm;
ToxFriendFauxOfflineMessaging tffom; ToxFriendFauxOfflineMessaging tffom;
#if TOMATO_TOX_AV
ToxAVI tav;
ToxAVVoIPModel tavvoip;
#endif
Theme& theme; Theme& theme;

View File

@ -7,18 +7,7 @@
// https://almogfx.bandcamp.com/track/crushed-w-cassade // https://almogfx.bandcamp.com/track/crushed-w-cassade
struct ToxAVFriendCallState final { ToxAVI::ToxAVI(Tox* tox) : _tox(tox) {
const uint32_t state {TOXAV_FRIEND_CALL_STATE_NONE};
[[nodiscard]] bool is_error(void) const { return state & TOXAV_FRIEND_CALL_STATE_ERROR; }
[[nodiscard]] bool is_finished(void) const { return state & TOXAV_FRIEND_CALL_STATE_FINISHED; }
[[nodiscard]] bool is_sending_a(void) const { return state & TOXAV_FRIEND_CALL_STATE_SENDING_A; }
[[nodiscard]] bool is_sending_v(void) const { return state & TOXAV_FRIEND_CALL_STATE_SENDING_V; }
[[nodiscard]] bool is_accepting_a(void) const { return state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A; }
[[nodiscard]] bool is_accepting_v(void) const { return state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V; }
};
ToxAV::ToxAV(Tox* tox) : _tox(tox) {
Toxav_Err_New err_new {TOXAV_ERR_NEW_OK}; Toxav_Err_New err_new {TOXAV_ERR_NEW_OK};
_tox_av = toxav_new(_tox, &err_new); _tox_av = toxav_new(_tox, &err_new);
// TODO: throw // TODO: throw
@ -28,7 +17,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
_tox_av, _tox_av,
+[](ToxAV*, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) { +[](ToxAV*, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_call(friend_number, audio_enabled, video_enabled); static_cast<ToxAVI*>(user_data)->cb_call(friend_number, audio_enabled, video_enabled);
}, },
this this
); );
@ -36,7 +25,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
_tox_av, _tox_av,
+[](ToxAV*, uint32_t friend_number, uint32_t state, void *user_data) { +[](ToxAV*, uint32_t friend_number, uint32_t state, void *user_data) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_call_state(friend_number, state); static_cast<ToxAVI*>(user_data)->cb_call_state(friend_number, state);
}, },
this this
); );
@ -44,7 +33,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
_tox_av, _tox_av,
+[](ToxAV*, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data) { +[](ToxAV*, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_audio_bit_rate(friend_number, audio_bit_rate); static_cast<ToxAVI*>(user_data)->cb_audio_bit_rate(friend_number, audio_bit_rate);
}, },
this this
); );
@ -52,7 +41,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
_tox_av, _tox_av,
+[](ToxAV*, uint32_t friend_number, uint32_t video_bit_rate, void *user_data) { +[](ToxAV*, uint32_t friend_number, uint32_t video_bit_rate, void *user_data) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_video_bit_rate(friend_number, video_bit_rate); static_cast<ToxAVI*>(user_data)->cb_video_bit_rate(friend_number, video_bit_rate);
}, },
this this
); );
@ -60,7 +49,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
_tox_av, _tox_av,
+[](ToxAV*, uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate, void *user_data) { +[](ToxAV*, uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate, void *user_data) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_audio_receive_frame(friend_number, pcm, sample_count, channels, sampling_rate); static_cast<ToxAVI*>(user_data)->cb_audio_receive_frame(friend_number, pcm, sample_count, channels, sampling_rate);
}, },
this this
); );
@ -75,83 +64,83 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) {
void *user_data void *user_data
) { ) {
assert(user_data != nullptr); assert(user_data != nullptr);
static_cast<ToxAV*>(user_data)->cb_video_receive_frame(friend_number, width, height, y, u, v, ystride, ustride, vstride); static_cast<ToxAVI*>(user_data)->cb_video_receive_frame(friend_number, width, height, y, u, v, ystride, ustride, vstride);
}, },
this this
); );
} }
ToxAV::~ToxAV(void) { ToxAVI::~ToxAVI(void) {
toxav_kill(_tox_av); toxav_kill(_tox_av);
} }
uint32_t ToxAV::toxavIterationInterval(void) const { uint32_t ToxAVI::toxavIterationInterval(void) const {
return toxav_iteration_interval(_tox_av); return toxav_iteration_interval(_tox_av);
} }
void ToxAV::toxavIterate(void) { void ToxAVI::toxavIterate(void) {
toxav_iterate(_tox_av); toxav_iterate(_tox_av);
} }
uint32_t ToxAV::toxavAudioIterationInterval(void) const { uint32_t ToxAVI::toxavAudioIterationInterval(void) const {
return toxav_audio_iteration_interval(_tox_av); return toxav_audio_iteration_interval(_tox_av);
} }
void ToxAV::toxavAudioIterate(void) { void ToxAVI::toxavAudioIterate(void) {
toxav_audio_iterate(_tox_av); toxav_audio_iterate(_tox_av);
} }
uint32_t ToxAV::toxavVideoIterationInterval(void) const { uint32_t ToxAVI::toxavVideoIterationInterval(void) const {
return toxav_video_iteration_interval(_tox_av); return toxav_video_iteration_interval(_tox_av);
} }
void ToxAV::toxavVideoIterate(void) { void ToxAVI::toxavVideoIterate(void) {
toxav_video_iterate(_tox_av); toxav_video_iterate(_tox_av);
} }
Toxav_Err_Call ToxAV::toxavCall(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { Toxav_Err_Call ToxAVI::toxavCall(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
Toxav_Err_Call err {TOXAV_ERR_CALL_OK}; Toxav_Err_Call err {TOXAV_ERR_CALL_OK};
toxav_call(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err); toxav_call(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err);
return err; return err;
} }
Toxav_Err_Answer ToxAV::toxavAnswer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { Toxav_Err_Answer ToxAVI::toxavAnswer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
Toxav_Err_Answer err {TOXAV_ERR_ANSWER_OK}; Toxav_Err_Answer err {TOXAV_ERR_ANSWER_OK};
toxav_answer(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err); toxav_answer(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &err);
return err; return err;
} }
Toxav_Err_Call_Control ToxAV::toxavCallControl(uint32_t friend_number, Toxav_Call_Control control) { Toxav_Err_Call_Control ToxAVI::toxavCallControl(uint32_t friend_number, Toxav_Call_Control control) {
Toxav_Err_Call_Control err {TOXAV_ERR_CALL_CONTROL_OK}; Toxav_Err_Call_Control err {TOXAV_ERR_CALL_CONTROL_OK};
toxav_call_control(_tox_av, friend_number, control, &err); toxav_call_control(_tox_av, friend_number, control, &err);
return err; return err;
} }
Toxav_Err_Send_Frame ToxAV::toxavAudioSendFrame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate) { Toxav_Err_Send_Frame ToxAVI::toxavAudioSendFrame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate) {
Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK}; Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK};
toxav_audio_send_frame(_tox_av, friend_number, pcm, sample_count, channels, sampling_rate, &err); toxav_audio_send_frame(_tox_av, friend_number, pcm, sample_count, channels, sampling_rate, &err);
return err; return err;
} }
Toxav_Err_Bit_Rate_Set ToxAV::toxavAudioSetBitRate(uint32_t friend_number, uint32_t bit_rate) { Toxav_Err_Bit_Rate_Set ToxAVI::toxavAudioSetBitRate(uint32_t friend_number, uint32_t bit_rate) {
Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK}; Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK};
toxav_audio_set_bit_rate(_tox_av, friend_number, bit_rate, &err); toxav_audio_set_bit_rate(_tox_av, friend_number, bit_rate, &err);
return err; return err;
} }
Toxav_Err_Send_Frame ToxAV::toxavVideoSendFrame(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t y[], const uint8_t u[], const uint8_t v[]) { Toxav_Err_Send_Frame ToxAVI::toxavVideoSendFrame(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t y[], const uint8_t u[], const uint8_t v[]) {
Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK}; Toxav_Err_Send_Frame err {TOXAV_ERR_SEND_FRAME_OK};
toxav_video_send_frame(_tox_av, friend_number, width, height, y, u, v, &err); toxav_video_send_frame(_tox_av, friend_number, width, height, y, u, v, &err);
return err; return err;
} }
Toxav_Err_Bit_Rate_Set ToxAV::toxavVideoSetBitRate(uint32_t friend_number, uint32_t bit_rate) { Toxav_Err_Bit_Rate_Set ToxAVI::toxavVideoSetBitRate(uint32_t friend_number, uint32_t bit_rate) {
Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK}; Toxav_Err_Bit_Rate_Set err {TOXAV_ERR_BIT_RATE_SET_OK};
toxav_video_set_bit_rate(_tox_av, friend_number, bit_rate, &err); toxav_video_set_bit_rate(_tox_av, friend_number, bit_rate, &err);
return err; return err;
} }
void ToxAV::cb_call(uint32_t friend_number, bool audio_enabled, bool video_enabled) { void ToxAVI::cb_call(uint32_t friend_number, bool audio_enabled, bool video_enabled) {
std::cerr << "TOXAV: receiving call f:" << friend_number << " a:" << audio_enabled << " v:" << video_enabled << "\n"; std::cerr << "TOXAV: receiving call f:" << friend_number << " a:" << audio_enabled << " v:" << video_enabled << "\n";
//Toxav_Err_Answer err_answer { TOXAV_ERR_ANSWER_OK }; //Toxav_Err_Answer err_answer { TOXAV_ERR_ANSWER_OK };
//toxav_answer(_tox_av, friend_number, 0, 0, &err_answer); //toxav_answer(_tox_av, friend_number, 0, 0, &err_answer);
@ -169,7 +158,7 @@ void ToxAV::cb_call(uint32_t friend_number, bool audio_enabled, bool video_enabl
); );
} }
void ToxAV::cb_call_state(uint32_t friend_number, uint32_t state) { void ToxAVI::cb_call_state(uint32_t friend_number, uint32_t state) {
//ToxAVFriendCallState w_state{state}; //ToxAVFriendCallState w_state{state};
//w_state.is_error(); //w_state.is_error();
@ -185,7 +174,7 @@ void ToxAV::cb_call_state(uint32_t friend_number, uint32_t state) {
); );
} }
void ToxAV::cb_audio_bit_rate(uint32_t friend_number, uint32_t audio_bit_rate) { void ToxAVI::cb_audio_bit_rate(uint32_t friend_number, uint32_t audio_bit_rate) {
std::cerr << "TOXAV: audio bitrate f:" << friend_number << " abr:" << audio_bit_rate << "\n"; std::cerr << "TOXAV: audio bitrate f:" << friend_number << " abr:" << audio_bit_rate << "\n";
dispatch( dispatch(
@ -197,7 +186,7 @@ void ToxAV::cb_audio_bit_rate(uint32_t friend_number, uint32_t audio_bit_rate) {
); );
} }
void ToxAV::cb_video_bit_rate(uint32_t friend_number, uint32_t video_bit_rate) { void ToxAVI::cb_video_bit_rate(uint32_t friend_number, uint32_t video_bit_rate) {
std::cerr << "TOXAV: video bitrate f:" << friend_number << " vbr:" << video_bit_rate << "\n"; std::cerr << "TOXAV: video bitrate f:" << friend_number << " vbr:" << video_bit_rate << "\n";
dispatch( dispatch(
@ -209,7 +198,7 @@ void ToxAV::cb_video_bit_rate(uint32_t friend_number, uint32_t video_bit_rate) {
); );
} }
void ToxAV::cb_audio_receive_frame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate) { void ToxAVI::cb_audio_receive_frame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate) {
//std::cerr << "TOXAV: audio frame f:" << friend_number << " sc:" << sample_count << " ch:" << (int)channels << " sr:" << sampling_rate << "\n"; //std::cerr << "TOXAV: audio frame f:" << friend_number << " sc:" << sample_count << " ch:" << (int)channels << " sr:" << sampling_rate << "\n";
dispatch( dispatch(
@ -223,7 +212,7 @@ void ToxAV::cb_audio_receive_frame(uint32_t friend_number, const int16_t pcm[],
); );
} }
void ToxAV::cb_video_receive_frame( void ToxAVI::cb_video_receive_frame(
uint32_t friend_number, uint32_t friend_number,
uint16_t width, uint16_t height, uint16_t width, uint16_t height,
const uint8_t y[/*! max(width, abs(ystride)) * height */], const uint8_t y[/*! max(width, abs(ystride)) * height */],

View File

@ -82,14 +82,15 @@ struct ToxAVEventI {
}; };
using ToxAVEventProviderI = EventProviderI<ToxAVEventI>; using ToxAVEventProviderI = EventProviderI<ToxAVEventI>;
struct ToxAV : public ToxAVEventProviderI{ // TODO: seperate out implementation from interface
struct ToxAVI : public ToxAVEventProviderI {
Tox* _tox = nullptr; Tox* _tox = nullptr;
ToxAV* _tox_av = nullptr; ToxAV* _tox_av = nullptr;
static constexpr const char* version {"0"}; static constexpr const char* version {"0"};
ToxAV(Tox* tox); ToxAVI(Tox* tox);
virtual ~ToxAV(void); virtual ~ToxAVI(void);
// interface // interface
// if iterate is called on a different thread, it will fire events there // if iterate is called on a different thread, it will fire events there
@ -134,3 +135,14 @@ struct ToxAV : public ToxAVEventProviderI{
); );
}; };
struct ToxAVFriendCallState final {
const uint32_t state {TOXAV_FRIEND_CALL_STATE_NONE};
[[nodiscard]] bool is_error(void) const { return state & TOXAV_FRIEND_CALL_STATE_ERROR; }
[[nodiscard]] bool is_finished(void) const { return state & TOXAV_FRIEND_CALL_STATE_FINISHED; }
[[nodiscard]] bool is_sending_a(void) const { return state & TOXAV_FRIEND_CALL_STATE_SENDING_A; }
[[nodiscard]] bool is_sending_v(void) const { return state & TOXAV_FRIEND_CALL_STATE_SENDING_V; }
[[nodiscard]] bool is_accepting_a(void) const { return state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A; }
[[nodiscard]] bool is_accepting_v(void) const { return state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V; }
};

334
src/tox_av_voip_model.cpp Normal file
View File

@ -0,0 +1,334 @@
#include "./tox_av_voip_model.hpp"
#include <solanaceae/object_store/object_store.hpp>
#include <solanaceae/tox_contacts/components.hpp>
#include "./frame_streams/stream_manager.hpp"
#include "./frame_streams/audio_stream2.hpp"
#include "./frame_streams/locked_frame_stream.hpp"
#include <iostream>
namespace Contact::Components {
// session instead???
struct ToxAVCall {
bool incoming_audio {false};
bool incoming_video {false};
};
}
namespace Components {
struct ToxAVAudioSink {
ObjectHandle o;
// ptr?
};
struct ToxAVAudioSource {
ObjectHandle o;
// ptr?
};
} // Components
struct ToxAVCallAudioSink : public FrameStream2SinkI<AudioFrame2> {
ToxAVI& _toxav;
// bitrate for enabled state
uint32_t _audio_bitrate {32};
uint32_t _fid;
std::shared_ptr<LockedFrameStream2<AudioFrame2>> _writer;
ToxAVCallAudioSink(ToxAVI& toxav, uint32_t fid) : _toxav(toxav), _fid(fid) {}
~ToxAVCallAudioSink(void) {
if (_writer) {
_writer = nullptr;
_toxav.toxavAudioSetBitRate(_fid, 0);
}
}
// sink
std::shared_ptr<FrameStream2I<AudioFrame2>> subscribe(void) override {
if (_writer) {
// max 1 (exclusive for now)
return nullptr;
}
auto err = _toxav.toxavAudioSetBitRate(_fid, _audio_bitrate);
if (err != TOXAV_ERR_BIT_RATE_SET_OK) {
return nullptr;
}
_writer = std::make_shared<LockedFrameStream2<AudioFrame2>>();
return _writer;
}
bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame2>>& sub) override {
if (!sub || !_writer) {
// nah
return false;
}
if (sub == _writer) {
_writer = nullptr;
/*auto err = */_toxav.toxavAudioSetBitRate(_fid, 0);
// print warning? on error?
return true;
}
// what
return false;
}
};
ToxAVVoIPModel::ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm) :
_os(os), _av(av), _cr(cr), _tcm(tcm)
{
_av.subscribe(this, ToxAV_Event::friend_call);
_av.subscribe(this, ToxAV_Event::friend_call_state);
_av.subscribe(this, ToxAV_Event::friend_audio_bitrate);
_av.subscribe(this, ToxAV_Event::friend_video_bitrate);
_av.subscribe(this, ToxAV_Event::friend_audio_frame);
_av.subscribe(this, ToxAV_Event::friend_video_frame);
// attach to all tox friend contacts
for (const auto& [cv, _] : _cr.view<Contact::Components::ToxFriendPersistent>().each()) {
_cr.emplace<VoIPModelI*>(cv, this);
}
// TODO: events
}
ToxAVVoIPModel::~ToxAVVoIPModel(void) {
for (const auto& [ov, voipmodel] : _os.registry().view<VoIPModelI*>().each()) {
if (voipmodel == this) {
destroySession(_os.objectHandle(ov));
}
}
}
void ToxAVVoIPModel::destroySession(ObjectHandle session) {
if (!static_cast<bool>(session)) {
return;
}
// destory sources
if (auto* ss = session.try_get<Components::VoIP::StreamSources>(); ss != nullptr) {
for (const auto ssov : ss->streams) {
_os.throwEventDestroy(ssov);
_os.registry().destroy(ssov);
}
}
// destory sinks
if (auto* ss = session.try_get<Components::VoIP::StreamSinks>(); ss != nullptr) {
for (const auto ssov : ss->streams) {
_os.throwEventDestroy(ssov);
_os.registry().destroy(ssov);
}
}
// destory session
_os.throwEventDestroy(session);
_os.registry().destroy(session);
}
void ToxAVVoIPModel::tick(void) {
//for (const auto& [oc, asink, asrf] : _os.registry().view<ToxAVCallAudioSink*, AudioStreamReFramer>().each()) {
for (const auto& [oc, asink] : _os.registry().view<ToxAVCallAudioSink*>().each()) {
if (!asink->_writer) {
continue;
}
//asrf._stream = asink->_writer.get();
for (size_t i = 0; i < 10; i++) {
auto new_frame_opt = asink->_writer->pop();
//auto new_frame_opt = asrf.pop();
if (!new_frame_opt.has_value()) {
break;
}
const auto& new_frame = new_frame_opt.value();
//* @param sample_count Number of samples in this frame. Valid numbers here are
//* `((sample rate) * (audio length) / 1000)`, where audio length can be
//* 2.5, 5, 10, 20, 40 or 60 milliseconds.
// we likely needs to subdivide/repackage
// frame size should be an option exposed to the user
// with 10ms as a default ?
// the larger the frame size, the less overhead but the more delay
auto err = _av.toxavAudioSendFrame(
asink->_fid,
new_frame.getSpan().ptr,
new_frame.getSpan().size / new_frame.channels,
new_frame.channels,
new_frame.sample_rate
);
if (err != TOXAV_ERR_SEND_FRAME_OK) {
std::cerr << "DTC: failed to send audio frame " << err << "\n";
}
}
}
}
ObjectHandle ToxAVVoIPModel::enter(const Contact3 c, const DefaultConfig& defaults) {
if (!_cr.all_of<Contact::Components::ToxFriendEphemeral>(c)) {
return {};
}
const auto friend_number = _cr.get<Contact::Components::ToxFriendEphemeral>(c).friend_number;
std::cerr << "TAVVOIP: lol\n";
const auto err = _av.toxavCall(friend_number, 0, 0);
if (err != TOXAV_ERR_CALL_OK) {
std::cerr << "TAVVOIP error: failed to start call: " << err << "\n";
return {};
}
ObjectHandle new_session {_os.registry(), _os.registry().create()};
new_session.emplace<Components::VoIP::SessionContact>(c);
new_session.emplace<Components::VoIP::TagVoIPSession>(); // ??
new_session.emplace<Components::VoIP::SessionState>().state = Components::VoIP::SessionState::State::RINGING;
new_session.emplace<VoIPModelI*>(this);
_os.throwEventConstruct(new_session);
return new_session;
}
bool ToxAVVoIPModel::accept(ObjectHandle session) {
//_av.toxavAnswer(, 0, 0);
return false;
}
bool ToxAVVoIPModel::leave(ObjectHandle session) {
// rename to end?
if (!static_cast<bool>(session)) {
return false;
}
if (!session.all_of<Components::VoIP::TagVoIPSession, VoIPModelI*, Components::VoIP::SessionContact>()) {
return false;
}
// check if self
if (session.get<VoIPModelI*>() != this) {
return false;
}
const auto session_contact = session.get<Components::VoIP::SessionContact>().c;
if (!_cr.all_of<Contact::Components::ToxFriendEphemeral>(session_contact)) {
return false;
}
const auto friend_number = _cr.get<Contact::Components::ToxFriendEphemeral>(session_contact).friend_number;
// check error? (we delete anyway)
_av.toxavCallControl(friend_number, Toxav_Call_Control::TOXAV_CALL_CONTROL_CANCEL);
destroySession(session);
return true;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendCall& e) {
// new incoming call, create voip session, ready to be accepted
// (or rejected...)
const auto session_contact = _tcm.getContactFriend(e.friend_number);
if (!_cr.valid(session_contact)) {
return false;
}
ObjectHandle new_session {_os.registry(), _os.registry().create()};
new_session.emplace<Components::VoIP::SessionContact>(session_contact);
new_session.emplace<Components::VoIP::TagVoIPSession>(); // ??
new_session.emplace<Components::VoIP::SessionState>().state = Components::VoIP::SessionState::State::RINGING;
new_session.emplace<VoIPModelI*>(this);
_os.throwEventConstruct(new_session);
return true;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendCallState& e) {
const auto session_contact = _tcm.getContactFriend(e.friend_number);
if (!_cr.valid(session_contact)) {
return false;
}
ToxAVFriendCallState s{e.state};
// find session(s?)
// TODO: keep lookup table
for (const auto& [ov, voipmodel] : _os.registry().view<VoIPModelI*>().each()) {
if (voipmodel == this) {
auto o = _os.objectHandle(ov);
if (!o.all_of<Components::VoIP::SessionContact>()) {
continue;
}
if (session_contact != o.get<Components::VoIP::SessionContact>().c) {
continue;
}
if (s.is_error() || s.is_finished()) {
// destroy call
destroySession(o);
} else {
// remote accepted our call, or av send/recv conditions changed?
o.get<Components::VoIP::SessionState>().state; // set to in call
auto& stream_sinks = o.get_or_emplace<Components::VoIP::StreamSinks>().streams;
if (s.is_accepting_a() && !o.all_of<Components::ToxAVAudioSink>()) {
ObjectHandle outgoing_audio {_os.registry(), _os.registry().create()};
auto new_asink = std::make_unique<ToxAVCallAudioSink>(_av, e.friend_number);
outgoing_audio.emplace<ToxAVCallAudioSink*>(new_asink.get());
//outgoing_audio.emplace<AudioStreamReFramer>().frame_length_ms = 10;
outgoing_audio.emplace<Components::FrameStream2Sink<AudioFrame2>>(std::move(new_asink));
outgoing_audio.emplace<Components::StreamSink>(Components::StreamSink::create<AudioFrame2>("ToxAV Friend Call Outgoing Audio"));
outgoing_audio.emplace<Components::TagConnectToDefault>(); // depends on what was specified in enter()
stream_sinks.push_back(outgoing_audio);
o.emplace<Components::ToxAVAudioSink>(outgoing_audio);
// TODO: tie session to stream
_os.throwEventConstruct(outgoing_audio);
} else if (!s.is_accepting_a() && o.all_of<Components::ToxAVAudioSink>()) {
// remove asink?
}
// video
// add/update sources
// audio
// video
}
}
}
return true;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendAudioBitrate&) {
return false;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendVideoBitrate&) {
return false;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendAudioFrame&) {
return false;
}
bool ToxAVVoIPModel::onEvent(const Events::FriendVideoFrame&) {
return false;
}

35
src/tox_av_voip_model.hpp Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <solanaceae/object_store/fwd.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
#include "./frame_streams/voip_model.hpp"
#include "./tox_av.hpp"
struct ToxAVVoIPModel : protected ToxAVEventI, public VoIPModelI {
ObjectStore2& _os;
ToxAVI& _av;
Contact3Registry& _cr;
ToxContactModel2& _tcm;
ToxAVVoIPModel(ObjectStore2& os, ToxAVI& av, Contact3Registry& cr, ToxContactModel2& tcm);
~ToxAVVoIPModel(void);
void destroySession(ObjectHandle session);
void tick(void);
public: // voip model
ObjectHandle enter(const Contact3 c, const DefaultConfig& defaults) override;
bool accept(ObjectHandle session) override;
bool leave(ObjectHandle session) override;
protected: // toxav events
bool onEvent(const Events::FriendCall&) override;
bool onEvent(const Events::FriendCallState&) override;
bool onEvent(const Events::FriendAudioBitrate&) override;
bool onEvent(const Events::FriendVideoBitrate&) override;
bool onEvent(const Events::FriendAudioFrame&) override;
bool onEvent(const Events::FriendVideoFrame&) override;
};