diff --git a/src/content/frame_stream2.hpp b/src/content/frame_stream2.hpp index c6fa3b2..114a98f 100644 --- a/src/content/frame_stream2.hpp +++ b/src/content/frame_stream2.hpp @@ -19,6 +19,8 @@ template struct FrameStream2I { + virtual ~FrameStream2I(void) {} + // get number of available frames [[nodiscard]] virtual int32_t size(void) = 0; @@ -74,26 +76,30 @@ struct QueuedFrameStream2 : public FrameStream2I { } }; -template> -struct QueuedFrameStream2Multiplexer : public FrameStream2I { - using reader_type_t = ReaderType; +// implements a stream that pops or pushes to all sub streams +// you need to mind the direction you intend it to use +// release all streams before destructing! // TODO: improve lifetime here, maybe some shared semaphore? +template> +struct FrameStream2MultiStream : public FrameStream2I { + using sub_stream_type_t = SubStreamType; // pointer stability - std::vector> _readers; - std::mutex _readers_lock; // accessing the _readers array needs to be exclusive + std::vector> _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 - ReaderType* aquireReader(size_t queue_size = 10, bool lossy = true) { - std::lock_guard lg{_readers_lock}; - return _readers.emplace_back(std::make_unique(queue_size, lossy)).get(); + // TODO: forward args instead + SubStreamType* aquireSubStream(size_t queue_size = 10, bool lossy = true) { + std::lock_guard lg{_sub_stream_lock}; + return _sub_streams.emplace_back(std::make_unique(queue_size, lossy)).get(); } - void releaseReader(ReaderType* reader) { - std::lock_guard lg{_readers_lock}; - for (auto it = _readers.begin(); it != _readers.end(); it++) { - if (it->get() == reader) { - _readers.erase(it); + void releaseSubStream(SubStreamType* sub) { + std::lock_guard lg{_sub_stream_lock}; + for (auto it = _sub_streams.begin(); it != _sub_streams.end(); it++) { + if (it->get() == sub) { + _sub_streams.erase(it); break; } } @@ -107,15 +113,15 @@ struct QueuedFrameStream2Multiplexer : public FrameStream2I { } std::optional pop(void) override { - assert(false && "tried to pop from a multiplexer"); + 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{_readers_lock}; + std::lock_guard lg{_sub_stream_lock}; bool have_readers{false}; - for (auto& it : _readers) { + 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? diff --git a/src/content/sdl_audio_frame_stream2.cpp b/src/content/sdl_audio_frame_stream2.cpp index d79b003..23a5cf6 100644 --- a/src/content/sdl_audio_frame_stream2.cpp +++ b/src/content/sdl_audio_frame_stream2.cpp @@ -1,10 +1,9 @@ #include "./sdl_audio_frame_stream2.hpp" -#include "SDL_audio.h" #include #include -SDLAudioInputDevice::SDLAudioInputDevice(void) : _stream{nullptr, &SDL_DestroyAudioStream} { +SDLAudioInputDeviceDefault::SDLAudioInputDeviceDefault(void) : _stream{nullptr, &SDL_DestroyAudioStream} { constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 }; _stream = { @@ -70,10 +69,102 @@ SDLAudioInputDevice::SDLAudioInputDevice(void) : _stream{nullptr, &SDL_DestroyAu }); } -SDLAudioInputDevice::~SDLAudioInputDevice(void) { +SDLAudioInputDeviceDefault::~SDLAudioInputDeviceDefault(void) { // TODO: pause audio device? _thread_should_quit = true; _thread.join(); // TODO: what to do if readers are still present? } +int32_t SDLAudioOutputDeviceDefaultInstance::size(void) { + return -1; +} + +std::optional 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(_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 auto device_id = SDL_GetAudioStreamDevice(_stream.get()); + SDL_FlushAudioStream(_stream.get()); + + const SDL_AudioSpec spec = { + static_cast((value.isF32() ? SDL_AUDIO_F32 : SDL_AUDIO_S16)), + static_cast(value.channels), + static_cast(value.sample_rate) + }; + + _stream = { + SDL_OpenAudioDeviceStream(device_id, &spec, nullptr, nullptr), + &SDL_DestroyAudioStream + }; + } + + // HACK + assert(value.isS16()); + + auto data = value.getSpan(); + + if (data.size == 0) { + std::cerr << "empty audio frame??\n"; + } + + if (SDL_PutAudioStreamData(_stream.get(), data.ptr, data.size * sizeof(int16_t)) < 0) { + 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; +} + +SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(void) : _stream(nullptr, nullptr) { +} + +SDLAudioOutputDeviceDefaultInstance::SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other) : _stream(std::move(other._stream)) { +} + +SDLAudioOutputDeviceDefaultInstance::~SDLAudioOutputDeviceDefaultInstance(void) { +} + +SDLAudioOutputDeviceDefaultInstance SDLAudioOutputDeviceDefaultFactory::create(void) { + SDLAudioOutputDeviceDefaultInstance new_instance; + + constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 }; + + new_instance._stream = { + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &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(new_instance._stream)) { + std::cerr << "SDL open audio device failed!\n"; + } + + const auto audio_device_id = SDL_GetAudioStreamDevice(new_instance._stream.get()); + SDL_ResumeAudioDevice(audio_device_id); + + return new_instance; +} + diff --git a/src/content/sdl_audio_frame_stream2.hpp b/src/content/sdl_audio_frame_stream2.hpp index d27d829..11761b9 100644 --- a/src/content/sdl_audio_frame_stream2.hpp +++ b/src/content/sdl_audio_frame_stream2.hpp @@ -10,12 +10,14 @@ #include #include -// we dont have to multiplex ourself, because sdl streams and virtual devices already do this, but we do it anyway -using SDLAudioInputFrameStream2Multiplexer = QueuedFrameStream2Multiplexer; -using SDLAudioInputFrameStream2 = SDLAudioInputFrameStream2Multiplexer::reader_type_t; // just use the default for now +// we dont have to multicast ourself, because sdl streams and virtual devices already do this, but we do it anyway +using AudioFrameStream2MultiStream = FrameStream2MultiStream; +using AudioFrameStream2 = AudioFrameStream2MultiStream::sub_stream_type_t; // just use the default for now // object components? -struct SDLAudioInputDevice : protected SDLAudioInputFrameStream2Multiplexer { + +// source +struct SDLAudioInputDeviceDefault : protected AudioFrameStream2MultiStream { std::unique_ptr _stream; std::atomic _thread_should_quit {false}; @@ -23,15 +25,37 @@ struct SDLAudioInputDevice : protected SDLAudioInputFrameStream2Multiplexer { // construct source and start thread // TODO: optimize so the thread is not always running - SDLAudioInputDevice(void); + SDLAudioInputDeviceDefault(void); // stops the thread and closes the device? - ~SDLAudioInputDevice(void); + ~SDLAudioInputDeviceDefault(void); - using SDLAudioInputFrameStream2Multiplexer::aquireReader; - using SDLAudioInputFrameStream2Multiplexer::releaseReader; + using AudioFrameStream2MultiStream::aquireSubStream; + using AudioFrameStream2MultiStream::releaseSubStream; }; -struct SDLAudioOutputDevice { +// sink +struct SDLAudioOutputDeviceDefaultInstance : protected AudioFrameStream2I { + std::unique_ptr _stream; + + uint32_t _last_sample_rate {48'000}; + size_t _last_channels {0}; + SDL_AudioFormat _last_format {0}; + + SDLAudioOutputDeviceDefaultInstance(void); + SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other); + + ~SDLAudioOutputDeviceDefaultInstance(void); + + int32_t size(void) override; + std::optional pop(void) override; + bool push(const AudioFrame& value) override; +}; + +// constructs entirely new streams, since sdl handles sync and mixing for us (or should) +struct SDLAudioOutputDeviceDefaultFactory { + // TODO: pause device? + + SDLAudioOutputDeviceDefaultInstance create(void); }; diff --git a/src/content/sdl_video_frame_stream2.hpp b/src/content/sdl_video_frame_stream2.hpp index 6448036..e28100b 100644 --- a/src/content/sdl_video_frame_stream2.hpp +++ b/src/content/sdl_video_frame_stream2.hpp @@ -42,10 +42,10 @@ struct SDLVideoFrame { SDLVideoFrame& operator=(const SDLVideoFrame& other) = delete; }; -using SDLVideoFrameStream2Multiplexer = QueuedFrameStream2Multiplexer; -using SDLVideoFrameStream2 = SDLVideoFrameStream2Multiplexer::reader_type_t; // just use the default for now +using SDLVideoFrameStream2MultiStream = FrameStream2MultiStream; +using SDLVideoFrameStream2 = SDLVideoFrameStream2MultiStream::sub_stream_type_t; // just use the default for now -struct SDLVideoCameraContent : protected SDLVideoFrameStream2Multiplexer { +struct SDLVideoCameraContent : protected SDLVideoFrameStream2MultiStream { // meh, empty default std::unique_ptr _camera {nullptr, &SDL_CloseCamera}; std::atomic _thread_should_quit {false}; @@ -59,7 +59,7 @@ struct SDLVideoCameraContent : protected SDLVideoFrameStream2Multiplexer { ~SDLVideoCameraContent(void); // make only some of writer public - using SDLVideoFrameStream2Multiplexer::aquireReader; - using SDLVideoFrameStream2Multiplexer::releaseReader; + using SDLVideoFrameStream2MultiStream::aquireSubStream; + using SDLVideoFrameStream2MultiStream::releaseSubStream; }; diff --git a/src/main.cpp b/src/main.cpp index d57b54b..884e195 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,27 +78,30 @@ int main(int argc, char** argv) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { std::cerr << "SDL_Init AUDIO failed (" << SDL_GetError() << ")\n"; } else { - SDLAudioInputDevice aid; - auto* reader = aid.aquireReader(); + SDLAudioInputDeviceDefault aidd; + auto* reader = aidd.aquireSubStream(); - for (size_t i = 0; i < 20; i++) { + auto writer = SDLAudioOutputDeviceDefaultFactory{}.create(); + + for (size_t i = 0; i < 2000; 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().size:new_frame_opt.value().getSpan().size) << "\n"; + writer.push(new_frame_opt.value()); } else { std::cout << "no audio frame\n"; } } - aid.releaseReader(reader); + aidd.releaseSubStream(reader); } if (SDL_Init(SDL_INIT_CAMERA) < 0) { std::cerr << "SDL_Init CAMERA failed (" << SDL_GetError() << ")\n"; } else { // HACK SDLVideoCameraContent vcc; - auto* reader = vcc.aquireReader(); + auto* reader = vcc.aquireSubStream(); for (size_t i = 0; i < 20; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); auto new_frame_opt = reader->pop(); @@ -106,7 +109,7 @@ int main(int argc, char** argv) { std::cout << "video frame was " << new_frame_opt.value().surface->w << "x" << new_frame_opt.value().surface->h << " " << new_frame_opt.value().timestampNS << "ns\n"; } } - vcc.releaseReader(reader); + vcc.releaseSubStream(reader); } std::cout << "after sdl video stuffery\n";