Compare commits
	
		
			34 Commits
		
	
	
		
			1572044ade
			...
			content_de
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 87f7290301 | |||
| 7ebbad2b94 | |||
| 5b3e0e2a0b | |||
| 5eca1a99e0 | |||
| d0eeef2b94 | |||
| 4d5d708d6d | |||
| 9b7ba5875c | |||
| 9b5cb2cfab | |||
| 9f62e01ab8 | |||
| 8cdf2a2ca3 | |||
| 697611ff55 | |||
| 42c7ab8571 | |||
| 36e75c0fab | |||
| a934273714 | |||
| a618435f17 | |||
| a622b6aa3f | |||
| b4373e0d9a | |||
| 964f6de656 | |||
| a100eaae82 | |||
| 106c8e8403 | |||
| ca4ab01f77 | |||
| 4ff9386398 | |||
| 93d65ead89 | |||
| edd949879b | |||
| ef78c49e29 | |||
| e149873673 | |||
| 3a98e10007 | |||
| b3e5e4c950 | |||
| 165e80c456 | |||
| 495ec41234 | |||
| ddadc9bdbc | |||
| b657802e8d | |||
| bedf0b02bc | |||
| 1d0a4cafe2 | 
@@ -27,11 +27,19 @@ message("II TOMATO_TOX_AV: ${TOMATO_TOX_AV}")
 | 
				
			|||||||
if (TOMATO_ASAN)
 | 
					if (TOMATO_ASAN)
 | 
				
			||||||
	if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
 | 
						if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
 | 
				
			||||||
		if (NOT WIN32) # exclude mingw
 | 
							if (NOT WIN32) # exclude mingw
 | 
				
			||||||
			#link_libraries(-fsanitize=address)
 | 
								add_compile_options(-fsanitize=address,undefined)
 | 
				
			||||||
			link_libraries(-fsanitize=address,undefined)
 | 
								link_libraries(-fsanitize=address,undefined)
 | 
				
			||||||
			#link_libraries(-fsanitize=undefined)
 | 
								#link_libraries(-fsanitize=undefined)
 | 
				
			||||||
			link_libraries(-static-libasan) # make it "work" on nix
 | 
								link_libraries(-static-libasan) # make it "work" on nix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								#add_compile_options(-fsanitize=thread)
 | 
				
			||||||
 | 
								#link_libraries(-fsanitize=thread)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			message("II enabled ASAN")
 | 
								message("II enabled ASAN")
 | 
				
			||||||
 | 
								if (OFF) # TODO: switch for minimal runtime in deployed scenarios
 | 
				
			||||||
 | 
									add_compile_options(-fsanitize-minimal-runtime)
 | 
				
			||||||
 | 
									link_libraries(-fsanitize-minimal-runtime)
 | 
				
			||||||
 | 
								endif()
 | 
				
			||||||
		else()
 | 
							else()
 | 
				
			||||||
			message("!! can not enable ASAN on this platform (gcc/clang + win)")
 | 
								message("!! can not enable ASAN on this platform (gcc/clang + win)")
 | 
				
			||||||
		endif()
 | 
							endif()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								external/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								external/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							@@ -24,3 +24,4 @@ add_subdirectory(./libwebp)
 | 
				
			|||||||
add_subdirectory(./qoi)
 | 
					add_subdirectory(./qoi)
 | 
				
			||||||
