more refactoring, audio loopback kinda working
This commit is contained in:
parent
803bce93fb
commit
568d1a3f93
@ -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?
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
15
src/main.cpp
15
src/main.cpp
@ -64,27 +64,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();
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
vcc.releaseReader(reader);
|
||||
vcc.releaseSubStream(reader);
|
||||
}
|
||||
std::cout << "after sdl video stuffery\n";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user