diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1bfe5e64..74c0e525 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,6 +107,9 @@ target_sources(tomato PUBLIC ./frame_streams/audio_stream2.hpp ./frame_streams/stream_manager.hpp ./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.cpp @@ -123,6 +126,9 @@ if (TOMATO_TOX_AV) target_sources(tomato PUBLIC ./tox_av.hpp ./tox_av.cpp + + ./tox_av_voip_model.hpp + ./tox_av_voip_model.cpp ) target_compile_definitions(tomato PUBLIC TOMATO_TOX_AV) diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index b348ae42..ace44b76 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -9,6 +9,8 @@ #include #include +#include "./frame_streams/voip_model.hpp" + // HACK: remove them #include @@ -21,6 +23,7 @@ #include "./media_meta_info_loader.hpp" #include "./sdl_clipboard_utils.hpp" +#include "entt/entity/entity.hpp" #include #include @@ -30,6 +33,7 @@ #include #include #include +#include #include 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::BeginMenuBar()) { + // check if contact has voip model + // use activesessioncomp instead? + if (_cr.all_of(*_selected_contact)) { + if (ImGui::BeginMenu("VoIP")) { + auto* voip_model = _cr.get(*_selected_contact); + + std::vector contact_sessions; + for (const auto& [ov, o_vm, sc] : _os.registry().view().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")) { ImGui::Checkbox("show extra info", &_show_chat_extra_info); ImGui::Checkbox("show avatar transfers", &_show_chat_avatar_tf); diff --git a/src/frame_streams/locked_frame_stream.hpp b/src/frame_streams/locked_frame_stream.hpp new file mode 100644 index 00000000..a2a43ebd --- /dev/null +++ b/src/frame_streams/locked_frame_stream.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "./frame_stream2.hpp" + +#include +#include + +// threadsafe queue frame stream +// protected by a simple mutex lock +// prefer lockless queue implementations, when available +template +struct LockedFrameStream2 : public FrameStream2I { + std::mutex _lock; + + std::deque _frames; + + ~LockedFrameStream2(void) {} + + int32_t size(void) { return -1; } + + std::optional 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; + } +}; + diff --git a/src/frame_streams/voip_model.hpp b/src/frame_streams/voip_model.hpp index 6346aea8..83afeca5 100644 --- a/src/frame_streams/voip_model.hpp +++ b/src/frame_streams/voip_model.hpp @@ -10,9 +10,9 @@ namespace Components::VoIP { struct TagVoIPSession {}; // to talk to the model handling this session - struct VoIPModel { - VoIPModelI* ptr {nullptr}; - }; + //struct VoIPModel { + //VoIPModelI* ptr {nullptr}; + //}; struct SessionState { // ???? @@ -59,13 +59,13 @@ struct VoIPModelI { bool outgoing_audio {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 - virtual bool accept(ObjectHandle session); + virtual bool accept(ObjectHandle session) { return false; } // leaves a call // - VoIP session object - virtual bool leave(ObjectHandle session); + virtual bool leave(ObjectHandle session) { return false; } }; diff --git a/src/main_screen.cpp b/src/main_screen.cpp index c6f70605..b552645a 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -25,13 +25,14 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme tc(save_path, save_password), tpi(tc.getTox()), ad(tc), -#if TOMATO_TOX_AV - tav(tc.getTox()), -#endif tcm(cr, tc, tc), tmm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc, os), tffom(cr, rmm, tcm, tc, tc), +#if TOMATO_TOX_AV + tav(tc.getTox()), + tavvoip(os, tav, cr, tcm), +#endif theme(theme_), mmil(rmm), tam(/*rmm, */ os, cr, conf), @@ -80,7 +81,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme g_provideInstance("ToxPrivateI", "host", &tpi); g_provideInstance("ToxEventProviderI", "host", &tc); #if TOMATO_TOX_AV - g_provideInstance("ToxAV", "host", &tav); + g_provideInstance("ToxAVI", "host", &tav); #endif g_provideInstance("ToxContactModel2", "host", &tcm); @@ -528,6 +529,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) { tav.toxavIterate(); // HACK: pow by 1.18 to increase 200 -> ~500 const float av_interval = std::pow(tav.toxavIterationInterval(), 1.18)/1000.f; + + tavvoip.tick(); #endif tcm.iterate(time_delta); // compute diff --git a/src/main_screen.hpp b/src/main_screen.hpp index cd985a9a..daa3ab25 100644 --- a/src/main_screen.hpp +++ b/src/main_screen.hpp @@ -39,6 +39,7 @@ #if TOMATO_TOX_AV #include "./tox_av.hpp" +#include "./tox_av_voip_model.hpp" #endif #include @@ -67,13 +68,14 @@ struct MainScreen final : public Screen { ToxClient tc; ToxPrivateImpl tpi; AutoDirty ad; -#if TOMATO_TOX_AV - ToxAV tav; -#endif ToxContactModel2 tcm; ToxMessageManager tmm; ToxTransferManager ttm; ToxFriendFauxOfflineMessaging tffom; +#if TOMATO_TOX_AV + ToxAVI tav; + ToxAVVoIPModel tavvoip; +#endif Theme& theme; diff --git a/src/tox_av.cpp b/src/tox_av.cpp index 695744c3..4e6c044b 100644 --- a/src/tox_av.cpp +++ b/src/tox_av.cpp @@ -7,18 +7,7 @@ // https://almogfx.bandcamp.com/track/crushed-w-cassade -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; } -}; - -ToxAV::ToxAV(Tox* tox) : _tox(tox) { +ToxAVI::ToxAVI(Tox* tox) : _tox(tox) { Toxav_Err_New err_new {TOXAV_ERR_NEW_OK}; _tox_av = toxav_new(_tox, &err_new); // TODO: throw @@ -28,7 +17,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { _tox_av, +[](ToxAV*, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) { assert(user_data != nullptr); - static_cast(user_data)->cb_call(friend_number, audio_enabled, video_enabled); + static_cast(user_data)->cb_call(friend_number, audio_enabled, video_enabled); }, this ); @@ -36,7 +25,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { _tox_av, +[](ToxAV*, uint32_t friend_number, uint32_t state, void *user_data) { assert(user_data != nullptr); - static_cast(user_data)->cb_call_state(friend_number, state); + static_cast(user_data)->cb_call_state(friend_number, state); }, this ); @@ -44,7 +33,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { _tox_av, +[](ToxAV*, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data) { assert(user_data != nullptr); - static_cast(user_data)->cb_audio_bit_rate(friend_number, audio_bit_rate); + static_cast(user_data)->cb_audio_bit_rate(friend_number, audio_bit_rate); }, this ); @@ -52,7 +41,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { _tox_av, +[](ToxAV*, uint32_t friend_number, uint32_t video_bit_rate, void *user_data) { assert(user_data != nullptr); - static_cast(user_data)->cb_video_bit_rate(friend_number, video_bit_rate); + static_cast(user_data)->cb_video_bit_rate(friend_number, video_bit_rate); }, this ); @@ -60,7 +49,7 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { _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) { assert(user_data != nullptr); - static_cast(user_data)->cb_audio_receive_frame(friend_number, pcm, sample_count, channels, sampling_rate); + static_cast(user_data)->cb_audio_receive_frame(friend_number, pcm, sample_count, channels, sampling_rate); }, this ); @@ -75,83 +64,83 @@ ToxAV::ToxAV(Tox* tox) : _tox(tox) { void *user_data ) { assert(user_data != nullptr); - static_cast(user_data)->cb_video_receive_frame(friend_number, width, height, y, u, v, ystride, ustride, vstride); + static_cast(user_data)->cb_video_receive_frame(friend_number, width, height, y, u, v, ystride, ustride, vstride); }, this ); } -ToxAV::~ToxAV(void) { +ToxAVI::~ToxAVI(void) { toxav_kill(_tox_av); } -uint32_t ToxAV::toxavIterationInterval(void) const { +uint32_t ToxAVI::toxavIterationInterval(void) const { return toxav_iteration_interval(_tox_av); } -void ToxAV::toxavIterate(void) { +void ToxAVI::toxavIterate(void) { toxav_iterate(_tox_av); } -uint32_t ToxAV::toxavAudioIterationInterval(void) const { +uint32_t ToxAVI::toxavAudioIterationInterval(void) const { return toxav_audio_iteration_interval(_tox_av); } -void ToxAV::toxavAudioIterate(void) { +void ToxAVI::toxavAudioIterate(void) { toxav_audio_iterate(_tox_av); } -uint32_t ToxAV::toxavVideoIterationInterval(void) const { +uint32_t ToxAVI::toxavVideoIterationInterval(void) const { return toxav_video_iteration_interval(_tox_av); } -void ToxAV::toxavVideoIterate(void) { +void ToxAVI::toxavVideoIterate(void) { 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_call(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &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_answer(_tox_av, friend_number, audio_bit_rate, video_bit_rate, &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_call_control(_tox_av, friend_number, control, &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_audio_send_frame(_tox_av, friend_number, pcm, sample_count, channels, sampling_rate, &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_audio_set_bit_rate(_tox_av, friend_number, bit_rate, &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_video_send_frame(_tox_av, friend_number, width, height, y, u, v, &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_video_set_bit_rate(_tox_av, friend_number, bit_rate, &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"; //Toxav_Err_Answer err_answer { TOXAV_ERR_ANSWER_OK }; //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}; //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"; 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"; 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"; 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, uint16_t width, uint16_t height, const uint8_t y[/*! max(width, abs(ystride)) * height */], diff --git a/src/tox_av.hpp b/src/tox_av.hpp index bc00ce0f..7771118d 100644 --- a/src/tox_av.hpp +++ b/src/tox_av.hpp @@ -82,14 +82,15 @@ struct ToxAVEventI { }; using ToxAVEventProviderI = EventProviderI; -struct ToxAV : public ToxAVEventProviderI{ +// TODO: seperate out implementation from interface +struct ToxAVI : public ToxAVEventProviderI { Tox* _tox = nullptr; ToxAV* _tox_av = nullptr; static constexpr const char* version {"0"}; - ToxAV(Tox* tox); - virtual ~ToxAV(void); + ToxAVI(Tox* tox); + virtual ~ToxAVI(void); // interface // 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; } +}; + diff --git a/src/tox_av_voip_model.cpp b/src/tox_av_voip_model.cpp new file mode 100644 index 00000000..f0f535dc --- /dev/null +++ b/src/tox_av_voip_model.cpp @@ -0,0 +1,334 @@ +#include "./tox_av_voip_model.hpp" + +#include +#include + +#include "./frame_streams/stream_manager.hpp" +#include "./frame_streams/audio_stream2.hpp" +#include "./frame_streams/locked_frame_stream.hpp" + +#include + +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 { + ToxAVI& _toxav; + + // bitrate for enabled state + uint32_t _audio_bitrate {32}; + + uint32_t _fid; + std::shared_ptr> _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> 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>(); + + return _writer; + } + + bool unsubscribe(const std::shared_ptr>& 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().each()) { + _cr.emplace(cv, this); + } + // TODO: events +} + +ToxAVVoIPModel::~ToxAVVoIPModel(void) { + for (const auto& [ov, voipmodel] : _os.registry().view().each()) { + if (voipmodel == this) { + destroySession(_os.objectHandle(ov)); + } + } +} + +void ToxAVVoIPModel::destroySession(ObjectHandle session) { + if (!static_cast(session)) { + return; + } + + // destory sources + if (auto* ss = session.try_get(); ss != nullptr) { + for (const auto ssov : ss->streams) { + + _os.throwEventDestroy(ssov); + _os.registry().destroy(ssov); + } + } + + // destory sinks + if (auto* ss = session.try_get(); 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().each()) { + for (const auto& [oc, asink] : _os.registry().view().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(c)) { + return {}; + } + + const auto friend_number = _cr.get(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(c); + new_session.emplace(); // ?? + new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; + new_session.emplace(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(session)) { + return false; + } + + if (!session.all_of()) { + return false; + } + + // check if self + if (session.get() != this) { + return false; + } + + const auto session_contact = session.get().c; + if (!_cr.all_of(session_contact)) { + return false; + } + + const auto friend_number = _cr.get(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(session_contact); + new_session.emplace(); // ?? + new_session.emplace().state = Components::VoIP::SessionState::State::RINGING; + new_session.emplace(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().each()) { + if (voipmodel == this) { + auto o = _os.objectHandle(ov); + + if (!o.all_of()) { + continue; + } + if (session_contact != o.get().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().state; // set to in call + + auto& stream_sinks = o.get_or_emplace().streams; + if (s.is_accepting_a() && !o.all_of()) { + ObjectHandle outgoing_audio {_os.registry(), _os.registry().create()}; + + auto new_asink = std::make_unique(_av, e.friend_number); + outgoing_audio.emplace(new_asink.get()); + //outgoing_audio.emplace().frame_length_ms = 10; + outgoing_audio.emplace>(std::move(new_asink)); + outgoing_audio.emplace(Components::StreamSink::create("ToxAV Friend Call Outgoing Audio")); + outgoing_audio.emplace(); // depends on what was specified in enter() + + stream_sinks.push_back(outgoing_audio); + o.emplace(outgoing_audio); + // TODO: tie session to stream + + _os.throwEventConstruct(outgoing_audio); + } else if (!s.is_accepting_a() && o.all_of()) { + // 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; +} + diff --git a/src/tox_av_voip_model.hpp b/src/tox_av_voip_model.hpp new file mode 100644 index 00000000..f067fc88 --- /dev/null +++ b/src/tox_av_voip_model.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#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; +}; +