From 25542292114057c8510c6358b5c33672ca423f8f Mon Sep 17 00:00:00 2001 From: Green Sky Date: Wed, 2 Oct 2024 18:44:25 +0200 Subject: [PATCH] sdl camera input source --- src/CMakeLists.txt | 2 + src/chat_gui4.cpp | 5 +- .../sdl/sdl_video_frame_stream2.cpp | 168 ++++++++++++++++++ .../sdl/sdl_video_frame_stream2.hpp | 24 +++ src/main_screen.cpp | 22 +++ 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/frame_streams/sdl/sdl_video_frame_stream2.cpp create mode 100644 src/frame_streams/sdl/sdl_video_frame_stream2.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b7d1603..17932acf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -117,6 +117,8 @@ target_sources(tomato PUBLIC ./frame_streams/sdl/video.hpp ./frame_streams/sdl/video_push_converter.hpp ./frame_streams/sdl/video_push_converter.cpp + ./frame_streams/sdl/sdl_video_frame_stream2.hpp + ./frame_streams/sdl/sdl_video_frame_stream2.cpp ./stream_manager_ui.hpp ./stream_manager_ui.cpp diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 1cb34eec..cff6baa4 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -297,7 +297,10 @@ float ChatGui4::render(float time_delta) { acceptable_sessions.push_back(o); } - static Components::VoIP::DefaultConfig g_default_connections{}; + static Components::VoIP::DefaultConfig g_default_connections{ + true, true, + true, false + }; if (ImGui::BeginMenu("default connections")) { ImGui::MenuItem("incoming audio", nullptr, &g_default_connections.incoming_audio); diff --git a/src/frame_streams/sdl/sdl_video_frame_stream2.cpp b/src/frame_streams/sdl/sdl_video_frame_stream2.cpp new file mode 100644 index 00000000..7300a9fa --- /dev/null +++ b/src/frame_streams/sdl/sdl_video_frame_stream2.cpp @@ -0,0 +1,168 @@ +#include "./sdl_video_frame_stream2.hpp" + +#include + +#include + +SDLVideo2InputDevice::SDLVideo2InputDevice(void) { + int devcount {0}; + SDL_CameraID *devices = SDL_GetCameras(&devcount); + std::cout << "SDLVID: SDL Camera Driver: " << SDL_GetCurrentCameraDriver() << "\n"; + + if (devices == nullptr || devcount < 1) { + throw int(2); // TODO: proper error code + } + + std::cout << "SDLVID: found cameras:\n"; + for (int i = 0; i < devcount; i++) { + const SDL_CameraID device = devices[i]; + + const char *name = SDL_GetCameraName(device); + std::cout << " - Camera #" << i << ": " << name << "\n"; + + int speccount {0}; + SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(device, &speccount); + if (specs == nullptr) { + std::cout << " - no supported spec\n"; + } else { + for (int spec_i = 0; spec_i < speccount; spec_i++) { + std::cout << " - " << specs[spec_i]->width << "x" << specs[spec_i]->height << "@" << float(specs[spec_i]->framerate_numerator)/specs[spec_i]->framerate_denominator << "fps " << SDL_GetPixelFormatName(specs[spec_i]->format) << "\n"; + + } + SDL_free(specs); + } + } + SDL_free(devices); +} + +SDLVideo2InputDevice::~SDLVideo2InputDevice(void) { +} + +std::shared_ptr> SDLVideo2InputDevice::subscribe(void) { + const int prev_ref = _ref++; + if (prev_ref == 0) { + // there was previously no stream, we assume no thread + // open device here? or on the thread? + + int devcount {0}; + SDL_CameraID *devices = SDL_GetCameras(&devcount); + //std::cout << "SDL Camera Driver: " << SDL_GetCurrentCameraDriver() << "\n"; + + if (devices == nullptr || devcount < 1) { + _ref--; + // error/no devices, should we do this in the constructor? + SDL_free(devices); + return nullptr; + } + + // TODO: relist on device open? + //std::cout << "### found cameras:\n"; + //for (int i = 0; i < devcount; i++) { + // const SDL_CameraID device = devices[i]; + + // const char *name = SDL_GetCameraName(device); + // std::cout << " - Camera #" << i << ": " << name << "\n"; + + // int speccount {0}; + // SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(device, &speccount); + // if (specs == nullptr) { + // std::cout << " - no supported spec\n"; + // } else { + // for (int spec_i = 0; spec_i < speccount; spec_i++) { + // std::cout << " - " << specs[spec_i]->width << "x" << specs[spec_i]->height << "@" << float(specs[spec_i]->framerate_numerator)/specs[spec_i]->framerate_denominator << "fps " << SDL_GetPixelFormatName(specs[spec_i]->format) << "\n"; + + // } + // SDL_free(specs); + // } + //} + + std::unique_ptr camera {nullptr, &SDL_CloseCamera}; + + SDL_CameraSpec spec { + // FORCE a different pixel format + //SDL_PIXELFORMAT_UNKNOWN, + SDL_PIXELFORMAT_YUY2, + //SDL_COLORSPACE_UNKNOWN, + SDL_COLORSPACE_YUV_DEFAULT, + + 1280, 720, + + 30, 1 + }; + camera = { + //SDL_OpenCamera(devices[0], &spec), + SDL_OpenCamera(devices[0], nullptr), + &SDL_CloseCamera + }; + SDL_free(devices); + + // seems like we need this before get format() ? + // TODO: best would be waiting in thread, but that obv does not work well + // TODO: sometimes if the device is/was in use it will stay 0 for ever + while (SDL_GetCameraPermissionState(camera.get()) == 0) { + //std::cerr << "permission for camera not granted\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + if (SDL_GetCameraPermissionState(camera.get()) <= 0) { + std::cerr << "SDLVID error: user denied camera permission\n"; + _ref--; + return nullptr; + } + + float fps {1.f}; + if (!SDL_GetCameraFormat(camera.get(), &spec)) { + // meh + _ref--; + return nullptr; + } else { + fps = float(spec.framerate_numerator)/float(spec.framerate_denominator); + std::cout << "SDLVID: camera fps: " << fps << "fps (" << spec.framerate_numerator << "/" << spec.framerate_denominator << ")\n"; + auto* format_name = SDL_GetPixelFormatName(spec.format); + std::cout << "SDLVID: camera format: " << format_name << "\n"; + } + + _thread = std::thread([this, camera = std::move(camera), fps](void) { + while (_ref > 0) { + Uint64 timestampNS = 0; + + // aquire frame + SDL_Surface* sdl_frame_next = SDL_AcquireCameraFrame(camera.get(), ×tampNS); + + if (sdl_frame_next != nullptr) { + SDLVideoFrame new_frame_non_owning { + timestampNS/1000, + sdl_frame_next + }; + + // creates surface copies + push(new_frame_non_owning); + + SDL_ReleaseCameraFrame(camera.get(), sdl_frame_next); + } + + // sleep for interval + // TODO: do we really need half? + std::this_thread::sleep_for(std::chrono::milliseconds(int64_t((1000/fps)*0.5))); + } + // camera destructor closes device here + }); + std::cout << "SDLVID: started new cam thread\n"; + } + + return FrameStream2MultiSource::subscribe(); +} + +bool SDLVideo2InputDevice::unsubscribe(const std::shared_ptr>& sub) { + if (FrameStream2MultiSource::unsubscribe(sub)) { + if (--_ref == 0) { + // was last stream, close device and thread + _thread.join(); // TODO: defer to destructor or new thread? + // this might take a moment and lock up the main thread + std::cout << "SDLVID: ended cam thread\n"; + } + return true; + } + return false; +} + diff --git a/src/frame_streams/sdl/sdl_video_frame_stream2.hpp b/src/frame_streams/sdl/sdl_video_frame_stream2.hpp new file mode 100644 index 00000000..5c92795b --- /dev/null +++ b/src/frame_streams/sdl/sdl_video_frame_stream2.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "./video.hpp" +#include "../frame_stream2.hpp" +#include "../multi_source.hpp" + +#include +#include + +// while a stream is subscribed, have the camera device open +// and aquire and push frames from a thread +struct SDLVideo2InputDevice : public FrameStream2MultiSource { + std::atomic_uint _ref {0}; + std::thread _thread; + + // TODO: device id + SDLVideo2InputDevice(void); + virtual ~SDLVideo2InputDevice(void); + + // we hook int multi source + std::shared_ptr> subscribe(void) override; + bool unsubscribe(const std::shared_ptr>& sub) override; +}; + diff --git a/src/main_screen.cpp b/src/main_screen.cpp index 23ab68b6..e33d916b 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -6,6 +6,7 @@ #include #include "./frame_streams/sdl/sdl_audio2_frame_stream2.hpp" +#include "./frame_streams/sdl/sdl_video_frame_stream2.hpp" #include @@ -178,6 +179,27 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme } else { std::cerr << "MS warning: no sdl audio: " << SDL_GetError() << "\n"; } + + if (SDL_InitSubSystem(SDL_INIT_CAMERA)) { + { // video in + ObjectHandle vsrc {os.registry(), os.registry().create()}; + try { + vsrc.emplace>( + std::make_unique() + ); + + vsrc.emplace(Components::StreamSource::create("SDL Video Default Recording Device")); + vsrc.emplace(); + + os.throwEventConstruct(vsrc); + } catch (...) { + std::cerr << "MS error: failed constructing default video input source\n"; + os.registry().destroy(vsrc); + } + } + } else { + std::cerr << "MS warning: no sdl camera: " << SDL_GetError() << "\n"; + } } MainScreen::~MainScreen(void) {