more refactoring, audio loopback kinda working

This commit is contained in:
Green Sky 2024-05-03 13:59:50 +02:00
parent b3e5e4c950
commit 3a98e10007
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>
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<FrameType> {
}
};
template<typename FrameType, typename ReaderType = QueuedFrameStream2<FrameType>>
struct QueuedFrameStream2Multiplexer : public FrameStream2I<FrameType> {
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<typename FrameType, typename SubStreamType = QueuedFrameStream2<FrameType>>
struct FrameStream2MultiStream : public FrameStream2I<FrameType> {
using sub_stream_type_t = SubStreamType;
// pointer stability
std::vector<std::unique_ptr<ReaderType>> _readers;
std::mutex _readers_lock; // accessing the _readers array needs to be exclusive
std::vector<std::unique_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
ReaderType* aquireReader(size_t queue_size = 10, bool lossy = true) {
std::lock_guard lg{_readers_lock};
return _readers.emplace_back(std::make_unique<ReaderType>(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<SubStreamType>(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<FrameType> {
}
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;
}
// 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?

View File

@ -1,10 +1,9 @@
#include "./sdl_audio_frame_stream2.hpp"
#include "SDL_audio.h"
#include <iostream>
#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 };
_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<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 <thread>
// we dont have to multiplex ourself, because sdl streams and virtual devices already do this, but we do it anyway
using SDLAudioInputFrameStream2Multiplexer = QueuedFrameStream2Multiplexer<AudioFrame>;
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<AudioFrame>;
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<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
std::atomic<bool> _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<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;
};
using SDLVideoFrameStream2Multiplexer = QueuedFrameStream2Multiplexer<SDLVideoFrame>;
using SDLVideoFrameStream2 = SDLVideoFrameStream2Multiplexer::reader_type_t; // just use the default for now
using SDLVideoFrameStream2MultiStream = FrameStream2MultiStream<SDLVideoFrame>;
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<SDL_Camera, decltype(&SDL_CloseCamera)> _camera {nullptr, &SDL_CloseCamera};
std::atomic<bool> _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;
};

View File

@ -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<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.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";