Oops, did this a long time ago (for a23) and forgot to commit it ...
This commit is contained in:
@@ -179,6 +179,7 @@ DSoundBuf::DSoundBuf( DSound &ds, DSoundBuf::hw hardware,
|
||||
volume = -1; /* unset */
|
||||
buffer_locked = false;
|
||||
write_cursor_pos = write_cursor = buffer_bytes_filled = 0;
|
||||
extra_writeahead = 0;
|
||||
LastPosition = 0;
|
||||
playing = false;
|
||||
|
||||
@@ -282,6 +283,8 @@ DSoundBuf::DSoundBuf( DSound &ds, DSoundBuf::hw hardware,
|
||||
else if( (int) waveformat.nSamplesPerSec != samplerate )
|
||||
LOG->Warn( "Secondary buffer set to %i instead of %i", waveformat.nSamplesPerSec, samplerate );
|
||||
#endif
|
||||
|
||||
temp_buffer = new char[buffersize];
|
||||
}
|
||||
|
||||
void DSoundBuf::SetSampleRate(int hz)
|
||||
@@ -332,124 +335,25 @@ static bool contained( int start, int end, int pos )
|
||||
DSoundBuf::~DSoundBuf()
|
||||
{
|
||||
buf->Release();
|
||||
delete [] temp_buffer;
|
||||
}
|
||||
|
||||
/* Figure out if we've underrun, and act if appropriate. */
|
||||
void DSoundBuf::CheckUnderrun( int cursorstart, int cursorend, int chunksize )
|
||||
void round_up( int &i, int to )
|
||||
{
|
||||
/* XXX We can figure out if we've underrun, and increase the write-ahead
|
||||
* when it happens. Two problems:
|
||||
* 1. It's ugly to wait until we actually underrun. (We could store the
|
||||
* write-ahead, though.)
|
||||
* 2. We don't want a random underrun (eg. virus scanner starts) to
|
||||
* permanently increase our write-ahead. We want the smallest possible
|
||||
* that will give us reliable audio in normal conditions. I'm not sure
|
||||
* of a robust way to do this.
|
||||
*
|
||||
* Also, writeahead should be a static (all buffers write ahead the same
|
||||
* amount); writeahead in the ctor should be a hint only (initial value),
|
||||
* and the sound driver should query a sound to get the current writeahead
|
||||
* in GetLatencySeconds().
|
||||
*/
|
||||
i += (to-1);
|
||||
i /= to;
|
||||
i *= to;
|
||||
}
|
||||
|
||||
/* If the buffer is full, we can't be underrunning. */
|
||||
if( buffer_bytes_filled >= buffersize )
|
||||
/* Check to make sure that, given the current writeahead and chunksize, we're
|
||||
* capable of filling the prefetch region entirely. If we aren't, increase
|
||||
* the writeahead. If this happens, we're underruning. */
|
||||
void DSoundBuf::CheckWriteahead( int cursorstart, int cursorend )
|
||||
{
|
||||
/* If we're in a recovering-from-underrun state, stop. */
|
||||
if( extra_writeahead )
|
||||
return;
|
||||
|
||||
/* If nothing is expected to be filled, we can't underrun. */
|
||||
if( cursorstart == cursorend )
|
||||
return;
|
||||
|
||||
/* If there's no data in the buffer at all, then we've completely underrun. Our
|
||||
* write cursor is irrelevant; we might be unrelated to the play position completely.
|
||||
* Realign. */
|
||||
if( buffer_bytes_filled == 0 )
|
||||
{
|
||||
/* There's no data filled at all. We've completely underrun. Pretend that
|
||||
* the prefetch is full, and point our write cursor just after it. This way,
|
||||
* we don't write into the prefetch; doing so shouldn't cause problems, but
|
||||
* let's play it safe. Pretending the prefetch is full (instead of just moving
|
||||
* the write cursor) takes us out of a "major underrun" state. */
|
||||
int missed_by = cursorend - write_cursor;
|
||||
wrap( missed_by, buffersize );
|
||||
int first_byte_filled = write_cursor-buffer_bytes_filled;
|
||||
wrap( first_byte_filled, buffersize );
|
||||
|
||||
LOG->Trace( "major underrun: %i..%i filled but cursor at %i..%i (missed it by %i)",
|
||||
first_byte_filled, write_cursor, cursorstart, cursorend, missed_by );
|
||||
|
||||
write_cursor = cursorend;
|
||||
buffer_bytes_filled = int(cursorend) - cursorstart;
|
||||
wrap( buffer_bytes_filled, buffersize );
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invariant: the filled region now starts at the beginning of the play region.
|
||||
* The "Update buffer_bytes_filled" logic, combined with the above empty buffer
|
||||
* check, guarantees this.
|
||||
*
|
||||
* This is important, so let's consider all cases as of the start of get_output_buf:
|
||||
*
|
||||
* 1: ....ffff....pppp... (no overlap)
|
||||
* invalid: buffer_bytes_filled should have been set to 0.
|
||||
*
|
||||
* 2: ....ffff.... (overlap, filled is earlier)
|
||||
* ..pppp......
|
||||
* invalid: buffer_bytes_filled should have been set to 2, resulting in:
|
||||
* ......ff....
|
||||
* ...pppp.....
|
||||
*
|
||||
* 3: ......ffff.. (overlap, filled is later)
|
||||
* ....pppp....
|
||||
* invalid: This case can only happen if the play cursor has wrapped all the
|
||||
* way around, in which case buffer_bytes_filled should have been set to 0.
|
||||
*
|
||||
* 4: ....ffff.... valid
|
||||
* ....pppp....
|
||||
* 5: ....ff...... valid
|
||||
* ....pppp....
|
||||
* 6: ............
|
||||
* ....pppp....
|
||||
* invalid: buffer_bytes_filled == 0; we handled this above
|
||||
*/
|
||||
int first_byte_filled = write_cursor-buffer_bytes_filled;
|
||||
wrap( first_byte_filled, buffersize );
|
||||
if( first_byte_filled != cursorstart )
|
||||
{
|
||||
LOG->Trace("%i..%i filled but cursor at %i..%i (%i max)",
|
||||
first_byte_filled, write_cursor, cursorstart, cursorend, buffer_bytes_filled );
|
||||
FAIL_M( "DSoundBuf::CheckUnderrun internal error" );
|
||||
}
|
||||
|
||||
if( contained(first_byte_filled, write_cursor, cursorend) )
|
||||
{
|
||||
/* The end of the play cursor has data. We haven't underrun (case #4). */
|
||||
return;
|
||||
}
|
||||
|
||||
/* We've underrun. Let's figure out whether, if we continue filling, we'll fill
|
||||
* the buffer enough to stop underrunning. This is a little ugly, since we need
|
||||
* to simulate. */
|
||||
int fake_buffer_bytes_filled = buffer_bytes_filled;
|
||||
int fake_write_cursor = write_cursor;
|
||||
while(1)
|
||||
{
|
||||
if( fake_buffer_bytes_filled > writeahead )
|
||||
break;
|
||||
|
||||
int num_bytes_empty = writeahead-fake_buffer_bytes_filled;
|
||||
if( num_bytes_empty < chunksize )
|
||||
break;
|
||||
|
||||
num_bytes_empty = min(num_bytes_empty, buffersize - fake_write_cursor);
|
||||
// num_bytes_empty = min(num_bytes_empty, chunksize);
|
||||
|
||||
fake_buffer_bytes_filled += num_bytes_empty;
|
||||
fake_write_cursor += num_bytes_empty;
|
||||
wrap( fake_write_cursor, buffersize );
|
||||
}
|
||||
|
||||
/* If the driver is requesting an unreasonably large prefetch, ignore it entirely.
|
||||
* Some drivers seem to give broken write cursors sporadically, requesting that
|
||||
* almost the entire buffer be filled. There's no reason a driver should ever need
|
||||
@@ -469,56 +373,61 @@ void DSoundBuf::CheckUnderrun( int cursorstart, int cursorend, int chunksize )
|
||||
return;
|
||||
}
|
||||
|
||||
LOG->Trace("write_cursor %i, fake_write_cursor %i, fake_buffer_bytes_filled %i",
|
||||
write_cursor, fake_write_cursor, fake_buffer_bytes_filled );
|
||||
|
||||
bool bCanCatchUp = fake_buffer_bytes_filled >= buffersize || contained(first_byte_filled, fake_write_cursor, cursorend);
|
||||
/*
|
||||
* If bCanCatchUp is false, then based on our writeahead and the chunksize, we'll
|
||||
* never fill the buffer. This isn't fuzzy; we simply aren't filling enough, and
|
||||
* we need to increase the writeahead.
|
||||
*
|
||||
* If bCanCatchUp is true, we simply fell behind and we can catch up. If this
|
||||
* happens repeatedly, then while the writeahead is sufficient for the prefetch,
|
||||
* the scheduler isn't keeping up, and we probably need to increase the writeahead.
|
||||
*
|
||||
* This is a tricky support issue. We don't want to have to adjust the writeahead
|
||||
* dynamically if we can help it, since that means we've already underrun. It's
|
||||
* much better to have a properly tuned writeahead for all systems to begin with.
|
||||
* I'd much prefer to receive bug reports when the writeahead wasn't enough, so we
|
||||
* can figure out the correct writeahead and use it by default.
|
||||
*
|
||||
* Also, unless we write the writeahead to a preference to keep it long-term (which
|
||||
* I'm wary of doing), we'll go through this every game.
|
||||
*/
|
||||
if( bCanCatchUp )
|
||||
{
|
||||
/* If we simply continue, we'll catch up. We'll probably have an audible
|
||||
* glitch, but we can't prevent that. However, we can probably avoid an
|
||||
* arrow skip. */
|
||||
/* XXX: if this happens repeatedly over a period of time, increase writeahead */
|
||||
int missed_by = cursorend - write_cursor;
|
||||
wrap( missed_by, buffersize );
|
||||
LOG->Trace("minor underrun: %i..%i filled but cursor at %i..%i (missed it by %i) %i/%i",
|
||||
first_byte_filled, write_cursor, cursorstart, cursorend,
|
||||
missed_by, buffer_bytes_filled, buffersize);
|
||||
if( writeahead >= prefetch )
|
||||
return;
|
||||
|
||||
/* We need to increase the writeahead. */
|
||||
LOG->Trace("insufficient writeahead: wants %i (cursor at %i..%i), writeahead adjusted from %i to %i",
|
||||
prefetch/bytes_per_frame(), cursorstart, cursorend, writeahead, prefetch );
|
||||
|
||||
writeahead = prefetch;
|
||||
}
|
||||
|
||||
/* Figure out if we've underrun, and act if appropriate. */
|
||||
void DSoundBuf::CheckUnderrun( int cursorstart, int cursorend )
|
||||
{
|
||||
/* If the buffer is full, we can't be underrunning. */
|
||||
if( buffer_bytes_filled >= buffersize )
|
||||
return;
|
||||
|
||||
/* If nothing is expected to be filled, we can't underrun. */
|
||||
if( cursorstart == cursorend )
|
||||
return;
|
||||
|
||||
/* If we're already in a recovering-from-underrun state, stop. */
|
||||
if( extra_writeahead )
|
||||
return;
|
||||
|
||||
int first_byte_filled = write_cursor-buffer_bytes_filled;
|
||||
wrap( first_byte_filled, buffersize );
|
||||
|
||||
/* If the end of the play cursor has data, we haven't underrun. */
|
||||
if( contained(first_byte_filled, write_cursor, cursorend) )
|
||||
return;
|
||||
|
||||
/* Extend the writeahead to force fill as much as required to stop underrunning.
|
||||
* This has a major benefit: if we havn't skipped so long we've passed a whole
|
||||
* buffer (64k = ~350ms), this doesn't break stride. We'll skip forward, but
|
||||
* the beat won't be lost, which is a lot easier to recover from in play. */
|
||||
/* XXX: If this happens repeatedly over a period of time, increase writeahead. */
|
||||
int needed_writeahead = (cursorstart + writeahead) - write_cursor;
|
||||
wrap( needed_writeahead, buffersize );
|
||||
if( needed_writeahead > writeahead )
|
||||
{
|
||||
extra_writeahead = needed_writeahead - writeahead;
|
||||
writeahead = needed_writeahead;
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on our writeahead and the chunksize, we'll never fill the buffer. We
|
||||
* need to increase the writeahead.
|
||||
*/
|
||||
int missed_by = cursorend - write_cursor;
|
||||
wrap( missed_by, buffersize );
|
||||
|
||||
int old_writeahead = writeahead;
|
||||
writeahead = writeahead * 4 / 3;
|
||||
/* Snap to bytes_per_frame. */
|
||||
writeahead = (writeahead / bytes_per_frame()) * bytes_per_frame();
|
||||
writeahead = min( writeahead, buffersize );
|
||||
CString s = ssprintf( "underrun: %i..%i (%i) filled but cursor at %i..%i; missed it by %i",
|
||||
first_byte_filled, write_cursor, buffer_bytes_filled, cursorstart, cursorend, missed_by );
|
||||
|
||||
LOG->Trace("insufficient writeahead: wants %i (cursor at %i..%i), but we'll only fill to %i; writeahead adjusted from %i to %i",
|
||||
prefetch/bytes_per_frame(), cursorstart, cursorend,
|
||||
fake_buffer_bytes_filled/bytes_per_frame(), old_writeahead/bytes_per_frame(), writeahead/bytes_per_frame() );
|
||||
if( extra_writeahead )
|
||||
s += ssprintf( "; extended writeahead by %i to %i", extra_writeahead, writeahead );
|
||||
|
||||
LOG->Trace( "%s", s.c_str() );
|
||||
}
|
||||
|
||||
bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
|
||||
@@ -564,9 +473,18 @@ bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
|
||||
|
||||
buffer_bytes_filled -= bytes_played;
|
||||
buffer_bytes_filled = max( 0, buffer_bytes_filled );
|
||||
|
||||
if( extra_writeahead )
|
||||
{
|
||||
int used = min( extra_writeahead, bytes_played );
|
||||
LOG->Trace("used %i of %i", used, extra_writeahead);
|
||||
writeahead -= used;
|
||||
extra_writeahead -= used;
|
||||
}
|
||||
}
|
||||
|
||||
CheckUnderrun( cursorstart, cursorend, chunksize );
|
||||
CheckWriteahead( cursorstart, cursorend );
|
||||
CheckUnderrun( cursorstart, cursorend );
|
||||
|
||||
/* If we already have enough bytes written ahead, stop. */
|
||||
if( buffer_bytes_filled > writeahead )
|
||||
@@ -579,34 +497,16 @@ bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
|
||||
if( num_bytes_empty < chunksize )
|
||||
return false;
|
||||
|
||||
/* I don't want to deal with DSound's split-circular-buffer locking stuff, so clamp
|
||||
* the writing space at the end of the physical buffer. */
|
||||
num_bytes_empty = min(num_bytes_empty, buffersize - write_cursor);
|
||||
|
||||
/* Don't fill more than one chunk at a time. This reduces the maximum
|
||||
* amount of time until we give data; that way, if we're short on time,
|
||||
* we'll give some data soon instead of lots of data later. */
|
||||
/* Let's not do this; treat chunksize as a "min bytes to fill" above (so we're not
|
||||
* constantly filling in a few frames at a time), but not "max bytes to fill". This
|
||||
* reduces cases where we don't fill the buffer as much as we should, and in practice
|
||||
* makes the "increase the writeahead" logic work much better. */
|
||||
// num_bytes_empty = min(num_bytes_empty, chunksize);
|
||||
|
||||
// LOG->Trace("gave %i at %i (%i, %i) %i filled", num_bytes_empty, write_cursor, cursor, write, buffer_bytes_filled);
|
||||
|
||||
/* Lock the audio buffer. */
|
||||
#ifdef _XBOX
|
||||
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, NULL, 0 );
|
||||
#else
|
||||
DWORD junk;
|
||||
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0 );
|
||||
#endif
|
||||
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)&locked_buf1, (DWORD *) &locked_size1, (LPVOID *)&locked_buf2, (DWORD *) &locked_size2, 0 );
|
||||
|
||||
#ifndef _XBOX
|
||||
if ( result == DSERR_BUFFERLOST )
|
||||
{
|
||||
buf->Restore();
|
||||
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0 );
|
||||
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)&locked_buf1, (DWORD *) &locked_size1, (LPVOID *)&locked_buf2, (DWORD *) &locked_size2, 0 );
|
||||
}
|
||||
#endif
|
||||
if ( result != DS_OK )
|
||||
@@ -615,6 +515,9 @@ bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = temp_buffer;
|
||||
*bufsiz = locked_size1 + locked_size2;
|
||||
|
||||
write_cursor += num_bytes_empty;
|
||||
if( write_cursor >= buffersize )
|
||||
write_cursor -= buffersize;
|
||||
@@ -629,7 +532,9 @@ bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
|
||||
|
||||
void DSoundBuf::release_output_buf( char *buffer, unsigned bufsiz )
|
||||
{
|
||||
buf->Unlock( buffer, bufsiz, NULL, 0 );
|
||||
memcpy( locked_buf1, buffer, locked_size1 );
|
||||
memcpy( locked_buf2, buffer+locked_size1, locked_size2 );
|
||||
buf->Unlock( locked_buf1, locked_size1, locked_buf2, locked_size2 );
|
||||
buffer_locked = false;
|
||||
}
|
||||
|
||||
@@ -678,6 +583,9 @@ void DSoundBuf::Stop()
|
||||
write_cursor_pos = write_cursor = buffer_bytes_filled = 0;
|
||||
LastPosition = 0;
|
||||
|
||||
writeahead -= extra_writeahead;
|
||||
extra_writeahead = 0;
|
||||
|
||||
/* When stopped and rewound, the play and write cursors should both be 0. */
|
||||
/* This isn't true on some broken cards. */
|
||||
// DWORD play, write;
|
||||
|
||||
@@ -53,7 +53,8 @@ private:
|
||||
int buffersize_frames() const { return buffersize / bytes_per_frame(); }
|
||||
int bytes_per_frame() const { return channels*samplebits/8; }
|
||||
|
||||
void CheckUnderrun( int cursorstart, int cursorend, int chunksize );
|
||||
void CheckWriteahead( int cursorstart, int cursorend );
|
||||
void CheckUnderrun( int cursorstart, int cursorend );
|
||||
|
||||
IDirectSoundBuffer *buf;
|
||||
|
||||
@@ -63,13 +64,15 @@ private:
|
||||
int buffersize;
|
||||
|
||||
int write_cursor, buffer_bytes_filled; /* bytes */
|
||||
int extra_writeahead;
|
||||
int64_t write_cursor_pos; /* frames */
|
||||
mutable int64_t LastPosition;
|
||||
bool playing;
|
||||
|
||||
bool buffer_locked;
|
||||
char *locked_buf;
|
||||
int locked_len;
|
||||
char *locked_buf1, *locked_buf2;
|
||||
int locked_size1, locked_size2;
|
||||
char *temp_buffer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user