comparison foosdk/sdk/foobar2000/SDK/output.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "foobar2000-sdk-pch.h"
2 #include "output.h"
3 #include "audio_chunk_impl.h"
4 #include "dsp.h"
5 #include "resampler.h"
6
7 pfc::string8 output_entry::get_device_name( const GUID & deviceID ) {
8 pfc::string8 temp;
9 if (!get_device_name(deviceID, temp)) temp = "[unknown device]";
10 return temp;
11 }
12
13 namespace {
14 class output_device_enum_callback_getname : public output_device_enum_callback {
15 public:
16 output_device_enum_callback_getname( const GUID & wantID, pfc::string_base & strOut ) : m_strOut(strOut), m_wantID(wantID) {}
17 void on_device(const GUID & p_guid,const char * p_name,unsigned p_name_length) {
18 if (!m_got && p_guid == m_wantID) {
19 m_strOut.set_string(p_name, p_name_length);
20 m_got = true;
21 }
22 }
23 bool m_got = false;
24 pfc::string_base & m_strOut;
25 const GUID m_wantID;
26 };
27
28 }
29
30 bool output_entry::get_device_name( const GUID & deviceID, pfc::string_base & out ) {
31 output_device_enum_callback_getname cb(deviceID, out);
32 this->enum_devices(cb);
33 return cb.m_got;
34 }
35
36 bool output_entry::g_find( const GUID & outputID, output_entry::ptr & outObj ) {
37 for (auto obj : enumerate()) {
38 if (obj->get_guid() == outputID) {
39 outObj = obj; return true;
40 }
41 }
42 return false;
43 }
44
45 output_entry::ptr output_entry::g_find( const GUID & outputID ) {
46 output_entry::ptr ret;
47 if (!g_find( outputID, ret ) ) throw exception_output_module_not_found();
48 return ret;
49 }
50
51
52 bool output::is_progressing_() {
53 output_v4::ptr v4;
54 if ( v4 &= this ) return v4->is_progressing();
55 return true;
56 }
57
58 size_t output::update_v2_() {
59 output_v4::ptr v4;
60 if ( v4 &= this ) return v4->update_v2();
61 bool bReady = false;
62 this->update(bReady);
63 return bReady ? SIZE_MAX : 0;
64 }
65
66 pfc::eventHandle_t output::get_trigger_event_() {
67 output_v4::ptr v4;
68 if ( v4 &= this ) return v4->get_trigger_event();
69 return pfc::eventInvalid;
70 }
71
72 size_t output::process_samples_v2_(const audio_chunk& c) {
73 output_v6::ptr v6;
74 if (v6 &= this) return v6->process_samples_v2(c);
75 this->process_samples(c);
76 return c.get_sample_count();
77 }
78
79 void output_impl::on_flush_internal() {
80 m_eos = false; m_sent_force_play = false;
81 m_incoming_ptr = 0;
82 m_incoming.set_size(0);
83 }
84
85 void output_impl::flush() {
86 on_flush_internal();
87 on_flush();
88 }
89
90 void output_impl::flush_changing_track() {
91 on_flush_internal();
92 on_flush_changing_track();
93 }
94
95 void output_impl::update(bool & p_ready) {
96 p_ready = update_v2() > 0;
97 }
98 size_t output_impl::update_v2() {
99
100 // Clear preemptively
101 m_can_write = 0;
102
103 on_update();
104
105 // No data yet, nothing to do, want data, can't signal how much because we don't know the format
106 if (!m_incoming_spec.is_valid()) return SIZE_MAX;
107
108 // First chunk in or format change
109 if (m_incoming_spec != m_active_spec) {
110 if (get_latency_samples() == 0) {
111 // Ready for new format
112 m_sent_force_play = false;
113 open(m_incoming_spec);
114 m_active_spec = m_incoming_spec;
115 } else {
116 // Previous format still playing, accept no more data
117 this->send_force_play();
118 return 0;
119 }
120 }
121
122 // opened for m_incoming_spec stream
123
124 // Store & update m_can_write on our end
125 // We don't know what can_write_samples() actually does, could be expensive, avoid calling it repeatedly
126 m_can_write = this->can_write_samples();
127
128 if (m_incoming_ptr < m_incoming.get_size()) {
129 t_size delta = pfc::min_t(m_incoming.get_size() - m_incoming_ptr, m_can_write * m_incoming_spec.chanCount);
130 if (delta > 0) {
131 PFC_ASSERT(!m_sent_force_play);
132 write(audio_chunk_temp_impl(m_incoming.get_ptr() + m_incoming_ptr, delta / m_incoming_spec.chanCount, m_incoming_spec.sampleRate, m_incoming_spec.chanCount, m_incoming_spec.chanMask));
133 m_incoming_ptr += delta;
134 if (m_eos && this->queue_empty()) {
135 this->send_force_play();
136 }
137 }
138
139 m_can_write -= delta / m_incoming_spec.chanCount;
140 }
141 return m_can_write;
142 }
143
144 double output_impl::get_latency() {
145 double ret = 0;
146 if (m_incoming_spec.is_valid()) {
147 ret += audio_math::samples_to_time( (m_incoming.get_size() - m_incoming_ptr) / m_incoming_spec.chanCount, m_incoming_spec.sampleRate );
148 }
149 if (m_active_spec.is_valid()) {
150 ret += audio_math::samples_to_time( get_latency_samples() , m_active_spec.sampleRate );
151 }
152 return ret;
153 }
154
155 void output_impl::force_play() {
156 if ( m_eos ) return;
157 m_eos = true;
158 if (queue_empty()) send_force_play();
159 }
160 void output_impl::send_force_play() {
161 if (m_sent_force_play) return;
162 m_sent_force_play = true;
163 this->on_force_play();
164 }
165
166 static void spec_sanity(audio_chunk::spec_t const& spec) {
167 if (!spec.is_valid()) pfc::throw_exception_with_message< exception_io_data >("Invalid audio stream specifications");
168 }
169
170 size_t output_impl::process_samples_v2(const audio_chunk& p_chunk) {
171 PFC_ASSERT(queue_empty());
172 PFC_ASSERT(!m_eos);
173 const auto spec = p_chunk.get_spec();
174 if (m_incoming_spec != spec) {
175 spec_sanity(spec);
176 m_incoming_spec = spec;
177 return 0;
178 }
179
180 auto in = p_chunk.get_sample_count();
181 if (in > m_can_write) in = m_can_write;
182 if (in > 0) {
183 write(audio_chunk_partial_ref(p_chunk, 0, in));
184 m_can_write -= in;
185 }
186 return in;
187 }
188
189 void output_impl::process_samples(const audio_chunk & p_chunk) {
190 PFC_ASSERT(queue_empty());
191 PFC_ASSERT( !m_eos );
192 const auto spec = p_chunk.get_spec();
193 size_t taken = 0;
194 if (m_incoming_spec == spec) {
195 // Try bypassing intermediate buffer
196 taken = this->process_samples_v2(p_chunk);
197 if (taken == p_chunk.get_sample_count()) return; // all written, success
198 taken *= spec.chanCount;
199 } else {
200 spec_sanity(spec);
201 m_incoming_spec = spec;
202 }
203 // Queue what's left for update() to eat later
204 m_incoming.set_data_fromptr(p_chunk.get_data() + taken, p_chunk.get_used_size() - taken);
205 m_incoming_ptr = 0;
206 }
207
208 void output_v3::get_injected_dsps( dsp_chain_config & dsps ) {
209 dsps.remove_all();
210 }
211
212 size_t output_v4::update_v2() {
213 bool bReady = false;
214 update(bReady);
215 return bReady ? SIZE_MAX : 0;
216 }
217
218 uint32_t output_entry::get_config_flags_compat() {
219 uint32_t ret = get_config_flags();
220 if ((ret & (flag_low_latency | flag_high_latency)) == 0) {
221 // output predating flag_high_latency + flag_low_latency
222 // if it's old foo_out_upnp, report high latency, otherwise low latency.
223 static const GUID guid_foo_out_upnp = { 0x9900b4f6, 0x8431, 0x4b0a, { 0x95, 0x56, 0xa7, 0xfc, 0xb9, 0x5b, 0x74, 0x3 } };
224 if (this->get_guid() == guid_foo_out_upnp) ret |= flag_high_latency;
225 else ret |= flag_low_latency;
226 }
227 return ret;
228 }
229
230 bool output_entry::is_high_latency() {
231 return (this->get_config_flags_compat() & flag_high_latency) != 0;
232 }
233
234 bool output_entry::is_low_latency() {
235 return (this->get_config_flags_compat() & flag_low_latency) != 0;
236 }
237
238 // {EEEB07DE-C2C8-44c2-985C-C85856D96DA1}
239 const GUID output_id_null =
240 { 0xeeeb07de, 0xc2c8, 0x44c2, { 0x98, 0x5c, 0xc8, 0x58, 0x56, 0xd9, 0x6d, 0xa1 } };
241
242 // {D41D2423-FBB0-4635-B233-7054F79814AB}
243 const GUID output_id_default =
244 { 0xd41d2423, 0xfbb0, 0x4635, { 0xb2, 0x33, 0x70, 0x54, 0xf7, 0x98, 0x14, 0xab } };
245
246 outputCoreConfig_t outputCoreConfig_t::defaults() {
247 outputCoreConfig_t cfg = {};
248 cfg.m_bitDepth = 16;
249 cfg.m_buffer_length = 1.0;
250 cfg.m_output = output_id_default;
251 // remaining fields nulled by {}
252 return cfg;
253 }
254 namespace {
255 class output_device_list_callback_impl : public output_device_list_callback {
256 public:
257 void onDevice( const char * fullName, const GUID & output, const GUID & device ) {
258 f(fullName, output, device);
259 }
260 std::function< void ( const char*, const GUID&, const GUID&) > f;
261 };
262
263 class output_config_change_callback_impl : public output_config_change_callback {
264 public:
265 void outputConfigChanged() {
266 f();
267 }
268 std::function<void () > f;
269 };
270 }
271 void output_manager_v2::listDevices( std::function< void ( const char*, const GUID&, const GUID&) > f ) {
272 output_device_list_callback_impl cb; cb.f = f;
273 this->listDevices( cb );
274 }
275
276 service_ptr output_manager_v2::addCallback( std::function<void() > f ) {
277 output_config_change_callback_impl * obj = new output_config_change_callback_impl();
278 obj->f = f;
279 this->addCallback( obj );
280 service_ptr_t<output_manager_v2> selfRef ( this );
281 return fb2k::callOnRelease( [obj, selfRef] {
282 selfRef->removeCallback( obj ); delete obj;
283 } );
284 }
285
286 void output_manager_v2::addCallbackPermanent( std::function<void()> f ) {
287 output_config_change_callback_impl * obj = new output_config_change_callback_impl();
288 obj->f = f;
289 addCallback( obj );
290 }
291
292 void output_manager::getCoreConfig(outputCoreConfig_t& out) {
293 getCoreConfig(&out, sizeof(out));
294 }
295
296 outputCoreConfig_t output_manager::getCoreConfig() {
297 outputCoreConfig_t ret; getCoreConfig(ret); return ret;
298 }