mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
4af006e869
## Link to GitHub Issue or related Pull Request, if one exists Building on #745 ## Description of change With `-wasapishared`, some users see `AUDCLNT_E_BUFFER_TOO_LARGE` (`0x88890006`). The existing one-device-period buffer clamp wasn't enough on endpoints with a small shared buffer; the shared buffer is double-buffered, so a full-buffer write often exceeds the free space. ### Fix Added a FIFO bridge to `SharedRedirect` that decouples the game's per-event writes from the shared engine's clock: - **`GetBuffer`** hands the game a pointer into the FIFO tail to write in place. - **`ReleaseBuffer`** commits the write and drains `min(pending, device_free)` frames to the device - so a write can never exceed what the device accepts, structurally preventing the overflow on any endpoint. - **`GetCurrentPadding`** reports the FIFO fill level (capped to the reported buffer size) so poll/timer-driven games pace correctly against the virtual buffer. ## Testing
84 lines
4.3 KiB
C++
84 lines
4.3 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include <windows.h>
|
|
#include <mmreg.h>
|
|
#include <audioclient.h>
|
|
|
|
struct IAudioRenderClient;
|
|
|
|
namespace hooks::audio {
|
|
|
|
// The -wasapishared option redirects an exclusive WASAPI stream to shared mode, so other apps
|
|
// can play sound and devices that can't open the exclusive format still work, at the cost of
|
|
// some latency. Only PCM / float is converted; bitstream (AC-3 / DTS) is left alone.
|
|
struct SharedRedirect {
|
|
|
|
// true once apply() has redirected an exclusive request. gates the buffer clamp; stays false
|
|
// for a natively-shared stream (it paces itself, so must not be clamped).
|
|
bool redirected_from_exclusive = false;
|
|
|
|
// whether an exclusive-mode request should be redirected, given the -wasapishared option.
|
|
// only PCM / float is eligible; bitstream (AC-3 / DTS) is left in exclusive mode.
|
|
static bool wants(AUDCLNT_SHAREMODE share_mode, const WAVEFORMATEX *format);
|
|
|
|
// redirect an exclusive request to shared mode. caller must have checked wants() first.
|
|
void apply(AUDCLNT_SHAREMODE *share_mode, DWORD *stream_flags, REFERENCE_TIME *periodicity);
|
|
|
|
// clamp a reported buffer size to one device period. the FIFO bridge below is what prevents
|
|
// the overflow; this just keeps the game's per-event writes small so the bridge adds minimal
|
|
// latency. caches the chosen value for virtual_padding. a no-op unless redirected.
|
|
UINT32 clamp_buffer_size(IAudioClient *real, uint32_t sample_rate, UINT32 device_frames);
|
|
|
|
// FIFO bridge: the redirected game writes a whole reported buffer per event paced by its own
|
|
// callback, not the shared engine clock, so a full-buffer write can intermittently exceed the
|
|
// double-buffered shared free space (AUDCLNT_E_BUFFER_TOO_LARGE). The game instead writes
|
|
// directly into a FIFO that is drained to the device only as fast as it frees space - the
|
|
// same free-space-clamped approach the timer-driven resampler uses.
|
|
|
|
// arm the bridge once the redirected stream is initialized. frame_bytes is one frame's size
|
|
// in the game's (== device, via AUTOCONVERTPCM) format.
|
|
void enable_bridge(int frame_bytes);
|
|
|
|
// whether the FIFO bridge is active (a redirect was applied and armed).
|
|
bool bridge_enabled() const { return this->frame_bytes > 0; }
|
|
|
|
// reserve `frames` at the FIFO tail and hand the game a pointer into it to write in place.
|
|
// must be paired with commit_write, which trims the reservation to the frames written.
|
|
BYTE *begin_write(UINT32 frames);
|
|
|
|
// trim the reservation from begin_write to the `frames` actually written (zeroing if silent).
|
|
void commit_write(UINT32 frames, bool silent);
|
|
|
|
// padding to report to a game that polls GetCurrentPadding while the bridge is active: the
|
|
// FIFO fill level, capped to the reported buffer size so the game's free-space calculation
|
|
// (reported_buffer - padding) reflects room in the virtual buffer rather than the device's.
|
|
UINT32 virtual_padding() const;
|
|
|
|
// push as many queued frames as the real device has free, applying `boost`, keeping the rest
|
|
// for the next call. `real` is the wrapped render client's underlying interface; `client` is
|
|
// the underlying audio client used to query the device's free space.
|
|
HRESULT drain(IAudioRenderClient *real, IAudioClient *client,
|
|
const WAVEFORMATEXTENSIBLE &device_format, float boost);
|
|
|
|
private:
|
|
|
|
// frames currently queued in the FIFO and not yet handed to the device.
|
|
UINT32 pending_frames() const;
|
|
|
|
// FIFO bridge state (see enable_bridge). fifo holds audio queued for the device in the
|
|
// game's interleaved frame format; the game writes new frames directly into its tail between
|
|
// begin_write and commit_write. frame_bytes > 0 doubles as the "bridge armed" flag (see
|
|
// bridge_enabled). pending_write_offset marks the tail reservation handed to begin_write.
|
|
int frame_bytes = 0;
|
|
UINT32 device_buffer_frames = 0;
|
|
UINT32 reported_frames = 0;
|
|
int buffers_to_mute = 4;
|
|
size_t pending_write_offset = 0;
|
|
std::vector<BYTE> fifo;
|
|
};
|
|
}
|
|
|