forked from Green-Sky/tomato
add sdl audio input/output devices and add by default (if audio works)
This commit is contained in:
parent
d89ab0bf42
commit
61b9044f94
@ -108,6 +108,9 @@ target_sources(tomato PUBLIC
|
|||||||
./frame_streams/stream_manager.hpp
|
./frame_streams/stream_manager.hpp
|
||||||
./frame_streams/stream_manager.cpp
|
./frame_streams/stream_manager.cpp
|
||||||
|
|
||||||
|
./frame_streams/sdl/sdl_audio2_frame_stream2.hpp
|
||||||
|
./frame_streams/sdl/sdl_audio2_frame_stream2.cpp
|
||||||
|
|
||||||
./stream_manager_ui.hpp
|
./stream_manager_ui.hpp
|
||||||
./stream_manager_ui.cpp
|
./stream_manager_ui.cpp
|
||||||
)
|
)
|
||||||
|
271
src/frame_streams/sdl/sdl_audio2_frame_stream2.cpp
Normal file
271
src/frame_streams/sdl/sdl_audio2_frame_stream2.cpp
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
#include "./sdl_audio2_frame_stream2.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
// "thin" wrapper around sdl audio streams
|
||||||
|
// we dont needs to get fance, as they already provide everything we need
|
||||||
|
struct SDLAudio2StreamReader : public AudioFrame2Stream2I {
|
||||||
|
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
|
||||||
|
|
||||||
|
uint32_t _sample_rate {48'000};
|
||||||
|
size_t _channels {0};
|
||||||
|
|
||||||
|
// buffer gets reused!
|
||||||
|
std::vector<int16_t> _buffer;
|
||||||
|
|
||||||
|
SDLAudio2StreamReader(void) : _stream(nullptr, nullptr) {}
|
||||||
|
SDLAudio2StreamReader(SDLAudio2StreamReader&& other) :
|
||||||
|
_stream(std::move(other._stream)),
|
||||||
|
_sample_rate(other._sample_rate),
|
||||||
|
_channels(other._channels)
|
||||||
|
{
|
||||||
|
const size_t buffer_size {960*_channels};
|
||||||
|
_buffer.resize(buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SDLAudio2StreamReader(void) {
|
||||||
|
if (_stream) {
|
||||||
|
SDL_UnbindAudioStream(_stream.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t size(void) override {
|
||||||
|
//assert(_stream);
|
||||||
|
// returns bytes
|
||||||
|
//SDL_GetAudioStreamAvailable(_stream.get());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AudioFrame2> pop(void) override {
|
||||||
|
assert(_stream);
|
||||||
|
if (!_stream) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t buffer_size {960*_channels};
|
||||||
|
_buffer.resize(buffer_size); // noop?
|
||||||
|
|
||||||
|
const auto read_bytes = SDL_GetAudioStreamData(
|
||||||
|
_stream.get(),
|
||||||
|
_buffer.data(),
|
||||||
|
_buffer.size()*sizeof(int16_t)
|
||||||
|
);
|
||||||
|
|
||||||
|
// no new frame yet, or error
|
||||||
|
if (read_bytes <= 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AudioFrame2 {
|
||||||
|
_sample_rate, _channels,
|
||||||
|
Span<int16_t>(_buffer.data(), read_bytes/sizeof(int16_t)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool push(const AudioFrame2&) override {
|
||||||
|
// TODO: make universal sdl stream wrapper (combine with SDLAudioOutputDeviceDefaultInstance)
|
||||||
|
assert(false && "read only");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SDLAudio2InputDevice::SDLAudio2InputDevice(void) : SDLAudio2InputDevice(SDL_AUDIO_DEVICE_DEFAULT_RECORDING) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLAudio2InputDevice::SDLAudio2InputDevice(SDL_AudioDeviceID conf_device_id) : _configured_device_id(conf_device_id) {
|
||||||
|
if (_configured_device_id == 0) {
|
||||||
|
// TODO: proper error handling
|
||||||
|
throw int(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLAudio2InputDevice::~SDLAudio2InputDevice(void) {
|
||||||
|
_streams.clear();
|
||||||
|
|
||||||
|
if (_virtual_device_id != 0) {
|
||||||
|
SDL_CloseAudioDevice(_virtual_device_id);
|
||||||
|
_virtual_device_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FrameStream2I<AudioFrame2>> SDLAudio2InputDevice::subscribe(void) {
|
||||||
|
if (_virtual_device_id == 0) {
|
||||||
|
// first stream, open device
|
||||||
|
// this spec is more like a hint to the hardware
|
||||||
|
SDL_AudioSpec spec {
|
||||||
|
SDL_AUDIO_S16,
|
||||||
|
1, // TODO: conf
|
||||||
|
48'000,
|
||||||
|
};
|
||||||
|
_virtual_device_id = SDL_OpenAudioDevice(_configured_device_id, &spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_virtual_device_id == 0) {
|
||||||
|
std::cerr << "SDLAID error: failed opening device " << _configured_device_id << "\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AudioSpec spec {
|
||||||
|
SDL_AUDIO_S16, // required, as AudioFrame2 only supports s16
|
||||||
|
1, // TODO: conf
|
||||||
|
48'000,
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_AudioSpec device_spec {
|
||||||
|
SDL_AUDIO_S16,
|
||||||
|
1, // TODO: conf
|
||||||
|
48'000,
|
||||||
|
};
|
||||||
|
// TODO: error check
|
||||||
|
SDL_GetAudioDeviceFormat(_virtual_device_id, &device_spec, nullptr);
|
||||||
|
|
||||||
|
// error check
|
||||||
|
auto* sdl_stream = SDL_CreateAudioStream(&device_spec, &spec);
|
||||||
|
|
||||||
|
// error check
|
||||||
|
SDL_BindAudioStream(_virtual_device_id, sdl_stream);
|
||||||
|
|
||||||
|
auto new_stream = std::make_shared<SDLAudio2StreamReader>();
|
||||||
|
// TODO: move to ctr
|
||||||
|
new_stream->_stream = {sdl_stream, &SDL_DestroyAudioStream};
|
||||||
|
new_stream->_sample_rate = spec.freq;
|
||||||
|
new_stream->_channels = spec.channels;
|
||||||
|
|
||||||
|
_streams.emplace_back(new_stream);
|
||||||
|
|
||||||
|
return new_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLAudio2InputDevice::unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame2>>& sub) {
|
||||||
|
for (auto it = _streams.cbegin(); it != _streams.cend(); it++) {
|
||||||
|
if (*it == sub) {
|
||||||
|
_streams.erase(it);
|
||||||
|
if (_streams.empty()) {
|
||||||
|
// last stream, close
|
||||||
|
// TODO: make sure no shared ptr still exists???
|
||||||
|
SDL_CloseAudioDevice(_virtual_device_id);
|
||||||
|
std::cout << "SDLAID: closing device " << _virtual_device_id << " (" << _configured_device_id << ")\n";
|
||||||
|
_virtual_device_id = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not need to be visible in the header
|
||||||
|
struct SDLAudio2OutputDeviceDefaultInstance : public AudioFrame2Stream2I {
|
||||||
|
std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)> _stream;
|
||||||
|
|
||||||
|
uint32_t _last_sample_rate {48'000};
|
||||||
|
size_t _last_channels {0};
|
||||||
|
|
||||||
|
// TODO: audio device
|
||||||
|
SDLAudio2OutputDeviceDefaultInstance(void);
|
||||||
|
SDLAudio2OutputDeviceDefaultInstance(SDLAudio2OutputDeviceDefaultInstance&& other);
|
||||||
|
|
||||||
|
~SDLAudio2OutputDeviceDefaultInstance(void);
|
||||||
|
|
||||||
|
int32_t size(void) override;
|
||||||
|
std::optional<AudioFrame2> pop(void) override;
|
||||||
|
bool push(const AudioFrame2& value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
SDLAudio2OutputDeviceDefaultInstance::SDLAudio2OutputDeviceDefaultInstance(void) : _stream(nullptr, nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLAudio2OutputDeviceDefaultInstance::SDLAudio2OutputDeviceDefaultInstance(SDLAudio2OutputDeviceDefaultInstance&& other) : _stream(std::move(other._stream)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLAudio2OutputDeviceDefaultInstance::~SDLAudio2OutputDeviceDefaultInstance(void) {
|
||||||
|
}
|
||||||
|
int32_t SDLAudio2OutputDeviceDefaultInstance::size(void) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AudioFrame2> SDLAudio2OutputDeviceDefaultInstance::pop(void) {
|
||||||
|
assert(false);
|
||||||
|
// this is an output device, there is no data to pop
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLAudio2OutputDeviceDefaultInstance::push(const AudioFrame2& value) {
|
||||||
|
if (!static_cast<bool>(_stream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify here the fame has the same 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
|
||||||
|
) {
|
||||||
|
const SDL_AudioSpec spec = {
|
||||||
|
static_cast<SDL_AudioFormat>(SDL_AUDIO_S16),
|
||||||
|
static_cast<int>(value.channels),
|
||||||
|
static_cast<int>(value.sample_rate)
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_SetAudioStreamFormat(_stream.get(), &spec, nullptr);
|
||||||
|
|
||||||
|
std::cerr << "SDLAOD: audio format changed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
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))) {
|
||||||
|
std::cerr << "put data error\n";
|
||||||
|
return false; // return true?
|
||||||
|
}
|
||||||
|
|
||||||
|
_last_sample_rate = value.sample_rate;
|
||||||
|
_last_channels = value.channels;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SDLAudio2OutputDeviceDefaultSink::~SDLAudio2OutputDeviceDefaultSink(void) {
|
||||||
|
// TODO: pause and close device?
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FrameStream2I<AudioFrame2>> SDLAudio2OutputDeviceDefaultSink::subscribe(void) {
|
||||||
|
auto new_instance = std::make_shared<SDLAudio2OutputDeviceDefaultInstance>();
|
||||||
|
|
||||||
|
constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 48000 };
|
||||||
|
|
||||||
|
new_instance->_stream = {
|
||||||
|
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr),
|
||||||
|
&SDL_DestroyAudioStream
|
||||||
|
};
|
||||||
|
new_instance->_last_sample_rate = spec.freq;
|
||||||
|
new_instance->_last_channels = spec.channels;
|
||||||
|
|
||||||
|
if (!static_cast<bool>(new_instance->_stream)) {
|
||||||
|
std::cerr << "SDL open audio device failed!\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto audio_device_id = SDL_GetAudioStreamDevice(new_instance->_stream.get());
|
||||||
|
SDL_ResumeAudioDevice(audio_device_id);
|
||||||
|
|
||||||
|
return new_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLAudio2OutputDeviceDefaultSink::unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame2>>& sub) {
|
||||||
|
// TODO: i think we should keep track of them
|
||||||
|
if (!sub) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
43
src/frame_streams/sdl/sdl_audio2_frame_stream2.hpp
Normal file
43
src/frame_streams/sdl/sdl_audio2_frame_stream2.hpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../frame_stream2.hpp"
|
||||||
|
#include "../audio_stream2.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// we dont have to multicast ourself, because sdl streams and virtual devices already do this
|
||||||
|
|
||||||
|
// source
|
||||||
|
// opens device
|
||||||
|
// creates a sdl audio stream for each subscribed reader stream
|
||||||
|
struct SDLAudio2InputDevice : public FrameStream2SourceI<AudioFrame2> {
|
||||||
|
// held by instances
|
||||||
|
using sdl_stream_type = std::unique_ptr<SDL_AudioStream, decltype(&SDL_DestroyAudioStream)>;
|
||||||
|
|
||||||
|
SDL_AudioDeviceID _configured_device_id {0};
|
||||||
|
SDL_AudioDeviceID _virtual_device_id {0};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<FrameStream2I<AudioFrame2>>> _streams;
|
||||||
|
|
||||||
|
SDLAudio2InputDevice(void);
|
||||||
|
SDLAudio2InputDevice(SDL_AudioDeviceID conf_device_id);
|
||||||
|
~SDLAudio2InputDevice(void);
|
||||||
|
|
||||||
|
std::shared_ptr<FrameStream2I<AudioFrame2>> subscribe(void) override;
|
||||||
|
bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame2>>& sub) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// sink
|
||||||
|
// constructs entirely new streams, since sdl handles sync and mixing for us (or should)
|
||||||
|
struct SDLAudio2OutputDeviceDefaultSink : public FrameStream2SinkI<AudioFrame2> {
|
||||||
|
// TODO: pause device?
|
||||||
|
|
||||||
|
~SDLAudio2OutputDeviceDefaultSink(void);
|
||||||
|
|
||||||
|
std::shared_ptr<FrameStream2I<AudioFrame2>> subscribe(void) override;
|
||||||
|
bool unsubscribe(const std::shared_ptr<FrameStream2I<AudioFrame2>>& sub) override;
|
||||||
|
};
|
||||||
|
|
@ -28,6 +28,8 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
runSysCheck();
|
runSysCheck();
|
||||||
|
|
||||||
|
SDL_SetAppMetadata("tomato", "0.0.0-wip", nullptr);
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
// change current working dir to internal storage
|
// change current working dir to internal storage
|
||||||
std::filesystem::current_path(SDL_GetAndroidInternalStoragePath());
|
std::filesystem::current_path(SDL_GetAndroidInternalStoragePath());
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <solanaceae/contact/components.hpp>
|
#include <solanaceae/contact/components.hpp>
|
||||||
|
|
||||||
|
#include "./frame_streams/sdl/sdl_audio2_frame_stream2.hpp"
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
@ -138,9 +140,44 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf.dump();
|
conf.dump();
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
|
// add system audio devices
|
||||||
|
{ // audio in
|
||||||
|
ObjectHandle asrc {os.registry(), os.registry().create()};
|
||||||
|
try {
|
||||||
|
asrc.emplace<Components::FrameStream2Source<AudioFrame2>>(
|
||||||
|
std::make_unique<SDLAudio2InputDevice>()
|
||||||
|
);
|
||||||
|
|
||||||
|
asrc.emplace<Components::StreamSource>(Components::StreamSource::create<AudioFrame2>("SDL Audio Default Recording Device"));
|
||||||
|
|
||||||
|
os.throwEventConstruct(asrc);
|
||||||
|
} catch (...) {
|
||||||
|
os.registry().destroy(asrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // audio out
|
||||||
|
ObjectHandle asink {os.registry(), os.registry().create()};
|
||||||
|
try {
|
||||||
|
asink.emplace<Components::FrameStream2Sink<AudioFrame2>>(
|
||||||
|
std::make_unique<SDLAudio2OutputDeviceDefaultSink>()
|
||||||
|
);
|
||||||
|
|
||||||
|
asink.emplace<Components::StreamSink>(Components::StreamSink::create<AudioFrame2>("SDL Audio Default Playback Device"));
|
||||||
|
|
||||||
|
os.throwEventConstruct(asink);
|
||||||
|
} catch (...) {
|
||||||
|
os.registry().destroy(asink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "MS warning: no sdl audio: " << SDL_GetError() << "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainScreen::~MainScreen(void) {
|
MainScreen::~MainScreen(void) {
|
||||||
|
// TODO: quit sdl audio
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainScreen::handleEvent(SDL_Event& e) {
|
bool MainScreen::handleEvent(SDL_Event& e) {
|
||||||
|
Loading…
Reference in New Issue
Block a user