mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-10 02:08:38 +09:00
cubeb_winmm.c overflow fix by mixit@MSFN, Thanks!
This should fix the famous 23m18s freeze bug for audio/video playback. original post: https://msfn.org/board/topic/182647-my-browser-builds-part-3/?do=findComment&comment=1202804 original file: https://pastebin.com/Eh7reucN
This commit is contained in:
parent
7093426d3c
commit
85149582f1
1 changed files with 76 additions and 11 deletions
|
|
@ -109,6 +109,10 @@ struct cubeb_stream {
|
|||
CRITICAL_SECTION lock;
|
||||
uint64_t written;
|
||||
float soft_volume;
|
||||
/* For position wrap-around handling: */
|
||||
size_t frame_size;
|
||||
DWORD prev_pos_lo_dword;
|
||||
DWORD pos_hi_dword;
|
||||
};
|
||||
|
||||
static size_t
|
||||
|
|
@ -530,6 +534,10 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
|
|||
winmm_refill_stream(stm);
|
||||
}
|
||||
|
||||
stm->frame_size = bytes_per_frame(stm->params);
|
||||
stm->prev_pos_lo_dword = 0;
|
||||
stm->pos_hi_dword = 0;
|
||||
|
||||
*stream = stm;
|
||||
|
||||
return CUBEB_OK;
|
||||
|
|
@ -680,6 +688,58 @@ winmm_stream_stop(cubeb_stream * stm)
|
|||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
Microsoft wave audio docs say "samples are the preferred time format in which
|
||||
to represent the current position", but relying on this causes problems on
|
||||
Windows XP, the only OS cubeb_winmm is used on.
|
||||
|
||||
While the wdmaud.sys driver internally tracks a 64-bit position and ensures no
|
||||
backward movement, the WinMM API limits the position returned from
|
||||
waveOutGetPosition() to a 32-bit DWORD (this applies equally to XP x64). The
|
||||
higher 32 bits are chopped off, and to an API consumer the position can appear
|
||||
to move backward.
|
||||
|
||||
In theory, even a 32-bit TIME_SAMPLES position should provide plenty of
|
||||
playback time for typical use cases before this pseudo wrap-around, e.g:
|
||||
(2^32 - 1)/48000 = ~24:51:18 for 48.0 kHz stereo;
|
||||
(2^32 - 1)/44100 = ~27:03:12 for 44.1 kHz stereo.
|
||||
In reality, wdmaud.sys doesn't provide a TIME_SAMPLES position at all, only a
|
||||
32-bit TIME_BYTES position, from which wdmaud.drv derives TIME_SAMPLES:
|
||||
SamplePos = (BytePos * 8) / BitsPerFrame,
|
||||
where BitsPerFrame = Channels * BitsPerSample,
|
||||
Per dom\media\AudioSampleFormat.h, desktop builds always use 32-bit FLOAT32
|
||||
samples, so the maximum for TIME_SAMPLES should be:
|
||||
(2^29 - 1)/48000 = ~03:06:25;
|
||||
(2^29 - 1)/44100 = ~03:22:54.
|
||||
This might still be OK for typical browser usage, but there's also a bug in the
|
||||
formula above: BytePos * 8 (BytePos << 3) is done on a 32-bit BytePos, without
|
||||
first casting it to 64 bits, so the highest 3 bits, if set, would get shifted
|
||||
out, and the maximum possible TIME_SAMPLES drops unacceptably low:
|
||||
(2^26 - 1)/48000 = ~00:23:18;
|
||||
(2^26 - 1)/44100 = ~00:25:22.
|
||||
|
||||
To work around these limitations, we just get the position in TIME_BYTES,
|
||||
recover the 64-bit value, and do our own conversion to samples.
|
||||
*/
|
||||
|
||||
/* Convert chopped 32-bit waveOutGetPosition() into 64-bit true position. */
|
||||
static uint64_t
|
||||
update_64bit_position(cubeb_stream * stm, DWORD pos_lo_dword)
|
||||
{
|
||||
/* Caller should be holding stm->lock. */
|
||||
if (pos_lo_dword < stm->prev_pos_lo_dword) {
|
||||
stm->pos_hi_dword++;
|
||||
LOG("waveOutGetPosition() has wrapped around: %#lx -> %#lx",
|
||||
stm->prev_pos_lo_dword, pos_lo_dword);
|
||||
LOG("Wrap-around count = %#lx", stm->pos_hi_dword);
|
||||
LOG("Current 64-bit position = %#llx",
|
||||
(((uint64_t) stm->pos_hi_dword)<<32) | ((uint64_t) pos_lo_dword));
|
||||
}
|
||||
stm->prev_pos_lo_dword = pos_lo_dword;
|
||||
|
||||
return (((uint64_t) stm->pos_hi_dword)<<32) | ((uint64_t) pos_lo_dword);
|
||||
}
|
||||
|
||||
static int
|
||||
winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
||||
{
|
||||
|
|
@ -687,15 +747,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
|||
MMTIME time;
|
||||
|
||||
EnterCriticalSection(&stm->lock);
|
||||
time.wType = TIME_SAMPLES;
|
||||
/* See the long comment above for why not just use TIME_SAMPLES here. */
|
||||
time.wType = TIME_BYTES;
|
||||
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
|
||||
if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*position = time.u.sample;
|
||||
*position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
|
@ -705,20 +767,23 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
|||
{
|
||||
MMRESULT r;
|
||||
MMTIME time;
|
||||
uint64_t written;
|
||||
|
||||
EnterCriticalSection(&stm->lock);
|
||||
time.wType = TIME_SAMPLES;
|
||||
/* See the long comment above for why not just use TIME_SAMPLES here. */
|
||||
time.wType = TIME_BYTES;
|
||||
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
|
||||
written = stm->written;
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
|
||||
if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
XASSERT(written - time.u.sample <= UINT32_MAX);
|
||||
*latency = (uint32_t) (written - time.u.sample);
|
||||
uint64_t position = update_64bit_position(stm, time.u.cb);
|
||||
uint64_t written = stm->written;
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
XASSERT((written - (position / stm->frame_size)) <= UINT32_MAX);
|
||||
*latency = (uint32_t) (written - (position / stm->frame_size));
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue