Mercurial > foo_out_sdl
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 } |
