Mercurial > foo_out_sdl
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/SDK/output.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,298 @@ +#include "foobar2000-sdk-pch.h" +#include "output.h" +#include "audio_chunk_impl.h" +#include "dsp.h" +#include "resampler.h" + +pfc::string8 output_entry::get_device_name( const GUID & deviceID ) { + pfc::string8 temp; + if (!get_device_name(deviceID, temp)) temp = "[unknown device]"; + return temp; +} + +namespace { + class output_device_enum_callback_getname : public output_device_enum_callback { + public: + output_device_enum_callback_getname( const GUID & wantID, pfc::string_base & strOut ) : m_strOut(strOut), m_wantID(wantID) {} + void on_device(const GUID & p_guid,const char * p_name,unsigned p_name_length) { + if (!m_got && p_guid == m_wantID) { + m_strOut.set_string(p_name, p_name_length); + m_got = true; + } + } + bool m_got = false; + pfc::string_base & m_strOut; + const GUID m_wantID; + }; + +} + +bool output_entry::get_device_name( const GUID & deviceID, pfc::string_base & out ) { + output_device_enum_callback_getname cb(deviceID, out); + this->enum_devices(cb); + return cb.m_got; +} + +bool output_entry::g_find( const GUID & outputID, output_entry::ptr & outObj ) { + for (auto obj : enumerate()) { + if (obj->get_guid() == outputID) { + outObj = obj; return true; + } + } + return false; +} + +output_entry::ptr output_entry::g_find( const GUID & outputID ) { + output_entry::ptr ret; + if (!g_find( outputID, ret ) ) throw exception_output_module_not_found(); + return ret; +} + + +bool output::is_progressing_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->is_progressing(); + return true; +} + +size_t output::update_v2_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->update_v2(); + bool bReady = false; + this->update(bReady); + return bReady ? SIZE_MAX : 0; +} + +pfc::eventHandle_t output::get_trigger_event_() { + output_v4::ptr v4; + if ( v4 &= this ) return v4->get_trigger_event(); + return pfc::eventInvalid; +} + +size_t output::process_samples_v2_(const audio_chunk& c) { + output_v6::ptr v6; + if (v6 &= this) return v6->process_samples_v2(c); + this->process_samples(c); + return c.get_sample_count(); +} + +void output_impl::on_flush_internal() { + m_eos = false; m_sent_force_play = false; + m_incoming_ptr = 0; + m_incoming.set_size(0); +} + +void output_impl::flush() { + on_flush_internal(); + on_flush(); +} + +void output_impl::flush_changing_track() { + on_flush_internal(); + on_flush_changing_track(); +} + +void output_impl::update(bool & p_ready) { + p_ready = update_v2() > 0; +} +size_t output_impl::update_v2() { + + // Clear preemptively + m_can_write = 0; + + on_update(); + + // No data yet, nothing to do, want data, can't signal how much because we don't know the format + if (!m_incoming_spec.is_valid()) return SIZE_MAX; + + // First chunk in or format change + if (m_incoming_spec != m_active_spec) { + if (get_latency_samples() == 0) { + // Ready for new format + m_sent_force_play = false; + open(m_incoming_spec); + m_active_spec = m_incoming_spec; + } else { + // Previous format still playing, accept no more data + this->send_force_play(); + return 0; + } + } + + // opened for m_incoming_spec stream + + // Store & update m_can_write on our end + // We don't know what can_write_samples() actually does, could be expensive, avoid calling it repeatedly + m_can_write = this->can_write_samples(); + + if (m_incoming_ptr < m_incoming.get_size()) { + t_size delta = pfc::min_t(m_incoming.get_size() - m_incoming_ptr, m_can_write * m_incoming_spec.chanCount); + if (delta > 0) { + PFC_ASSERT(!m_sent_force_play); + 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)); + m_incoming_ptr += delta; + if (m_eos && this->queue_empty()) { + this->send_force_play(); + } + } + + m_can_write -= delta / m_incoming_spec.chanCount; + } + return m_can_write; +} + +double output_impl::get_latency() { + double ret = 0; + if (m_incoming_spec.is_valid()) { + ret += audio_math::samples_to_time( (m_incoming.get_size() - m_incoming_ptr) / m_incoming_spec.chanCount, m_incoming_spec.sampleRate ); + } + if (m_active_spec.is_valid()) { + ret += audio_math::samples_to_time( get_latency_samples() , m_active_spec.sampleRate ); + } + return ret; +} + +void output_impl::force_play() { + if ( m_eos ) return; + m_eos = true; + if (queue_empty()) send_force_play(); +} +void output_impl::send_force_play() { + if (m_sent_force_play) return; + m_sent_force_play = true; + this->on_force_play(); +} + +static void spec_sanity(audio_chunk::spec_t const& spec) { + if (!spec.is_valid()) pfc::throw_exception_with_message< exception_io_data >("Invalid audio stream specifications"); +} + +size_t output_impl::process_samples_v2(const audio_chunk& p_chunk) { + PFC_ASSERT(queue_empty()); + PFC_ASSERT(!m_eos); + const auto spec = p_chunk.get_spec(); + if (m_incoming_spec != spec) { + spec_sanity(spec); + m_incoming_spec = spec; + return 0; + } + + auto in = p_chunk.get_sample_count(); + if (in > m_can_write) in = m_can_write; + if (in > 0) { + write(audio_chunk_partial_ref(p_chunk, 0, in)); + m_can_write -= in; + } + return in; +} + +void output_impl::process_samples(const audio_chunk & p_chunk) { + PFC_ASSERT(queue_empty()); + PFC_ASSERT( !m_eos ); + const auto spec = p_chunk.get_spec(); + size_t taken = 0; + if (m_incoming_spec == spec) { + // Try bypassing intermediate buffer + taken = this->process_samples_v2(p_chunk); + if (taken == p_chunk.get_sample_count()) return; // all written, success + taken *= spec.chanCount; + } else { + spec_sanity(spec); + m_incoming_spec = spec; + } + // Queue what's left for update() to eat later + m_incoming.set_data_fromptr(p_chunk.get_data() + taken, p_chunk.get_used_size() - taken); + m_incoming_ptr = 0; +} + +void output_v3::get_injected_dsps( dsp_chain_config & dsps ) { + dsps.remove_all(); +} + +size_t output_v4::update_v2() { + bool bReady = false; + update(bReady); + return bReady ? SIZE_MAX : 0; +} + +uint32_t output_entry::get_config_flags_compat() { + uint32_t ret = get_config_flags(); + if ((ret & (flag_low_latency | flag_high_latency)) == 0) { + // output predating flag_high_latency + flag_low_latency + // if it's old foo_out_upnp, report high latency, otherwise low latency. + static const GUID guid_foo_out_upnp = { 0x9900b4f6, 0x8431, 0x4b0a, { 0x95, 0x56, 0xa7, 0xfc, 0xb9, 0x5b, 0x74, 0x3 } }; + if (this->get_guid() == guid_foo_out_upnp) ret |= flag_high_latency; + else ret |= flag_low_latency; + } + return ret; +} + +bool output_entry::is_high_latency() { + return (this->get_config_flags_compat() & flag_high_latency) != 0; +} + +bool output_entry::is_low_latency() { + return (this->get_config_flags_compat() & flag_low_latency) != 0; +} + +// {EEEB07DE-C2C8-44c2-985C-C85856D96DA1} +const GUID output_id_null = +{ 0xeeeb07de, 0xc2c8, 0x44c2, { 0x98, 0x5c, 0xc8, 0x58, 0x56, 0xd9, 0x6d, 0xa1 } }; + +// {D41D2423-FBB0-4635-B233-7054F79814AB} +const GUID output_id_default = +{ 0xd41d2423, 0xfbb0, 0x4635, { 0xb2, 0x33, 0x70, 0x54, 0xf7, 0x98, 0x14, 0xab } }; + +outputCoreConfig_t outputCoreConfig_t::defaults() { + outputCoreConfig_t cfg = {}; + cfg.m_bitDepth = 16; + cfg.m_buffer_length = 1.0; + cfg.m_output = output_id_default; + // remaining fields nulled by {} + return cfg; +} +namespace { + class output_device_list_callback_impl : public output_device_list_callback { + public: + void onDevice( const char * fullName, const GUID & output, const GUID & device ) { + f(fullName, output, device); + } + std::function< void ( const char*, const GUID&, const GUID&) > f; + }; + + class output_config_change_callback_impl : public output_config_change_callback { + public: + void outputConfigChanged() { + f(); + } + std::function<void () > f; + }; +} +void output_manager_v2::listDevices( std::function< void ( const char*, const GUID&, const GUID&) > f ) { + output_device_list_callback_impl cb; cb.f = f; + this->listDevices( cb ); +} + +service_ptr output_manager_v2::addCallback( std::function<void() > f ) { + output_config_change_callback_impl * obj = new output_config_change_callback_impl(); + obj->f = f; + this->addCallback( obj ); + service_ptr_t<output_manager_v2> selfRef ( this ); + return fb2k::callOnRelease( [obj, selfRef] { + selfRef->removeCallback( obj ); delete obj; + } ); +} + +void output_manager_v2::addCallbackPermanent( std::function<void()> f ) { + output_config_change_callback_impl * obj = new output_config_change_callback_impl(); + obj->f = f; + addCallback( obj ); +} + +void output_manager::getCoreConfig(outputCoreConfig_t& out) { + getCoreConfig(&out, sizeof(out)); +} + +outputCoreConfig_t output_manager::getCoreConfig() { + outputCoreConfig_t ret; getCoreConfig(ret); return ret; +}
