more refactoring, audio loopback kinda working
This commit is contained in:
parent
803bce93fb
commit
568d1a3f93
@ -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?
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) {
|
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";
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user