Files
itgmania212121/src/RageSoundReader_ThreadedBuffer.cpp
T
Martin Natano b68ca517e6 Clean up math functions
- Remove checking for standard functions from the build system
- Prefix all invocations with std::
- Replace suffixed functions with unprefixed versions
- Include <cmath> in all files that use it and remove the global include

e.g. floorf(x) -> std::floor(x)
2023-04-19 19:31:40 +02:00

375 lines
9.8 KiB
C++

#include "global.h"
#include "RageSoundReader_ThreadedBuffer.h"
#include "RageUtil.h"
#include "RageTimer.h"
#include "RageLog.h"
#include <cmath>
/* Implement threaded read-ahead buffering.
*
* If a buffer is low on data, keep filling until it has a g_iMinFillFrames.
* Once beyond that, fill at a rate relative to realtime.
*
* This allows a stream to have a large buffer, for higher reliability, without
* causing major CPU bursts when the stream starts or underruns. Filling 32k
* takes more CPU than filling 4k frames, and may cause a skip. */
// The amount of data to read at once:
static const unsigned g_iReadBlockSizeFrames = 1024;
// The maximum number of frames to buffer:
static const int g_iStreamingBufferFrames = 1024*32;
/* When a sound has fewer than g_iMinFillFrames buffered, buffer at maximum speed.
* Once beyond that, fill at a limited rate. */
static const int g_iMinFillFrames = 1024*4;
RageSoundReader_ThreadedBuffer::RageSoundReader_ThreadedBuffer( RageSoundReader *pSource ):
RageSoundReader_Filter( pSource ),
m_Event( "ThreadedBuffer" )
{
m_iSampleRate = pSource->GetSampleRate();
m_iChannels = pSource->GetNumChannels();
int iFrameSize = sizeof(float) * this->GetNumChannels();
m_DataBuffer.reserve( g_iStreamingBufferFrames * iFrameSize, iFrameSize );
m_bEOF = false;
m_bShutdownThread = false;
m_bEnabled = false;
m_bFilling = false;
m_StreamPosition.push_back( Mapping() );
m_StreamPosition.back().iPositionOfFirstFrame = pSource->GetNextSourceFrame();
m_StreamPosition.back().fRate = pSource->GetStreamToSourceRatio();
m_Thread.SetName( "Streaming sound buffering" );
m_Thread.Create( StartBufferingThread, this );
}
RageSoundReader_ThreadedBuffer::RageSoundReader_ThreadedBuffer( const RageSoundReader_ThreadedBuffer &cpy ):
RageSoundReader_Filter(nullptr), // don't touch m_pSource before DisableBuffering
m_Event( "ThreadedBuffer" )
{
bool bWasEnabled = cpy.DisableBuffering();
m_pSource = cpy.m_pSource->Copy();
m_iSampleRate = cpy.m_iSampleRate;
m_iChannels = cpy.m_iChannels;
m_DataBuffer = cpy.m_DataBuffer;
m_bEOF = cpy.m_bEOF;
m_bShutdownThread = cpy.m_bShutdownThread;
m_bEnabled = cpy.m_bEnabled;
m_bFilling = cpy.m_bFilling;
m_StreamPosition = cpy.m_StreamPosition;
m_Thread.Create( StartBufferingThread, this );
if( bWasEnabled )
{
cpy.EnableBuffering();
EnableBuffering();
}
}
RageSoundReader_ThreadedBuffer::~RageSoundReader_ThreadedBuffer()
{
DisableBuffering();
m_Event.Lock();
m_bShutdownThread = true;
m_Event.Broadcast();
m_Event.Unlock();
m_Thread.Wait();
}
void RageSoundReader_ThreadedBuffer::EnableBuffering()
{
m_Event.Lock();
m_bEnabled = true;
m_Event.Broadcast();
m_Event.Unlock();
}
bool RageSoundReader_ThreadedBuffer::DisableBuffering()
{
m_Event.Lock();
bool bRet = m_bEnabled;
m_bEnabled = false;
m_Event.Broadcast();
while( m_bFilling )
m_Event.Wait();
m_Event.Unlock();
return bRet;
}
void RageSoundReader_ThreadedBuffer::WaitUntilFrames( int iWaitUntilFrames )
{
m_Event.Lock();
ASSERT( m_bEnabled );
while( GetFilledFrames() < iWaitUntilFrames )
m_Event.Wait();
m_Event.Unlock();
}
int RageSoundReader_ThreadedBuffer::SetPosition( int iFrame )
{
bool bWasEnabled = DisableBuffering();
m_DataBuffer.clear();
int iRet = RageSoundReader_Filter::SetPosition( iFrame );
m_StreamPosition.clear();
m_StreamPosition.push_back( Mapping() );
m_StreamPosition.back().iPositionOfFirstFrame = iFrame;
m_bEOF = iRet == 0;
if( bWasEnabled )
EnableBuffering();
return iRet;
}
int RageSoundReader_ThreadedBuffer::GetEmptyFrames() const
{
int iSamplesPerFrame = this->GetNumChannels();
if( g_iReadBlockSizeFrames * iSamplesPerFrame > m_DataBuffer.num_writable() )
return 0;
return m_DataBuffer.num_writable() / iSamplesPerFrame;
}
int RageSoundReader_ThreadedBuffer::GetFilledFrames() const
{
int iSamplesPerFrame = this->GetNumChannels();
return m_DataBuffer.num_readable() / iSamplesPerFrame;
}
int RageSoundReader_ThreadedBuffer::GetNextSourceFrame() const
{
m_Event.Lock();
int iRet = m_StreamPosition.front().iPositionOfFirstFrame;
m_Event.Unlock();
return iRet;
}
float RageSoundReader_ThreadedBuffer::GetStreamToSourceRatio() const
{
m_Event.Lock();
float fRet = m_StreamPosition.front().fRate;
m_Event.Unlock();
return fRet;
}
int RageSoundReader_ThreadedBuffer::GetLength() const
{
bool bWasEnabled = DisableBuffering();
int iRet = m_pSource->GetLength();
if( bWasEnabled )
EnableBuffering();
return iRet;
}
int RageSoundReader_ThreadedBuffer::GetLength_Fast() const
{
bool bWasEnabled = DisableBuffering();
int iRet = m_pSource->GetLength_Fast();
if( bWasEnabled )
EnableBuffering();
return iRet;
}
bool RageSoundReader_ThreadedBuffer::SetProperty( const RString &sProperty, float fValue )
{
return m_pSource->SetProperty( sProperty, fValue );
}
void RageSoundReader_ThreadedBuffer::BufferingThread()
{
m_Event.Lock();
while( !m_bShutdownThread )
{
if( !m_bEnabled )
{
m_Event.Wait();
continue;
}
// Fill some data.
m_bFilling = true;
int iFramesToFill = g_iReadBlockSizeFrames;
if( GetFilledFrames() < g_iMinFillFrames )
iFramesToFill = std::max( iFramesToFill, g_iMinFillFrames - GetFilledFrames() );
int iRet = FillFrames( iFramesToFill );
// Release m_bFilling, and signal the event to wake anyone waiting for it.
m_bFilling = false;
m_Event.Broadcast();
// On error or end of file, stop buffering the sound.
if( iRet < 0 )
{
m_bEnabled = false;
continue;
}
/* Sleep proportionately to the amount of data we buffered, so we
* fill at a reasonable pace. */
float fTimeFilled = float(g_iReadBlockSizeFrames) / m_iSampleRate;
float fTimeToSleep = fTimeFilled / 2;
if( fTimeToSleep == 0 )
fTimeToSleep = float(g_iReadBlockSizeFrames) / m_iSampleRate;
if( m_Event.WaitTimeoutSupported() )
{
RageTimer time;
time.Touch();
time += fTimeToSleep;
m_Event.Wait( &time );
}
else
{
m_Event.Unlock();
usleep( std::lrint(fTimeToSleep * 1000000) );
m_Event.Lock();
}
}
m_Event.Unlock();
}
int RageSoundReader_ThreadedBuffer::FillFrames( int iFrames )
{
int iFramesFilled = 0;
while( iFramesFilled < iFrames )
{
int iRet = FillBlock();
if( iRet == 0 )
break;
// On error or end of file, stop buffering the sound.
if( iRet < 0 )
return iRet;
iFramesFilled += iRet;
}
return iFramesFilled;
}
int RageSoundReader_ThreadedBuffer::FillBlock()
{
if( GetEmptyFrames() == 0 )
return 0;
int iSamplesPerFrame = this->GetNumChannels();
ASSERT( g_iReadBlockSizeFrames * iSamplesPerFrame <= m_DataBuffer.num_writable() );
int iGotFrames;
int iNextSourceFrame = 0;
float fRate = 0;
m_Event.Unlock();
{
// We own m_pSource, even after unlocking, because m_bFilling is true.
unsigned iBufSize;
float *pBuf = m_DataBuffer.get_write_pointer( &iBufSize );
ASSERT( (iBufSize % iSamplesPerFrame) == 0 );
iGotFrames = m_pSource->RetriedRead( pBuf, std::min(g_iReadBlockSizeFrames, iBufSize / iSamplesPerFrame), &iNextSourceFrame, &fRate );
}
m_Event.Lock();
if( iGotFrames > 0 )
{
// Add the data to the buffer.
m_DataBuffer.advance_write_pointer( iGotFrames * iSamplesPerFrame );
if( iNextSourceFrame != m_StreamPosition.back().iPositionOfFirstFrame + m_StreamPosition.back().iFramesBuffered ||
fRate != m_StreamPosition.back().fRate )
{
m_StreamPosition.push_back( Mapping() );
m_StreamPosition.back().iPositionOfFirstFrame = iNextSourceFrame;
m_StreamPosition.back().fRate = fRate;
}
m_StreamPosition.back().iFramesBuffered += iGotFrames;
}
m_bEOF = (iGotFrames == END_OF_FILE);
return iGotFrames;
}
int RageSoundReader_ThreadedBuffer::Read( float *pBuffer, int iFrames )
{
if( !m_bEOF )
EnableBuffering();
m_Event.Lock();
{
/* Delete any empty mappings from the beginning, but don't empty the list,
* so we always have the current position and rate. If we delete an item,
* the rate or position has probably changed, so return. */
std::list<Mapping>::iterator it = m_StreamPosition.begin();
++it;
if( it != m_StreamPosition.end() && !m_StreamPosition.front().iFramesBuffered )
{
++it;
m_StreamPosition.pop_front();
m_Event.Unlock();
return 0;
}
}
int iRet;
if( m_StreamPosition.front().iFramesBuffered )
{
Mapping &pos = m_StreamPosition.front();
int iFramesToRead = std::min( iFrames, pos.iFramesBuffered );
int iSamplesPerFrame = this->GetNumChannels();
m_DataBuffer.read( pBuffer, iFramesToRead * iSamplesPerFrame );
pos.iPositionOfFirstFrame += iFramesToRead;
pos.iFramesBuffered -= iFramesToRead;
iRet = iFramesToRead;
}
else if( m_bEOF )
iRet = END_OF_FILE;
else
iRet = WOULD_BLOCK;
m_Event.Unlock();
return iRet;
}
/*
* Copyright (c) 2006 Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/