diff --git a/CMakeLists.txt b/CMakeLists.txt index 54256ba0e0746e5a48a1df20c0d2ee841326e0b0..3592611681ad0c31c087a38a3af84b8f71cf7d96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,25 +222,6 @@ else() add_library(realsense INTERFACE) endif() -# ============================================================================== - -if (BUILD_GUI) - set(HAVE_NANOGUI TRUE) - - # Disable building extras we won't need (pure C++ project) - set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE) - set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE) - set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) - set(NANOGUI_INSTALL OFF CACHE BOOL " " FORCE) - set(NANOGUI_EIGEN_INCLUDE_DIR ${EIGEN_INCLUDE_DIR} CACHE STRING " " FORCE) - - # Add the configurations from nanogui - add_subdirectory(ext/nanogui) - - # For reliability of parallel build, make the NanoGUI targets dependencies - set_property(TARGET glfw glfw_objects nanogui PROPERTY FOLDER "dependencies") -endif() - # ==== Portaudio v19 =========================================================== # Portaudio v19 library @@ -390,16 +371,16 @@ include(ftl_paths) if (WIN32) # TODO(nick) Should do based upon compiler (VS) add_definitions(-DWIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP4 /std:c++17 /wd4996") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX512 /MP4 /std:c++17 /wd4996") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DFTL_DEBUG /Wall") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") set(OS_LIBS "") else() add_definitions(-DUNIX) # -fdiagnostics-color - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -msse3 -Wall -Werror=unused-result -Werror=return-type") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -march=native -mfpmath=sse -Wall -Werror=unused-result -Werror=return-type") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -mfpmath=sse") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(OS_LIBS "dl") endif() @@ -441,6 +422,27 @@ else() add_library(sgm INTERFACE) endif() +# ============================================================================== + +if (BUILD_GUI) + set(HAVE_NANOGUI TRUE) + + # Disable building extras we won't need (pure C++ project) + set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE) + set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE) + set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) + set(NANOGUI_INSTALL OFF CACHE BOOL " " FORCE) + set(NANOGUI_EIGEN_INCLUDE_DIR ${EIGEN_INCLUDE_DIR} CACHE STRING " " FORCE) + + # Add the configurations from nanogui + add_subdirectory(ext/nanogui) + + # For reliability of parallel build, make the NanoGUI targets dependencies + set_property(TARGET glfw glfw_objects nanogui PROPERTY FOLDER "dependencies") +endif() + +# ============================================================================= + add_subdirectory(components/common/cpp) add_subdirectory(components/codecs) diff --git a/applications/gui2/src/views/addsource.hpp b/applications/gui2/src/views/addsource.hpp index 7e6ca31938b57ce063be81c8d0b77dc73e3b92da..34e8353fe496454e8146787f00149e390b002cc2 100644 --- a/applications/gui2/src/views/addsource.hpp +++ b/applications/gui2/src/views/addsource.hpp @@ -34,7 +34,7 @@ private: nanogui::TabWidget *tabs_; public: - // EIGEN_MAKE_ALIGNED_OPERATOR_NEW + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; } diff --git a/components/audio/CMakeLists.txt b/components/audio/CMakeLists.txt index cae7b4080b8c8fa96e96327b60762b3049faf603..7a2721087b04834506d0af31671a8e06030fb62f 100644 --- a/components/audio/CMakeLists.txt +++ b/components/audio/CMakeLists.txt @@ -15,5 +15,6 @@ target_include_directories(ftlaudio PUBLIC target_link_libraries(ftlaudio ftlcommon Eigen3::Eigen ftlstreams ftldata portaudio Opus) -#add_subdirectory(test) - +if (BUILD_TESTS) +add_subdirectory(test) +endif() diff --git a/components/audio/include/ftl/audio/buffer.hpp b/components/audio/include/ftl/audio/buffer.hpp index da39fd9c3802f01c99dce542de3f5c4f6ee5821b..545e15bf129c9a7e429c5f325916a331d29031db 100644 --- a/components/audio/include/ftl/audio/buffer.hpp +++ b/components/audio/include/ftl/audio/buffer.hpp @@ -72,7 +72,7 @@ class FixedBuffer : public ftl::audio::Buffer<T> { inline void writeFrame(const T *d) { const T *in = d; - T *out = &data_[(write_position_++) % SIZE][0]; + T *out = data_[(write_position_++) % SIZE]; for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = *in++; if (write_position_ > 5 && read_position_ < 0) read_position_ = 0; } @@ -105,11 +105,17 @@ class FixedBuffer : public ftl::audio::Buffer<T> { read_position_ = 0; } + inline T *data() { return (T*)data_; } + inline T *data(int f) { return data_[f]; } + + inline int writePosition() const { return write_position_; } + inline void setWritePosition(int p) { write_position_ = p; } + private: int write_position_; int read_position_; int offset_; - T data_[SIZE][CHAN*FRAME]; + alignas(32) T data_[SIZE][CHAN*FRAME]; }; // ==== Implementations ======================================================== @@ -153,7 +159,7 @@ void FixedBuffer<T,CHAN,FRAME,SIZE>::write(const std::vector<T> &in) { ++write_position_; } } - if (write_position_ > 20 && read_position_ < 0) read_position_ = 0; + if (write_position_ > 5 && read_position_ < 0) read_position_ = 0; } template <typename T, int CHAN, int FRAME, int SIZE> diff --git a/components/audio/include/ftl/audio/mixer.hpp b/components/audio/include/ftl/audio/mixer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b8e41d16e54fb4a476a54ca598c82c11ac155dcd --- /dev/null +++ b/components/audio/include/ftl/audio/mixer.hpp @@ -0,0 +1,158 @@ +#ifndef _FTL_AUDIO_MIXER_HPP_ +#define _FTL_AUDIO_MIXER_HPP_ + +#include <Eigen/Eigen> +#include <vector> +#include <cmath> +#include "buffer.hpp" + +namespace ftl { +namespace audio { + +//static constexpr int kBufferCount = 100; + +/** + * A fast circular buffer to capture, play and manipulate audio data. + * This class can be used directly with portaudio. The hardware uses + * `readFrame` and `writeFrame` to consume or append audio data. A more + * advanced `write` function allows for non-frame aligned data and for time + * dilation / shifting, and amplitude control. + */ +template <typename T, int CHAN, int FRAME, int SIZE> +class FixedMixer : public ftl::audio::Buffer<T> { + public: + FixedMixer() : Buffer<T>(CHAN, FRAME, 44100), write_position_(0), read_position_(0), offset_(0) { resize(1); } + explicit FixedMixer(int tracks) : Buffer<T>(CHAN, FRAME, 44100), write_position_(0), read_position_(0), offset_(0) { resize(tracks); } + FixedMixer(int tracks, int rate) : Buffer<T>(CHAN, FRAME, rate), write_position_(0), read_position_(0), offset_(0) { resize(tracks); } + + + inline int maxFrames() const { return SIZE; } + + inline void readFrame(T *d) { + T *out = d; + if (read_position_ >= write_position_) { + for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = 0; + } else { + T *in = data_[(read_position_++) % SIZE]; + for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = *in++; + } + } + + int size() const override { return (read_position_>=0) ? write_position_ - read_position_ : 0; } + int frames() const override { return (read_position_>=0) ? write_position_ - read_position_ : 0; } + + /** + * Append sound samples to the end of the buffer. The samples may be over + * or under sampled so as to gradually introduce or remove a requested + * delay and hence change the latency of the audio. + */ + void write(const std::vector<T> &in) override; + + inline void write(int track, const std::vector<T> &in) { + tracks_[track].write(in); + } + + void mix(); + + void read(std::vector<T> &out, int frames) override; + + void reset() override { + Buffer<T>::reset(); + write_position_ = 0; //int(this->cur_delay_); + read_position_ = 0; + } + + inline int writePosition() const { return write_position_; } + inline int readPosition() const { return read_position_; } + inline int tracks() const { return track_num_; } + + inline void setDelay(int track, float d) { tracks_[track].setDelay(d); } + inline float delay(int track) const { return tracks_[track].delay(); } + + inline void setGain(float g) { gain_ = g; } + inline float gain() const { return gain_; } + + void resize(int tracks); + + private: + int track_num_=0; + int write_position_; + int read_position_; + int offset_; + float gain_ = 1.0f; + alignas(32) T data_[SIZE][CHAN*FRAME]; + std::vector<ftl::audio::FixedBuffer<T,CHAN,FRAME,SIZE>> tracks_; +}; + +// ==== Implementations ======================================================== + +template <typename T, int CHAN, int FRAME, int SIZE> +void FixedMixer<T,CHAN,FRAME,SIZE>::write(const std::vector<T> &in) { + // Not supported... +} + +template <typename T, int CHAN, int FRAME, int SIZE> +void FixedMixer<T,CHAN,FRAME,SIZE>::mix() { + // Add together up to most recent frame + int min_write = std::numeric_limits<int>::max(); + for (auto &t : tracks_) { + min_write = std::min(t.writePosition(), min_write); + } + + // For each frame + while (write_position_ < min_write) { + int wp = write_position_ % SIZE; + float *ptr1 = data_[wp]; + + // For each block of 8 float samples + for (size_t i=0; i<CHAN*FRAME; i+=8) { + Eigen::Map<Eigen::Matrix<float,8,1>,Eigen::Aligned32> v1(ptr1+i); + v1.setZero(); + + // For each track, accumulate the samples + for (auto &t : tracks_) { + const Eigen::Map<Eigen::Matrix<float,8,1>,Eigen::Aligned32> v2(&t.data(wp)[i]); + v1 += v2; + } + + v1 *= gain_; + } + + ++write_position_; + } +} + +template <typename T, int CHAN, int FRAME, int SIZE> +void FixedMixer<T,CHAN,FRAME,SIZE>::read(std::vector<T> &out, int count) { + out.resize(FRAME*count*CHAN); + T *ptr = out.data(); + for (int i=0; i<count; ++i) { + readFrame(ptr); + ptr += FRAME*CHAN; + } +} + +template <typename T, int CHAN, int FRAME, int SIZE> +void FixedMixer<T,CHAN,FRAME,SIZE>::resize(int t) { + if (track_num_ == t) return; + + track_num_ = t; + tracks_.reserve(t); + while (static_cast<int>(tracks_.size()) < t) { + auto &tr = tracks_.emplace_back(); + tr.setWritePosition(write_position_); + } +} + +// ==== Common forms =========================================================== + +template <int SIZE> +using StereoMixerF = ftl::audio::FixedMixer<float,2,960,SIZE>; + +template <int SIZE> +using MonoMixerF = ftl::audio::FixedMixer<float,1,960,SIZE>; + +} +} + +#endif // _FTL_AUDIO_BUFFER_HPP_ diff --git a/components/audio/test/CMakeLists.txt b/components/audio/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f1ab14fb0922e8c91e16220f4d8726d82d5e9da9 --- /dev/null +++ b/components/audio/test/CMakeLists.txt @@ -0,0 +1,11 @@ +### OpenCV Codec Unit ################################################################ +add_executable(mixer_unit +$<TARGET_OBJECTS:CatchTest> + mixer_unit.cpp +) +target_include_directories(mixer_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(mixer_unit + Threads::Threads ${OS_LIBS} ftlcommon) + + +add_test(MixerUnitTest mixer_unit) diff --git a/components/audio/test/mixer_unit.cpp b/components/audio/test/mixer_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2339144469e01fa5f1cc3183f985383c0ad6521b --- /dev/null +++ b/components/audio/test/mixer_unit.cpp @@ -0,0 +1,133 @@ +#include "catch.hpp" +#include <ftl/audio/mixer.hpp> + +using ftl::audio::StereoMixerF; + +TEST_CASE("Audio Mixer Stereo Float", "") { + SECTION("Add two in sync tracks") { + StereoMixerF<100> mixer(2); + + // Three 960 sample stereo frames + std::vector<float> in1(960*2*3); + std::vector<float> in2(960*2*3); + + for (int i=0; i<960*2*3; ++i) in1[i] = float(i)+1.0f; + for (int i=0; i<960*2*3; ++i) in2[i] = float(i)+2.0f; + + mixer.write(0, in1); + mixer.write(1, in2); + mixer.mix(); + + REQUIRE( mixer.writePosition() == 3 ); + REQUIRE( mixer.readPosition() == 0 ); + + // Read one of the three valid frames + std::vector<float> out; + mixer.read(out, 1); + bool correct = true; + + // Check all values are correct + for (int i=0; i<960*2*1; ++i) { + float e = float(i)+1.0f + float(i)+2.0f; + correct &= int(e) == int(out[i]); + } + + REQUIRE( correct ); + } + + SECTION("Add two out of sync tracks") { + StereoMixerF<100> mixer(2); + + // Three 960 sample stereo frames + std::vector<float> in1(960*2*3); + std::vector<float> in2(960*2*2); + + for (int i=0; i<960*2*3; ++i) in1[i] = float(i)+1.0f; + for (int i=0; i<960*2*2; ++i) in2[i] = float(i)+2.0f; + + mixer.write(0, in1); + mixer.write(1, in2); + mixer.mix(); + + REQUIRE( mixer.writePosition() == 2 ); + REQUIRE( mixer.readPosition() == 0 ); + + // Read one of the three valid frames + std::vector<float> out; + mixer.read(out, 2); + bool correct = true; + + // Check all values are correct + for (int i=0; i<960*2*2; ++i) { + float e = float(i)+1.0f + float(i)+2.0f; + correct &= int(e) == int(out[i]); + } + + REQUIRE( correct ); + + // Now add final frame + std::vector<float> in3(960*2*1); + for (int i=0; i<960*2*1; ++i) in3[i] = float(i)+1.0f; + + mixer.write(1, in3); + mixer.mix(); + + REQUIRE( mixer.writePosition() == 3 ); + REQUIRE( mixer.readPosition() == 2 ); + + mixer.read(out, 1); + + // Check all values are correct + for (int i=0; i<960*2*1; ++i) { + float e = float(i)+1.0f + float(i+960*2*2)+1.0f; + correct &= int(e) == int(out[i]); + } + + REQUIRE( correct ); + } +} + +TEST_CASE("Audio Mixer Stereo Float Dynamic Tracks", "") { + SECTION("Add one track after write") { + StereoMixerF<100> mixer(1); + + // Three 960 sample stereo frames + std::vector<float> in1(960*2*3); + for (int i=0; i<960*2*3; ++i) in1[i] = float(i)+1.0f; + + mixer.write(0, in1); + mixer.mix(); + + REQUIRE( mixer.writePosition() == 3 ); + REQUIRE( mixer.readPosition() == 0 ); + + std::vector<float> in2(960*2*3); + for (int i=0; i<960*2*3; ++i) in2[i] = float(i)+2.0f; + + mixer.resize(2); + mixer.write(0, in1); + mixer.write(1, in2); + mixer.mix(); + + REQUIRE( mixer.writePosition() == 6 ); + REQUIRE( mixer.readPosition() == 0 ); + REQUIRE( mixer.frames() == 6 ); + + // Read one of the three valid frames + std::vector<float> out; + mixer.read(out, mixer.frames()); + bool correct = true; + + // Check all values are correct + for (int i=0; i<960*2*3; ++i) { + float e = float(i)+1.0f; + correct &= int(e) == int(out[i]); + } + for (int i=960*2*3; i<960*2*6; ++i) { + float e = float(i-960*2*3)+1.0f + float(i-960*2*3)+2.0f; + correct &= int(e) == int(out[i]); + } + + REQUIRE( correct ); + } +} diff --git a/components/streams/src/renderers/screen_render.cpp b/components/streams/src/renderers/screen_render.cpp index d06d57f7f243ac0acdbe0bb11786997b6b6ec932..b9b5bb73721574ab6e0345d7233718f9ce7c2879 100644 --- a/components/streams/src/renderers/screen_render.cpp +++ b/components/streams/src/renderers/screen_render.cpp @@ -155,12 +155,24 @@ bool ScreenRender::retrieve(ftl::data::Frame &frame_out) { for (auto &s : sets) { if (s->frameset() == my_id_) continue; // Skip self + auto &mixmap = mixmap_[s->id().id]; + if (mixmap.track == -1) mixmap.track = tracks_++; + // TODO: Render audio also... // Use another thread to merge all audio channels along with // some degree of volume adjustment. Later, do 3D audio. + mixer_.resize(tracks_); // Inject and copy data items - for (const auto &f : s->frames) { + for (size_t i=0; i<s->frames.size(); ++i) { + auto &f = s->frames[i]; + + if (mixmap.last_timestamp != f.timestamp() && f.hasChannel(Channel::AudioStereo)) { + const auto &audio = f.get<std::list<ftl::audio::Audio>>(Channel::AudioStereo).front(); + mixer_.write(mixmap.track, audio.data()); + } + mixmap.last_timestamp = f.timestamp(); + // Add pose as a camera shape auto &shape = shapes.list.emplace_back(); shape.id = f.id().id; @@ -177,6 +189,17 @@ bool ScreenRender::retrieve(ftl::data::Frame &frame_out) { } } + mixer_.mix(); + + // Write mixed audio to frame. + if (mixer_.frames() > 0) { + auto &list = frame_out.create<std::list<ftl::audio::Audio>>(Channel::AudioStereo).list; + list.clear(); + + int fcount = mixer_.frames(); + mixer_.read(list.emplace_front().data(), fcount); + } + // This waits for GPU also if (!data_only) renderer_->end(); } catch (const ftl::exception &e) { diff --git a/components/streams/src/renderers/screen_render.hpp b/components/streams/src/renderers/screen_render.hpp index e8a5635a57d9c96bf327625c32ad3e9785d182a6..a24bb747676b8731eb1a472995d85c523d67b7cf 100644 --- a/components/streams/src/renderers/screen_render.hpp +++ b/components/streams/src/renderers/screen_render.hpp @@ -6,6 +6,7 @@ #include <ftl/render/renderer.hpp> #include <ftl/render/CUDARender.hpp> #include <ftl/streams/feed.hpp> +#include <ftl/audio/mixer.hpp> #include "../baserender.hpp" @@ -35,6 +36,15 @@ class ScreenRender : public ftl::render::BaseSourceImpl { uint32_t my_id_; ftl::operators::Graph *post_pipe_; std::atomic_flag calibration_uptodate_; + + struct AudioMixerMapping { + int64_t last_timestamp=0; + int track=-1; + }; + + int tracks_=0; + ftl::audio::StereoMixerF<100> mixer_; + std::unordered_map<uint32_t, AudioMixerMapping> mixmap_; }; }