comparison foosdk/sdk/foobar2000/SDK/output.h @ 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 #pragma once
2
3 #include <functional>
4
5 PFC_DECLARE_EXCEPTION(exception_output_device_not_found, pfc::exception, "Audio device not found")
6 PFC_DECLARE_EXCEPTION(exception_output_module_not_found, exception_output_device_not_found, "Output module not found")
7 PFC_DECLARE_EXCEPTION(exception_output_invalidated, pfc::exception, "Audio device invalidated")
8 PFC_DECLARE_EXCEPTION(exception_output_device_in_use, pfc::exception, "Audio device in use")
9 PFC_DECLARE_EXCEPTION(exception_output_unsupported_stream_format, pfc::exception, "Unsupported audio stream format")
10
11 //! Output interrupted due to another application taking exclusive access to the device - do not complain to user.
12 PFC_DECLARE_EXCEPTION(exception_output_interrupted, pfc::exception, "Output interrupted");
13
14 //! Structure describing PCM audio data format, with basic helper functions.
15 struct t_pcmspec
16 {
17 unsigned m_sample_rate = 0;
18 unsigned m_bits_per_sample = 0;
19 unsigned m_channels = 0,m_channel_config = 0;
20 bool m_float = false;
21
22 inline unsigned align() const {return (m_bits_per_sample / 8) * m_channels;}
23
24 uint64_t time_to_bytes(double p_time) const {return audio_math::time_to_samples(p_time,m_sample_rate) * (m_bits_per_sample / 8) * m_channels;}
25 double bytes_to_time(uint64_t p_bytes) const {return (double) (p_bytes / ((m_bits_per_sample / 8) * m_channels)) / (double) m_sample_rate;}
26
27 inline bool operator==(/*const t_pcmspec & p_spec1,*/const t_pcmspec & p_spec2) const
28 {
29 return /*p_spec1.*/m_sample_rate == p_spec2.m_sample_rate
30 && /*p_spec1.*/m_bits_per_sample == p_spec2.m_bits_per_sample
31 && /*p_spec1.*/m_channels == p_spec2.m_channels
32 && /*p_spec1.*/m_channel_config == p_spec2.m_channel_config
33 && /*p_spec1.*/m_float == p_spec2.m_float;
34 }
35
36 inline bool operator!=(/*const t_pcmspec & p_spec1,*/const t_pcmspec & p_spec2) const
37 {
38 return !(*this == p_spec2);
39 }
40
41 inline void reset() { *this = t_pcmspec(); }
42 inline bool is_valid() const
43 {
44 return m_sample_rate >= 1000 && m_sample_rate <= 1000000 &&
45 m_channels > 0 && m_channels <= 256 && m_channel_config != 0 &&
46 (m_bits_per_sample == 8 || m_bits_per_sample == 16 || m_bits_per_sample == 24 || m_bits_per_sample == 32);
47 }
48 };
49
50 class NOVTABLE output_device_enum_callback
51 {
52 public:
53 virtual void on_device(const GUID & p_guid,const char * p_name,unsigned p_name_length) = 0;
54 };
55
56 class NOVTABLE output : public service_base {
57 FB2K_MAKE_SERVICE_INTERFACE(output,service_base);
58 public:
59 //! Retrieves amount of audio data queued for playback, in seconds.
60 virtual double get_latency() = 0;
61 //! Sends new samples to the device. Allowed to be called only when update() indicates that the device is ready. \n
62 //! update() should be called AGAIN after each process_samples() to know if the device is ready for more. \n
63 //! This method SHOULD NOT block, only copy passed chunk and return immediately.
64 virtual void process_samples(const audio_chunk & p_chunk) = 0;
65 //! Updates playback; queries whether the device is ready to receive new data. \n
66 //! This method SHOULD NOT block, only update internal state and return immediately.
67 //! @param p_ready On success, receives value indicating whether the device is ready for next process_samples() call.
68 virtual void update(bool & p_ready) = 0;
69 //! Pauses/unpauses playback.
70 virtual void pause(bool p_state) = 0;
71 //! Flushes queued audio data. Called after seeking.
72 virtual void flush() = 0;
73 //! Forces playback of queued data. Called when there's no more data to send, to prevent infinite waiting if output implementation starts actually playing after amount of data in internal buffer reaches some level.
74 virtual void force_play() = 0;
75
76 //! Sets playback volume.
77 //! @p_val Volume level in dB. Value of 0 indicates full ("100%") volume, negative values indciate different attenuation levels.
78 virtual void volume_set(double p_val) = 0;
79
80 //! Helper, see output_v4::is_progressing().
81 bool is_progressing_();
82 //! Helper, see output_v4::update_v2()
83 size_t update_v2_();
84 //! Helper, see output_v4::get_trigger_event()
85 pfc::eventHandle_t get_trigger_event_();
86 //! Helper, see output_v6::process_samples_v2()
87 size_t process_samples_v2_(const audio_chunk&);
88
89 //! Helper for output_entry implementation.
90 static uint32_t g_extra_flags() { return 0; }
91
92 };
93
94 class NOVTABLE output_v2 : public output {
95 FB2K_MAKE_SERVICE_INTERFACE(output_v2, output);
96 public:
97 //! Obsolete, do not use.
98 virtual bool want_track_marks() {return false;}
99 //! Obsolete, do not use.
100 virtual void on_track_mark() {}
101 //! Obsolete, do not use.
102 virtual void enable_fading(bool) { }
103 //! Called when flushing due to manual track change rather than seek-within-track
104 virtual void flush_changing_track() {flush();}
105 };
106
107 class dsp_chain_config;
108
109 //! \since 1.4
110 class NOVTABLE output_v3 : public output_v2 {
111 FB2K_MAKE_SERVICE_INTERFACE(output_v3, output_v2);
112 public:
113 //! Does this output require a specific sample rate? If yes, return the value, otherwise return zero. \n
114 //! Returning a nonzero will cause a resampler DSP to be injected.
115 virtual unsigned get_forced_sample_rate() { return 0; }
116 //! Allows the output to inject specific DSPs at the end of the used chain. \n
117 //! Default implementation queries get_forced_sample_rate() and injects a resampler.
118 virtual void get_injected_dsps( dsp_chain_config & );
119 };
120
121 //! \since 1.6
122 class NOVTABLE output_v4 : public output_v3 {
123 FB2K_MAKE_SERVICE_INTERFACE(output_v4, output_v3);
124 public:
125 //! Returns an event handle that becomes signaled once the output wants an update() call and possibly process_samples(). \n
126 //! Optional; may return pfc::eventInvalid if not available at this time or not supported. \n
127 //! Use the event only if update() signals that it cannot take any more data at this time. \n
128 virtual pfc::eventHandle_t get_trigger_event() {return pfc::eventInvalid;}
129 //! Returns whether the audio stream is currently being played or not. \n
130 //! Typically, for a short period of time, initially sent data is not played until a sufficient amount is queued to initiate playback without glitches. \n
131 //! For old outputs that do not implement this, the value can be assumed to be true.
132 virtual bool is_progressing() = 0;
133
134 //! Improved version of update(); returns 0 if the output isn't ready to receive any new data, otherwise an advisory number of samples - at the current stream format - that the output expects to take now. \n
135 //! If the caller changes the stream format, the value is irrelevant. \n
136 //! The output may return SIZE_MAX to indicate that it can take data but does not currently have any hints to tell how much. \n
137 //! This method SHOULD NOT block, only update output state and return immediately.
138 virtual size_t update_v2();
139 };
140
141 //! \since 1.6
142 class output_v5 : public output_v4 {
143 FB2K_MAKE_SERVICE_INTERFACE(output_v5, output_v4);
144 public:
145 virtual unsigned get_forced_channel_mask() { return 0; }
146 };
147
148 //! \since 2.2
149 class output_v6 : public output_v5 {
150 FB2K_MAKE_SERVICE_INTERFACE(output_v6, output_v5);
151 public:
152 //! Extended process_samples(), allowed to read only part of the chunk if out of buffer space to take whole.
153 //! @returns Number of samples actually taken.
154 virtual size_t process_samples_v2(const audio_chunk&) = 0;
155 };
156
157 //! \since 2.25
158 //! foobar2000 v2.25 functionality draft, use only for testing live info delivery in v2.25 beta.
159 class output_v7 : public output_v6 {
160 FB2K_MAKE_SERVICE_INTERFACE(output_v7, output_v6);
161 public:
162 //! Optional, gets notified about current playback metadata.
163 //! @param audioSource can be any type (metadb_handle, metadb_info_container, possibly other), use operator &= to determine type.
164 virtual void hint_source(fb2k::objRef audioSource) { (void)audioSource; }
165 };
166
167 class NOVTABLE output_entry : public service_base {
168 FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(output_entry);
169 public:
170 //! Instantiates output class.
171 virtual void instantiate(service_ptr_t<output> & p_out,const GUID & p_device,double p_buffer_length,bool p_dither,t_uint32 p_bitdepth) = 0;
172 //! Enumerates devices supported by this output_entry implementation.
173 virtual void enum_devices(output_device_enum_callback & p_callback) = 0;
174 //! For internal use by backend. Each output_entry implementation must have its own guid.
175 virtual GUID get_guid() = 0;
176 //! For internal use by backend. Retrieves human-readable name of this output_entry implementation.
177 virtual const char * get_name() = 0;
178
179 #ifdef _WIN32
180 //! Obsolete, do not use.
181 virtual void advanced_settings_popup(HWND,POINT) {}
182 #endif
183
184 enum {
185 flag_needs_bitdepth_config = 1 << 0,
186 flag_needs_dither_config = 1 << 1,
187 //! Obsolete, do not use.
188 flag_needs_advanced_config = 1 << 2,
189 flag_needs_device_list_prefixes = 1 << 3,
190
191 //! Supports playing multiple simultaneous audio streams thru one device?
192 flag_supports_multiple_streams = 1 << 4,
193
194 //! High latency operation (such as remote network playback), mutually exclusive with flag_low_latency
195 flag_high_latency = 1 << 5,
196 //! Low latency operation (local playback), mutually exclusive with flag_high_latency
197 flag_low_latency = 1 << 6,
198 //! When set, the output will be used in special compatibility mode: guaranteed regular update() calls, injected padding (silence) at the end of stream.
199 flag_needs_shims = 1 << 7,
200 };
201
202 virtual t_uint32 get_config_flags() = 0;
203
204 uint32_t get_config_flags_compat();
205
206 bool is_high_latency();
207 bool is_low_latency();
208
209 pfc::string8 get_device_name( const GUID & deviceID);
210 bool get_device_name( const GUID & deviceID, pfc::string_base & out );
211
212 static bool g_find( const GUID & outputID, output_entry::ptr & outObj );
213 static output_entry::ptr g_find(const GUID & outputID );
214 };
215
216 //! Helper; implements output_entry for specific output class implementation. output_entry methods are forwarded to static methods of your output class. Use output_factory_t<myoutputclass> instead of using this class directly.
217 template<typename T, typename E = output_entry>
218 class output_entry_impl_t : public E
219 {
220 public:
221 void instantiate(service_ptr_t<output> & p_out,const GUID & p_device,double p_buffer_length,bool p_dither,t_uint32 p_bitdepth) {
222 p_out = new service_impl_t<T>(p_device,p_buffer_length,p_dither,p_bitdepth);
223 }
224 void enum_devices(output_device_enum_callback & p_callback) {T::g_enum_devices(p_callback);}
225 GUID get_guid() {return T::g_get_guid();}
226 const char * get_name() {return T::g_get_name();}
227
228 t_uint32 get_config_flags() {
229 t_uint32 flags = 0;
230 if (T::g_advanced_settings_query()) flags |= output_entry::flag_needs_advanced_config;
231 if (T::g_needs_bitdepth_config()) flags |= output_entry::flag_needs_bitdepth_config;
232 if (T::g_needs_dither_config()) flags |= output_entry::flag_needs_dither_config;
233 if (T::g_needs_device_list_prefixes()) flags |= output_entry::flag_needs_device_list_prefixes;
234 if (T::g_supports_multiple_streams()) flags |= output_entry::flag_supports_multiple_streams;
235 if (T::g_is_high_latency()) flags |= output_entry::flag_high_latency;
236 else flags |= output_entry::flag_low_latency;
237 flags |= T::g_extra_flags();
238 return flags;
239 }
240 };
241
242
243 //! Use this to register your output implementation.
244 template<class T>
245 class output_factory_t : public service_factory_single_t<output_entry_impl_t<T> > {};
246
247 //! Helper base class for output implementations. \n
248 //! This is the preferred way of implementing output. \n
249 //! This is NOT a public interface and its layout changes between foobar2000 SDK versions, do not assume other outputs to implement it.
250 class output_impl : public output_v7 {
251 protected:
252 output_impl() {}
253
254 //! Called periodically. You can update your state in this method. Can do nothing if not needed.
255 virtual void on_update() = 0;
256 //! Writes an audio chunk to your output. \n
257 //! Will never get more than last can_write_samples() asked for. \n
258 //! Format being send will match last open().
259 virtual void write(const audio_chunk & p_data) = 0;
260 //! @returns How many samples write() can take at this moment. \n
261 //! It's called immediately after on_update() and can reuse value calculated in last on_update().
262 virtual t_size can_write_samples() = 0;
263 //! @returns Current latency, delay between last written sample and currently heard audio.
264 virtual t_size get_latency_samples() = 0;
265 //! Flush output, after seek etc.
266 virtual void on_flush() = 0;
267 //! Flush output due to manual track change in progress. \n
268 //! Same as on_flush() by default.
269 virtual void on_flush_changing_track() {on_flush();}
270 //! Called before first chunk and on stream format change. \n
271 //! Following write() calls will deliver chunks in the same format as specified here.
272 virtual void open(audio_chunk::spec_t const & p_spec) = 0;
273
274
275 //! Override this, not force_play(). \n
276 //! output_impl will defer call to on_force_play() until out of data in its buffer.
277 virtual void on_force_play() = 0;
278
279 // base class virtual methods which derived class must also implement
280 // virtual void pause(bool p_state) = 0;
281 // virtual void volume_set(double p_val) = 0;
282 // virtual bool is_progressing() = 0;
283 protected:
284 void on_need_reopen() {m_active_spec.clear(); }
285 private:
286 void flush() override final;
287 void flush_changing_track() override final;
288 void update(bool & p_ready) override final;
289 size_t update_v2() override final;
290 double get_latency() override final;
291 void process_samples(const audio_chunk & p_chunk) override final;
292 size_t process_samples_v2(const audio_chunk&) override final;
293 void force_play() override final;
294 void on_flush_internal();
295 void send_force_play();
296
297 bool queue_empty() const { return m_incoming_ptr == m_incoming.get_size(); }
298
299 pfc::array_t<audio_sample,pfc::alloc_fast_aggressive> m_incoming;
300 size_t m_incoming_ptr = 0, m_can_write = 0;
301 audio_chunk::spec_t m_incoming_spec,m_active_spec;
302 bool m_eos = false; // EOS issued by caller / no more data expected until a flush
303 bool m_sent_force_play = false; // set if sent on_force_play()
304 };
305
306
307 class NOVTABLE volume_callback {
308 public:
309 virtual void on_volume_scale(float v) = 0;
310 virtual void on_volume_arbitrary(int v) = 0;
311 };
312
313 class NOVTABLE volume_control : public service_base {
314 FB2K_MAKE_SERVICE_INTERFACE(volume_control, service_base)
315 public:
316 virtual void add_callback(volume_callback * ptr) = 0;
317 virtual void remove_callback(volume_callback * ptr) = 0;
318
319 enum style_t {
320 styleScale,
321 styleArbitrary
322 };
323
324 virtual style_t getStyle() = 0;
325
326 virtual float scaleGet() = 0;
327 virtual void scaleSet(float v) = 0;
328
329 virtual void arbitrarySet(int val) = 0;
330 virtual int arbitraryGet() = 0;
331 virtual int arbitraryGetMin() = 0;
332 virtual int arbitraryGetMax() = 0;
333 virtual bool arbitraryGetMute() = 0;
334 virtual void arbitrarySetMute(bool val) = 0;
335 };
336
337
338 class NOVTABLE output_entry_v2 : public output_entry {
339 FB2K_MAKE_SERVICE_INTERFACE(output_entry_v2, output_entry)
340 public:
341 virtual bool get_volume_control(const GUID & id, volume_control::ptr & out) = 0;
342 virtual bool hasVisualisation() = 0;
343 };
344
345 //! \since 1.5
346 class NOVTABLE output_devices_notify {
347 public:
348 virtual void output_devices_changed() = 0;
349 protected:
350 output_devices_notify() {}
351 private:
352 output_devices_notify(const output_devices_notify &) = delete;
353 void operator=(const output_devices_notify &) = delete;
354 };
355
356 //! \since 1.5
357 class NOVTABLE output_entry_v3 : public output_entry_v2 {
358 FB2K_MAKE_SERVICE_INTERFACE(output_entry_v3, output_entry_v2)
359 public:
360
361 //! Main thread only!
362 virtual void add_notify(output_devices_notify *) = 0;
363 //! Main thread only!
364 virtual void remove_notify(output_devices_notify *) = 0;
365
366 //! Main thread only!
367 virtual void set_pinned_device(const GUID & guid) = 0;
368 };
369
370 #pragma pack(push, 1)
371 //! \since 1.3.5
372 struct outputCoreConfig_t {
373
374 static outputCoreConfig_t defaults();
375
376 GUID m_output;
377 GUID m_device;
378 double m_buffer_length;
379 uint32_t m_flags;
380 uint32_t m_bitDepth;
381 enum { flagUseDither = 1 << 0, flagUseFades = 1 << 1 };
382 };
383 #pragma pack(pop)
384
385 //! \since 1.3.5
386 //! Allows components to access foobar2000 core's output settings.
387 class NOVTABLE output_manager : public service_base {
388 FB2K_MAKE_SERVICE_COREAPI(output_manager);
389 public:
390 //! Instantiates an output instance with core settings.
391 //! @param overrideBufferLength Specify non zero to override user-configured buffer length in core settings.
392 //! @returns The new output instance. Throws exceptions on failure (invalid settings or other).
393 virtual output::ptr instantiateCoreDefault(double overrideBufferLength = 0) = 0;
394 virtual void getCoreConfig( void * out, size_t outSize ) = 0;
395
396 void getCoreConfig(outputCoreConfig_t& out);
397 outputCoreConfig_t getCoreConfig();
398 };
399
400 //! \since 1.3.16
401 class NOVTABLE output_device_list_callback {
402 public:
403 virtual void onDevice( const char * fullName, const GUID & output, const GUID & device ) = 0;
404 };
405
406 //! \since 1.3.16
407 class NOVTABLE output_config_change_callback {
408 public:
409 virtual void outputConfigChanged() = 0;
410 };
411
412 //! \since 1.4
413 class NOVTABLE output_manager_v2 : public output_manager {
414 FB2K_MAKE_SERVICE_COREAPI_EXTENSION(output_manager_v2, output_manager);
415 public:
416 virtual void setCoreConfig( const void * in, size_t inSize, bool bSuppressPlaybackRestart = false ) = 0;
417 void setCoreConfig( const outputCoreConfig_t & in ) { setCoreConfig(&in, sizeof(in) ); }
418 virtual void setCoreConfigDevice( const GUID & output, const GUID & device ) = 0;
419 virtual void listDevices( output_device_list_callback & callback ) = 0;
420 void listDevices( std::function< void ( const char*, const GUID&, const GUID&) > f );
421 virtual void addCallback( output_config_change_callback * ) = 0;
422 virtual void removeCallback( output_config_change_callback * ) = 0;
423
424 service_ptr addCallback( std::function<void()> f );
425 void addCallbackPermanent( std::function<void()> f );
426 };
427
428 extern const GUID output_id_null;
429 extern const GUID output_id_default;