add_subdirectory(./sdl_image)
 | 
					add_subdirectory(./sdl_image)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_subdirectory(./spscqueue)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								external/spscqueue/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								external/spscqueue/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_library(SPSCQueue INTERFACE
 | 
				
			||||||
 | 
						./SPSCQueue.h
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_compile_features(SPSCQueue INTERFACE cxx_std_17)
 | 
				
			||||||
 | 
					target_include_directories(SPSCQueue INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										237
									
								
								external/spscqueue/SPSCQueue.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								external/spscqueue/SPSCQueue.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include <memory> // std::allocator
 | 
				
			||||||
 | 
					#include <new>    // std::hardware_destructive_interference_size
 | 
				
			||||||
 | 
					#include <stdexcept>
 | 
				
			||||||
 | 
					#include <type_traits> // std::enable_if, std::is_*_constructible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __has_cpp_attribute
 | 
				
			||||||
 | 
					#if __has_cpp_attribute(nodiscard)
 | 
				
			||||||
 | 
					#define RIGTORP_NODISCARD [[nodiscard]]
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifndef RIGTORP_NODISCARD
 | 
				
			||||||
 | 
					#define RIGTORP_NODISCARD
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace rigtorp {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T, typename Allocator = std::allocator<T>> class SPSCQueue {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
 | 
				
			||||||
 | 
					  template <typename Alloc2, typename = void>
 | 
				
			||||||
 | 
					  struct has_allocate_at_least : std::false_type {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename Alloc2>
 | 
				
			||||||
 | 
					  struct has_allocate_at_least<
 | 
				
			||||||
 | 
					      Alloc2, std::void_t<typename Alloc2::value_type,
 | 
				
			||||||
 | 
					                          decltype(std::declval<Alloc2 &>().allocate_at_least(
 | 
				
			||||||
 | 
					                              size_t{}))>> : std::true_type {};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  explicit SPSCQueue(const size_t capacity,
 | 
				
			||||||
 | 
					                     const Allocator &allocator = Allocator())
 | 
				
			||||||
 | 
					      : capacity_(capacity), allocator_(allocator) {
 | 
				
			||||||
 | 
					    // The queue needs at least one element
 | 
				
			||||||
 | 
					    if (capacity_ < 1) {
 | 
				
			||||||
 | 
					      capacity_ = 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    capacity_++; // Needs one slack element
 | 
				
			||||||
 | 
					    // Prevent overflowing size_t
 | 
				
			||||||
 | 
					    if (capacity_ > SIZE_MAX - 2 * kPadding) {
 | 
				
			||||||
 | 
					      capacity_ = SIZE_MAX - 2 * kPadding;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
 | 
				
			||||||
 | 
					    if constexpr (has_allocate_at_least<Allocator>::value) {
 | 
				
			||||||
 | 
					      auto res = allocator_.allocate_at_least(capacity_ + 2 * kPadding);
 | 
				
			||||||
 | 
					      slots_ = res.ptr;
 | 
				
			||||||
 | 
					      capacity_ = res.count - 2 * kPadding;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      slots_ = std::allocator_traits<Allocator>::allocate(
 | 
				
			||||||
 | 
					          allocator_, capacity_ + 2 * kPadding);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    slots_ = std::allocator_traits<Allocator>::allocate(
 | 
				
			||||||
 | 
					        allocator_, capacity_ + 2 * kPadding);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static_assert(alignof(SPSCQueue<T>) == kCacheLineSize, "");
 | 
				
			||||||
 | 
					    static_assert(sizeof(SPSCQueue<T>) >= 3 * kCacheLineSize, "");
 | 
				
			||||||
 | 
					    assert(reinterpret_cast<char *>(&readIdx_) -
 | 
				
			||||||
 | 
					               reinterpret_cast<char *>(&writeIdx_) >=
 | 
				
			||||||
 | 
					           static_cast<std::ptrdiff_t>(kCacheLineSize));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ~SPSCQueue() {
 | 
				
			||||||
 | 
					    while (front()) {
 | 
				
			||||||
 | 
					      pop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    std::allocator_traits<Allocator>::deallocate(allocator_, slots_,
 | 
				
			||||||
 | 
					                                                 capacity_ + 2 * kPadding);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // non-copyable and non-movable
 | 
				
			||||||
 | 
					  SPSCQueue(const SPSCQueue &) = delete;
 | 
				
			||||||
 | 
					  SPSCQueue &operator=(const SPSCQueue &) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename... Args>
 | 
				
			||||||
 | 
					  void emplace(Args &&...args) noexcept(
 | 
				
			||||||
 | 
					      std::is_nothrow_constructible<T, Args &&...>::value) {
 | 
				
			||||||
 | 
					    static_assert(std::is_constructible<T, Args &&...>::value,
 | 
				
			||||||
 | 
					                  "T must be constructible with Args&&...");
 | 
				
			||||||
 | 
					    auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
 | 
				
			||||||
 | 
					    auto nextWriteIdx = writeIdx + 1;
 | 
				
			||||||
 | 
					    if (nextWriteIdx == capacity_) {
 | 
				
			||||||
 | 
					      nextWriteIdx = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    while (nextWriteIdx == readIdxCache_) {
 | 
				
			||||||
 | 
					      readIdxCache_ = readIdx_.load(std::memory_order_acquire);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
 | 
				
			||||||
 | 
					    writeIdx_.store(nextWriteIdx, std::memory_order_release);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename... Args>
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD bool try_emplace(Args &&...args) noexcept(
 | 
				
			||||||
 | 
					      std::is_nothrow_constructible<T, Args &&...>::value) {
 | 
				
			||||||
 | 
					    static_assert(std::is_constructible<T, Args &&...>::value,
 | 
				
			||||||
 | 
					                  "T must be constructible with Args&&...");
 | 
				
			||||||
 | 
					    auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
 | 
				
			||||||
 | 
					    auto nextWriteIdx = writeIdx + 1;
 | 
				
			||||||
 | 
					    if (nextWriteIdx == capacity_) {
 | 
				
			||||||
 | 
					      nextWriteIdx = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (nextWriteIdx == readIdxCache_) {
 | 
				
			||||||
 | 
					      readIdxCache_ = readIdx_.load(std::memory_order_acquire);
 | 
				
			||||||
 | 
					      if (nextWriteIdx == readIdxCache_) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
 | 
				
			||||||
 | 
					    writeIdx_.store(nextWriteIdx, std::memory_order_release);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
 | 
				
			||||||
 | 
					    static_assert(std::is_copy_constructible<T>::value,
 | 
				
			||||||
 | 
					                  "T must be copy constructible");
 | 
				
			||||||
 | 
					    emplace(v);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename P, typename = typename std::enable_if<
 | 
				
			||||||
 | 
					                            std::is_constructible<T, P &&>::value>::type>
 | 
				
			||||||
 | 
					  void push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
 | 
				
			||||||
 | 
					    emplace(std::forward<P>(v));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD bool
 | 
				
			||||||
 | 
					  try_push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
 | 
				
			||||||
 | 
					    static_assert(std::is_copy_constructible<T>::value,
 | 
				
			||||||
 | 
					                  "T must be copy constructible");
 | 
				
			||||||
 | 
					    return try_emplace(v);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename P, typename = typename std::enable_if<
 | 
				
			||||||
 | 
					                            std::is_constructible<T, P &&>::value>::type>
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD bool
 | 
				
			||||||
 | 
					  try_push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
 | 
				
			||||||
 | 
					    return try_emplace(std::forward<P>(v));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD T *front() noexcept {
 | 
				
			||||||
 | 
					    auto const readIdx = readIdx_.load(std::memory_order_relaxed);
 | 
				
			||||||
 | 
					    if (readIdx == writeIdxCache_) {
 | 
				
			||||||
 | 
					      writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
 | 
				
			||||||
 | 
					      if (writeIdxCache_ == readIdx) {
 | 
				
			||||||
 | 
					        return nullptr;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return &slots_[readIdx + kPadding];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pop() noexcept {
 | 
				
			||||||
 | 
					    static_assert(std::is_nothrow_destructible<T>::value,
 | 
				
			||||||
 | 
					                  "T must be nothrow destructible");
 | 
				
			||||||
 | 
					    auto const readIdx = readIdx_.load(std::memory_order_relaxed);
 | 
				
			||||||
 | 
					    assert(writeIdx_.load(std::memory_order_acquire) != readIdx &&
 | 
				
			||||||
 | 
					           "Can only call pop() after front() has returned a non-nullptr");
 | 
				
			||||||
 | 
					    slots_[readIdx + kPadding].~T();
 | 
				
			||||||
 | 
					    auto nextReadIdx = readIdx + 1;
 | 
				
			||||||
 | 
					    if (nextReadIdx == capacity_) {
 | 
				
			||||||
 | 
					      nextReadIdx = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    readIdx_.store(nextReadIdx, std::memory_order_release);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD size_t size() const noexcept {
 | 
				
			||||||
 | 
					    std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
 | 
				
			||||||
 | 
					                          readIdx_.load(std::memory_order_acquire);
 | 
				
			||||||
 | 
					    if (diff < 0) {
 | 
				
			||||||
 | 
					      diff += capacity_;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return static_cast<size_t>(diff);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD bool empty() const noexcept {
 | 
				
			||||||
 | 
					    return writeIdx_.load(std::memory_order_acquire) ==
 | 
				
			||||||
 | 
					           readIdx_.load(std::memory_order_acquire);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RIGTORP_NODISCARD size_t capacity() const noexcept { return capacity_ - 1; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					#ifdef __cpp_lib_hardware_interference_size
 | 
				
			||||||
 | 
					  static constexpr size_t kCacheLineSize =
 | 
				
			||||||
 | 
					      std::hardware_destructive_interference_size;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  static constexpr size_t kCacheLineSize = 64;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Padding to avoid false sharing between slots_ and adjacent allocations
 | 
				
			||||||
 | 
					  static constexpr size_t kPadding = (kCacheLineSize - 1) / sizeof(T) + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  size_t capacity_;
 | 
				
			||||||
 | 
					  T *slots_;
 | 
				
			||||||
 | 
					#if defined(__has_cpp_attribute) && __has_cpp_attribute(no_unique_address)
 | 
				
			||||||
 | 
					  Allocator allocator_ [[no_unique_address]];
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  Allocator allocator_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Align to cache line size in order to avoid false sharing
 | 
				
			||||||
 | 
					  // readIdxCache_ and writeIdxCache_ is used to reduce the amount of cache
 | 
				
			||||||
 | 
					  // coherency traffic
 | 
				
			||||||
 | 
					  alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
 | 
				
			||||||
 | 
					  alignas(kCacheLineSize) size_t readIdxCache_ = 0;
 | 
				
			||||||
 | 
					  alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
 | 
				
			||||||
 | 
					  alignas(kCacheLineSize) size_t writeIdxCache_ = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					} // namespace rigtorp
 | 
				
			||||||
@@ -80,6 +80,8 @@
 | 
				
			|||||||
        ] ++ self.packages.${system}.default.dlopenBuildInputs;
 | 
					        ] ++ self.packages.${system}.default.dlopenBuildInputs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cmakeFlags = [
 | 
					        cmakeFlags = [
 | 
				
			||||||
 | 
					          "-DTOMATO_TOX_AV=ON"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          "-DTOMATO_ASAN=OFF"
 | 
					          "-DTOMATO_ASAN=OFF"
 | 
				
			||||||
          "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
 | 
					          "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
 | 
				
			||||||
          #"-DCMAKE_BUILD_TYPE=Debug"
 | 
					          #"-DCMAKE_BUILD_TYPE=Debug"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,12 +102,30 @@ target_sources(tomato PUBLIC
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	./chat_gui4.hpp
 | 
						./chat_gui4.hpp
 | 
				
			||||||
	./chat_gui4.cpp
 | 
						./chat_gui4.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						./stream_manager.hpp
 | 
				
			||||||
 | 
						./stream_manager_ui.hpp
 | 
				
			||||||
 | 
						./stream_manager_ui.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						./debug_video_tap.hpp
 | 
				
			||||||
 | 
						./debug_video_tap.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						./content/content.hpp
 | 
				
			||||||
 | 
						./content/frame_stream2.hpp
 | 
				
			||||||
 | 
						./content/sdl_video_frame_stream2.hpp
 | 
				
			||||||
 | 
						./content/sdl_video_frame_stream2.cpp
 | 
				
			||||||
 | 
						./content/audio_stream.hpp
 | 
				
			||||||
 | 
						./content/sdl_audio_frame_stream2.hpp
 | 
				
			||||||
 | 
						./content/sdl_audio_frame_stream2.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (TOMATO_TOX_AV)
 | 
					if (TOMATO_TOX_AV)
 | 
				
			||||||
	target_sources(tomato PUBLIC
 | 
						target_sources(tomato PUBLIC
 | 
				
			||||||
		./tox_av.hpp
 | 
							./tox_av.hpp
 | 
				
			||||||
		./tox_av.cpp
 | 
							./tox_av.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							./debug_tox_call.hpp
 | 
				
			||||||
 | 
							./debug_tox_call.cpp
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target_compile_definitions(tomato PUBLIC TOMATO_TOX_AV)
 | 
						target_compile_definitions(tomato PUBLIC TOMATO_TOX_AV)
 | 
				
			||||||
@@ -140,6 +158,8 @@ target_link_libraries(tomato PUBLIC
 | 
				
			|||||||
	libwebpmux # the f why (needed for anim encode)
 | 
						libwebpmux # the f why (needed for anim encode)
 | 
				
			||||||
	qoi
 | 
						qoi
 | 
				
			||||||
	SDL3_image::SDL3_image
 | 
						SDL3_image::SDL3_image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SPSCQueue
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# probably not enough
 | 
					# probably not enough
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								src/content/audio_stream.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/content/audio_stream.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/util/span.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <variant>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// raw audio
 | 
				
			||||||
 | 
					// channels make samples interleaved,
 | 
				
			||||||
 | 
					// planar channels are not supported
 | 
				
			||||||
 | 
					struct AudioFrame {
 | 
				
			||||||
 | 
						// sequence number, to detect gaps
 | 
				
			||||||
 | 
						uint32_t seq {0};
 | 
				
			||||||
 | 
						// TODO: maybe use ts instead to discard old?
 | 
				
			||||||
 | 
						// since buffer size is variable, some timestamp would be needed to estimate the lost time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// samples per second
 | 
				
			||||||
 | 
						uint32_t sample_rate {48'000};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size_t channels {0};
 | 
				
			||||||
 | 
						std::variant<
 | 
				
			||||||
 | 
							std::vector<int16_t>, // S16, platform endianess
 | 
				
			||||||
 | 
							Span<int16_t>, // non owning variant, for direct consumption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::vector<float>, // f32
 | 
				
			||||||
 | 
							Span<float> // non owning variant, for direct consumption
 | 
				
			||||||
 | 
						> buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// helpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool isS16(void) const {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
								std::holds_alternative<std::vector<int16_t>>(buffer) ||
 | 
				
			||||||
 | 
								std::holds_alternative<Span<int16_t>>(buffer)
 | 
				
			||||||
 | 
							;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						bool isF32(void) const {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
								std::holds_alternative<std::vector<float>>(buffer) ||
 | 
				
			||||||
 | 
								std::holds_alternative<Span<float>>(buffer)
 | 
				
			||||||
 | 
							;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						template<typename T>
 | 
				
			||||||
 | 
						Span<T> getSpan(void) const {
 | 
				
			||||||
 | 
							static_assert(std::is_same_v<int16_t, T> || std::is_same_v<float, T>);
 | 
				
			||||||
 | 
							if constexpr (std::is_same_v<int16_t, T>) {
 | 
				
			||||||
 | 
								assert(isS16());
 | 
				
			||||||
 | 
								if (std::holds_alternative<std::vector<int16_t>>(buffer)) {
 | 
				
			||||||
 | 
									return Span<int16_t>{std::get<std::vector<int16_t>>(buffer)};
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return std::get<Span<int16_t>>(buffer);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else if constexpr (std::is_same_v<float, T>) {
 | 
				
			||||||
 | 
								assert(isF32());
 | 
				
			||||||
 | 
								if (std::holds_alternative<std::vector<float>>(buffer)) {
 | 
				
			||||||
 | 
									return Span<float>{std::get<std::vector<float>>(buffer)};
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return std::get<Span<float>>(buffer);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using AudioFrameStream2I = FrameStream2I<AudioFrame>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using AudioFrameStream2MultiSource = FrameStream2MultiSource<AudioFrame>;
 | 
				
			||||||
 | 
					using AudioFrameStream2 = AudioFrameStream2MultiSource::sub_stream_type_t; // just use the default for now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/content/content.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/content/content.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <entt/container/dense_set.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/object_store.hpp>
 | 
				
			||||||
 | 
					#include <solanaceae/contact/contact_model3.hpp>
 | 
				
			||||||
 | 
					#include <solanaceae/message3/registry_message_model.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/file/file2.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Content1::Components {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: design it as a tree?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// or something
 | 
				
			||||||
 | 
						struct TagFile {};
 | 
				
			||||||
 | 
						struct TagAudioStream {};
 | 
				
			||||||
 | 
						struct TagVideoStream {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct TimingTiedTo {
 | 
				
			||||||
 | 
							entt::dense_set<ObjectHandle> ties;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the associated messages, if any
 | 
				
			||||||
 | 
						// useful if you want to update progress on the message
 | 
				
			||||||
 | 
						struct Messages {
 | 
				
			||||||
 | 
							std::vector<Message3Handle> messages;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ?
 | 
				
			||||||
 | 
						struct SuspectedParticipants {
 | 
				
			||||||
 | 
							entt::dense_set<Contact3> participants;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // Content::Components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										153
									
								
								src/content/frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/content/frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <optional>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SPSCQueue.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Frames ofen consist of:
 | 
				
			||||||
 | 
					// - seq id // incremental sequential id, gaps in ids can be used to detect loss
 | 
				
			||||||
 | 
					// - data // the frame data
 | 
				
			||||||
 | 
					// eg:
 | 
				
			||||||
 | 
					//struct ExampleFrame {
 | 
				
			||||||
 | 
						//int64_t seq_id {0};
 | 
				
			||||||
 | 
						//std::vector<uint8_t> data;
 | 
				
			||||||
 | 
					//};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename FrameType>
 | 
				
			||||||
 | 
					struct FrameStream2I {
 | 
				
			||||||
 | 
						virtual ~FrameStream2I(void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get number of available frames
 | 
				
			||||||
 | 
						[[nodiscard]] virtual int32_t size(void) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get next frame
 | 
				
			||||||
 | 
						// TODO: optional instead?
 | 
				
			||||||
 | 
						// data sharing? -> no, data is copied for each fsr, if concurency supported
 | 
				
			||||||
 | 
						[[nodiscard]] virtual std::optional<FrameType> pop(void) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// returns true if there are readers (or we dont know)
 | 
				
			||||||
 | 
						virtual bool push(const FrameType& value) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename FrameType>
 | 
				
			||||||
 | 
					struct FrameStream2SourceI {
 | 
				
			||||||
 | 
						virtual ~FrameStream2SourceI(void) {}
 | 
				
			||||||
 | 
						[[nodiscard]] virtual std::shared_ptr<FrameStream2I<FrameType>> subscribe(void) = 0;
 | 
				
			||||||
 | 
						virtual bool unsubscribe(const std::shared_ptr<FrameStream2I<FrameType>>& sub) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename FrameType>
 | 
				
			||||||
 | 
					struct FrameStream2SinkI {
 | 
				
			||||||
 | 
						virtual ~FrameStream2SinkI(void) {}
 | 
				
			||||||
 | 
						[[nodiscard]] virtual std::shared_ptr<FrameStream2I<FrameType>> subscribe(void) = 0;
 | 
				
			||||||
 | 
						virtual bool unsubscribe(const std::shared_ptr<FrameStream2I<FrameType>>& sub) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// needs count frames queue size
 | 
				
			||||||
 | 
					// having ~1-2sec buffer size is often sufficent
 | 
				
			||||||
 | 
					template<typename FrameType>
 | 
				
			||||||
 | 
					struct QueuedFrameStream2 : public FrameStream2I<FrameType> {
 | 
				
			||||||
 | 
						using frame_type = FrameType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rigtorp::SPSCQueue<FrameType> _queue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// discard values if queue full
 | 
				
			||||||
 | 
						// will block if not lossy and full on push
 | 
				
			||||||
 | 
						const bool _lossy {true};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						explicit QueuedFrameStream2(size_t queue_size, bool lossy = true) : _queue(queue_size), _lossy(lossy) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32_t size(void) override {
 | 
				
			||||||
 | 
							return _queue.size();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::optional<FrameType> pop(void) override {
 | 
				
			||||||
 | 
							auto* ret_ptr = _queue.front();
 | 
				
			||||||
 | 
							if (ret_ptr == nullptr) {
 | 
				
			||||||
 | 
								return std::nullopt;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// move away
 | 
				
			||||||
 | 
							FrameType ret = std::move(*ret_ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_queue.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool push(const FrameType& value) override {
 | 
				
			||||||
 | 
							if (_lossy) {
 | 
				
			||||||
 | 
								[[maybe_unused]] auto _ = _queue.try_emplace(value);
 | 
				
			||||||
 | 
								// TODO: maybe return ?
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_queue.push(value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// implements a stream that pushes to all sub streams
 | 
				
			||||||
 | 
					// release all streams before destructing! // TODO: improve lifetime here, maybe some shared semaphore?
 | 
				
			||||||
 | 
					template<typename FrameType, typename SubStreamType = QueuedFrameStream2<FrameType>>
 | 
				
			||||||
 | 
					struct FrameStream2MultiSource : public FrameStream2SourceI<FrameType>, public FrameStream2I<FrameType> {
 | 
				
			||||||
 | 
						using sub_stream_type_t = SubStreamType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pointer stability
 | 
				
			||||||
 | 
						std::vector<std::shared_ptr<SubStreamType>> _sub_streams;
 | 
				
			||||||
 | 
						std::mutex _sub_stream_lock; // accessing the _sub_streams array needs to be exclusive
 | 
				
			||||||
 | 
						// a simple lock here is ok, since this tends to be a rare operation,
 | 
				
			||||||
 | 
						// except for the push, which is always on the same thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual ~FrameStream2MultiSource(void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: forward args instead
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<FrameType>> subscribe(void) override {
 | 
				
			||||||
 | 
							// TODO: args???
 | 
				
			||||||
 | 
							size_t queue_size = 8;
 | 
				
			||||||
 | 
							bool lossy = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::lock_guard lg{_sub_stream_lock};
 | 
				
			||||||
 | 
							return _sub_streams.emplace_back(std::make_unique<SubStreamType>(queue_size, lossy));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<FrameType>>& sub) override {
 | 
				
			||||||
 | 
							std::lock_guard lg{_sub_stream_lock};
 | 
				
			||||||
 | 
							for (auto it = _sub_streams.begin(); it != _sub_streams.end(); it++) {
 | 
				
			||||||
 | 
								if (*it == sub) {
 | 
				
			||||||
 | 
									_sub_streams.erase(it);
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return false; // ?
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// stream interface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32_t size(void) override {
 | 
				
			||||||
 | 
							// TODO: return something sensible?
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::optional<FrameType> pop(void) override {
 | 
				
			||||||
 | 
							// nope
 | 
				
			||||||
 | 
							assert(false && "this logic is very frame type specific, provide an impl");
 | 
				
			||||||
 | 
							return std::nullopt;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// returns true if there are readers
 | 
				
			||||||
 | 
						bool push(const FrameType& value) override {
 | 
				
			||||||
 | 
							std::lock_guard lg{_sub_stream_lock};
 | 
				
			||||||
 | 
							bool have_readers{false};
 | 
				
			||||||
 | 
							for (auto& it : _sub_streams) {
 | 
				
			||||||
 | 
								[[maybe_unused]] auto _ = it->push(value);
 | 
				
			||||||
 | 
								have_readers = true; // even if queue full, we still continue believing in them
 | 
				
			||||||
 | 
								// maybe consider push return value?
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return have_readers;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										289
									
								
								src/content/sdl_audio_frame_stream2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								src/content/sdl_audio_frame_stream2.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,289 @@
 | 
				
			|||||||
 | 
					#include "./sdl_audio_frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// "thin" wrapper around sdl audio streams
 | 
				
			||||||
 | 
					// we dont needs to get fance, as they already provide everything we need
 | 
				
			||||||
 | 
					struct SDLAudioStreamReader : public AudioFrameStream2I {
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t _seq_counter {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t _sample_rate {48'000};
 | 
				
			||||||
 | 
						size_t _channels {0};
 | 
				
			||||||
 | 
						SDL_AudioFormat _format {SDL_AUDIO_S16};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::vector<int16_t> _buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDLAudioStreamReader(void) : _stream(nullptr, nullptr) {}
 | 
				
			||||||
 | 
						SDLAudioStreamReader(SDLAudioStreamReader&& other) :
 | 
				
			||||||
 | 
							_stream(std::move(other._stream)),
 | 
				
			||||||
 | 
							_sample_rate(other._sample_rate),
 | 
				
			||||||
 | 
							_channels(other._channels),
 | 
				
			||||||
 | 
							_format(other._format)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							static const size_t buffer_size {960*_channels};
 | 
				
			||||||
 | 
							_buffer.resize(buffer_size);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~SDLAudioStreamReader(void) {
 | 
				
			||||||
 | 
							if (_stream) {
 | 
				
			||||||
 | 
								SDL_UnbindAudioStream(_stream.get());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32_t size(void) override {
 | 
				
			||||||
 | 
							//assert(_stream);
 | 
				
			||||||
 | 
							// returns bytes
 | 
				
			||||||
 | 
							//SDL_GetAudioStreamAvailable(_stream.get());
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::optional<AudioFrame> pop(void) override {
 | 
				
			||||||
 | 
							assert(_stream);
 | 
				
			||||||
 | 
							assert(_format == SDL_AUDIO_S16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static const size_t buffer_size {960*_channels};
 | 
				
			||||||
 | 
							_buffer.resize(buffer_size); // noop?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto read_bytes = SDL_GetAudioStreamData(
 | 
				
			||||||
 | 
								_stream.get(),
 | 
				
			||||||
 | 
								_buffer.data(),
 | 
				
			||||||
 | 
								_buffer.size()*sizeof(int16_t)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// no new frame yet, or error
 | 
				
			||||||
 | 
							if (read_bytes <= 0) {
 | 
				
			||||||
 | 
								return std::nullopt;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return AudioFrame {
 | 
				
			||||||
 | 
								_seq_counter++,
 | 
				
			||||||
 | 
								_sample_rate, _channels,
 | 
				
			||||||
 | 
								Span<int16_t>(_buffer.data(), read_bytes/sizeof(int16_t)),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool push(const AudioFrame&) override {
 | 
				
			||||||
 | 
							// TODO: make universal sdl stream wrapper (combine with SDLAudioOutputDeviceDefaultInstance)
 | 
				
			||||||
 | 
							assert(false && "read only");
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioInputDevice::SDLAudioInputDevice(void) : SDLAudioInputDevice(SDL_AUDIO_DEVICE_DEFAULT_RECORDING) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioInputDevice::SDLAudioInputDevice(SDL_AudioDeviceID conf_device_id) : _configured_device_id(conf_device_id) {
 | 
				
			||||||
 | 
						if (_configured_device_id == 0) {
 | 
				
			||||||
 | 
							// TODO: proper error handling
 | 
				
			||||||
 | 
							throw int(1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioInputDevice::~SDLAudioInputDevice(void) {
 | 
				
			||||||
 | 
						_streams.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (_virtual_device_id != 0) {
 | 
				
			||||||
 | 
							SDL_CloseAudioDevice(_virtual_device_id);
 | 
				
			||||||
 | 
							_virtual_device_id = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::shared_ptr<FrameStream2I<AudioFrame>> SDLAudioInputDevice::subscribe(void) {
 | 
				
			||||||
 | 
						if (_virtual_device_id == 0) {
 | 
				
			||||||
 | 
							// first stream, open device
 | 
				
			||||||
 | 
							// this spec is more like a hint to the hardware
 | 
				
			||||||
 | 
							SDL_AudioSpec spec {
 | 
				
			||||||
 | 
								SDL_AUDIO_S16,
 | 
				
			||||||
 | 
								1, // TODO: conf
 | 
				
			||||||
 | 
								48'000,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							_virtual_device_id = SDL_OpenAudioDevice(_configured_device_id, &spec);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (_virtual_device_id == 0) {
 | 
				
			||||||
 | 
							std::cerr << "SDLAID error: failed opening device " << _configured_device_id << "\n";
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_AudioSpec spec {
 | 
				
			||||||
 | 
							SDL_AUDIO_S16,
 | 
				
			||||||
 | 
							1, // TODO: conf
 | 
				
			||||||
 | 
							48'000,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_AudioSpec device_spec {
 | 
				
			||||||
 | 
							SDL_AUDIO_S16,
 | 
				
			||||||
 | 
							1, // TODO: conf
 | 
				
			||||||
 | 
							48'000,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						// TODO: error check
 | 
				
			||||||
 | 
						SDL_GetAudioDeviceFormat(_virtual_device_id, &device_spec, nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// error check
 | 
				
			||||||
 | 
						auto* sdl_stream = SDL_CreateAudioStream(&device_spec, &spec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// error check
 | 
				
			||||||
 | 
						SDL_BindAudioStream(_virtual_device_id, sdl_stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto new_stream = std::make_shared<SDLAudioStreamReader>();
 | 
				
			||||||
 | 
						// TODO: move to ctr
 | 
				
			||||||
 | 
						new_stream->_stream = {sdl_stream, &SDL_DestroyAudioStream};
 | 
				
			||||||
 | 
						new_stream->_sample_rate = spec.freq;
 | 
				
			||||||
 | 
						new_stream->_channels = spec.channels;
 | 
				
			||||||
 | 
						new_stream->_format = spec.format;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_streams.emplace_back(new_stream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return new_stream;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SDLAudioInputDevice::unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame>>& sub) {
 | 
				
			||||||
 | 
						for (auto it = _streams.cbegin(); it != _streams.cend(); it++) {
 | 
				
			||||||
 | 
							if (*it == sub) {
 | 
				
			||||||
 | 
								_streams.erase(it);
 | 
				
			||||||
 | 
								if (_streams.empty()) {
 | 
				
			||||||
 | 
									// last stream, close
 | 
				
			||||||
 | 
									// TODO: make sure no shared ptr still exists???
 | 
				
			||||||
 | 
									SDL_CloseAudioDevice(_virtual_device_id);
 | 
				
			||||||
 | 
									std::cout << "SDLAID: closing device " << _virtual_device_id << " (" << _configured_device_id << ")\n";
 | 
				
			||||||
 | 
									_virtual_device_id = 0;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// does not need to be visible in the header
 | 
				
			||||||
 | 
					struct SDLAudioOutputDeviceDefaultInstance : public AudioFrameStream2I {
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t _last_sample_rate {48'000};
 | 
				
			||||||
 | 
						size_t _last_channels {0};
 | 
				
			||||||
 | 
						SDL_AudioFormat _last_format {SDL_AUDIO_S16};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: audio device
 | 
				
			||||||
 | 
						SDLAudioOutputDeviceDefaultInstance(void);
 | 
				
			||||||
 | 
						SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~SDLAudioOutputDeviceDefaultInstance(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32_t size(void) override;
 | 
				
			||||||
 | 
						std::optional<AudioFrame> pop(void) override;
 | 
				
			||||||
 | 
						bool push(const AudioFrame& value) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(void) : _stream(nullptr, nullptr) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other) : _stream(std::move(other._stream)) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioOutputDeviceDefaultInstance::~SDLAudioOutputDeviceDefaultInstance(void) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					int32_t SDLAudioOutputDeviceDefaultInstance::size(void) {
 | 
				
			||||||
 | 
						return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::optional<AudioFrame> SDLAudioOutputDeviceDefaultInstance::pop(void) {
 | 
				
			||||||
 | 
						assert(false);
 | 
				
			||||||
 | 
						// this is an output device, there is no data to pop
 | 
				
			||||||
 | 
						return std::nullopt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SDLAudioOutputDeviceDefaultInstance::push(const AudioFrame& value) {
 | 
				
			||||||
 | 
						if (!static_cast<bool>(_stream)) {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// verify here the fame has the same sample type, channel count and sample freq
 | 
				
			||||||
 | 
						// if something changed, we need to either use a temporary stream, just for conversion, or reopen _stream with the new params
 | 
				
			||||||
 | 
						// because of data temporality, the second options looks like a better candidate
 | 
				
			||||||
 | 
						if (
 | 
				
			||||||
 | 
							value.sample_rate != _last_sample_rate ||
 | 
				
			||||||
 | 
							value.channels != _last_channels ||
 | 
				
			||||||
 | 
							(value.isF32() && _last_format != SDL_AUDIO_F32) ||
 | 
				
			||||||
 | 
							(value.isS16() && _last_format != SDL_AUDIO_S16)
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const SDL_AudioSpec spec = {
 | 
				
			||||||
 | 
								static_cast<SDL_AudioFormat>((value.isF32() ? SDL_AUDIO_F32 :  SDL_AUDIO_S16)),
 | 
				
			||||||
 | 
								static_cast<int>(value.channels),
 | 
				
			||||||
 | 
								static_cast<int>(value.sample_rate)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SDL_SetAudioStreamFormat(_stream.get(), &spec, nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cerr << "SDLAOD: audio format changed\n";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (value.isS16()) {
 | 
				
			||||||
 | 
							auto data = value.getSpan<int16_t>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (data.size == 0) {
 | 
				
			||||||
 | 
								std::cerr << "empty audio frame??\n";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!SDL_PutAudioStreamData(_stream.get(), data.ptr, data.size * sizeof(int16_t))) {
 | 
				
			||||||
 | 
								std::cerr << "put data error\n";
 | 
				
			||||||
 | 
								return false; // return true?
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (value.isF32()) {
 | 
				
			||||||
 | 
							auto data = value.getSpan<float>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (data.size == 0) {
 | 
				
			||||||
 | 
								std::cerr << "empty audio frame??\n";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!SDL_PutAudioStreamData(_stream.get(), data.ptr, data.size * sizeof(float))) {
 | 
				
			||||||
 | 
								std::cerr << "put data error\n";
 | 
				
			||||||
 | 
								return false; // return true?
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_last_sample_rate = value.sample_rate;
 | 
				
			||||||
 | 
						_last_channels = value.channels;
 | 
				
			||||||
 | 
						_last_format = value.isF32() ? SDL_AUDIO_F32 :  SDL_AUDIO_S16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioOutputDeviceDefaultSink::~SDLAudioOutputDeviceDefaultSink(void) {
 | 
				
			||||||
 | 
						// TODO: pause and close device?
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::shared_ptr<FrameStream2I<AudioFrame>> SDLAudioOutputDeviceDefaultSink::subscribe(void) {
 | 
				
			||||||
 | 
						auto new_instance = std::make_shared<SDLAudioOutputDeviceDefaultInstance>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						new_instance->_stream = {
 | 
				
			||||||
 | 
							SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr),
 | 
				
			||||||
 | 
							&SDL_DestroyAudioStream
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						new_instance->_last_sample_rate = spec.freq;
 | 
				
			||||||
 | 
						new_instance->_last_channels = spec.channels;
 | 
				
			||||||
 | 
						new_instance->_last_format = spec.format;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!static_cast<bool>(new_instance->_stream)) {
 | 
				
			||||||
 | 
							std::cerr << "SDL open audio device failed!\n";
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto audio_device_id = SDL_GetAudioStreamDevice(new_instance->_stream.get());
 | 
				
			||||||
 | 
						SDL_ResumeAudioDevice(audio_device_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return new_instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SDLAudioOutputDeviceDefaultSink::unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame>>& sub) {
 | 
				
			||||||
 | 
						if (!sub) {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/content/sdl_audio_frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/content/sdl_audio_frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./frame_stream2.hpp"
 | 
				
			||||||
 | 
					#include "./audio_stream.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// we dont have to multicast ourself, because sdl streams and virtual devices already do this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// source
 | 
				
			||||||
 | 
					// opens device
 | 
				
			||||||
 | 
					// creates a sdl audio stream for each subscribed reader stream
 | 
				
			||||||
 | 
					struct SDLAudioInputDevice : public FrameStream2SourceI<AudioFrame> {
 | 
				
			||||||
 | 
						// held by instances
 | 
				
			||||||
 | 
						using sdl_stream_type = std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_AudioDeviceID _configured_device_id {0};
 | 
				
			||||||
 | 
						SDL_AudioDeviceID _virtual_device_id {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::vector<std::shared_ptr<FrameStream2I<AudioFrame>>> _streams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDLAudioInputDevice(void);
 | 
				
			||||||
 | 
						SDLAudioInputDevice(SDL_AudioDeviceID conf_device_id);
 | 
				
			||||||
 | 
						~SDLAudioInputDevice(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<AudioFrame>> subscribe(void) override;
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame>>& sub) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// sink
 | 
				
			||||||
 | 
					// constructs entirely new streams, since sdl handles sync and mixing for us (or should)
 | 
				
			||||||
 | 
					struct SDLAudioOutputDeviceDefaultSink : public FrameStream2SinkI<AudioFrame> {
 | 
				
			||||||
 | 
						// TODO: pause device?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						~SDLAudioOutputDeviceDefaultSink(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<AudioFrame>> subscribe(void) override;
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame>>& sub) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										167
									
								
								src/content/sdl_video_frame_stream2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/content/sdl_video_frame_stream2.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
				
			|||||||
 | 
					#include "./sdl_video_frame_stream2.hpp"
 | 
				
			||||||
 | 
					#include "SDL3/SDL_camera.h"
 | 
				
			||||||
 | 
					#include "SDL3/SDL_pixels.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: move out and create lazy cam for each device
 | 
				
			||||||
 | 
					SDLVideoCameraContent::SDLVideoCameraContent(void) {
 | 
				
			||||||
 | 
						int devcount {0};
 | 
				
			||||||
 | 
						//SDL_CameraDeviceID *devices = SDL_GetCameraDevices(&devcount);
 | 
				
			||||||
 | 
						SDL_CameraID *devices = SDL_GetCameras(&devcount);
 | 
				
			||||||
 | 
						std::cout << "SDL Camera Driver: " << SDL_GetCurrentCameraDriver() << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (devices == nullptr || devcount < 1) {
 | 
				
			||||||
 | 
							throw int(1); // TODO: proper exception?
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							SDL_CameraSpec spec {
 | 
				
			||||||
 | 
								// FORCE a diffrent pixel format
 | 
				
			||||||
 | 
								//SDL_PIXELFORMAT_RGBA8888,
 | 
				
			||||||
 | 
								//SDL_PIXELFORMAT_UNKNOWN,
 | 
				
			||||||
 | 
								//SDL_PIXELFORMAT_IYUV,
 | 
				
			||||||
 | 
								SDL_PIXELFORMAT_YUY2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//SDL_COLORSPACE_UNKNOWN,
 | 
				
			||||||
 | 
								//SDL_COLORSPACE_SRGB,
 | 
				
			||||||
 | 
								//SDL_COLORSPACE_SRGB_LINEAR,
 | 
				
			||||||
 | 
								SDL_COLORSPACE_YUV_DEFAULT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//1280, 720,
 | 
				
			||||||
 | 
								//640, 360,
 | 
				
			||||||
 | 
								//640, 480,
 | 
				
			||||||
 | 
								696, 392,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//1, 30
 | 
				
			||||||
 | 
								30, 1
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							_camera = {
 | 
				
			||||||
 | 
								//SDL_OpenCamera(devices[devcount-1], &spec),
 | 
				
			||||||
 | 
								SDL_OpenCamera(devices[0], nullptr),
 | 
				
			||||||
 | 
								//SDL_OpenCamera(devices[0], &spec),
 | 
				
			||||||
 | 
								&SDL_CloseCamera
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							SDL_GetCameraFormat(_camera.get(), &spec);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						SDL_free(devices);
 | 
				
			||||||
 | 
						if (!static_cast<bool>(_camera)) {
 | 
				
			||||||
 | 
							throw int(2);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 << "user denied camera permission\n";
 | 
				
			||||||
 | 
							throw int(3);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_CameraSpec spec;
 | 
				
			||||||
 | 
						float fps {1.f};
 | 
				
			||||||
 | 
						if (!SDL_GetCameraFormat(_camera.get(), &spec)) {
 | 
				
			||||||
 | 
							// meh
 | 
				
			||||||
 | 
							throw int(5);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fps = float(spec.framerate_numerator)/float(spec.framerate_denominator);
 | 
				
			||||||
 | 
							std::cout << "camera fps: " << fps << "fps (" << spec.framerate_numerator << "/" << spec.framerate_denominator << ")\n";
 | 
				
			||||||
 | 
							auto* format_name = SDL_GetPixelFormatName(spec.format);
 | 
				
			||||||
 | 
							std::cout << "camera format: " << format_name << "\n";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_thread = std::thread([this, fps](void) {
 | 
				
			||||||
 | 
							while (!_thread_should_quit) {
 | 
				
			||||||
 | 
								Uint64 timestampNS = 0;
 | 
				
			||||||
 | 
								SDL_Surface* sdl_frame_next = SDL_AcquireCameraFrame(_camera.get(), ×tampNS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// no new frame yet, or error
 | 
				
			||||||
 | 
								if (sdl_frame_next == nullptr) {
 | 
				
			||||||
 | 
									// only sleep 1/10, we expected a frame
 | 
				
			||||||
 | 
									std::this_thread::sleep_for(std::chrono::milliseconds(int64_t((1000/fps) / 10)));
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if 0
 | 
				
			||||||
 | 
								{ // test copy to trigger bug
 | 
				
			||||||
 | 
									SDL_Surface* test_surf = SDL_CreateSurface(
 | 
				
			||||||
 | 
										sdl_frame_next->w,
 | 
				
			||||||
 | 
										sdl_frame_next->h,
 | 
				
			||||||
 | 
										SDL_PIXELFORMAT_RGBA8888
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									assert(test_surf != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									SDL_BlitSurface(sdl_frame_next, nullptr, test_surf, nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									SDL_DestroySurface(test_surf);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bool someone_listening {false};
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									SDLVideoFrame new_frame_non_owning {
 | 
				
			||||||
 | 
										timestampNS/1000,
 | 
				
			||||||
 | 
										sdl_frame_next
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// creates surface copies
 | 
				
			||||||
 | 
									someone_listening = push(new_frame_non_owning);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								SDL_ReleaseCameraFrame(_camera.get(), sdl_frame_next);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (someone_listening) {
 | 
				
			||||||
 | 
									// double the interval on acquire
 | 
				
			||||||
 | 
									std::this_thread::sleep_for(std::chrono::milliseconds(int64_t((1000/fps)*0.5)));
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									std::cerr << "i guess no one is listening\n";
 | 
				
			||||||
 | 
									// we just sleep 4x as long, bc no one is listening
 | 
				
			||||||
 | 
									std::this_thread::sleep_for(std::chrono::milliseconds(int64_t((1000/fps)*4)));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLVideoCameraContent::~SDLVideoCameraContent(void) {
 | 
				
			||||||
 | 
						_thread_should_quit = true;
 | 
				
			||||||
 | 
						_thread.join();
 | 
				
			||||||
 | 
						// TODO: what to do if readers are still present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HACK: sdl is buggy and freezes otherwise. it is likely still possible, but rare to freeze here
 | 
				
			||||||
 | 
						// flush unused frames
 | 
				
			||||||
 | 
					#if 1
 | 
				
			||||||
 | 
						while (true) {
 | 
				
			||||||
 | 
							SDL_Surface* sdl_frame_next = SDL_AcquireCameraFrame(_camera.get(), nullptr);
 | 
				
			||||||
 | 
							if (sdl_frame_next != nullptr) {
 | 
				
			||||||
 | 
								SDL_ReleaseCameraFrame(_camera.get(), sdl_frame_next);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/content/sdl_video_frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/content/sdl_video_frame_stream2.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline void nopSurfaceDestructor(SDL_Surface*) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// this is very sdl specific
 | 
				
			||||||
 | 
					struct SDLVideoFrame {
 | 
				
			||||||
 | 
						// TODO: sequence numbering?
 | 
				
			||||||
 | 
						// micro seconds (nano is way too much)
 | 
				
			||||||
 | 
						uint64_t timestampUS {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface {nullptr, &SDL_DestroySurface};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// special non-owning constructor?
 | 
				
			||||||
 | 
						SDLVideoFrame(
 | 
				
			||||||
 | 
							uint64_t ts,
 | 
				
			||||||
 | 
							SDL_Surface* surf
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							timestampUS = ts;
 | 
				
			||||||
 | 
							surface = {surf, &nopSurfaceDestructor};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						SDLVideoFrame(SDLVideoFrame&& other) = default;
 | 
				
			||||||
 | 
						// copy
 | 
				
			||||||
 | 
						SDLVideoFrame(const SDLVideoFrame& other) {
 | 
				
			||||||
 | 
							timestampUS = other.timestampUS;
 | 
				
			||||||
 | 
							if (static_cast<bool>(other.surface)) {
 | 
				
			||||||
 | 
								//surface = {
 | 
				
			||||||
 | 
								//    SDL_CreateSurface(
 | 
				
			||||||
 | 
								//        other.surface->w,
 | 
				
			||||||
 | 
								//        other.surface->h,
 | 
				
			||||||
 | 
								//        other.surface->format
 | 
				
			||||||
 | 
								//    ),
 | 
				
			||||||
 | 
								//    &SDL_DestroySurface
 | 
				
			||||||
 | 
								//};
 | 
				
			||||||
 | 
								//SDL_BlitSurface(other.surface.get(), nullptr, surface.get(), nullptr);
 | 
				
			||||||
 | 
								surface = {
 | 
				
			||||||
 | 
									SDL_DuplicateSurface(other.surface.get()),
 | 
				
			||||||
 | 
									&SDL_DestroySurface
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						SDLVideoFrame& operator=(const SDLVideoFrame& other) = delete;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using SDLVideoFrameStream2MultiSource = FrameStream2MultiSource<SDLVideoFrame>;
 | 
				
			||||||
 | 
					using SDLVideoFrameStream2 = SDLVideoFrameStream2MultiSource::sub_stream_type_t; // just use the default for now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SDLVideoCameraContent : public SDLVideoFrameStream2MultiSource {
 | 
				
			||||||
 | 
						// meh, empty default
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera {nullptr, &SDL_CloseCamera};
 | 
				
			||||||
 | 
						std::atomic<bool> _thread_should_quit {false};
 | 
				
			||||||
 | 
						std::thread _thread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// construct source and start thread
 | 
				
			||||||
 | 
						// TODO: optimize so the thread is not always running
 | 
				
			||||||
 | 
						SDLVideoCameraContent(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// stops the thread and closes the camera
 | 
				
			||||||
 | 
						~SDLVideoCameraContent(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make only some of writer public
 | 
				
			||||||
 | 
						using SDLVideoFrameStream2MultiSource::subscribe;
 | 
				
			||||||
 | 
						using SDLVideoFrameStream2MultiSource::unsubscribe;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/content/stream_reader.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/content/stream_reader.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/util/span.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// most media that can be counted as "stream" comes in packets/frames/messages
 | 
				
			||||||
 | 
					// so this class provides an interface for ideal async fetching of frames
 | 
				
			||||||
 | 
					struct RawFrameStreamReaderI {
 | 
				
			||||||
 | 
						// return the number of ready frames in cache
 | 
				
			||||||
 | 
						// returns -1 if unknown
 | 
				
			||||||
 | 
						virtual int64_t have(void) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get next frame, empty if none
 | 
				
			||||||
 | 
						virtual ByteSpan getNext(void) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/content/stream_reader_sdl_audio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/content/stream_reader_sdl_audio.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					#include "./stream_reader_sdl_audio.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLAudioFrameStreamReader::SDLAudioFrameStreamReader(int32_t buffer_size) : _buffer_size(buffer_size),  _stream{nullptr, &SDL_DestroyAudioStream} {
 | 
				
			||||||
 | 
						_buffer.resize(_buffer_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_stream = {
 | 
				
			||||||
 | 
							SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_CAPTURE, &spec, nullptr, nullptr),
 | 
				
			||||||
 | 
							&SDL_DestroyAudioStream
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Span<int16_t> SDLAudioFrameStreamReader::getNextAudioFrame(void) {
 | 
				
			||||||
 | 
						const int32_t needed_bytes = (_buffer.size() - _remaining_size) * sizeof(int16_t);
 | 
				
			||||||
 | 
						const auto read_bytes = SDL_GetAudioStreamData(_stream.get(), _buffer.data()+_remaining_size, needed_bytes);
 | 
				
			||||||
 | 
						if (read_bytes < 0) {
 | 
				
			||||||
 | 
							// error
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (read_bytes < needed_bytes) {
 | 
				
			||||||
 | 
							// HACK: we are just assuming here that sdl never gives us half a sample!
 | 
				
			||||||
 | 
							_remaining_size += read_bytes / sizeof(int16_t);
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_remaining_size = 0;
 | 
				
			||||||
 | 
						return Span<int16_t>{_buffer};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int64_t SDLAudioFrameStreamReader::have(void) {
 | 
				
			||||||
 | 
						return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ByteSpan SDLAudioFrameStreamReader::getNext(void) {
 | 
				
			||||||
 | 
						auto next_frame_span = getNextAudioFrame();
 | 
				
			||||||
 | 
						return ByteSpan{reinterpret_cast<const uint8_t*>(next_frame_span.ptr), next_frame_span.size};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/content/stream_reader_sdl_audio.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/content/stream_reader_sdl_audio.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./stream_reader.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SDLAudioFrameStreamReader : public RawFrameStreamReaderI {
 | 
				
			||||||
 | 
						// count samples per buffer
 | 
				
			||||||
 | 
						const int32_t _buffer_size {1024};
 | 
				
			||||||
 | 
						std::vector<int16_t> _buffer;
 | 
				
			||||||
 | 
						size_t _remaining_size {0}; // data still in buffer, that was remaining from last call and not enough to fill a full frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// meh, empty default
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// buffer_size in number of samples
 | 
				
			||||||
 | 
						SDLAudioFrameStreamReader(int32_t buffer_size = 1024);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// data owned by StreamReader, overwritten by next call to getNext*()
 | 
				
			||||||
 | 
						Span<int16_t> getNextAudioFrame(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public: // interface
 | 
				
			||||||
 | 
							int64_t have(void) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ByteSpan getNext(void) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/content/stream_reader_sdl_video.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/content/stream_reader_sdl_video.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					#include "./stream_reader_sdl_video.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLVideoFrameStreamReader::SDLVideoFrameStreamReader() : _camera{nullptr, &SDL_CloseCamera}, _surface{nullptr, &SDL_DestroySurface} {
 | 
				
			||||||
 | 
						// enumerate
 | 
				
			||||||
 | 
						int devcount = 0;
 | 
				
			||||||
 | 
						SDL_CameraDeviceID *devices = SDL_GetCameraDevices(&devcount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (devices == nullptr || devcount < 1) {
 | 
				
			||||||
 | 
							throw int(1); // TODO: proper exception?
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "### found cameras:\n";
 | 
				
			||||||
 | 
						for (int i = 0; i < devcount; i++) {
 | 
				
			||||||
 | 
							const SDL_CameraDeviceID device = devices[i];
 | 
				
			||||||
 | 
							char *name = SDL_GetCameraDeviceName(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << "  - Camera #" << i << ": " << name << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SDL_free(name);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_camera = {
 | 
				
			||||||
 | 
							SDL_OpenCameraDevice(devices[0], nullptr),
 | 
				
			||||||
 | 
							&SDL_CloseCamera
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (!static_cast<bool>(_camera)) {
 | 
				
			||||||
 | 
							throw int(2);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_CameraSpec spec;
 | 
				
			||||||
 | 
						if (SDL_GetCameraFormat(_camera.get(), &spec) < 0) {
 | 
				
			||||||
 | 
							// meh
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// interval
 | 
				
			||||||
 | 
							float interval = float(spec.interval_numerator)/float(spec.interval_denominator);
 | 
				
			||||||
 | 
							std::cout << "camera interval: " << interval*1000 << "ms\n";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SDLVideoFrameStreamReader::VideoFrame SDLVideoFrameStreamReader::getNextVideoFrameRGBA(void) {
 | 
				
			||||||
 | 
						if (!static_cast<bool>(_camera)) {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Uint64 timestampNS = 0;
 | 
				
			||||||
 | 
						SDL_Surface* frame_next = SDL_AcquireCameraFrame(_camera.get(), ×tampNS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no new frame yet, or error
 | 
				
			||||||
 | 
						if (frame_next == nullptr) {
 | 
				
			||||||
 | 
							//std::cout << "failed acquiring frame\n";
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: investigate zero copy
 | 
				
			||||||
 | 
						_surface = {
 | 
				
			||||||
 | 
							SDL_ConvertSurfaceFormat(frame_next, SDL_PIXELFORMAT_RGBA8888),
 | 
				
			||||||
 | 
							&SDL_DestroySurface
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_ReleaseCameraFrame(_camera.get(), frame_next);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_LockSurface(_surface.get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							_surface->w,
 | 
				
			||||||
 | 
							_surface->h,
 | 
				
			||||||
 | 
							timestampNS,
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								reinterpret_cast<const uint8_t*>(_surface->pixels),
 | 
				
			||||||
 | 
								uint64_t(_surface->w*_surface->h*4) // rgba
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int64_t SDLVideoFrameStreamReader::have(void) {
 | 
				
			||||||
 | 
						return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ByteSpan SDLVideoFrameStreamReader::getNext(void) {
 | 
				
			||||||
 | 
						return {};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/content/stream_reader_sdl_video.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/content/stream_reader_sdl_video.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./stream_reader.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SDLVideoFrameStreamReader : public RawFrameStreamReaderI {
 | 
				
			||||||
 | 
						// meh, empty default
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera;
 | 
				
			||||||
 | 
						std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> _surface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDLVideoFrameStreamReader(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct VideoFrame {
 | 
				
			||||||
 | 
							int32_t width {0};
 | 
				
			||||||
 | 
							int32_t height {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint64_t timestampNS {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ByteSpan data;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// data owned by StreamReader, overwritten by next call to getNext*()
 | 
				
			||||||
 | 
						VideoFrame getNextVideoFrameRGBA(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public: // interface
 | 
				
			||||||
 | 
							int64_t have(void) override;
 | 
				
			||||||
 | 
							ByteSpan getNext(void) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										718
									
								
								src/debug_tox_call.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										718
									
								
								src/debug_tox_call.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,718 @@
 | 
				
			|||||||
 | 
					#include "./debug_tox_call.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./stream_manager.hpp"
 | 
				
			||||||
 | 
					#include "./content/audio_stream.hpp"
 | 
				
			||||||
 | 
					#include "./content/sdl_video_frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <imgui/imgui.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <optional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fwd
 | 
				
			||||||
 | 
					namespace Message {
 | 
				
			||||||
 | 
						uint64_t getTimeMS();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
						;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SDL_Surface* convertYUY2_IYUV(SDL_Surface* surf) {
 | 
				
			||||||
 | 
						if (surf->format != SDL_PIXELFORMAT_YUY2) {
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ((surf->w % 2) != 0) {
 | 
				
			||||||
 | 
							SDL_SetError("YUY2->IYUV does not support odd widths");
 | 
				
			||||||
 | 
							// hmmm, we dont handle odd widths
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_LockSurface(surf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_Surface* conv_surf = SDL_CreateSurface(surf->w, surf->h, SDL_PIXELFORMAT_IYUV);
 | 
				
			||||||
 | 
						SDL_LockSurface(conv_surf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// YUY2 is 4:2:2 packed
 | 
				
			||||||
 | 
						// Y is simple, we just copy it over
 | 
				
			||||||
 | 
						// U V are double the resolution (vertically), so we avg both
 | 
				
			||||||
 | 
						// Packed mode: Y0+U0+Y1+V0 (1 plane)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint8_t* y_plane = static_cast<uint8_t*>(conv_surf->pixels);
 | 
				
			||||||
 | 
						uint8_t* u_plane = static_cast<uint8_t*>(conv_surf->pixels) + conv_surf->w*conv_surf->h;
 | 
				
			||||||
 | 
						uint8_t* v_plane = static_cast<uint8_t*>(conv_surf->pixels) + conv_surf->w*conv_surf->h + (conv_surf->w/2)*(conv_surf->h/2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const uint8_t* yuy2_data = static_cast<const uint8_t*>(surf->pixels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int y = 0; y < surf->h; y++) {
 | 
				
			||||||
 | 
							for (int x = 0; x < surf->w; x += 2) {
 | 
				
			||||||
 | 
								// every pixel uses 2 bytes
 | 
				
			||||||
 | 
								const uint8_t* yuy2_curser = yuy2_data + y*surf->w*2 + x*2;
 | 
				
			||||||
 | 
								uint8_t src_y0 = yuy2_curser[0];
 | 
				
			||||||
 | 
								uint8_t src_u = yuy2_curser[1];
 | 
				
			||||||
 | 
								uint8_t src_y1 = yuy2_curser[2];
 | 
				
			||||||
 | 
								uint8_t src_v = yuy2_curser[3];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								y_plane[y*conv_surf->w + x] = src_y0;
 | 
				
			||||||
 | 
								y_plane[y*conv_surf->w + x+1] = src_y1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								size_t uv_index = (y/2) * (conv_surf->w/2) + x/2;
 | 
				
			||||||
 | 
								if (y % 2 == 0) {
 | 
				
			||||||
 | 
									// first write
 | 
				
			||||||
 | 
									u_plane[uv_index] = src_u;
 | 
				
			||||||
 | 
									v_plane[uv_index] = src_v;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// second write, mix with existing value
 | 
				
			||||||
 | 
									u_plane[uv_index] = (int(u_plane[uv_index]) + int(src_u)) / 2;
 | 
				
			||||||
 | 
									v_plane[uv_index] = (int(v_plane[uv_index]) + int(src_v)) / 2;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_UnlockSurface(conv_surf);
 | 
				
			||||||
 | 
						SDL_UnlockSurface(surf);
 | 
				
			||||||
 | 
						return conv_surf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 != _forced_format) {
 | 
				
			||||||
 | 
								//std::cerr << "DTC: need to convert from " << SDL_GetPixelFormatName(converted_surf->format) << " to SDL_PIXELFORMAT_IYUV\n";
 | 
				
			||||||
 | 
								if (converted_surf->format == SDL_PIXELFORMAT_YUY2 && _forced_format == SDL_PIXELFORMAT_IYUV) {
 | 
				
			||||||
 | 
									// optimized custom impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									//auto start = Message::getTimeMS();
 | 
				
			||||||
 | 
									converted_surf = convertYUY2_IYUV(converted_surf);
 | 
				
			||||||
 | 
									//auto end = Message::getTimeMS();
 | 
				
			||||||
 | 
									// 3ms
 | 
				
			||||||
 | 
									//std::cerr << "DTC: timing " << SDL_GetPixelFormatName(converted_surf->format) << "->SDL_PIXELFORMAT_IYUV: " << end-start << "ms\n";
 | 
				
			||||||
 | 
								} else 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();
 | 
				
			||||||
 | 
									// 1ms
 | 
				
			||||||
 | 
									//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();
 | 
				
			||||||
 | 
									// 60ms
 | 
				
			||||||
 | 
									//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(std::move(new_value));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return QueuedFrameStream2<SDLVideoFrame>::push(value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// exlusive
 | 
				
			||||||
 | 
					// TODO: replace with something better than a queue
 | 
				
			||||||
 | 
					struct ToxAVCallVideoSink : public FrameStream2SinkI<SDLVideoFrame> {
 | 
				
			||||||
 | 
						ToxAV& _toxav;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// bitrate for enabled state
 | 
				
			||||||
 | 
						uint32_t _video_bitrate {2};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t _fid;
 | 
				
			||||||
 | 
						std::shared_ptr<PushConversionQueuedVideoStream> _writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ToxAVCallVideoSink(ToxAV& toxav, uint32_t fid) : _toxav(toxav), _fid(fid) {}
 | 
				
			||||||
 | 
						~ToxAVCallVideoSink(void) {
 | 
				
			||||||
 | 
							if (_writer) {
 | 
				
			||||||
 | 
								_writer = nullptr;
 | 
				
			||||||
 | 
								_toxav.toxavVideoSetBitRate(_fid, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sink
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<SDLVideoFrame>> subscribe(void) override {
 | 
				
			||||||
 | 
							if (_writer) {
 | 
				
			||||||
 | 
								// max 1 (exclusive, composite video somewhere else)
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto err = _toxav.toxavVideoSetBitRate(_fid, _video_bitrate);
 | 
				
			||||||
 | 
							if (err != TOXAV_ERR_BIT_RATE_SET_OK) {
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_writer = std::make_shared<PushConversionQueuedVideoStream>(10, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return _writer;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<SDLVideoFrame>>& sub) override {
 | 
				
			||||||
 | 
							if (!sub || !_writer) {
 | 
				
			||||||
 | 
								// nah
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sub == _writer) {
 | 
				
			||||||
 | 
								_writer = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/*auto err = */_toxav.toxavVideoSetBitRate(_fid, 0);
 | 
				
			||||||
 | 
								// print warning? on error?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// what
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: make proper adapter
 | 
				
			||||||
 | 
					struct AudioStreamReFramer {
 | 
				
			||||||
 | 
						FrameStream2I<AudioFrame>* _stream {nullptr};
 | 
				
			||||||
 | 
						uint32_t frame_length_ms {10};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t own_seq_counter {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::vector<int16_t> buffer;
 | 
				
			||||||
 | 
						size_t samples_in_buffer {0}; // absolute, so divide by ch for actual length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t seq {0};
 | 
				
			||||||
 | 
						uint32_t sample_rate {48'000};
 | 
				
			||||||
 | 
						size_t channels {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::optional<AudioFrame> pop(void) {
 | 
				
			||||||
 | 
							assert(_stream != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto new_in = _stream->pop();
 | 
				
			||||||
 | 
							if (new_in.has_value()) {
 | 
				
			||||||
 | 
								auto& new_value = new_in.value();
 | 
				
			||||||
 | 
								assert(new_value.isS16());
 | 
				
			||||||
 | 
								if (!new_value.isS16()) {
 | 
				
			||||||
 | 
									return std::nullopt;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (
 | 
				
			||||||
 | 
									(buffer.empty()) || // buffer not yet inited
 | 
				
			||||||
 | 
									(sample_rate != new_value.sample_rate || channels != new_value.channels) // not the same
 | 
				
			||||||
 | 
								) {
 | 
				
			||||||
 | 
									seq = 0;
 | 
				
			||||||
 | 
									sample_rate = new_value.sample_rate;
 | 
				
			||||||
 | 
									channels = new_value.channels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// buffer does not exist or config changed and we discard
 | 
				
			||||||
 | 
									// preallocate to 2x desired buffer size
 | 
				
			||||||
 | 
									buffer = std::vector<int16_t>(2 * (channels*sample_rate*frame_length_ms)/1000);
 | 
				
			||||||
 | 
									samples_in_buffer = 0;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: this will be very important in the future
 | 
				
			||||||
 | 
								// replace seq with timestapsUS like video??
 | 
				
			||||||
 | 
					#if 0
 | 
				
			||||||
 | 
								// some time / seq comparison shit
 | 
				
			||||||
 | 
								if (seq != 0 && new_value.seq != 0) {
 | 
				
			||||||
 | 
									if (seq+1 != new_value.seq) {
 | 
				
			||||||
 | 
										// we skipped shit
 | 
				
			||||||
 | 
										// TODO: insert silence to pad?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// drop existing
 | 
				
			||||||
 | 
										samples_in_buffer = 0;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// update to latest
 | 
				
			||||||
 | 
								seq = new_value.seq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto new_span = new_value.getSpan<int16_t>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//std::cout << "new incoming frame is " << new_value.getSpan<int16_t>().size/new_value.channels*1000/new_value.sample_rate << "ms\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// now append
 | 
				
			||||||
 | 
								// buffer too small
 | 
				
			||||||
 | 
								if (buffer.size() - samples_in_buffer < new_value.getSpan<int16_t>().size) {
 | 
				
			||||||
 | 
									buffer.resize(buffer.size() + new_value.getSpan<int16_t>().size - (buffer.size() - samples_in_buffer));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: memcpy
 | 
				
			||||||
 | 
								for (size_t i = 0; i < new_span.size; i++) {
 | 
				
			||||||
 | 
									buffer.at(samples_in_buffer+i) = new_span[i];
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								samples_in_buffer += new_span.size;
 | 
				
			||||||
 | 
							} else if (buffer.empty() || samples_in_buffer == 0) {
 | 
				
			||||||
 | 
								// first pop might result in invalid state
 | 
				
			||||||
 | 
								return std::nullopt;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const size_t desired_size {frame_length_ms * sample_rate * channels / 1000};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// > threshold?
 | 
				
			||||||
 | 
							if (samples_in_buffer < desired_size) {
 | 
				
			||||||
 | 
								return std::nullopt;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::vector<int16_t> return_buffer(desired_size);
 | 
				
			||||||
 | 
							// copy data
 | 
				
			||||||
 | 
							for (size_t i = 0; i < return_buffer.size(); i++) {
 | 
				
			||||||
 | 
								return_buffer.at(i) = buffer.at(i);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// now crop buffer (meh)
 | 
				
			||||||
 | 
							// move data from back to front
 | 
				
			||||||
 | 
							for (size_t i = 0; i < samples_in_buffer-return_buffer.size(); i++) {
 | 
				
			||||||
 | 
								buffer.at(i) = buffer.at(desired_size + i);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							samples_in_buffer -= return_buffer.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return AudioFrame{
 | 
				
			||||||
 | 
								own_seq_counter++,
 | 
				
			||||||
 | 
								sample_rate,
 | 
				
			||||||
 | 
								channels,
 | 
				
			||||||
 | 
								std::move(return_buffer),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ToxAVCallAudioSink : public FrameStream2SinkI<AudioFrame> {
 | 
				
			||||||
 | 
						ToxAV& _toxav;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// bitrate for enabled state
 | 
				
			||||||
 | 
						uint32_t _audio_bitrate {32};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t _fid;
 | 
				
			||||||
 | 
						std::shared_ptr<QueuedFrameStream2<AudioFrame>> _writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ToxAVCallAudioSink(ToxAV& toxav, uint32_t fid) : _toxav(toxav), _fid(fid) {}
 | 
				
			||||||
 | 
						~ToxAVCallAudioSink(void) {
 | 
				
			||||||
 | 
							if (_writer) {
 | 
				
			||||||
 | 
								_writer = nullptr;
 | 
				
			||||||
 | 
								_toxav.toxavAudioSetBitRate(_fid, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sink
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<AudioFrame>> 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<QueuedFrameStream2<AudioFrame>>(16, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return _writer;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame>>& 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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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_state);
 | 
				
			||||||
 | 
						_toxav.subscribe(this, ToxAV_Event::friend_audio_bitrate);
 | 
				
			||||||
 | 
						_toxav.subscribe(this, ToxAV_Event::friend_video_bitrate);
 | 
				
			||||||
 | 
						_toxav.subscribe(this, ToxAV_Event::friend_audio_frame);
 | 
				
			||||||
 | 
						_toxav.subscribe(this, ToxAV_Event::friend_video_frame);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DebugToxCall::~DebugToxCall(void) {
 | 
				
			||||||
 | 
						// destroy all calls/connections/sources/sinks here
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto& [fid, call] : _calls) {
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.incoming_vsrc)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.incoming_vsrc);
 | 
				
			||||||
 | 
								call.incoming_vsrc.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.incoming_asrc)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.incoming_asrc);
 | 
				
			||||||
 | 
								call.incoming_asrc.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.outgoing_vsink)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.outgoing_vsink);
 | 
				
			||||||
 | 
								call.outgoing_vsink.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.outgoing_asink)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.outgoing_asink);
 | 
				
			||||||
 | 
								call.outgoing_asink.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DebugToxCall::tick(float) {
 | 
				
			||||||
 | 
						// pump sinks to tox
 | 
				
			||||||
 | 
						// TODO: own thread or direct on push (requires thread save toxcore)
 | 
				
			||||||
 | 
						// TODO: pump at double the frame rate
 | 
				
			||||||
 | 
						for (const auto& [oc, vsink] : _os.registry().view<ToxAVCallVideoSink*>().each()) {
 | 
				
			||||||
 | 
							if (!vsink->_writer) {
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (size_t i = 0; i < 10; i++) {
 | 
				
			||||||
 | 
								auto new_frame_opt = vsink->_writer->pop();
 | 
				
			||||||
 | 
								if (!new_frame_opt.has_value()) {
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!new_frame_opt.value().surface) {
 | 
				
			||||||
 | 
									// wtf?
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// conversion is done in the sinks stream
 | 
				
			||||||
 | 
								SDL_Surface* surf = new_frame_opt.value().surface.get();
 | 
				
			||||||
 | 
								assert(surf != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								SDL_LockSurface(surf);
 | 
				
			||||||
 | 
								_toxav.toxavVideoSendFrame(
 | 
				
			||||||
 | 
									vsink->_fid,
 | 
				
			||||||
 | 
									surf->w, surf->h,
 | 
				
			||||||
 | 
									static_cast<const uint8_t*>(surf->pixels),
 | 
				
			||||||
 | 
									static_cast<const uint8_t*>(surf->pixels) + surf->w * surf->h,
 | 
				
			||||||
 | 
									static_cast<const uint8_t*>(surf->pixels) + surf->w * surf->h + (surf->w/2) * (surf->h/2)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								SDL_UnlockSurface(surf);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const auto& [oc, asink, asrf] : _os.registry().view<ToxAVCallAudioSink*, AudioStreamReFramer>().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();
 | 
				
			||||||
 | 
								assert(new_frame.isS16());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//* @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 = _toxav.toxavAudioSendFrame(
 | 
				
			||||||
 | 
									asink->_fid,
 | 
				
			||||||
 | 
									new_frame.getSpan<int16_t>().ptr,
 | 
				
			||||||
 | 
									new_frame.getSpan<int16_t>().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";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float DebugToxCall::render(void) {
 | 
				
			||||||
 | 
						float next_frame {2.f};
 | 
				
			||||||
 | 
						if (ImGui::Begin("toxav debug")) {
 | 
				
			||||||
 | 
							ImGui::Text("Calls:");
 | 
				
			||||||
 | 
							ImGui::Indent();
 | 
				
			||||||
 | 
							for (auto& [fid, call] : _calls) {
 | 
				
			||||||
 | 
								ImGui::PushID(fid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ImGui::Text("fid:%d state:%d", fid, call.state);
 | 
				
			||||||
 | 
								if (call.incoming) {
 | 
				
			||||||
 | 
									ImGui::SameLine();
 | 
				
			||||||
 | 
									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) {
 | 
				
			||||||
 | 
											call.incoming = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// create sinks
 | 
				
			||||||
 | 
											call.outgoing_vsink = {_os.registry(), _os.registry().create()};
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												auto new_vsink = std::make_unique<ToxAVCallVideoSink>(_toxav, 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>(Components::StreamSink::create<SDLVideoFrame>("ToxAV Friend Call Outgoing Video"));
 | 
				
			||||||
 | 
												_os.throwEventConstruct(call.outgoing_vsink);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											call.outgoing_asink = {_os.registry(), _os.registry().create()};
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												auto new_asink = std::make_unique<ToxAVCallAudioSink>(_toxav, fid);
 | 
				
			||||||
 | 
												call.outgoing_asink.emplace<ToxAVCallAudioSink*>(new_asink.get());
 | 
				
			||||||
 | 
												call.outgoing_asink.emplace<AudioStreamReFramer>().frame_length_ms = 10;
 | 
				
			||||||
 | 
												call.outgoing_asink.emplace<Components::FrameStream2Sink<AudioFrame>>(std::move(new_asink));
 | 
				
			||||||
 | 
												call.outgoing_asink.emplace<Components::StreamSink>(Components::StreamSink::create<AudioFrame>("ToxAV Friend Call Outgoing Audio"));
 | 
				
			||||||
 | 
												_os.throwEventConstruct(call.outgoing_asink);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// 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>(Components::StreamSource::create<SDLVideoFrame>("ToxAV Friend Call Incoming Video"));
 | 
				
			||||||
 | 
													_os.throwEventConstruct(call.incoming_vsrc);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (call.incoming_a) {
 | 
				
			||||||
 | 
												call.incoming_asrc = {_os.registry(), _os.registry().create()};
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													auto new_asrc = std::make_unique<AudioFrameStream2MultiSource>();
 | 
				
			||||||
 | 
													call.incoming_asrc.emplace<AudioFrameStream2MultiSource*>(new_asrc.get());
 | 
				
			||||||
 | 
													call.incoming_asrc.emplace<Components::FrameStream2Source<AudioFrame>>(std::move(new_asrc));
 | 
				
			||||||
 | 
													call.incoming_asrc.emplace<Components::StreamSource>(Components::StreamSource::create<AudioFrame>("ToxAV Friend Call Incoming Audio"));
 | 
				
			||||||
 | 
													_os.throwEventConstruct(call.incoming_asrc);
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if (call.state != TOXAV_FRIEND_CALL_STATE_FINISHED) {
 | 
				
			||||||
 | 
									next_frame = std::min(next_frame, 0.1f);
 | 
				
			||||||
 | 
									ImGui::SameLine();
 | 
				
			||||||
 | 
									if (ImGui::SmallButton("hang up")) {
 | 
				
			||||||
 | 
										const auto ret = _toxav.toxavCallControl(fid, TOXAV_CALL_CONTROL_CANCEL);
 | 
				
			||||||
 | 
										if (ret == TOXAV_ERR_CALL_CONTROL_OK) {
 | 
				
			||||||
 | 
											// 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?
 | 
				
			||||||
 | 
											call.state = TOXAV_FRIEND_CALL_STATE_FINISHED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// TODO: stream manager disconnectAll()
 | 
				
			||||||
 | 
											if (static_cast<bool>(call.incoming_vsrc)) {
 | 
				
			||||||
 | 
												_os.throwEventDestroy(call.incoming_vsrc);
 | 
				
			||||||
 | 
												call.incoming_vsrc.destroy();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (static_cast<bool>(call.incoming_asrc)) {
 | 
				
			||||||
 | 
												_os.throwEventDestroy(call.incoming_asrc);
 | 
				
			||||||
 | 
												call.incoming_asrc.destroy();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (static_cast<bool>(call.outgoing_vsink)) {
 | 
				
			||||||
 | 
												_os.throwEventDestroy(call.outgoing_vsink);
 | 
				
			||||||
 | 
												call.outgoing_vsink.destroy();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (static_cast<bool>(call.outgoing_asink)) {
 | 
				
			||||||
 | 
												_os.throwEventDestroy(call.outgoing_asink);
 | 
				
			||||||
 | 
												call.outgoing_asink.destroy();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									//if (ImGui::BeginCombo("audio src", "---")) {
 | 
				
			||||||
 | 
									//    ImGui::EndCombo();
 | 
				
			||||||
 | 
									//}
 | 
				
			||||||
 | 
									//if (ImGui::BeginCombo("video src", "---")) {
 | 
				
			||||||
 | 
									//    ImGui::EndCombo();
 | 
				
			||||||
 | 
									//}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ImGui::PopID();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ImGui::Unindent();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ImGui::End();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return next_frame;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendCall& e)  {
 | 
				
			||||||
 | 
						auto& call = _calls[e.friend_number];
 | 
				
			||||||
 | 
						call.incoming = true;
 | 
				
			||||||
 | 
						call.incoming_a = e.audio_enabled;
 | 
				
			||||||
 | 
						call.incoming_v = e.video_enabled;
 | 
				
			||||||
 | 
						call.state = TOXAV_FRIEND_CALL_STATE_NONE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendCallState& e)  {
 | 
				
			||||||
 | 
						auto& call = _calls[e.friend_number];
 | 
				
			||||||
 | 
						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.incoming_vsrc)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.incoming_vsrc);
 | 
				
			||||||
 | 
								call.incoming_vsrc.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.incoming_asrc)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.incoming_asrc);
 | 
				
			||||||
 | 
								call.incoming_asrc.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.outgoing_vsink)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.outgoing_vsink);
 | 
				
			||||||
 | 
								call.outgoing_vsink.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (static_cast<bool>(call.outgoing_asink)) {
 | 
				
			||||||
 | 
								_os.throwEventDestroy(call.outgoing_asink);
 | 
				
			||||||
 | 
								call.outgoing_asink.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendAudioBitrate&)  {
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendVideoBitrate&)  {
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendAudioFrame& e)  {
 | 
				
			||||||
 | 
						auto& call = _calls[e.friend_number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!static_cast<bool>(call.incoming_asrc)) {
 | 
				
			||||||
 | 
							// missing src to put frame into ??
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert(call.incoming_asrc.all_of<AudioFrameStream2MultiSource*>());
 | 
				
			||||||
 | 
						assert(call.incoming_asrc.all_of<Components::FrameStream2Source<AudioFrame>>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call.num_a_frames++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call.incoming_asrc.get<AudioFrameStream2MultiSource*>()->push(AudioFrame{
 | 
				
			||||||
 | 
							0, //seq
 | 
				
			||||||
 | 
							e.sampling_rate,
 | 
				
			||||||
 | 
							e.channels,
 | 
				
			||||||
 | 
							std::vector<int16_t>(e.pcm.begin(), e.pcm.end()) // copy
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DebugToxCall::onEvent(const Events::FriendVideoFrame& e)  {
 | 
				
			||||||
 | 
						// TODO: skip if we dont know about this call
 | 
				
			||||||
 | 
						auto& call = _calls[e.friend_number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!static_cast<bool>(call.incoming_vsrc)) {
 | 
				
			||||||
 | 
							// missing src to put frame into ??
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
						assert(new_surf);
 | 
				
			||||||
 | 
						if (SDL_LockSurface(new_surf)) {
 | 
				
			||||||
 | 
							// copy the data
 | 
				
			||||||
 | 
							// we know how the implementation works, its y u v consecutivlely
 | 
				
			||||||
 | 
							// y
 | 
				
			||||||
 | 
							for (size_t y = 0; y < e.height; y++) {
 | 
				
			||||||
 | 
								std::memcpy(
 | 
				
			||||||
 | 
									//static_cast<uint8_t*>(new_surf->pixels) + new_surf->pitch*y,
 | 
				
			||||||
 | 
									static_cast<uint8_t*>(new_surf->pixels) + e.width*y,
 | 
				
			||||||
 | 
									e.y.ptr + e.ystride*y,
 | 
				
			||||||
 | 
									e.width
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// u
 | 
				
			||||||
 | 
							for (size_t y = 0; y < e.height/2; y++) {
 | 
				
			||||||
 | 
								std::memcpy(
 | 
				
			||||||
 | 
									static_cast<uint8_t*>(new_surf->pixels) + (e.width*e.height) + (e.width/2)*y,
 | 
				
			||||||
 | 
									e.u.ptr + e.ustride*y,
 | 
				
			||||||
 | 
									e.width/2
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// v
 | 
				
			||||||
 | 
							for (size_t y = 0; y < e.height/2; y++) {
 | 
				
			||||||
 | 
								std::memcpy(
 | 
				
			||||||
 | 
									static_cast<uint8_t*>(new_surf->pixels) + (e.width*e.height) + ((e.width/2)*(e.height/2)) + (e.width/2)*y,
 | 
				
			||||||
 | 
									e.v.ptr + e.vstride*y,
 | 
				
			||||||
 | 
									e.width/2
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SDL_UnlockSurface(new_surf);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call.incoming_vsrc.get<SDLVideoFrameStream2MultiSource*>()->push({
 | 
				
			||||||
 | 
							// ms -> us
 | 
				
			||||||
 | 
							Message::getTimeMS() * 1000, // TODO: make more precise
 | 
				
			||||||
 | 
							new_surf
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SDL_DestroySurface(new_surf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/debug_tox_call.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/debug_tox_call.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#include <solanaceae/object_store/fwd.hpp>
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/object_store.hpp>
 | 
				
			||||||
 | 
					#include "./tox_av.hpp"
 | 
				
			||||||
 | 
					#include "./texture_uploader.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DebugToxCall : public ToxAVEventI {
 | 
				
			||||||
 | 
						ObjectStore2& _os;
 | 
				
			||||||
 | 
						ToxAV& _toxav;
 | 
				
			||||||
 | 
						TextureUploaderI& _tu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct Call {
 | 
				
			||||||
 | 
							bool incoming {false};
 | 
				
			||||||
 | 
							bool incoming_a {false};
 | 
				
			||||||
 | 
							bool incoming_v {false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint32_t state {0}; // ? just last state ?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint32_t incomming_abr {0};
 | 
				
			||||||
 | 
							uint32_t incomming_vbr {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							size_t num_a_frames {0};
 | 
				
			||||||
 | 
							size_t num_v_frames {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ObjectHandle incoming_vsrc;
 | 
				
			||||||
 | 
							ObjectHandle incoming_asrc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ObjectHandle outgoing_vsink;
 | 
				
			||||||
 | 
							ObjectHandle outgoing_asink;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						// tox friend id -> call
 | 
				
			||||||
 | 
						std::map<uint32_t, Call> _calls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							DebugToxCall(ObjectStore2& os, ToxAV& toxav, TextureUploaderI& tu);
 | 
				
			||||||
 | 
							~DebugToxCall(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void tick(float time_delta);
 | 
				
			||||||
 | 
							float render(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										210
									
								
								src/debug_video_tap.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/debug_video_tap.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					#include "./debug_video_tap.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/object_store.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <entt/entity/entity.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <imgui/imgui.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./content/sdl_video_frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct DebugVideoTapSink : public FrameStream2SinkI<SDLVideoFrame> {
 | 
				
			||||||
 | 
						std::shared_ptr<QueuedFrameStream2<SDLVideoFrame>> _writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DebugVideoTapSink(void) {}
 | 
				
			||||||
 | 
						~DebugVideoTapSink(void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sink
 | 
				
			||||||
 | 
						std::shared_ptr<FrameStream2I<SDLVideoFrame>> subscribe(void) override {
 | 
				
			||||||
 | 
							if (_writer) {
 | 
				
			||||||
 | 
								// max 1 (exclusive)
 | 
				
			||||||
 | 
								return nullptr;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_writer = std::make_shared<QueuedFrameStream2<SDLVideoFrame>>(1, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return _writer;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool unsubscribe(const std::shared_ptr<FrameStream2I<SDLVideoFrame>>& sub) override {
 | 
				
			||||||
 | 
							if (!sub || !_writer) {
 | 
				
			||||||
 | 
								// nah
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sub == _writer) {
 | 
				
			||||||
 | 
								_writer =  nullptr;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// what
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DebugVideoTap::DebugVideoTap(ObjectStore2& os, StreamManager& sm, TextureUploaderI& tu) : _os(os), _sm(sm), _tu(tu) {
 | 
				
			||||||
 | 
						// post self as video sink
 | 
				
			||||||
 | 
						_tap = {_os.registry(), _os.registry().create()};
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							auto dvts = std::make_unique<DebugVideoTapSink>();
 | 
				
			||||||
 | 
							_tap.emplace<DebugVideoTapSink*>(dvts.get()); // to get our data back
 | 
				
			||||||
 | 
							_tap.emplace<Components::FrameStream2Sink<SDLVideoFrame>>(
 | 
				
			||||||
 | 
								std::move(dvts)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_tap.emplace<Components::StreamSink>(Components::StreamSink::create<SDLVideoFrame>("DebugVideoTap"));
 | 
				
			||||||
 | 
						} catch (...) {
 | 
				
			||||||
 | 
							_os.registry().destroy(_tap);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DebugVideoTap::~DebugVideoTap(void) {
 | 
				
			||||||
 | 
						if (static_cast<bool>(_tap)) {
 | 
				
			||||||
 | 
							_os.registry().destroy(_tap);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float DebugVideoTap::render(void) {
 | 
				
			||||||
 | 
						if (ImGui::Begin("DebugVideoTap")) {
 | 
				
			||||||
 | 
							{ // first pull the latest img from sink and update the texture
 | 
				
			||||||
 | 
								assert(static_cast<bool>(_tap));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto& dvtsw = _tap.get<DebugVideoTapSink*>()->_writer;
 | 
				
			||||||
 | 
								if (dvtsw) {
 | 
				
			||||||
 | 
									while (true) {
 | 
				
			||||||
 | 
										auto new_frame_opt = dvtsw->pop();
 | 
				
			||||||
 | 
										if (new_frame_opt.has_value()) {
 | 
				
			||||||
 | 
											// timing
 | 
				
			||||||
 | 
											if (_v_last_ts == 0) {
 | 
				
			||||||
 | 
												_v_last_ts = new_frame_opt.value().timestampUS;
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												auto delta = int64_t(new_frame_opt.value().timestampUS) - int64_t(_v_last_ts);
 | 
				
			||||||
 | 
												_v_last_ts = new_frame_opt.value().timestampUS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												//delta = std::min<int64_t>(delta, 10*1000*1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												if (_v_interval_avg == 0) {
 | 
				
			||||||
 | 
													_v_interval_avg = delta/1'000'000.f;
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													const float r = 0.2f;
 | 
				
			||||||
 | 
													_v_interval_avg = _v_interval_avg * (1.f-r) + (delta/1'000'000.f) * r;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											SDL_Surface* new_frame_surf = new_frame_opt.value().surface.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											SDL_Surface* converted_surf = new_frame_surf;
 | 
				
			||||||
 | 
											if (new_frame_surf->format != SDL_PIXELFORMAT_RGBA32) {
 | 
				
			||||||
 | 
												// we need to convert
 | 
				
			||||||
 | 
												//std::cerr << "DVT: need to convert\n";
 | 
				
			||||||
 | 
												converted_surf = SDL_ConvertSurfaceAndColorspace(new_frame_surf, SDL_PIXELFORMAT_RGBA32, nullptr, SDL_COLORSPACE_RGB_DEFAULT, 0);
 | 
				
			||||||
 | 
												assert(converted_surf->format == SDL_PIXELFORMAT_RGBA32);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											SDL_LockSurface(converted_surf);
 | 
				
			||||||
 | 
											if (_tex == 0 || (int)_tex_w != converted_surf->w || (int)_tex_h != converted_surf->h) {
 | 
				
			||||||
 | 
												_tu.destroy(_tex);
 | 
				
			||||||
 | 
												_tex = _tu.uploadRGBA(
 | 
				
			||||||
 | 
													static_cast<const uint8_t*>(converted_surf->pixels),
 | 
				
			||||||
 | 
													converted_surf->w,
 | 
				
			||||||
 | 
													converted_surf->h,
 | 
				
			||||||
 | 
													TextureUploaderI::LINEAR,
 | 
				
			||||||
 | 
													TextureUploaderI::STREAMING
 | 
				
			||||||
 | 
												);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												_tex_w = converted_surf->w;
 | 
				
			||||||
 | 
												_tex_h = converted_surf->h;
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												_tu.updateRGBA(_tex, static_cast<const uint8_t*>(converted_surf->pixels), converted_surf->w * converted_surf->h * 4);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											SDL_UnlockSurface(converted_surf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (new_frame_surf != converted_surf) {
 | 
				
			||||||
 | 
												// clean up temp
 | 
				
			||||||
 | 
												SDL_DestroySurface(converted_surf);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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(entt::to_entity(_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(entt::to_entity(oc))) + " (" + ss.name + ")";
 | 
				
			||||||
 | 
									if (ImGui::Selectable(label.c_str())) {
 | 
				
			||||||
 | 
										switchTo({_os.registry(), oc});
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ImGui::EndCombo();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							//ImGui::SetNextItemWidth(0);
 | 
				
			||||||
 | 
							ImGui::Checkbox("mirror", &_mirror);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// img here
 | 
				
			||||||
 | 
							if (_tex != 0) {
 | 
				
			||||||
 | 
								ImGui::SameLine();
 | 
				
			||||||
 | 
								ImGui::Text("moving avg interval: %f", _v_interval_avg);
 | 
				
			||||||
 | 
								const float img_w = ImGui::GetContentRegionAvail().x;
 | 
				
			||||||
 | 
								ImGui::Image(
 | 
				
			||||||
 | 
									reinterpret_cast<ImTextureID>(_tex),
 | 
				
			||||||
 | 
									ImVec2{img_w, img_w * float(_tex_h)/_tex_w},
 | 
				
			||||||
 | 
									ImVec2{_mirror?1.f:0.f, 0},
 | 
				
			||||||
 | 
									ImVec2{_mirror?0.f:1.f, 1}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ImGui::End();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (_v_interval_avg != 0) {
 | 
				
			||||||
 | 
							return _v_interval_avg;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return 2.f;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DebugVideoTap::switchTo(ObjectHandle o) {
 | 
				
			||||||
 | 
						if (o == _selected_src) {
 | 
				
			||||||
 | 
							std::cerr << "DVT: switch to same ...\n";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_tu.destroy(_tex);
 | 
				
			||||||
 | 
						_tex = 0;
 | 
				
			||||||
 | 
						_v_last_ts = 0;
 | 
				
			||||||
 | 
						_v_interval_avg = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (static_cast<bool>(_selected_src)) {
 | 
				
			||||||
 | 
							_sm.disconnect<SDLVideoFrame>(_selected_src, _tap);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (static_cast<bool>(o) && _sm.connect<SDLVideoFrame>(o, _tap)) {
 | 
				
			||||||
 | 
							_selected_src = o;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							std::cerr << "DVT: cleared video source\n";
 | 
				
			||||||
 | 
							_selected_src = {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/debug_video_tap.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/debug_video_tap.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/fwd.hpp>
 | 
				
			||||||
 | 
					#include "./stream_manager.hpp"
 | 
				
			||||||
 | 
					#include "./texture_uploader.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// provides a sink and a small window displaying a SDLVideoFrame
 | 
				
			||||||
 | 
					class DebugVideoTap {
 | 
				
			||||||
 | 
						ObjectStore2& _os;
 | 
				
			||||||
 | 
						StreamManager& _sm;
 | 
				
			||||||
 | 
						TextureUploaderI& _tu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ObjectHandle _selected_src;
 | 
				
			||||||
 | 
						ObjectHandle _tap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint64_t _tex {0};
 | 
				
			||||||
 | 
						uint32_t _tex_w {0};
 | 
				
			||||||
 | 
						uint32_t _tex_h {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool _mirror {false}; // flip horizontally
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint64_t _v_last_ts {0}; // us
 | 
				
			||||||
 | 
						float _v_interval_avg {0.f}; // s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							DebugVideoTap(ObjectStore2& os, StreamManager& sm, TextureUploaderI& tu);
 | 
				
			||||||
 | 
							~DebugVideoTap(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							float render(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void switchTo(ObjectHandle o);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								src/main.cpp
									
									
									
									
									
								
							@@ -12,6 +12,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "./start_screen.hpp"
 | 
					#include "./start_screen.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./content/sdl_video_frame_stream2.hpp"
 | 
				
			||||||
 | 
					#include "./content/sdl_audio_frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <filesystem>
 | 
					#include <filesystem>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
@@ -35,7 +38,7 @@ int main(int argc, char** argv) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// setup hints
 | 
						// setup hints
 | 
				
			||||||
#ifndef __ANDROID__
 | 
					#ifndef __ANDROID__
 | 
				
			||||||
	if (SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1") != SDL_TRUE) {
 | 
						if (!SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1")) {
 | 
				
			||||||
		std::cerr << "Failed to set '" << SDL_HINT_VIDEO_ALLOW_SCREENSAVER << "' to 1\n";
 | 
							std::cerr << "Failed to set '" << SDL_HINT_VIDEO_ALLOW_SCREENSAVER << "' to 1\n";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -72,10 +75,51 @@ int main(int argc, char** argv) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	std::cout << "SDL Renderer: " << SDL_GetRendererName(renderer.get()) << "\n";
 | 
						std::cout << "SDL Renderer: " << SDL_GetRendererName(renderer.get()) << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// optionally init audio and camera
 | 
				
			||||||
 | 
						if (!SDL_Init(SDL_INIT_AUDIO)) {
 | 
				
			||||||
 | 
							std::cerr << "SDL_Init AUDIO failed (" << SDL_GetError() << ")\n";
 | 
				
			||||||
 | 
						} else if (false) {
 | 
				
			||||||
 | 
							SDLAudioInputDevice aid;
 | 
				
			||||||
 | 
							auto reader = aid.subscribe();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto writer = SDLAudioOutputDeviceDefaultSink{}.subscribe();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (size_t i = 0; i < 200; i++) {
 | 
				
			||||||
 | 
								std::this_thread::sleep_for(std::chrono::milliseconds(10));
 | 
				
			||||||
 | 
								auto new_frame_opt = reader->pop();
 | 
				
			||||||
 | 
								if (new_frame_opt.has_value()) {
 | 
				
			||||||
 | 
									std::cout << "audio frame was seq:" << new_frame_opt.value().seq << " sr:" << new_frame_opt.value().sample_rate << " " << (new_frame_opt.value().isS16()?"S16":"F32") << " l:" << (new_frame_opt.value().isS16()?new_frame_opt.value().getSpan<int16_t>().size:new_frame_opt.value().getSpan<float>().size) << "\n";
 | 
				
			||||||
 | 
									writer->push(new_frame_opt.value());
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									std::cout << "no audio frame\n";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							aid.unsubscribe(reader);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!SDL_Init(SDL_INIT_CAMERA)) {
 | 
				
			||||||
 | 
							std::cerr << "SDL_Init CAMERA failed (" << SDL_GetError() << ")\n";
 | 
				
			||||||
 | 
						} else if (false) { // HACK
 | 
				
			||||||
 | 
							std::cerr << "CAMERA initialized\n";
 | 
				
			||||||
 | 
							SDLVideoCameraContent vcc;
 | 
				
			||||||
 | 
							auto reader = vcc.subscribe();
 | 
				
			||||||
 | 
							for (size_t i = 0; i < 20; i++) {
 | 
				
			||||||
 | 
								std::this_thread::sleep_for(std::chrono::milliseconds(50));
 | 
				
			||||||
 | 
								auto new_frame_opt = reader->pop();
 | 
				
			||||||
 | 
								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().timestampUS << "us " << new_frame_opt.value().surface->format << "sf\n";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							vcc.unsubscribe(reader);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						std::cout << "after sdl video stuffery\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	IMGUI_CHECKVERSION();
 | 
						IMGUI_CHECKVERSION();
 | 
				
			||||||
	ImGui::CreateContext();
 | 
						ImGui::CreateContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: test android behaviour
 | 
						// TODO: test android behaviour
 | 
				
			||||||
 | 
						// -> its too big, dpi does not take eye-screen-distance into account
 | 
				
			||||||
	float display_scale = SDL_GetWindowDisplayScale(window.get());
 | 
						float display_scale = SDL_GetWindowDisplayScale(window.get());
 | 
				
			||||||
	if (display_scale < 0.001f) {
 | 
						if (display_scale < 0.001f) {
 | 
				
			||||||
		// error?
 | 
							// error?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <SDL3/SDL.h>
 | 
					#include <SDL3/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./content/sdl_video_frame_stream2.hpp"
 | 
				
			||||||
 | 
					#include "content/audio_stream.hpp"
 | 
				
			||||||
 | 
					#include "content/sdl_audio_frame_stream2.hpp"
 | 
				
			||||||
 | 
					#include "stream_manager.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <cmath>
 | 
					#include <cmath>
 | 
				
			||||||
#include <string_view>
 | 
					#include <string_view>
 | 
				
			||||||
@@ -19,11 +24,13 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
 | 
				
			|||||||
	rmm(cr),
 | 
						rmm(cr),
 | 
				
			||||||
	msnj{cr, {}, {}},
 | 
						msnj{cr, {}, {}},
 | 
				
			||||||
	mts(rmm),
 | 
						mts(rmm),
 | 
				
			||||||
 | 
						sm(os),
 | 
				
			||||||
	tc(save_path, save_password),
 | 
						tc(save_path, save_password),
 | 
				
			||||||
	tpi(tc.getTox()),
 | 
						tpi(tc.getTox()),
 | 
				
			||||||
	ad(tc),
 | 
						ad(tc),
 | 
				
			||||||
#if TOMATO_TOX_AV
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
	tav(tc.getTox()),
 | 
						tav(tc.getTox()),
 | 
				
			||||||
 | 
						dtc(os, tav, sdlrtu),
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
	tcm(cr, tc, tc),
 | 
						tcm(cr, tc, tc),
 | 
				
			||||||
	tmm(rmm, cr, tcm, tc, tc),
 | 
						tmm(rmm, cr, tcm, tc, tc),
 | 
				
			||||||
@@ -40,6 +47,8 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
 | 
				
			|||||||
	cg(conf, os, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
 | 
						cg(conf, os, rmm, cr, sdlrtu, contact_tc, msg_tc, theme),
 | 
				
			||||||
	sw(conf),
 | 
						sw(conf),
 | 
				
			||||||
	osui(os),
 | 
						osui(os),
 | 
				
			||||||
 | 
						smui(os, sm),
 | 
				
			||||||
 | 
						dvt(os, sm, sdlrtu),
 | 
				
			||||||
	tuiu(tc, conf),
 | 
						tuiu(tc, conf),
 | 
				
			||||||
	tdch(tpi)
 | 
						tdch(tpi)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -136,6 +145,53 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	conf.dump();
 | 
						conf.dump();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ // add system av devices
 | 
				
			||||||
 | 
							if (false) {
 | 
				
			||||||
 | 
								ObjectHandle vsrc {os.registry(), os.registry().create()};
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									vsrc.emplace<Components::FrameStream2Source<SDLVideoFrame>>(
 | 
				
			||||||
 | 
										std::make_unique<SDLVideoCameraContent>()
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									vsrc.emplace<Components::StreamSource>(Components::StreamSource::create<SDLVideoFrame>("WebCam"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									os.throwEventConstruct(vsrc);
 | 
				
			||||||
 | 
								} catch (...) {
 | 
				
			||||||
 | 
									os.registry().destroy(vsrc);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (true) { // audio in
 | 
				
			||||||
 | 
								ObjectHandle asrc {os.registry(), os.registry().create()};
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									asrc.emplace<Components::FrameStream2Source<AudioFrame>>(
 | 
				
			||||||
 | 
										std::make_unique<SDLAudioInputDevice>()
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									asrc.emplace<Components::StreamSource>(Components::StreamSource::create<AudioFrame>("SDL Audio Default Recording Device"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									os.throwEventConstruct(asrc);
 | 
				
			||||||
 | 
								} catch (...) {
 | 
				
			||||||
 | 
									os.registry().destroy(asrc);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{ // audio out
 | 
				
			||||||
 | 
								ObjectHandle asink {os.registry(), os.registry().create()};
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									asink.emplace<Components::FrameStream2Sink<AudioFrame>>(
 | 
				
			||||||
 | 
										std::make_unique<SDLAudioOutputDeviceDefaultSink>()
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									asink.emplace<Components::StreamSink>(Components::StreamSink::create<AudioFrame>("SDL Audio Default Playback Device"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									os.throwEventConstruct(asink);
 | 
				
			||||||
 | 
								} catch (...) {
 | 
				
			||||||
 | 
									os.registry().destroy(asink);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MainScreen::~MainScreen(void) {
 | 
					MainScreen::~MainScreen(void) {
 | 
				
			||||||
@@ -252,14 +308,19 @@ Screen* MainScreen::render(float time_delta, bool&) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
 | 
						// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
 | 
				
			||||||
	// it might unload textures, so it needs to be done before rendering
 | 
						// it might unload textures, so it needs to be done before rendering
 | 
				
			||||||
	const float ctc_interval = contact_tc.update();
 | 
						float animation_interval = contact_tc.update();
 | 
				
			||||||
	const float msgtc_interval = msg_tc.update();
 | 
						animation_interval = std::min<float>(animation_interval, msg_tc.update());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const float cg_interval = cg.render(time_delta); // render
 | 
						const float cg_interval = cg.render(time_delta); // render
 | 
				
			||||||
	sw.render(); // render
 | 
						sw.render(); // render
 | 
				
			||||||
	osui.render();
 | 
						osui.render();
 | 
				
			||||||
 | 
						smui.render();
 | 
				
			||||||
 | 
						animation_interval = std::min<float>(animation_interval, dvt.render());
 | 
				
			||||||
	tuiu.render(); // render
 | 
						tuiu.render(); // render
 | 
				
			||||||
	tdch.render(); // render
 | 
						tdch.render(); // render
 | 
				
			||||||
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
 | 
						animation_interval = std::min<float>(animation_interval, dtc.render());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{ // main window menubar injection
 | 
						{ // main window menubar injection
 | 
				
			||||||
		if (ImGui::Begin("tomato")) {
 | 
							if (ImGui::Begin("tomato")) {
 | 
				
			||||||
@@ -440,8 +501,7 @@ Screen* MainScreen::render(float time_delta, bool&) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// low delay time window
 | 
						// low delay time window
 | 
				
			||||||
	if (!_window_hidden && _time_since_event < curr_profile.low_delay_window) {
 | 
						if (!_window_hidden && _time_since_event < curr_profile.low_delay_window) {
 | 
				
			||||||
		_render_interval = std::min<float>(_render_interval, ctc_interval);
 | 
							_render_interval = std::min<float>(_render_interval, animation_interval);
 | 
				
			||||||
		_render_interval = std::min<float>(_render_interval, msgtc_interval);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_render_interval = std::clamp(
 | 
							_render_interval = std::clamp(
 | 
				
			||||||
			_render_interval,
 | 
								_render_interval,
 | 
				
			||||||
@@ -450,8 +510,7 @@ Screen* MainScreen::render(float time_delta, bool&) {
 | 
				
			|||||||
		);
 | 
							);
 | 
				
			||||||
	// mid delay time window
 | 
						// mid delay time window
 | 
				
			||||||
	} else if (!_window_hidden && _time_since_event < curr_profile.mid_delay_window) {
 | 
						} else if (!_window_hidden && _time_since_event < curr_profile.mid_delay_window) {
 | 
				
			||||||
		_render_interval = std::min<float>(_render_interval, ctc_interval);
 | 
							_render_interval = std::min<float>(_render_interval, animation_interval);
 | 
				
			||||||
		_render_interval = std::min<float>(_render_interval, msgtc_interval);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_render_interval = std::clamp(
 | 
							_render_interval = std::clamp(
 | 
				
			||||||
			_render_interval,
 | 
								_render_interval,
 | 
				
			||||||
@@ -476,8 +535,16 @@ Screen* MainScreen::render(float time_delta, bool&) {
 | 
				
			|||||||
Screen* MainScreen::tick(float time_delta, bool& quit) {
 | 
					Screen* MainScreen::tick(float time_delta, bool& quit) {
 | 
				
			||||||
	quit = !tc.iterate(time_delta); // compute
 | 
						quit = !tc.iterate(time_delta); // compute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
 | 
						tav.toxavIterate();
 | 
				
			||||||
 | 
						const float av_interval = tav.toxavIterationInterval()/1000.f;
 | 
				
			||||||
 | 
						dtc.tick(time_delta);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tcm.iterate(time_delta); // compute
 | 
						tcm.iterate(time_delta); // compute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const float sm_interval = sm.tick(time_delta);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const float fo_interval = tffom.tick(time_delta);
 | 
						const float fo_interval = tffom.tick(time_delta);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tam.iterate(); // compute
 | 
						tam.iterate(); // compute
 | 
				
			||||||
@@ -510,6 +577,18 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
 | 
				
			|||||||
		fo_interval
 | 
							fo_interval
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
 | 
						_min_tick_interval = std::min<float>(
 | 
				
			||||||
 | 
							_min_tick_interval,
 | 
				
			||||||
 | 
							av_interval
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_min_tick_interval = std::min<float>(
 | 
				
			||||||
 | 
							_min_tick_interval,
 | 
				
			||||||
 | 
							sm_interval
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//std::cout << "MS: min tick interval: " << _min_tick_interval << "\n";
 | 
						//std::cout << "MS: min tick interval: " << _min_tick_interval << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (_compute_perf_mode) {
 | 
						switch (_compute_perf_mode) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,8 @@
 | 
				
			|||||||
#include <solanaceae/tox_messages/tox_message_manager.hpp>
 | 
					#include <solanaceae/tox_messages/tox_message_manager.hpp>
 | 
				
			||||||
#include <solanaceae/tox_messages/tox_transfer_manager.hpp>
 | 
					#include <solanaceae/tox_messages/tox_transfer_manager.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./stream_manager.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "./tox_client.hpp"
 | 
					#include "./tox_client.hpp"
 | 
				
			||||||
#include "./auto_dirty.hpp"
 | 
					#include "./auto_dirty.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,12 +32,15 @@
 | 
				
			|||||||
#include "./chat_gui4.hpp"
 | 
					#include "./chat_gui4.hpp"
 | 
				
			||||||
#include "./chat_gui/settings_window.hpp"
 | 
					#include "./chat_gui/settings_window.hpp"
 | 
				
			||||||
#include "./object_store_ui.hpp"
 | 
					#include "./object_store_ui.hpp"
 | 
				
			||||||
 | 
					#include "./stream_manager_ui.hpp"
 | 
				
			||||||
 | 
					#include "./debug_video_tap.hpp"
 | 
				
			||||||
#include "./tox_ui_utils.hpp"
 | 
					#include "./tox_ui_utils.hpp"
 | 
				
			||||||
#include "./tox_dht_cap_histo.hpp"
 | 
					#include "./tox_dht_cap_histo.hpp"
 | 
				
			||||||
#include "./tox_friend_faux_offline_messaging.hpp"
 | 
					#include "./tox_friend_faux_offline_messaging.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if TOMATO_TOX_AV
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
#include "./tox_av.hpp"
 | 
					#include "./tox_av.hpp"
 | 
				
			||||||
 | 
					#include "./debug_tox_call.hpp"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
@@ -58,12 +63,15 @@ struct MainScreen final : public Screen {
 | 
				
			|||||||
	MessageSerializerNJ msnj;
 | 
						MessageSerializerNJ msnj;
 | 
				
			||||||
	MessageTimeSort mts;
 | 
						MessageTimeSort mts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						StreamManager sm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ToxEventLogger tel{std::cout};
 | 
						ToxEventLogger tel{std::cout};
 | 
				
			||||||
	ToxClient tc;
 | 
						ToxClient tc;
 | 
				
			||||||
	ToxPrivateImpl tpi;
 | 
						ToxPrivateImpl tpi;
 | 
				
			||||||
	AutoDirty ad;
 | 
						AutoDirty ad;
 | 
				
			||||||
#if TOMATO_TOX_AV
 | 
					#if TOMATO_TOX_AV
 | 
				
			||||||
	ToxAV tav;
 | 
						ToxAV tav;
 | 
				
			||||||
 | 
						DebugToxCall dtc;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
	ToxContactModel2 tcm;
 | 
						ToxContactModel2 tcm;
 | 
				
			||||||
	ToxMessageManager tmm;
 | 
						ToxMessageManager tmm;
 | 
				
			||||||
@@ -86,6 +94,8 @@ struct MainScreen final : public Screen {
 | 
				
			|||||||
	ChatGui4 cg;
 | 
						ChatGui4 cg;
 | 
				
			||||||
	SettingsWindow sw;
 | 
						SettingsWindow sw;
 | 
				
			||||||
	ObjectStoreUI osui;
 | 
						ObjectStoreUI osui;
 | 
				
			||||||
 | 
						StreamManagerUI smui;
 | 
				
			||||||
 | 
						DebugVideoTap dvt;
 | 
				
			||||||
	ToxUIUtils tuiu;
 | 
						ToxUIUtils tuiu;
 | 
				
			||||||
	ToxDHTCapHisto tdch;
 | 
						ToxDHTCapHisto tdch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ uint64_t SDLRendererTextureUploader::uploadRGBA(const uint8_t* data, uint32_t wi
 | 
				
			|||||||
	SDL_UpdateTexture(tex, nullptr, surf->pixels, surf->pitch);
 | 
						SDL_UpdateTexture(tex, nullptr, surf->pixels, surf->pitch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	SDL_BlendMode surf_blend_mode = SDL_BLENDMODE_NONE;
 | 
						SDL_BlendMode surf_blend_mode = SDL_BLENDMODE_NONE;
 | 
				
			||||||
	if (SDL_GetSurfaceBlendMode(surf, &surf_blend_mode) == 0) {
 | 
						if (SDL_GetSurfaceBlendMode(surf, &surf_blend_mode)) {
 | 
				
			||||||
		SDL_SetTextureBlendMode(tex, surf_blend_mode);
 | 
							SDL_SetTextureBlendMode(tex, surf_blend_mode);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										320
									
								
								src/stream_manager.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								src/stream_manager.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,320 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/fwd.hpp>
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/object_store.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <entt/core/type_info.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "./content/frame_stream2.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fwd
 | 
				
			||||||
 | 
					class StreamManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Components {
 | 
				
			||||||
 | 
						struct StreamSource {
 | 
				
			||||||
 | 
							std::string name;
 | 
				
			||||||
 | 
							std::string frame_type_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::function<bool(StreamManager&, Object, Object, bool)> connect_fn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							template<typename FrameType>
 | 
				
			||||||
 | 
							static StreamSource create(const std::string& name);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct StreamSink {
 | 
				
			||||||
 | 
							std::string name;
 | 
				
			||||||
 | 
							std::string frame_type_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							template<typename FrameType>
 | 
				
			||||||
 | 
							static StreamSink create(const std::string& name);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template<typename FrameType>
 | 
				
			||||||
 | 
						using FrameStream2Source = std::unique_ptr<FrameStream2SourceI<FrameType>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template<typename FrameType>
 | 
				
			||||||
 | 
						using FrameStream2Sink = std::unique_ptr<FrameStream2SinkI<FrameType>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // Components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StreamManager {
 | 
				
			||||||
 | 
						friend class StreamManagerUI; // TODO: make this go away
 | 
				
			||||||
 | 
						ObjectStore2& _os;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct Connection {
 | 
				
			||||||
 | 
							ObjectHandle src;
 | 
				
			||||||
 | 
							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&)> unsubscribe_fn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool on_main_thread {true};
 | 
				
			||||||
 | 
							std::atomic_bool stop {false}; // disconnect
 | 
				
			||||||
 | 
							std::atomic_bool finished {false}; // disconnect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// pump thread
 | 
				
			||||||
 | 
							std::thread pump_thread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// frame interval counters and estimates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Connection(void) = default;
 | 
				
			||||||
 | 
							Connection(
 | 
				
			||||||
 | 
								ObjectHandle src_,
 | 
				
			||||||
 | 
								ObjectHandle sink_,
 | 
				
			||||||
 | 
								std::unique_ptr<Data>&& data_,
 | 
				
			||||||
 | 
								std::function<void(Connection&)>&& pump_fn_,
 | 
				
			||||||
 | 
								std::function<void(Connection&)>&& unsubscribe_fn_,
 | 
				
			||||||
 | 
								bool on_main_thread_ = true
 | 
				
			||||||
 | 
							) :
 | 
				
			||||||
 | 
								src(src_),
 | 
				
			||||||
 | 
								sink(sink_),
 | 
				
			||||||
 | 
								data(std::move(data_)),
 | 
				
			||||||
 | 
								pump_fn(std::move(pump_fn_)),
 | 
				
			||||||
 | 
								unsubscribe_fn(std::move(unsubscribe_fn_)),
 | 
				
			||||||
 | 
								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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							StreamManager(ObjectStore2& os) : _os(os) {}
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// stream type is FrameStream2I<FrameType>
 | 
				
			||||||
 | 
							// TODO: improve this design
 | 
				
			||||||
 | 
							// src and sink need to be a FrameStream2MultiStream<FrameType>
 | 
				
			||||||
 | 
							template<typename FrameType>
 | 
				
			||||||
 | 
							bool connect(Object src, Object sink, bool threaded = true) {
 | 
				
			||||||
 | 
								auto res = std::find_if(
 | 
				
			||||||
 | 
									_connections.cbegin(), _connections.cend(),
 | 
				
			||||||
 | 
									[&](const auto& a) { return a->src == src && a->sink == sink; }
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								if (res != _connections.cend()) {
 | 
				
			||||||
 | 
									// already exists
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto h_src = _os.objectHandle(src);
 | 
				
			||||||
 | 
								auto h_sink = _os.objectHandle(sink);
 | 
				
			||||||
 | 
								if (!static_cast<bool>(h_src) || !static_cast<bool>(h_sink)) {
 | 
				
			||||||
 | 
									// an object does not exist
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!h_src.all_of<Components::FrameStream2Source<FrameType>>()) {
 | 
				
			||||||
 | 
									// src not stream source
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!h_sink.all_of<Components::FrameStream2Sink<FrameType>>()) {
 | 
				
			||||||
 | 
									// sink not stream sink
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto& src_stream = h_src.get<Components::FrameStream2Source<FrameType>>();
 | 
				
			||||||
 | 
								auto& sink_stream = h_sink.get<Components::FrameStream2Sink<FrameType>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								struct inlineData : public Connection::Data {
 | 
				
			||||||
 | 
									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;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								our_data->writer = sink_stream->subscribe();
 | 
				
			||||||
 | 
								if (!our_data->writer) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_connections.push_back(std::make_unique<Connection>(
 | 
				
			||||||
 | 
									h_src,
 | 
				
			||||||
 | 
									h_sink,
 | 
				
			||||||
 | 
									std::move(our_data),
 | 
				
			||||||
 | 
									[](Connection& con) -> void {
 | 
				
			||||||
 | 
										// there might be more stored
 | 
				
			||||||
 | 
										for (size_t i = 0; i < 10; i++) {
 | 
				
			||||||
 | 
											auto new_frame_opt = static_cast<inlineData*>(con.data.get())->reader->pop();
 | 
				
			||||||
 | 
											// TODO: frame interval estimates
 | 
				
			||||||
 | 
											if (new_frame_opt.has_value()) {
 | 
				
			||||||
 | 
												static_cast<inlineData*>(con.data.get())->writer->push(new_frame_opt.value());
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									[](Connection& con) -> void {
 | 
				
			||||||
 | 
										auto* src_stream_ptr = con.src.try_get<Components::FrameStream2Source<FrameType>>();
 | 
				
			||||||
 | 
										if (src_stream_ptr != nullptr) {
 | 
				
			||||||
 | 
											(*src_stream_ptr)->unsubscribe(static_cast<inlineData*>(con.data.get())->reader);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										auto* sink_stream_ptr = con.sink.try_get<Components::FrameStream2Sink<FrameType>>();
 | 
				
			||||||
 | 
										if (sink_stream_ptr != nullptr) {
 | 
				
			||||||
 | 
											(*sink_stream_ptr)->unsubscribe(static_cast<inlineData*>(con.data.get())->writer);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									!threaded
 | 
				
			||||||
 | 
								));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool connect(Object src, Object sink, bool threaded = true) {
 | 
				
			||||||
 | 
								auto h_src = _os.objectHandle(src);
 | 
				
			||||||
 | 
								auto h_sink = _os.objectHandle(sink);
 | 
				
			||||||
 | 
								if (!static_cast<bool>(h_src) || !static_cast<bool>(h_sink)) {
 | 
				
			||||||
 | 
									// an object does not exist
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// get src and sink comps
 | 
				
			||||||
 | 
								if (!h_src.all_of<Components::StreamSource>()) {
 | 
				
			||||||
 | 
									// src not stream source
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!h_sink.all_of<Components::StreamSink>()) {
 | 
				
			||||||
 | 
									// sink not stream sink
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const auto& ssrc = h_src.get<Components::StreamSource>();
 | 
				
			||||||
 | 
								const auto& ssink = h_sink.get<Components::StreamSink>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// compare type
 | 
				
			||||||
 | 
								if (ssrc.frame_type_name != ssink.frame_type_name) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// always fail in debug mode
 | 
				
			||||||
 | 
								assert(static_cast<bool>(ssrc.connect_fn));
 | 
				
			||||||
 | 
								if (!static_cast<bool>(ssrc.connect_fn)) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// use connect fn from src
 | 
				
			||||||
 | 
								return ssrc.connect_fn(*this, src, sink, threaded);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							template<typename StreamType>
 | 
				
			||||||
 | 
							bool disconnect(Object src, Object sink) {
 | 
				
			||||||
 | 
								auto res = std::find_if(
 | 
				
			||||||
 | 
									_connections.cbegin(), _connections.cend(),
 | 
				
			||||||
 | 
									[&](const auto& a) { return a->src == src && a->sink == sink; }
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								if (res == _connections.cend()) {
 | 
				
			||||||
 | 
									// not found
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// do disconnect
 | 
				
			||||||
 | 
								(*res)->stop = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							template<typename StreamType>
 | 
				
			||||||
 | 
							bool disconnectAll(Object o) {
 | 
				
			||||||
 | 
								bool succ {false};
 | 
				
			||||||
 | 
								for (const auto& con : _connections) {
 | 
				
			||||||
 | 
									if (con->src == o || con->sink == o) {
 | 
				
			||||||
 | 
										con->stop = true;
 | 
				
			||||||
 | 
										succ = true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return succ;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// do we need the time delta?
 | 
				
			||||||
 | 
							float tick(float) {
 | 
				
			||||||
 | 
								// pump all mainthread connections
 | 
				
			||||||
 | 
								for (auto it = _connections.begin(); it != _connections.end();) {
 | 
				
			||||||
 | 
									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) {
 | 
				
			||||||
 | 
										con.pump_fn(con);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										it++;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// return min over intervals instead
 | 
				
			||||||
 | 
								return 0.01f;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Components {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// we require the complete sm type here
 | 
				
			||||||
 | 
						template<typename FrameType>
 | 
				
			||||||
 | 
						StreamSource StreamSource::create(const std::string& name) {
 | 
				
			||||||
 | 
							return StreamSource{
 | 
				
			||||||
 | 
								name,
 | 
				
			||||||
 | 
								std::string{entt::type_name<FrameType>::value()},
 | 
				
			||||||
 | 
								+[](StreamManager& sm, Object src, Object sink, bool threaded) {
 | 
				
			||||||
 | 
									return sm.connect<FrameType>(src, sink, threaded);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template<typename FrameType>
 | 
				
			||||||
 | 
						StreamSink StreamSink::create(const std::string& name) {
 | 
				
			||||||
 | 
							return StreamSink{
 | 
				
			||||||
 | 
								name,
 | 
				
			||||||
 | 
								std::string{entt::type_name<FrameType>::value()},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // Components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										222
									
								
								src/stream_manager_ui.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/stream_manager_ui.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					#include "./stream_manager_ui.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/object_store.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <imgui/imgui.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					StreamManagerUI::StreamManagerUI(ObjectStore2& os, StreamManager& sm) : _os(os), _sm(sm) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void StreamManagerUI::render(void) {
 | 
				
			||||||
 | 
						{ // main window menubar injection
 | 
				
			||||||
 | 
							// assumes the window "tomato" was rendered already by cg
 | 
				
			||||||
 | 
							if (ImGui::Begin("tomato")) {
 | 
				
			||||||
 | 
								if (ImGui::BeginMenuBar()) {
 | 
				
			||||||
 | 
									// TODO: drop all menu sep?
 | 
				
			||||||
 | 
									//ImGui::Separator(); // os already exists (very hacky)
 | 
				
			||||||
 | 
									if (ImGui::BeginMenu("ObjectStore")) {
 | 
				
			||||||
 | 
										if (ImGui::MenuItem("Stream Manger", nullptr, _show_window)) {
 | 
				
			||||||
 | 
											_show_window = !_show_window;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										ImGui::EndMenu();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ImGui::EndMenuBar();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ImGui::End();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!_show_window) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ImGui::Begin("StreamManagerUI", &_show_window)) {
 | 
				
			||||||
 | 
							// TODO: node canvas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// by fametype ??
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ImGui::CollapsingHeader("Sources", ImGuiTreeNodeFlags_DefaultOpen)) {
 | 
				
			||||||
 | 
								// list sources
 | 
				
			||||||
 | 
								if (ImGui::BeginTable("sources_and_sinks", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV)) {
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("id");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("name");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("##conn");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("type");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ImGui::TableHeadersRow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (const auto& [oc, ss] : _os.registry().view<Components::StreamSource>().each()) {
 | 
				
			||||||
 | 
										//ImGui::Text("src  %d (%s)[%s]", entt::to_integral(entt::to_entity(oc)), ss.name.c_str(), ss.frame_type_name.c_str());
 | 
				
			||||||
 | 
										ImGui::PushID(entt::to_integral(oc));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::Text("%d", entt::to_integral(entt::to_entity(oc)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const auto *ssrc = _os.registry().try_get<Components::StreamSource>(oc);
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssrc!=nullptr?ssrc->name.c_str():"none");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										if (ImGui::SmallButton("->")) {
 | 
				
			||||||
 | 
											ImGui::OpenPopup("src_connect");
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if (ImGui::BeginPopup("src_connect")) {
 | 
				
			||||||
 | 
											if (ImGui::BeginMenu("connect to")) {
 | 
				
			||||||
 | 
												for (const auto& [oc_sink, s_sink] : _os.registry().view<Components::StreamSink>().each()) {
 | 
				
			||||||
 | 
													if (s_sink.frame_type_name != ss.frame_type_name) {
 | 
				
			||||||
 | 
														continue;
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													ImGui::PushID(entt::to_integral(oc_sink));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													std::string sink_label {"src "};
 | 
				
			||||||
 | 
													sink_label += std::to_string(entt::to_integral(entt::to_entity(oc_sink)));
 | 
				
			||||||
 | 
													sink_label += " (";
 | 
				
			||||||
 | 
													sink_label += s_sink.name;
 | 
				
			||||||
 | 
													sink_label += ")[";
 | 
				
			||||||
 | 
													sink_label += s_sink.frame_type_name;
 | 
				
			||||||
 | 
													sink_label += "]";
 | 
				
			||||||
 | 
													if (ImGui::MenuItem(sink_label.c_str())) {
 | 
				
			||||||
 | 
														_sm.connect(oc, oc_sink);
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													ImGui::PopID();
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												ImGui::EndMenu();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											ImGui::EndPopup();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssrc!=nullptr?ssrc->frame_type_name.c_str():"???");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::PopID();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ImGui::EndTable();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} // sources header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ImGui::CollapsingHeader("Sinks", ImGuiTreeNodeFlags_DefaultOpen)) {
 | 
				
			||||||
 | 
								// list sinks
 | 
				
			||||||
 | 
								if (ImGui::BeginTable("sources_and_sinks", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV)) {
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("id");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("name");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("##conn");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("type");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ImGui::TableHeadersRow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (const auto& [oc, ss] : _os.registry().view<Components::StreamSink>().each()) {
 | 
				
			||||||
 | 
										ImGui::PushID(entt::to_integral(oc));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::Text("%d", entt::to_integral(entt::to_entity(oc)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const auto *ssink = _os.registry().try_get<Components::StreamSink>(oc);
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssink!=nullptr?ssink->name.c_str():"none");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										if (ImGui::SmallButton("->")) {
 | 
				
			||||||
 | 
											ImGui::OpenPopup("sink_connect");
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings
 | 
				
			||||||
 | 
										if (ImGui::BeginPopup("sink_connect")) {
 | 
				
			||||||
 | 
											if (ImGui::BeginMenu("connect to")) {
 | 
				
			||||||
 | 
												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(oc_src, oc);
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													ImGui::PopID();
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												ImGui::EndMenu();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											ImGui::EndPopup();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssink!=nullptr?ssink->frame_type_name.c_str():"???");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::PopID();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ImGui::EndTable();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} // sink header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ImGui::CollapsingHeader("Connections", ImGuiTreeNodeFlags_DefaultOpen)) {
 | 
				
			||||||
 | 
								// list connections
 | 
				
			||||||
 | 
								if (ImGui::BeginTable("connections", 6, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV)) {
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("##id"); // TODO: remove?
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("##disco");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("##qdesc");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("from");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("to");
 | 
				
			||||||
 | 
									ImGui::TableSetupColumn("type");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ImGui::TableHeadersRow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (size_t i = 0; i < _sm._connections.size(); i++) {
 | 
				
			||||||
 | 
										const auto& con = _sm._connections[i];
 | 
				
			||||||
 | 
										//ImGui::Text("con %d->%d", entt::to_integral(entt::to_entity(con->src.entity())), entt::to_integral(entt::to_entity(con->sink.entity())));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::PushID(i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::Text("%zu", i); // do connections have ids?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										if (ImGui::SmallButton("X")) {
 | 
				
			||||||
 | 
											con->stop = true;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::Text("%d->%d", entt::to_integral(entt::to_entity(con->src.entity())), entt::to_integral(entt::to_entity(con->sink.entity())));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const auto *ssrc = con->src.try_get<Components::StreamSource>();
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssrc!=nullptr?ssrc->name.c_str():"none");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const auto *ssink = con->sink.try_get<Components::StreamSink>();
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(ssink!=nullptr?ssink->name.c_str():"none");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::TableNextColumn();
 | 
				
			||||||
 | 
										ImGui::TextUnformatted(
 | 
				
			||||||
 | 
											(ssrc!=nullptr)?
 | 
				
			||||||
 | 
												ssrc->frame_type_name.c_str():
 | 
				
			||||||
 | 
												(ssink!=nullptr)?
 | 
				
			||||||
 | 
													ssink->frame_type_name.c_str()
 | 
				
			||||||
 | 
													:"???"
 | 
				
			||||||
 | 
										);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										ImGui::PopID();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ImGui::EndTable();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} // con header
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ImGui::End();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/stream_manager_ui.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/stream_manager_ui.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/object_store/fwd.hpp>
 | 
				
			||||||
 | 
					#include "./stream_manager.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StreamManagerUI {
 | 
				
			||||||
 | 
						ObjectStore2& _os;
 | 
				
			||||||
 | 
						StreamManager& _sm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool _show_window {true};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							StreamManagerUI(ObjectStore2& os, StreamManager& sm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void render(void);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										169
									
								
								src/tox_av.cpp
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								src/tox_av.cpp
									
									
									
									
									
								
							@@ -2,14 +2,85 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <cassert>
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://almogfx.bandcamp.com/track/crushed-w-cassade
 | 
					// 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) {
 | 
					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
 | 
				
			||||||
	assert(err_new == TOXAV_ERR_NEW_OK);
 | 
						assert(err_new == TOXAV_ERR_NEW_OK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						toxav_callback_call(
 | 
				
			||||||
 | 
							_tox_av,
 | 
				
			||||||
 | 
							+[](ToxAV*, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) {
 | 
				
			||||||
 | 
								assert(user_data != nullptr);
 | 
				
			||||||
 | 
								static_cast<ToxAV*>(user_data)->cb_call(friend_number, audio_enabled, video_enabled);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						toxav_callback_call_state(
 | 
				
			||||||
 | 
							_tox_av,
 | 
				
			||||||
 | 
							+[](ToxAV*, uint32_t friend_number, uint32_t state, void *user_data) {
 | 
				
			||||||
 | 
								assert(user_data != nullptr);
 | 
				
			||||||
 | 
								static_cast<ToxAV*>(user_data)->cb_call_state(friend_number, state);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						toxav_callback_audio_bit_rate(
 | 
				
			||||||
 | 
							_tox_av,
 | 
				
			||||||
 | 
							+[](ToxAV*, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data) {
 | 
				
			||||||
 | 
								assert(user_data != nullptr);
 | 
				
			||||||
 | 
								static_cast<ToxAV*>(user_data)->cb_audio_bit_rate(friend_number, audio_bit_rate);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						toxav_callback_video_bit_rate(
 | 
				
			||||||
 | 
							_tox_av,
 | 
				
			||||||
 | 
							+[](ToxAV*, uint32_t friend_number, uint32_t video_bit_rate, void *user_data) {
 | 
				
			||||||
 | 
								assert(user_data != nullptr);
 | 
				
			||||||
 | 
								static_cast<ToxAV*>(user_data)->cb_video_bit_rate(friend_number, video_bit_rate);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						toxav_callback_audio_receive_frame(
 | 
				
			||||||
 | 
							_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<ToxAV*>(user_data)->cb_audio_receive_frame(friend_number, pcm, sample_count, channels, sampling_rate);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						toxav_callback_video_receive_frame(
 | 
				
			||||||
 | 
							_tox_av,
 | 
				
			||||||
 | 
							+[](ToxAV*, uint32_t friend_number,
 | 
				
			||||||
 | 
								uint16_t width, uint16_t height,
 | 
				
			||||||
 | 
								const uint8_t y[/*! max(width, abs(ystride)) * height */],
 | 
				
			||||||
 | 
								const uint8_t u[/*! max(width/2, abs(ustride)) * (height/2) */],
 | 
				
			||||||
 | 
								const uint8_t v[/*! max(width/2, abs(vstride)) * (height/2) */],
 | 
				
			||||||
 | 
								int32_t ystride, int32_t ustride, int32_t vstride,
 | 
				
			||||||
 | 
								void *user_data
 | 
				
			||||||
 | 
							) {
 | 
				
			||||||
 | 
								assert(user_data != nullptr);
 | 
				
			||||||
 | 
								static_cast<ToxAV*>(user_data)->cb_video_receive_frame(friend_number, width, height, y, u, v, ystride, ustride, vstride);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ToxAV::~ToxAV(void) {
 | 
					ToxAV::~ToxAV(void) {
 | 
				
			||||||
	toxav_kill(_tox_av);
 | 
						toxav_kill(_tox_av);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -80,3 +151,101 @@ Toxav_Err_Bit_Rate_Set ToxAV::toxavVideoSetBitRate(uint32_t friend_number, uint3
 | 
				
			|||||||
	return err;
 | 
						return err;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ToxAV::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);
 | 
				
			||||||
 | 
						//if (err_answer != TOXAV_ERR_ANSWER_OK) {
 | 
				
			||||||
 | 
						//    std::cerr << "!!!!!!!! answer failed " << err_answer << "\n";
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dispatch(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_call,
 | 
				
			||||||
 | 
							Events::FriendCall{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								audio_enabled,
 | 
				
			||||||
 | 
								video_enabled,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ToxAV::cb_call_state(uint32_t friend_number, uint32_t state) {
 | 
				
			||||||
 | 
						//ToxAVFriendCallState w_state{state};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//w_state.is_error();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cerr << "TOXAV: call state f:" << friend_number << " s:" << state << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dispatch(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_call_state,
 | 
				
			||||||
 | 
							Events::FriendCallState{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								state,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ToxAV::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(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_audio_bitrate,
 | 
				
			||||||
 | 
							Events::FriendAudioBitrate{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								audio_bit_rate,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ToxAV::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(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_video_bitrate,
 | 
				
			||||||
 | 
							Events::FriendVideoBitrate{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								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) {
 | 
				
			||||||
 | 
						//std::cerr << "TOXAV: audio frame f:" <<  friend_number << " sc:" << sample_count << " ch:" << (int)channels << " sr:" << sampling_rate << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dispatch(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_audio_frame,
 | 
				
			||||||
 | 
							Events::FriendAudioFrame{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								Span<int16_t>(pcm, sample_count*channels), // TODO: is sample count *ch or /ch?
 | 
				
			||||||
 | 
								channels,
 | 
				
			||||||
 | 
								sampling_rate,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ToxAV::cb_video_receive_frame(
 | 
				
			||||||
 | 
						uint32_t friend_number,
 | 
				
			||||||
 | 
						uint16_t width, uint16_t height,
 | 
				
			||||||
 | 
						const uint8_t y[/*! max(width, abs(ystride)) * height */],
 | 
				
			||||||
 | 
						const uint8_t u[/*! max(width/2, abs(ustride)) * (height/2) */],
 | 
				
			||||||
 | 
						const uint8_t v[/*! max(width/2, abs(vstride)) * (height/2) */],
 | 
				
			||||||
 | 
						int32_t ystride, int32_t ustride, int32_t vstride
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						//std::cerr << "TOXAV: video frame f:" <<  friend_number << " w:" << width << " h:" << height << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dispatch(
 | 
				
			||||||
 | 
							ToxAV_Event::friend_video_frame,
 | 
				
			||||||
 | 
							Events::FriendVideoFrame{
 | 
				
			||||||
 | 
								friend_number,
 | 
				
			||||||
 | 
								width,
 | 
				
			||||||
 | 
								height,
 | 
				
			||||||
 | 
								Span<uint8_t>(y, std::max<int64_t>(width, std::abs(ystride)) * height),
 | 
				
			||||||
 | 
								Span<uint8_t>(u, std::max<int64_t>(width/2, std::abs(ustride)) * (height/2)),
 | 
				
			||||||
 | 
								Span<uint8_t>(v, std::max<int64_t>(width/2, std::abs(vstride)) * (height/2)),
 | 
				
			||||||
 | 
								ystride,
 | 
				
			||||||
 | 
								ustride,
 | 
				
			||||||
 | 
								vstride,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										101
									
								
								src/tox_av.hpp
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								src/tox_av.hpp
									
									
									
									
									
								
							@@ -1,15 +1,98 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <solanaceae/util/span.hpp>
 | 
				
			||||||
 | 
					#include <solanaceae/util/event_provider.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <tox/toxav.h>
 | 
					#include <tox/toxav.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ToxAV {
 | 
					namespace /*toxav*/ Events {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendCall {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
							bool audio_enabled;
 | 
				
			||||||
 | 
							bool video_enabled;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendCallState {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
							uint32_t state;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendAudioBitrate {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
							uint32_t audio_bit_rate;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendVideoBitrate {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
							uint32_t video_bit_rate;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendAudioFrame {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Span<int16_t> pcm;
 | 
				
			||||||
 | 
							//size_t sample_count;
 | 
				
			||||||
 | 
							uint8_t channels;
 | 
				
			||||||
 | 
							uint32_t sampling_rate;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct FriendVideoFrame {
 | 
				
			||||||
 | 
							uint32_t friend_number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uint16_t width;
 | 
				
			||||||
 | 
							uint16_t height;
 | 
				
			||||||
 | 
							//const uint8_t y[[>! max(width, abs(ystride)) * height <]];
 | 
				
			||||||
 | 
							//const uint8_t u[[>! max(width/2, abs(ustride)) * (height/2) <]];
 | 
				
			||||||
 | 
							//const uint8_t v[[>! max(width/2, abs(vstride)) * (height/2) <]];
 | 
				
			||||||
 | 
							// mdspan would be nice here
 | 
				
			||||||
 | 
							// bc of the stride, span might be larger than the actual data it contains
 | 
				
			||||||
 | 
							Span<uint8_t> y;
 | 
				
			||||||
 | 
							Span<uint8_t> u;
 | 
				
			||||||
 | 
							Span<uint8_t> v;
 | 
				
			||||||
 | 
							int32_t ystride;
 | 
				
			||||||
 | 
							int32_t ustride;
 | 
				
			||||||
 | 
							int32_t vstride;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // Event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class ToxAV_Event : uint32_t {
 | 
				
			||||||
 | 
						friend_call,
 | 
				
			||||||
 | 
						friend_call_state,
 | 
				
			||||||
 | 
						friend_audio_bitrate,
 | 
				
			||||||
 | 
						friend_video_bitrate,
 | 
				
			||||||
 | 
						friend_audio_frame,
 | 
				
			||||||
 | 
						friend_video_frame,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MAX
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ToxAVEventI {
 | 
				
			||||||
 | 
						using enumType = ToxAV_Event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual ~ToxAVEventI(void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendCall&) { return false; }
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendCallState&) { return false; }
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendAudioBitrate&) { return false; }
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendVideoBitrate&) { return false; }
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendAudioFrame&) { return false; }
 | 
				
			||||||
 | 
						virtual bool onEvent(const Events::FriendVideoFrame&) { return false; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					using ToxAVEventProviderI = EventProviderI<ToxAVEventI>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ToxAV : public ToxAVEventProviderI{
 | 
				
			||||||
	Tox* _tox = nullptr;
 | 
						Tox* _tox = nullptr;
 | 
				
			||||||
	ToxAV* _tox_av = nullptr;
 | 
						ToxAV* _tox_av = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static constexpr const char* version {"0"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ToxAV(Tox* tox);
 | 
						ToxAV(Tox* tox);
 | 
				
			||||||
	virtual ~ToxAV(void);
 | 
						virtual ~ToxAV(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// interface
 | 
						// interface
 | 
				
			||||||
 | 
						// if iterate is called on a different thread, it will fire events there
 | 
				
			||||||
	uint32_t toxavIterationInterval(void) const;
 | 
						uint32_t toxavIterationInterval(void) const;
 | 
				
			||||||
	void toxavIterate(void);
 | 
						void toxavIterate(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,5 +116,21 @@ struct ToxAV {
 | 
				
			|||||||
//int32_t toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber);
 | 
					//int32_t toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber);
 | 
				
			||||||
//bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber);
 | 
					//bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// toxav callbacks
 | 
				
			||||||
 | 
						void cb_call(uint32_t friend_number, bool audio_enabled, bool video_enabled);
 | 
				
			||||||
 | 
						void cb_call_state(uint32_t friend_number, uint32_t state);
 | 
				
			||||||
 | 
						void cb_audio_bit_rate(uint32_t friend_number, uint32_t audio_bit_rate);
 | 
				
			||||||
 | 
						void cb_video_bit_rate(uint32_t friend_number, uint32_t video_bit_rate);
 | 
				
			||||||
 | 
						void cb_audio_receive_frame(uint32_t friend_number, const int16_t pcm[], size_t sample_count, uint8_t channels, uint32_t sampling_rate);
 | 
				
			||||||
 | 
						void cb_video_receive_frame(
 | 
				
			||||||
 | 
							uint32_t friend_number,
 | 
				
			||||||
 | 
							uint16_t width, uint16_t height,
 | 
				
			||||||
 | 
							const uint8_t y[/*! max(width, abs(ystride)) * height */],
 | 
				
			||||||
 | 
							const uint8_t u[/*! max(width/2, abs(ustride)) * (height/2) */],
 | 
				
			||||||
 | 
							const uint8_t v[/*! max(width/2, abs(vstride)) * (height/2) */],
 | 
				
			||||||
 | 
							int32_t ystride, int32_t ustride, int32_t vstride
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user