OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
sdl_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#if defined(OSSIA_ENABLE_SDL)
5#if __has_include(<SDL2/SDL_audio.h>)
6#include <SDL2/SDL_config.h>
7#if !defined(SDL_AUDIO_DISABLED)
8#include <ossia/audio/audio_engine.hpp>
9#include <ossia/detail/pod_vector.hpp>
10#include <ossia/detail/thread.hpp>
11
12#include <SDL2/SDL.h>
13#include <SDL2/SDL_audio.h>
14
15#define OSSIA_AUDIO_SDL 1
16
17namespace ossia
18{
19class sdl_protocol final : public audio_engine
20{
21 static constexpr int inputs = 0;
22 static constexpr int outputs = 2;
23
24public:
25 sdl_protocol(int rate, int bs)
26 {
27 SDL_Init(SDL_INIT_AUDIO);
28 m_desired.freq = rate;
29 m_desired.format = AUDIO_F32SYS;
30 m_desired.channels = outputs;
31 m_desired.samples = bs;
32 m_desired.callback = SDLCallback;
33 m_desired.userdata = this;
34
35 m_deviceId = SDL_OpenAudioDevice(nullptr, 0, &m_desired, &m_obtained, 0);
36
37 if(m_deviceId < 2)
38 {
39 using namespace std::literals;
40 throw std::runtime_error("SDL: Couldn't open audio: "s + SDL_GetError());
41 }
42
43 this->effective_sample_rate = m_obtained.freq;
44 this->effective_buffer_size = m_obtained.samples;
45 this->effective_inputs = 0;
46 this->effective_outputs = m_obtained.channels;
47
48 // Preallocate so that the audio thread does not have to: this only ever
49 // gets resized if SDL changes the callback buffer size under our feet.
50 m_channels.resize(m_obtained.channels);
51 m_scratch.resize(std::size_t(m_obtained.samples) * m_obtained.channels);
52
53 SDL_PauseAudioDevice(m_deviceId, 0);
54 m_activated = true;
55 }
56
57 ~sdl_protocol() override { stop(); }
58
59 bool running() const override
60 {
61 return m_activated && SDL_GetAudioDeviceStatus(m_deviceId) == SDL_AUDIO_PLAYING;
62 }
63
64 void stop() override
65 {
66 audio_engine::stop();
67 if(m_activated)
68 {
69 SDL_CloseAudioDevice(m_deviceId);
70 m_activated = false;
71 }
72 // Not SDL_Quit(): other parts of ossia (joystick, gamecontroller, sensors,
73 // haptics) may be using SDL at the same time.
74 SDL_QuitSubSystem(SDL_INIT_AUDIO);
75 }
76
77private:
78 static void SDLCallback(void* userData, Uint8* data, int bytes)
79 {
80 [[maybe_unused]]
81 static const thread_local auto _
82 = [] {
83 ossia::set_thread_name("ossia audio 0");
84 ossia::set_thread_pinned(thread_type::Audio, 0);
85 return 0;
86 }();
87
88 auto& self = *static_cast<sdl_protocol*>(userData);
89 self.tick_start();
90 if(!self.m_start)
91 self.m_start = std::chrono::steady_clock::now();
92
93 auto audio_out = reinterpret_cast<float*>(data);
94 const int out_chan = self.m_obtained.channels;
95 assert(out_chan > 0);
96
97 // Note: the amount of data asked for here is *not* guaranteed to match the
98 // SDL_AudioSpec we got when opening the device. SDL keeps its own copy of
99 // that spec and backends are free to rewrite it while the stream runs: on
100 // Windows, WASAPI resizes the callback buffer to the device period every
101 // time the device is reset (default device changed, format changed in the
102 // control panel, endpoint invalidated...), see UpdateAudioStream() in
103 // SDL_wasapi.c. Our m_obtained is a stale copy at that point, so the byte
104 // count we are given is the only thing we can trust.
105 const int frames = bytes / int(sizeof(float) * out_chan);
106
107 if(self.stop_processing || frames <= 0)
108 {
109 self.tick_clear();
110 memset(data, 0, bytes);
111 return;
112 }
113
114 {
115 const std::size_t samples = std::size_t(frames) * out_chan;
116 if(self.m_scratch.size() < samples)
117 self.m_scratch.resize(samples);
118
119 float* const float_data = self.m_scratch.data();
120 memset(float_data, 0, sizeof(float) * samples);
121
122 float** const float_output = self.m_channels.data();
123 for(int c = 0; c < out_chan; c++)
124 {
125 float_output[c] = float_data + c * frames;
126 }
127
128 // if one day there's input... samples[j++] / 32768.;
129
130 auto now = std::chrono::steady_clock::now();
131 auto nsecs
132 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
133 .count()
134 / 1e9;
135
136 ossia::audio_tick_state ts{nullptr, float_output, 0,
137 out_chan, (uint64_t)frames, nsecs};
138 self.audio_tick(ts);
139
140 for(int j = 0; j < frames; j++)
141 for(int c = 0; c < out_chan; c++)
142 *audio_out++ = float_output[c][j];
143
144 // Zero the trailing partial frame if SDL asked for a size which is not a
145 // multiple of the frame size
146 const int written = int(sizeof(float) * samples);
147 if(written < bytes)
148 memset(data + written, 0, bytes - written);
149
150 self.tick_end();
151 self.m_total_frames += frames;
152 }
153 }
154
155 SDL_AudioDeviceID m_deviceId{};
156 SDL_AudioSpec m_desired, m_obtained;
157 ossia::float_vector m_scratch;
158 ossia::pod_vector<float*> m_channels;
159 uint64_t m_total_frames{};
160 std::optional<std::chrono::steady_clock::time_point> m_start;
161 bool m_activated{};
162};
163}
164
165#endif
166#endif
167#endif
Definition git_info.h:7