more stream progress, threaded connections toxav video sending

This commit is contained in:
Green Sky 2024-09-16 20:24:07 +02:00
parent 964f6de656
commit b4373e0d9a
No known key found for this signature in database
10 changed files with 395 additions and 141 deletions

View File

@ -126,7 +126,7 @@ SDLVideoCameraContent::SDLVideoCameraContent(void) {
bool someone_listening {false}; bool someone_listening {false};
{ {
SDLVideoFrame new_frame_non_owning { SDLVideoFrame new_frame_non_owning {
timestampNS, timestampNS/1000,
sdl_frame_next sdl_frame_next
}; };

View File

@ -12,7 +12,8 @@ inline void nopSurfaceDestructor(SDL_Surface*) {}
// this is very sdl specific // this is very sdl specific
struct SDLVideoFrame { struct SDLVideoFrame {
// TODO: sequence numbering? // TODO: sequence numbering?
uint64_t timestampNS {0}; // micro seconds (nano is way too much)
uint64_t timestampUS {0};
std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface {nullptr, &SDL_DestroySurface}; std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface {nullptr, &SDL_DestroySurface};
@ -21,12 +22,12 @@ struct SDLVideoFrame {
uint64_t ts, uint64_t ts,
SDL_Surface* surf SDL_Surface* surf
) { ) {
timestampNS = ts; timestampUS = ts;
surface = {surf, &nopSurfaceDestructor}; surface = {surf, &nopSurfaceDestructor};
} }
// copy // copy
SDLVideoFrame(const SDLVideoFrame& other) { SDLVideoFrame(const SDLVideoFrame& other) {
timestampNS = other.timestampNS; timestampUS = other.timestampUS;
if (static_cast<bool>(other.surface)) { if (static_cast<bool>(other.surface)) {
//surface = { //surface = {
// SDL_CreateSurface( // SDL_CreateSurface(
@ -39,7 +40,7 @@ struct SDLVideoFrame {
//SDL_BlitSurface(other.surface.get(), nullptr, surface.get(), nullptr); //SDL_BlitSurface(other.surface.get(), nullptr, surface.get(), nullptr);
surface = { surface = {
SDL_DuplicateSurface(other.surface.get()), SDL_DuplicateSurface(other.surface.get()),
&SDL_DestroySurface &SDL_DestroySurface
}; };
} }
} }

View File

@ -1,21 +1,21 @@
#include "./debug_tox_call.hpp" #include "./debug_tox_call.hpp"
#include "./stream_manager.hpp"
#include "./content/sdl_video_frame_stream2.hpp"
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <cstdint>
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <cstring> #include <cstring>
#include <cstdint>
#include <iostream> #include <iostream>
#include <memory>
// fwd // fwd
namespace Message { namespace Message {
uint64_t getTimeMS(void); uint64_t getTimeMS();
}
static constexpr float lerp(float a, float b, float t) {
return a + t * (b - a);
} }
namespace Components { namespace Components {
@ -32,6 +32,110 @@ namespace Components {
}; };
} }
static bool isFormatPlanar(SDL_PixelFormat f) {
return
f == SDL_PIXELFORMAT_YV12 ||
f == SDL_PIXELFORMAT_IYUV ||
f == SDL_PIXELFORMAT_YUY2 ||
f == SDL_PIXELFORMAT_UYVY ||
f == SDL_PIXELFORMAT_YVYU ||
f == SDL_PIXELFORMAT_NV12 ||
f == SDL_PIXELFORMAT_NV21 ||
f == SDL_PIXELFORMAT_P010
;
}
struct PushConversionQueuedVideoStream : public QueuedFrameStream2<SDLVideoFrame> {
SDL_PixelFormat _forced_format {SDL_PIXELFORMAT_IYUV};
PushConversionQueuedVideoStream(size_t queue_size, bool lossy = true) : QueuedFrameStream2<SDLVideoFrame>(queue_size, lossy) {}
~PushConversionQueuedVideoStream(void) {}
bool push(const SDLVideoFrame& value) override {
SDL_Surface* converted_surf = value.surface.get();
if (converted_surf->format != SDL_PIXELFORMAT_IYUV) {
//std::cerr << "DTC: need to convert from " << SDL_GetPixelFormatName(converted_surf->format) << " to SDL_PIXELFORMAT_IYUV\n";
if (isFormatPlanar(converted_surf->format)) {
// meh, need to convert to rgb as a stopgap
//auto start = Message::getTimeMS();
//SDL_Surface* tmp_conv_surf = SDL_ConvertSurfaceAndColorspace(converted_surf, SDL_PIXELFORMAT_RGBA32, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
SDL_Surface* tmp_conv_surf = SDL_ConvertSurfaceAndColorspace(converted_surf, SDL_PIXELFORMAT_RGB24, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
//auto end = Message::getTimeMS();
//std::cerr << "DTC: timing " << SDL_GetPixelFormatName(converted_surf->format) << "->SDL_PIXELFORMAT_RGB24: " << end-start << "ms\n";
// TODO: fix sdl rgb->yuv conversion resulting in too dark (colorspace) issues
//start = Message::getTimeMS();
converted_surf = SDL_ConvertSurfaceAndColorspace(tmp_conv_surf, SDL_PIXELFORMAT_IYUV, nullptr, SDL_COLORSPACE_YUV_DEFAULT, 0);
//end = Message::getTimeMS();
//std::cerr << "DTC: timing SDL_PIXELFORMAT_RGB24->SDL_PIXELFORMAT_IYUV: " << end-start << "ms\n";
SDL_DestroySurface(tmp_conv_surf);
} else {
converted_surf = SDL_ConvertSurface(converted_surf, SDL_PIXELFORMAT_IYUV);
}
if (converted_surf == nullptr) {
// oh god
std::cerr << "DTC error: failed to convert surface to IYUV: " << SDL_GetError() << "\n";
return false;
}
}
assert(converted_surf != nullptr);
if (converted_surf != value.surface.get()) {
// TODO: add ctr with uptr
SDLVideoFrame new_value{value.timestampUS, nullptr};
new_value.surface = {
converted_surf,
&SDL_DestroySurface
};
return QueuedFrameStream2<SDLVideoFrame>::push(new_value);
} else {
return QueuedFrameStream2<SDLVideoFrame>::push(value);
}
}
};
// exlusive
// TODO: replace with something better than a queue
struct ToxAVCallVideoSink : public FrameStream2SinkI<SDLVideoFrame> {
uint32_t _fid;
std::shared_ptr<PushConversionQueuedVideoStream> _writer;
ToxAVCallVideoSink(uint32_t fid) : _fid(fid) {}
~ToxAVCallVideoSink(void) {}
// sink
std::shared_ptr<FrameStream2I<SDLVideoFrame>> subscribe(void) override {
if (_writer) {
// max 1 (exclusive)
return nullptr;
}
// TODO: enable video here
_writer = std::make_shared<PushConversionQueuedVideoStream>(1, true);
return _writer;
}
bool unsubscribe(const std::shared_ptr<FrameStream2I<SDLVideoFrame>>& sub) override {
if (!sub || !_writer) {
// nah
return false;
}
if (sub == _writer) {
// TODO: disable video here
_writer = nullptr;
return true;
}
// what
return false;
}
};
DebugToxCall::DebugToxCall(ObjectStore2& os, ToxAV& toxav, TextureUploaderI& tu) : _os(os), _toxav(toxav), _tu(tu) { DebugToxCall::DebugToxCall(ObjectStore2& os, ToxAV& toxav, TextureUploaderI& tu) : _os(os), _toxav(toxav), _tu(tu) {
_toxav.subscribe(this, ToxAV_Event::friend_call); _toxav.subscribe(this, ToxAV_Event::friend_call);
_toxav.subscribe(this, ToxAV_Event::friend_call_state); _toxav.subscribe(this, ToxAV_Event::friend_call_state);
@ -41,7 +145,70 @@ DebugToxCall::DebugToxCall(ObjectStore2& os, ToxAV& toxav, TextureUploaderI& tu)
_toxav.subscribe(this, ToxAV_Event::friend_video_frame); _toxav.subscribe(this, ToxAV_Event::friend_video_frame);
} }
void DebugToxCall::tick(float time_delta) { void DebugToxCall::tick(float) {
// pump sink to tox
// TODO: own thread or direct on push
// TODO: pump at double the frame rate
for (const auto& [oc, vsink] : _os.registry().view<ToxAVCallVideoSink*>().each()) {
if (!vsink->_writer) {
continue;
}
auto new_frame_opt = vsink->_writer->pop();
if (!new_frame_opt.has_value()) {
continue;
}
if (!new_frame_opt.value().surface) {
// wtf?
continue;
}
SDL_Surface* surf = new_frame_opt.value().surface.get();
SDL_Surface* converted_surf = surf;
if (converted_surf->format != SDL_PIXELFORMAT_IYUV) {
std::cerr << "DTC: need to convert from " << SDL_GetPixelFormatName(converted_surf->format) << " to SDL_PIXELFORMAT_IYUV\n";
if (isFormatPlanar(converted_surf->format)) {
// meh, need to convert to rgb as a stopgap
//SDL_Surface* tmp_conv_surf = SDL_ConvertSurfaceAndColorspace(converted_surf, SDL_PIXELFORMAT_RGBA32, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
auto start = Message::getTimeMS();
SDL_Surface* tmp_conv_surf = SDL_ConvertSurfaceAndColorspace(converted_surf, SDL_PIXELFORMAT_RGB24, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
auto end = Message::getTimeMS();
std::cerr << "DTC: timing " << SDL_GetPixelFormatName(converted_surf->format) << "->SDL_PIXELFORMAT_RGB24: " << end-start << "ms\n";
// TODO: fix sdl rgb->yuv conversion resulting in too dark (colorspace) issues
start = Message::getTimeMS();
converted_surf = SDL_ConvertSurfaceAndColorspace(tmp_conv_surf, SDL_PIXELFORMAT_IYUV, nullptr, SDL_COLORSPACE_YUV_DEFAULT, 0);
end = Message::getTimeMS();
std::cerr << "DTC: timing SDL_PIXELFORMAT_RGB24->SDL_PIXELFORMAT_IYUV: " << end-start << "ms\n";
SDL_DestroySurface(tmp_conv_surf);
} else {
converted_surf = SDL_ConvertSurface(converted_surf, SDL_PIXELFORMAT_IYUV);
}
if (converted_surf == nullptr) {
// oh god
std::cerr << "DTC error: failed to convert surface to IYUV: " << SDL_GetError() << "\n";
continue;
}
}
assert(converted_surf != nullptr);
SDL_LockSurface(converted_surf);
_toxav.toxavVideoSendFrame(
vsink->_fid,
converted_surf->w, converted_surf->h,
static_cast<const uint8_t*>(converted_surf->pixels),
static_cast<const uint8_t*>(converted_surf->pixels) + converted_surf->w * converted_surf->h,
static_cast<const uint8_t*>(converted_surf->pixels) + converted_surf->w * converted_surf->h + (converted_surf->w/2) * (converted_surf->h/2)
);
SDL_UnlockSurface(converted_surf);
if (converted_surf != surf) {
SDL_DestroySurface(converted_surf);
}
}
} }
float DebugToxCall::render(void) { float DebugToxCall::render(void) {
@ -56,9 +223,32 @@ float DebugToxCall::render(void) {
if (call.incoming) { if (call.incoming) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::SmallButton("answer")) { if (ImGui::SmallButton("answer")) {
const auto ret = _toxav.toxavAnswer(fid, 0, 0); //const auto ret = _toxav.toxavAnswer(fid, 0, 1); // 1mbit/s
const auto ret = _toxav.toxavAnswer(fid, 0, 2); // 2mbit/s
//const auto ret = _toxav.toxavAnswer(fid, 0, 100); // 100mbit/s
//const auto ret = _toxav.toxavAnswer(fid, 0, 2500); // 2500mbit/s
if (ret == TOXAV_ERR_ANSWER_OK) { if (ret == TOXAV_ERR_ANSWER_OK) {
call.incoming = false; call.incoming = false;
// create sinks
call.outgoing_vsink = {_os.registry(), _os.registry().create()};
{
auto new_vsink = std::make_unique<ToxAVCallVideoSink>(fid);
call.outgoing_vsink.emplace<ToxAVCallVideoSink*>(new_vsink.get());
call.outgoing_vsink.emplace<Components::FrameStream2Sink<SDLVideoFrame>>(std::move(new_vsink));
call.outgoing_vsink.emplace<Components::StreamSink>("ToxAV friend call video", std::string{entt::type_name<SDLVideoFrame>::value()});
}
// create sources
if (call.incoming_v) {
call.incoming_vsrc = {_os.registry(), _os.registry().create()};
{
auto new_vsrc = std::make_unique<SDLVideoFrameStream2MultiSource>();
call.incoming_vsrc.emplace<SDLVideoFrameStream2MultiSource*>(new_vsrc.get());
call.incoming_vsrc.emplace<Components::FrameStream2Source<SDLVideoFrame>>(std::move(new_vsrc));
call.incoming_vsrc.emplace<Components::StreamSource>("ToxAV friend call video", std::string{entt::type_name<SDLVideoFrame>::value()});
}
}
} }
} }
} else if (call.state != TOXAV_FRIEND_CALL_STATE_FINISHED) { } else if (call.state != TOXAV_FRIEND_CALL_STATE_FINISHED) {
@ -70,6 +260,14 @@ float DebugToxCall::render(void) {
// we hung up // we hung up
// not sure if its possible for toxcore to tell this us too when the other side does this at the same time? // not sure if its possible for toxcore to tell this us too when the other side does this at the same time?
call.state = TOXAV_FRIEND_CALL_STATE_FINISHED; call.state = TOXAV_FRIEND_CALL_STATE_FINISHED;
// TODO: stream manager disconnectAll()
if (static_cast<bool>(call.outgoing_vsink)) {
call.outgoing_vsink.destroy();
}
if (static_cast<bool>(call.incoming_vsrc)) {
call.incoming_vsrc.destroy();
}
} }
} }
@ -81,18 +279,6 @@ float DebugToxCall::render(void) {
//} //}
} }
//if (call.last_v_frame_tex != 0 && ImGui::BeginItemTooltip()) {
if (call.last_v_frame_tex != 0) {
next_frame = std::min(next_frame, call.v_frame_interval_avg);
ImGui::Text("vframe interval avg: %f", call.v_frame_interval_avg);
ImGui::Image(
reinterpret_cast<ImTextureID>(call.last_v_frame_tex),
//ImVec2{float(call.last_v_frame_width), float(call.last_v_frame_height)}
ImVec2{100.f, 100.f * float(call.last_v_frame_height)/call.last_v_frame_width}
);
//ImGui::EndTooltip();
}
ImGui::PopID(); ImGui::PopID();
} }
ImGui::Unindent(); ImGui::Unindent();
@ -107,7 +293,7 @@ bool DebugToxCall::onEvent(const Events::FriendCall& e) {
call.incoming = true; call.incoming = true;
call.incoming_a = e.audio_enabled; call.incoming_a = e.audio_enabled;
call.incoming_v = e.video_enabled; call.incoming_v = e.video_enabled;
//call.state = TOXAV_FRIEND_CALL_STATE_NONE; call.state = TOXAV_FRIEND_CALL_STATE_NONE;
return true; return true;
} }
@ -116,6 +302,18 @@ bool DebugToxCall::onEvent(const Events::FriendCallState& e) {
auto& call = _calls[e.friend_number]; auto& call = _calls[e.friend_number];
call.state = e.state; call.state = e.state;
if (
(call.state & TOXAV_FRIEND_CALL_STATE_FINISHED) != 0 ||
(call.state & TOXAV_FRIEND_CALL_STATE_ERROR) != 0
) {
if (static_cast<bool>(call.outgoing_vsink)) {
call.outgoing_vsink.destroy();
}
if (static_cast<bool>(call.incoming_vsrc)) {
call.incoming_vsrc.destroy();
}
}
return true; return true;
} }
@ -134,26 +332,19 @@ bool DebugToxCall::onEvent(const Events::FriendAudioFrame& e) {
} }
bool DebugToxCall::onEvent(const Events::FriendVideoFrame& e) { bool DebugToxCall::onEvent(const Events::FriendVideoFrame& e) {
// TODO: skip if we dont know about this call
auto& call = _calls[e.friend_number]; auto& call = _calls[e.friend_number];
call.num_v_frames++;
if (call.last_v_frame_timepoint == 0) { if (!static_cast<bool>(call.incoming_vsrc)) {
call.last_v_frame_timepoint = Message::getTimeMS(); // missing src to put frame into ??
} else { return false;
const auto new_time_point = Message::getTimeMS();
auto time_delta_ms = new_time_point - call.last_v_frame_timepoint;
call.last_v_frame_timepoint = new_time_point;
time_delta_ms = std::min<uint64_t>(time_delta_ms, 10*1000); // cap at 10sec
if (call.v_frame_interval_avg == 0) {
call.v_frame_interval_avg = time_delta_ms/1000.f;
} else {
std::cerr << "lerp(" << call.v_frame_interval_avg << ", " << time_delta_ms/1000.f << ", 0.2f) = ";
call.v_frame_interval_avg = lerp(call.v_frame_interval_avg, time_delta_ms/1000.f, 0.2f);
std::cerr << call.v_frame_interval_avg << "\n";
}
} }
assert(call.incoming_vsrc.all_of<SDLVideoFrameStream2MultiSource*>());
assert(call.incoming_vsrc.all_of<Components::FrameStream2Source<SDLVideoFrame>>());
call.num_v_frames++;
auto* new_surf = SDL_CreateSurface(e.width, e.height, SDL_PIXELFORMAT_IYUV); auto* new_surf = SDL_CreateSurface(e.width, e.height, SDL_PIXELFORMAT_IYUV);
assert(new_surf); assert(new_surf);
if (SDL_LockSurface(new_surf)) { if (SDL_LockSurface(new_surf)) {
@ -190,37 +381,12 @@ bool DebugToxCall::onEvent(const Events::FriendVideoFrame& e) {
SDL_UnlockSurface(new_surf); SDL_UnlockSurface(new_surf);
} }
auto* converted_surf = SDL_ConvertSurfaceAndColorspace(new_surf, SDL_PIXELFORMAT_RGBA32, nullptr, SDL_COLORSPACE_YUV_DEFAULT, 0); call.incoming_vsrc.get<SDLVideoFrameStream2MultiSource*>()->push({
SDL_DestroySurface(new_surf); // ms -> us
if (converted_surf == nullptr) { Message::getTimeMS() * 1000, // TODO: make more precise
assert(false); new_surf
return true; });
}
SDL_LockSurface(converted_surf); return true;
if (call.last_v_frame_tex == 0 || call.last_v_frame_width != e.width || call.last_v_frame_height != e.height) {
_tu.destroy(call.last_v_frame_tex);
call.last_v_frame_tex = _tu.uploadRGBA(
static_cast<const uint8_t*>(converted_surf->pixels),
converted_surf->w,
converted_surf->h,
TextureUploaderI::LINEAR,
TextureUploaderI::STREAMING
);
call.last_v_frame_width = e.width;
call.last_v_frame_height = e.height;
} else {
_tu.updateRGBA(call.last_v_frame_tex, static_cast<const uint8_t*>(converted_surf->pixels), converted_surf->w * converted_surf->h * 4);
}
SDL_UnlockSurface(converted_surf);
SDL_DestroySurface(converted_surf);
// TODO: use this instead
//SDL_UpdateYUVTexture(tex, nullptr, e.y.ptr, e.ystride,...
std::cout << "DTC: updated video texture " << call.last_v_frame_tex << "\n";
return false;
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <solanaceae/object_store/fwd.hpp> //#include <solanaceae/object_store/fwd.hpp>
#include <solanaceae/object_store/object_store.hpp>
#include "./tox_av.hpp" #include "./tox_av.hpp"
#include "./texture_uploader.hpp" #include "./texture_uploader.hpp"
@ -19,19 +20,17 @@ class DebugToxCall : public ToxAVEventI {
uint32_t state {0}; // ? just last state ? uint32_t state {0}; // ? just last state ?
uint32_t abr {0}; uint32_t incomming_abr {0};
uint32_t vbr {0}; uint32_t incomming_vbr {0};
size_t num_a_frames {0}; size_t num_a_frames {0};
size_t num_v_frames {0}; size_t num_v_frames {0};
// fps moving interval ObjectHandle incoming_vsrc;
uint64_t last_v_frame_timepoint {0}; ObjectHandle incoming_asrc;
float v_frame_interval_avg {0.f};
uint64_t last_v_frame_tex {0}; ObjectHandle outgoing_vsink;
uint64_t last_v_frame_width {0}; ObjectHandle outgoing_asink;
uint64_t last_v_frame_height {0};
}; };
// tox friend id -> call // tox friend id -> call
std::map<uint32_t, Call> _calls; std::map<uint32_t, Call> _calls;

View File

@ -72,30 +72,6 @@ DebugVideoTap::~DebugVideoTap(void) {
float DebugVideoTap::render(void) { float DebugVideoTap::render(void) {
if (ImGui::Begin("DebugVideoTap")) { if (ImGui::Begin("DebugVideoTap")) {
// list sources dropdown to connect too
std::string preview_label {"none"};
if (static_cast<bool>(_selected_src)) {
preview_label = std::to_string(entt::to_integral(_selected_src.entity())) + " (" + _selected_src.get<Components::StreamSource>().name + ")";
}
if (ImGui::BeginCombo("selected source", preview_label.c_str())) {
if (ImGui::Selectable("none")) {
switchTo({});
}
for (const auto& [oc, ss] : _os.registry().view<Components::StreamSource>().each()) {
if (ss.frame_type_name != entt::type_name<SDLVideoFrame>::value()) {
continue;
}
std::string label = std::to_string(entt::to_integral(oc)) + " (" + ss.name + ")";
if (ImGui::Selectable(label.c_str())) {
switchTo({_os.registry(), oc});
}
}
ImGui::EndCombo();
}
{ // first pull the latest img from sink and update the texture { // first pull the latest img from sink and update the texture
assert(static_cast<bool>(_tap)); assert(static_cast<bool>(_tap));
@ -106,18 +82,18 @@ float DebugVideoTap::render(void) {
if (new_frame_opt.has_value()) { if (new_frame_opt.has_value()) {
// timing // timing
if (_v_last_ts == 0) { if (_v_last_ts == 0) {
_v_last_ts = new_frame_opt.value().timestampNS; _v_last_ts = new_frame_opt.value().timestampUS;
} else { } else {
auto delta = int64_t(new_frame_opt.value().timestampNS) - int64_t(_v_last_ts); auto delta = int64_t(new_frame_opt.value().timestampUS) - int64_t(_v_last_ts);
_v_last_ts = new_frame_opt.value().timestampNS; _v_last_ts = new_frame_opt.value().timestampUS;
//delta = std::min<int64_t>(delta, 10*1000*1000); //delta = std::min<int64_t>(delta, 10*1000*1000);
if (_v_interval_avg == 0) { if (_v_interval_avg == 0) {
_v_interval_avg = delta/1'000'000'000.f; _v_interval_avg = delta/1'000'000.f;
} else { } else {
const float r = 0.2f; const float r = 0.2f;
_v_interval_avg = _v_interval_avg * (1-r) + (delta/1'000'000'000.f) * r; _v_interval_avg = _v_interval_avg * (1.f-r) + (delta/1'000'000.f) * r;
} }
} }
@ -160,6 +136,30 @@ float DebugVideoTap::render(void) {
} }
} }
// list sources dropdown to connect too
std::string preview_label {"none"};
if (static_cast<bool>(_selected_src)) {
preview_label = std::to_string(entt::to_integral(_selected_src.entity())) + " (" + _selected_src.get<Components::StreamSource>().name + ")";
}
if (ImGui::BeginCombo("selected source", preview_label.c_str())) {
if (ImGui::Selectable("none")) {
switchTo({});
}
for (const auto& [oc, ss] : _os.registry().view<Components::StreamSource>().each()) {
if (ss.frame_type_name != entt::type_name<SDLVideoFrame>::value()) {
continue;
}
std::string label = std::to_string(entt::to_integral(oc)) + " (" + ss.name + ")";
if (ImGui::Selectable(label.c_str())) {
switchTo({_os.registry(), oc});
}
}
ImGui::EndCombo();
}
// img here // img here
if (_tex != 0) { if (_tex != 0) {
ImGui::Text("moving avg interval: %f", _v_interval_avg); ImGui::Text("moving avg interval: %f", _v_interval_avg);

View File

@ -18,7 +18,7 @@ class DebugVideoTap {
uint32_t _tex_w {0}; uint32_t _tex_w {0};
uint32_t _tex_h {0}; uint32_t _tex_h {0};
uint64_t _v_last_ts {0}; // ns uint64_t _v_last_ts {0}; // us
float _v_interval_avg {0.f}; // s float _v_interval_avg {0.f}; // s
public: public:

View File

@ -108,7 +108,7 @@ int main(int argc, char** argv) {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
auto new_frame_opt = reader->pop(); auto new_frame_opt = reader->pop();
if (new_frame_opt.has_value()) { if (new_frame_opt.has_value()) {
std::cout << "video frame was " << new_frame_opt.value().surface->w << "x" << new_frame_opt.value().surface->h << " " << new_frame_opt.value().timestampNS << "ns " << new_frame_opt.value().surface->format << "sf\n"; std::cout << "video frame was " << new_frame_opt.value().surface->w << "x" << new_frame_opt.value().surface->h << " " << new_frame_opt.value().timestampUS << "us " << new_frame_opt.value().surface->format << "sf\n";
} }
} }
vcc.unsubscribe(reader); vcc.unsubscribe(reader);

View File

@ -147,7 +147,7 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
conf.dump(); conf.dump();
{ // add system av devices { // add system av devices
{ if (false) {
ObjectHandle vsrc {os.registry(), os.registry().create()}; ObjectHandle vsrc {os.registry(), os.registry().create()};
try { try {
vsrc.emplace<Components::FrameStream2Source<SDLVideoFrame>>( vsrc.emplace<Components::FrameStream2Source<SDLVideoFrame>>(

View File

@ -10,6 +10,8 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <thread>
#include <chrono>
namespace Components { namespace Components {
struct StreamSource { struct StreamSource {
@ -41,13 +43,19 @@ class StreamManager {
ObjectHandle src; ObjectHandle src;
ObjectHandle sink; ObjectHandle sink;
struct Data {
virtual ~Data(void) {}
};
std::unique_ptr<Data> data; // stores reader writer type erased
std::function<void(Connection&)> pump_fn; std::function<void(Connection&)> pump_fn;
std::function<void(Connection&)> unsubscribe_fn;
bool on_main_thread {true}; bool on_main_thread {true};
std::atomic_bool stop {false}; // disconnect std::atomic_bool stop {false}; // disconnect
std::atomic_bool finished {false}; // disconnect std::atomic_bool finished {false}; // disconnect
// pump thread // pump thread
std::thread pump_thread;
// frame interval counters and estimates // frame interval counters and estimates
@ -55,20 +63,44 @@ class StreamManager {
Connection( Connection(
ObjectHandle src_, ObjectHandle src_,
ObjectHandle sink_, ObjectHandle sink_,
std::unique_ptr<Data>&& data_,
std::function<void(Connection&)>&& pump_fn_, std::function<void(Connection&)>&& pump_fn_,
std::function<void(Connection&)>&& unsubscribe_fn_,
bool on_main_thread_ = true bool on_main_thread_ = true
) : ) :
src(src_), src(src_),
sink(sink_), sink(sink_),
data(std::move(data_)),
pump_fn(std::move(pump_fn_)), pump_fn(std::move(pump_fn_)),
unsubscribe_fn(std::move(unsubscribe_fn_)),
on_main_thread(on_main_thread_) on_main_thread(on_main_thread_)
{} {
if (!on_main_thread) {
// start thread
pump_thread = std::thread([this](void) {
while (!stop) {
pump_fn(*this);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
finished = true;
});
}
}
}; };
std::vector<std::unique_ptr<Connection>> _connections; std::vector<std::unique_ptr<Connection>> _connections;
public: public:
StreamManager(ObjectStore2& os) : _os(os) {} StreamManager(ObjectStore2& os) : _os(os) {}
virtual ~StreamManager(void) {} virtual ~StreamManager(void) {
// stop all connetions
for (const auto& con : _connections) {
con->stop = true;
if (!con->on_main_thread) {
con->pump_thread.join(); // we skip the finished check and wait
}
con->unsubscribe_fn(*con);
}
}
// TODO: default typed sources and sinks // TODO: default typed sources and sinks
@ -76,7 +108,7 @@ class StreamManager {
// TODO: improve this design // TODO: improve this design
// src and sink need to be a FrameStream2MultiStream<FrameType> // src and sink need to be a FrameStream2MultiStream<FrameType>
template<typename FrameType> template<typename FrameType>
bool connect(Object src, Object sink) { bool connect(Object src, Object sink, bool threaded = true) {
auto res = std::find_if( auto res = std::find_if(
_connections.cbegin(), _connections.cend(), _connections.cbegin(), _connections.cend(),
[&](const auto& a) { return a->src == src && a->sink == sink; } [&](const auto& a) { return a->src == src && a->sink == sink; }
@ -114,44 +146,50 @@ class StreamManager {
auto& src_stream = h_src.get<Components::FrameStream2Source<FrameType>>(); auto& src_stream = h_src.get<Components::FrameStream2Source<FrameType>>();
auto& sink_stream = h_sink.get<Components::FrameStream2Sink<FrameType>>(); auto& sink_stream = h_sink.get<Components::FrameStream2Sink<FrameType>>();
auto reader = src_stream->subscribe(); struct inlineData : public Connection::Data {
if (!reader) { virtual ~inlineData(void) {}
std::shared_ptr<FrameStream2I<FrameType>> reader;
std::shared_ptr<FrameStream2I<FrameType>> writer;
};
auto our_data = std::make_unique<inlineData>();
our_data->reader = src_stream->subscribe();
if (!our_data->reader) {
return false; return false;
} }
auto writer = sink_stream->subscribe(); our_data->writer = sink_stream->subscribe();
if (!writer) { if (!our_data->writer) {
return false; return false;
} }
_connections.push_back(std::make_unique<Connection>( _connections.push_back(std::make_unique<Connection>(
h_src, h_src,
h_sink, h_sink,
// refactor extract, we just need the type info here std::move(our_data),
[reader = std::move(reader), writer = std::move(writer)](Connection& con) -> void { [](Connection& con) -> void {
// there might be more stored // there might be more stored
for (size_t i = 0; i < 10; i++) { for (size_t i = 0; i < 10; i++) {
auto new_frame_opt = reader->pop(); auto new_frame_opt = static_cast<inlineData*>(con.data.get())->reader->pop();
// TODO: frame interval estimates // TODO: frame interval estimates
if (new_frame_opt.has_value()) { if (new_frame_opt.has_value()) {
writer->push(new_frame_opt.value()); static_cast<inlineData*>(con.data.get())->writer->push(new_frame_opt.value());
} else { } else {
break; break;
} }
} }
},
if (con.stop) { [](Connection& con) -> void {
auto* src_stream_ptr = con.src.try_get<Components::FrameStream2Source<FrameType>>(); auto* src_stream_ptr = con.src.try_get<Components::FrameStream2Source<FrameType>>();
if (src_stream_ptr != nullptr) { if (src_stream_ptr != nullptr) {
(*src_stream_ptr)->unsubscribe(reader); (*src_stream_ptr)->unsubscribe(static_cast<inlineData*>(con.data.get())->reader);
} }
auto* sink_stream_ptr = con.sink.try_get<Components::FrameStream2Sink<FrameType>>(); auto* sink_stream_ptr = con.sink.try_get<Components::FrameStream2Sink<FrameType>>();
if (sink_stream_ptr != nullptr) { if (sink_stream_ptr != nullptr) {
(*sink_stream_ptr)->unsubscribe(writer); (*sink_stream_ptr)->unsubscribe(static_cast<inlineData*>(con.data.get())->writer);
}
con.finished = true;
} }
}, },
true // TODO: threaded !threaded
)); ));
return true; return true;
@ -192,11 +230,23 @@ class StreamManager {
// pump all mainthread connections // pump all mainthread connections
for (auto it = _connections.begin(); it != _connections.end();) { for (auto it = _connections.begin(); it != _connections.end();) {
auto& con = **it; auto& con = **it;
if (!static_cast<bool>(con.src) || !static_cast<bool>(con.sink)) {
// either side disappeard without disconnectAll
// TODO: warn/error log
con.stop = true;
}
if (con.on_main_thread) { if (con.on_main_thread) {
con.pump_fn(con); con.pump_fn(con);
} }
if (con.stop && con.finished) { if (con.stop && (con.finished || con.on_main_thread)) {
if (!con.on_main_thread) {
assert(con.pump_thread.joinable());
con.pump_thread.join();
}
con.unsubscribe_fn(con);
it = _connections.erase(it); it = _connections.erase(it);
} else { } else {
it++; it++;

View File

@ -1,9 +1,13 @@
#include "./stream_manager_ui.hpp" #include "./stream_manager_ui.hpp"
#include "./content/sdl_video_frame_stream2.hpp"
#include <solanaceae/object_store/object_store.hpp> #include <solanaceae/object_store/object_store.hpp>
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <string>
StreamManagerUI::StreamManagerUI(ObjectStore2& os, StreamManager& sm) : _os(os), _sm(sm) { StreamManagerUI::StreamManagerUI(ObjectStore2& os, StreamManager& sm) : _os(os), _sm(sm) {
} }
@ -15,23 +19,57 @@ void StreamManagerUI::render(void) {
ImGui::SeparatorText("Sources"); ImGui::SeparatorText("Sources");
// TODO: tables of id, button connect->to, name, type
// list sources // list sources
for (const auto& [oc, ss] : _os.registry().view<Components::StreamSource>().each()) { for (const auto& [oc, ss] : _os.registry().view<Components::StreamSource>().each()) {
ImGui::Text("src %d (%s)[%s]", entt::to_integral(oc), ss.name.c_str(), ss.frame_type_name.c_str()); ImGui::Text("src %d (%s)[%s]", entt::to_integral(entt::to_entity(oc)), ss.name.c_str(), ss.frame_type_name.c_str());
} }
ImGui::SeparatorText("Sinks"); ImGui::SeparatorText("Sinks");
// list sinks // list sinks
for (const auto& [oc, ss] : _os.registry().view<Components::StreamSink>().each()) { for (const auto& [oc, ss] : _os.registry().view<Components::StreamSink>().each()) {
ImGui::Text("sink %d (%s)[%s]", entt::to_integral(oc), ss.name.c_str(), ss.frame_type_name.c_str()); ImGui::PushID(entt::to_integral(oc));
ImGui::Text("sink %d (%s)[%s]", entt::to_integral(entt::to_entity(oc)), ss.name.c_str(), ss.frame_type_name.c_str());
if (ImGui::BeginPopupContextItem("sink_connect")) {
if (ImGui::BeginMenu("connect video", ss.frame_type_name == entt::type_name<SDLVideoFrame>::value())) {
for (const auto& [oc_src, s_src] : _os.registry().view<Components::StreamSource>().each()) {
if (s_src.frame_type_name != ss.frame_type_name) {
continue;
}
ImGui::PushID(entt::to_integral(oc_src));
std::string source_label {"src "};
source_label += std::to_string(entt::to_integral(entt::to_entity(oc_src)));
source_label += " (";
source_label += s_src.name;
source_label += ")[";
source_label += s_src.frame_type_name;
source_label += "]";
if (ImGui::MenuItem(source_label.c_str())) {
_sm.connect<SDLVideoFrame>(oc_src, oc);
}
ImGui::PopID();
}
ImGui::EndMenu();
}
ImGui::EndPopup();
}
ImGui::PopID();
} }
ImGui::SeparatorText("Connections"); ImGui::SeparatorText("Connections");
// TODO: table of id, button disconnect, context x->y, from name, to name, type?
// list connections // list connections
for (const auto& con : _sm._connections) { for (const auto& con : _sm._connections) {
ImGui::Text("con %d->%d", entt::to_integral(con->src.entity()), entt::to_integral(con->sink.entity())); ImGui::Text("con %d->%d", entt::to_integral(entt::to_entity(con->src.entity())), entt::to_integral(entt::to_entity(con->sink.entity())));
} }
} }
ImGui::End(); ImGui::End();