more refactoring, audio loopback kinda working

This commit is contained in:
Green Sky 2024-05-03 13:59:50 +02:00
parent 803bce93fb
commit 568d1a3f93
No known key found for this signature in database
5 changed files with 163 additions and 39 deletions

View File

@ -19,6 +19,8 @@
template<typename FrameType> template<typename FrameType>
struct FrameStream2I { struct FrameStream2I {
virtual ~FrameStream2I(void) {}
// get number of available frames // get number of available frames
[[nodiscard]] virtual int32_t size(void) = 0; [[nodiscard]] virtual int32_t size(void) = 0;
@ -74,26 +76,30 @@ struct QueuedFrameStream2 : public FrameStream2I<FrameType> {
} }
}; };
template<typename FrameType, typename ReaderType = QueuedFrameStream2<FrameType>> // implements a stream that pops or pushes to all sub streams
struct QueuedFrameStream2Multiplexer : public FrameStream2I<FrameType> { // you need to mind the direction you intend it to use
using reader_type_t = ReaderType; // release all streams before destructing! // TODO: improve lifetime here, maybe some shared semaphore?
template<typename FrameType, typename SubStreamType = QueuedFrameStream2<FrameType>>
struct FrameStream2MultiStream : public FrameStream2I<FrameType> {
using sub_stream_type_t = SubStreamType;
// pointer stability // pointer stability
std::vector<std::unique_ptr<ReaderType>> _readers; std::vector<std::unique_ptr<SubStreamType>> _sub_streams;
std::mutex _readers_lock; // accessing the _readers array needs to be exclusive 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, // 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 // except for the push, which is always on the same thread
ReaderType* aquireReader(size_t queue_size = 10, bool lossy = true) { // TODO: forward args instead
std::lock_guard lg{_readers_lock}; SubStreamType* aquireSubStream(size_t queue_size = 10, bool lossy = true) {
return _readers.emplace_back(std::make_unique<ReaderType>(queue_size, lossy)).get(); std::lock_guard lg{_sub_stream_lock};
return _sub_streams.emplace_back(std::make_unique<SubStreamType>(queue_size, lossy)).get();
} }
void releaseReader(ReaderType* reader) { void releaseSubStream(SubStreamType* sub) {
std::lock_guard lg{_readers_lock}; std::lock_guard lg{_sub_stream_lock};
for (auto it = _readers.begin(); it != _readers.end(); it++) { for (auto it = _sub_streams.begin(); it != _sub_streams.end(); it++) {
if (it->get() == reader) { if (it->get() == sub) {
_readers.erase(it); _sub_streams.erase(it);
break; break;
} }
} }
@ -107,15 +113,15 @@ struct QueuedFrameStream2Multiplexer : public FrameStream2I<FrameType> {
} }
std::optional<FrameType> pop(void) override { std::optional<FrameType> 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; return std::nullopt;
} }
// returns true if there are readers // returns true if there are readers
bool push(const FrameType& value) override { bool push(const FrameType& value) override {
std::lock_guard lg{_readers_lock}; std::lock_guard lg{_sub_stream_lock};
bool have_readers{false}; bool have_readers{false};
for (auto& it : _readers) { for (auto& it : _sub_streams) {
[[maybe_unused]] auto _ = it->push(value); [[maybe_unused]] auto _ = it->push(value);
have_readers = true; // even if queue full, we still continue believing in them have_readers = true; // even if queue full, we still continue believing in them
// maybe consider push return value? // maybe consider push return value?

View File

@ -1,10 +1,9 @@
#include "./sdl_audio_frame_stream2.hpp" #include "./sdl_audio_frame_stream2.hpp"
#include "SDL_audio.h"
#include <iostream> #include <iostream>
#include <vector> #include <vector>
SDLAudioInputDevice::SDLAudioInputDevice(void) : _stream{nullptr, &SDL_DestroyAudioStream} { SDLAudioInputDeviceDefault::SDLAudioInputDeviceDefault(void) : _stream{nullptr, &SDL_DestroyAudioStream} {
constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 }; constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, 48000 };
_stream = { _stream = {
@ -70,10 +69,102 @@ SDLAudioInputDevice::SDLAudioInputDevice(void) : _stream{nullptr, &SDL_DestroyAu
}); });
} }
SDLAudioInputDevice::~SDLAudioInputDevice(void) { SDLAudioInputDeviceDefault::~SDLAudioInputDeviceDefault(void) {
// TODO: pause audio device? // TODO: pause audio device?
_thread_should_quit = true; _thread_should_quit = true;
_thread.join(); _thread.join();
// TODO: what to do if readers are still present? // TODO: what to do if readers are still present?
} }
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 auto device_id = SDL_GetAudioStreamDevice(_stream.get());
SDL_FlushAudioStream(_stream.get());
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)
};
_stream = {
SDL_OpenAudioDeviceStream(device_id, &spec, nullptr, nullptr),
&SDL_DestroyAudioStream
};
}
// HACK
assert(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)) < 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<bool>(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;
}

View File

@ -10,12 +10,14 @@
#include <vector> #include <vector>
#include <thread> #include <thread>
// we dont have to multiplex ourself, because sdl streams and virtual devices already do this, but we do it anyway // we dont have to multicast ourself, because sdl streams and virtual devices already do this, but we do it anyway
using SDLAudioInputFrameStream2Multiplexer = QueuedFrameStream2Multiplexer<AudioFrame>; using AudioFrameStream2MultiStream = FrameStream2MultiStream<AudioFrame>;
using SDLAudioInputFrameStream2 = SDLAudioInputFrameStream2Multiplexer::reader_type_t; // just use the default for now using AudioFrameStream2 = AudioFrameStream2MultiStream::sub_stream_type_t; // just use the default for now
// object components? // object components?
struct SDLAudioInputDevice : protected SDLAudioInputFrameStream2Multiplexer {
// source
struct SDLAudioInputDeviceDefault : protected AudioFrameStream2MultiStream {
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream; std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
std::atomic<bool> _thread_should_quit {false}; std::atomic<bool> _thread_should_quit {false};
@ -23,15 +25,37 @@ struct SDLAudioInputDevice : protected SDLAudioInputFrameStream2Multiplexer {
// construct source and start thread // construct source and start thread
// TODO: optimize so the thread is not always running // TODO: optimize so the thread is not always running
SDLAudioInputDevice(void); SDLAudioInputDeviceDefault(void);
// stops the thread and closes the device? // stops the thread and closes the device?
~SDLAudioInputDevice(void); ~SDLAudioInputDeviceDefault(void);
using SDLAudioInputFrameStream2Multiplexer::aquireReader; using AudioFrameStream2MultiStream::aquireSubStream;
using SDLAudioInputFrameStream2Multiplexer::releaseReader; using AudioFrameStream2MultiStream::releaseSubStream;
}; };
struct SDLAudioOutputDevice { // sink
struct SDLAudioOutputDeviceDefaultInstance : protected 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 {0};
SDLAudioOutputDeviceDefaultInstance(void);
SDLAudioOutputDeviceDefaultInstance(SDLAudioOutputDeviceDefaultInstance&& other);
~SDLAudioOutputDeviceDefaultInstance(void);
int32_t size(void) override;
std::optional<AudioFrame> 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);
}; };

View File

@ -42,10 +42,10 @@ struct SDLVideoFrame {
SDLVideoFrame& operator=(const SDLVideoFrame& other) = delete; SDLVideoFrame& operator=(const SDLVideoFrame& other) = delete;
}; };
using SDLVideoFrameStream2Multiplexer = QueuedFrameStream2Multiplexer<SDLVideoFrame>; using SDLVideoFrameStream2MultiStream = FrameStream2MultiStream<SDLVideoFrame>;
using SDLVideoFrameStream2 = SDLVideoFrameStream2Multiplexer::reader_type_t; // just use the default for now using SDLVideoFrameStream2 = SDLVideoFrameStream2MultiStream::sub_stream_type_t; // just use the default for now
struct SDLVideoCameraContent : protected SDLVideoFrameStream2Multiplexer { struct SDLVideoCameraContent : protected SDLVideoFrameStream2MultiStream {
// meh, empty default // meh, empty default
std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera {nullptr, &SDL_CloseCamera}; std::unique_ptr<SDL_Camera, decltype(&SDL_CloseCamera)> _camera {nullptr, &SDL_CloseCamera};
std::atomic<bool> _thread_should_quit {false}; std::atomic<bool> _thread_should_quit {false};
@ -59,7 +59,7 @@ struct SDLVideoCameraContent : protected SDLVideoFrameStream2Multiplexer {
~SDLVideoCameraContent(void); ~SDLVideoCameraContent(void);
// make only some of writer public // make only some of writer public
using SDLVideoFrameStream2Multiplexer::aquireReader; using SDLVideoFrameStream2MultiStream::aquireSubStream;
using SDLVideoFrameStream2Multiplexer::releaseReader; using SDLVideoFrameStream2MultiStream::releaseSubStream;
}; };

View File

@ -64,27 +64,30 @@ int main(int argc, char** argv) {
if (SDL_Init(SDL_INIT_AUDIO) < 0) { if (SDL_Init(SDL_INIT_AUDIO) < 0) {
std::cerr << "SDL_Init AUDIO failed (" << SDL_GetError() << ")\n"; std::cerr << "SDL_Init AUDIO failed (" << SDL_GetError() << ")\n";
} else { } else {
SDLAudioInputDevice aid; SDLAudioInputDeviceDefault aidd;
auto* reader = aid.aquireReader(); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
auto new_frame_opt = reader->pop(); auto new_frame_opt = reader->pop();
if (new_frame_opt.has_value()) { if (new_frame_opt.has_value()) {
std::cout << "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"; 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 { } else {
std::cout << "no audio frame\n"; std::cout << "no audio frame\n";
} }
} }
aid.releaseReader(reader); aidd.releaseSubStream(reader);
} }
if (SDL_Init(SDL_INIT_CAMERA) < 0) { if (SDL_Init(SDL_INIT_CAMERA) < 0) {
std::cerr << "SDL_Init CAMERA failed (" << SDL_GetError() << ")\n"; std::cerr << "SDL_Init CAMERA failed (" << SDL_GetError() << ")\n";
} else { // HACK } else { // HACK
SDLVideoCameraContent vcc; SDLVideoCameraContent vcc;
auto* reader = vcc.aquireReader(); auto* reader = vcc.aquireSubStream();
for (size_t i = 0; i < 20; i++) { for (size_t i = 0; i < 20; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
auto new_frame_opt = reader->pop(); auto new_frame_opt = reader->pop();
@ -92,7 +95,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"; 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"; std::cout << "after sdl video stuffery\n";