Files
itgmania212121/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp
T
2004-05-13 21:18:00 +00:00

357 lines
9.7 KiB
C++

#include "global.h"
#include "RageSoundDriver_ALSA9.h"
#include "RageLog.h"
#include "RageSound.h"
#include "RageSoundManager.h"
#include "RageUtil.h"
#include "ALSA9Dynamic.h"
const int channels = 2;
const int samplerate = 44100;
const int samples_per_frame = channels;
const int bytes_per_frame = sizeof(int16_t) * samples_per_frame;
const unsigned max_writeahead = 1024*8;
const int num_chunks = 8;
const int chunksize = max_writeahead / num_chunks;
int RageSound_ALSA9::MixerThread_start(void *p)
{
((RageSound_ALSA9 *) p)->MixerThread();
return 0;
}
void RageSound_ALSA9::MixerThread()
{
/* SOUNDMAN will be set once RageSoundManager's ctor returns and
* assigns it; we might get here before that happens, though. */
while( !SOUNDMAN && !shutdown )
usleep( 10000 );
while(!shutdown)
{
/* Sleep for the size of one chunk. */
const int chunksize_frames = max_writeahead / num_chunks;
float sleep_secs = (float(chunksize_frames) / samplerate);
usleep( 20000 ); // int(1000 * sleep_secs));
LockMut( m_Mutex );
for( unsigned i = 0; i < stream_pool.size(); ++i )
{
/* We're only interested in PLAYING and FLUSHING sounds. */
if( stream_pool[i]->state != stream::PLAYING &&
stream_pool[i]->state != stream::FLUSHING )
continue; /* inactive */
bool bEOF = false;
while( !shutdown && stream_pool[i]->GetData(bEOF) && !bEOF )
;
if( bEOF )
{
/* FLUSHING tells the mixer thread to release the stream once str->flush_bufs
* buffers have been flushed. */
stream_pool[i]->state = stream::FLUSHING;
/* Flush two buffers worth of data. */
stream_pool[i]->flush_pos = stream_pool[i]->pcm->GetPlayPos();
LOG->Trace("eof, fl to %i", (int)stream_pool[i]->flush_pos);
}
}
for(unsigned i = 0; i < stream_pool.size(); ++i)
{
if( stream_pool[i]->state != stream_pool[i]->FLUSHING )
continue;
int ps = stream_pool[i]->pcm->GetPosition();
if( ps < stream_pool[i]->flush_pos )
continue; /* stopping but still flushing */
/* The sound has stopped and flushed all of its buffers. */
stream_pool[i]->pcm->Stop();
stream_pool[i]->state = stream::FINISHED;
}
}
}
RageSound_ALSA9::stream::~stream()
{
delete pcm;
}
/* Returns the number of frames processed */
bool RageSound_ALSA9::stream::GetData( bool &bEOF )
{
bEOF = false;
int frames_to_fill = pcm->GetNumFramesToFill();
if( frames_to_fill < chunksize )
return false;
static int16_t *buf = NULL;
if ( !buf )
buf = new int16_t[max_writeahead*samples_per_frame];
char *cbuf = (char*) buf;
const int64_t play_pos = pcm->GetPlayPos();
const int64_t cur_play_pos = pcm->GetPosition();
int len = frames_to_fill*bytes_per_frame;
/* It might be INACTIVE, when we're prebuffering. We just don't want to
* fill anything in FLUSHING; in that case, we just clear the audio buffer. */
if( state != FLUSHING )
{
int bytes_read = 0;
int bytes_left = len;
/* Does the sound have a start time? */
if( !start_time.IsZero() )
{
/* If the sound is supposed to start at a time past this buffer, insert silence. */
const int64_t iFramesUntilThisBuffer = play_pos - cur_play_pos;
const float fSecondsBeforeStart = -start_time.Ago();
const int64_t iFramesBeforeStart = int64_t(fSecondsBeforeStart * pcm->GetSampleRate());
const int64_t iSilentFramesInThisBuffer = iFramesBeforeStart-iFramesUntilThisBuffer;
const int iSilentBytesInThisBuffer = clamp( int(iSilentFramesInThisBuffer * bytes_per_frame), 0, bytes_left );
memset( cbuf+bytes_read, 0, iSilentBytesInThisBuffer );
bytes_read += iSilentBytesInThisBuffer;
bytes_left -= iSilentBytesInThisBuffer;
if( !iSilentBytesInThisBuffer )
start_time.SetZero();
}
int got = snd->GetPCM( cbuf+bytes_read, len-bytes_read, play_pos + (bytes_read/bytes_per_frame) );
bytes_read += got;
bytes_left -= got;
RageSoundManager::AttenuateBuf( buf, got/sizeof(int16_t), snd->GetVolume() );
if( bytes_left > 0 )
{
/* Fill the remainder of the buffer with silence. */
memset( cbuf+bytes_read, 0, bytes_left );
LOG->Trace("eof");
bEOF = true;
}
} else {
/* Silence the buffer. */
memset( buf, 0, len );
}
pcm->Write( buf, frames_to_fill );
return true;
}
void RageSound_ALSA9::StartMixing(RageSoundBase *snd)
{
/* Lock INACTIVE sounds[], and reserve a slot. */
m_InactiveSoundMutex.Lock();
/* Find an unused buffer. */
unsigned i;
for( i = 0; i < stream_pool.size(); ++i )
{
if( stream_pool[i]->state == stream_pool[i]->INACTIVE )
break;
}
if( i == stream_pool.size() )
{
/* We don't have a free sound buffer. */
m_InactiveSoundMutex.Unlock();
return;
}
/* Place the sound in SETUP, where nobody else will touch it, until we put it
* in FLUSHING or PLAYING below. */
stream_pool[i]->state = stream::SETUP;
m_InactiveSoundMutex.Unlock();
/* Give the stream to the playing sound and remove it from the pool. */
stream_pool[i]->snd = snd;
stream_pool[i]->pcm->SetSampleRate( snd->GetSampleRate() );
stream_pool[i]->pcm->SetChunksize( chunksize );
stream_pool[i]->pcm->SetWriteahead( max_writeahead );
stream_pool[i]->start_time = snd->GetStartTime();
/* Pre-buffer the stream, and start it immediately. */
bool bEOF;
while( stream_pool[i]->GetData(bEOF) && !bEOF )
;
stream_pool[i]->pcm->Play();
/* If bEOF is true, we actually finished the whole file in the prebuffering
* GetData call above, and the sound should go straight to FLUSHING. Otherwise,
* set PLAYING. */
if( bEOF )
{
stream_pool[i]->state = stream_pool[i]->FLUSHING;
stream_pool[i]->flush_pos = stream_pool[i]->pcm->GetPosition();
}
else
stream_pool[i]->state = stream_pool[i]->PLAYING;
}
void RageSound_ALSA9::Update(float delta)
{
for(unsigned i = 0; i < stream_pool.size(); ++i)
{
if( stream_pool[i]->state != stream_pool[i]->FINISHED )
continue;
/* The sound has stopped and flushed all of its buffers. */
if( stream_pool[i]->snd )
stream_pool[i]->snd->SoundIsFinishedPlaying();
stream_pool[i]->snd = NULL;
/* Once we do this, the sound is once available for use; we must lock
* m_InactiveSoundMutex to take it out of INACTIVE again. */
stream_pool[i]->state = stream_pool[i]->INACTIVE;
}
}
void RageSound_ALSA9::StopMixing(RageSoundBase *snd)
{
/* Lock, to make sure the decoder thread isn't running on this sound while we do this. */
LockMut( m_Mutex );
ASSERT(snd != NULL);
unsigned i;
for( i = 0; i < stream_pool.size(); ++i )
if(stream_pool[i]->snd == snd)
break;
if( i == stream_pool.size() )
{
LOG->Trace("not stopping a sound because it's not playing");
return;
}
/* FLUSHING tells the mixer thread to release the stream once str->flush_bufs
* buffers have been flushed. */
stream_pool[i]->state = stream_pool[i]->FLUSHING;
/* Flush two buffers worth of data. */
stream_pool[i]->flush_pos = stream_pool[i]->pcm->GetPlayPos();
/* This function is called externally (by RageSoundBase) to stop immediately.
* We need to prevent SoundStopped from being called; it should only be
* called when we stop implicitely at the end of a sound. Set snd to NULL. */
stream_pool[i]->snd = NULL;
}
int64_t RageSound_ALSA9::GetPosition(const RageSoundBase *snd) const
{
unsigned i;
for( i = 0; i < stream_pool.size(); ++i )
if( stream_pool[i]->snd == snd )
break;
if( i == stream_pool.size() )
RageException::Throw("GetPosition: Sound %s is not being played", snd->GetLoadedFilePath().c_str());
/* XXX: This isn't quite threadsafe. */
return stream_pool[i]->pcm->GetPosition();
}
int RageSound_ALSA9::GetSampleRate( int rate ) const
{
stream *str = stream_pool[0];
return str->pcm->FindSampleRate( rate );
}
static void CheckMixingBlacklist()
{
CString sID = Alsa9Buf::GetHardwareID();
const CString blacklist[] = {
/* ALSA Driver: 0: Aureal Vortex au8830 [au8830], device 0: AU88x0 ADB [adb], 32/32 subdevices avail
* ALSA segfaults after creating about ten subdevices. */
"au8830",
}, *blacklist_end = blacklist+ARRAYSIZE(blacklist);
if( find( &blacklist[0], blacklist_end, sID ) != blacklist_end )
RageException::ThrowNonfatal( "ALSA driver \"%s\" not using hardware mixing", sID.c_str() );
}
RageSound_ALSA9::RageSound_ALSA9():
m_Mutex("ALSAMutex"),
m_InactiveSoundMutex("InactiveSoundMutex")
{
CString err = LoadALSA();
if( err != "" )
RageException::ThrowNonfatal("Driver unusable: %s", err.c_str());
try {
CheckMixingBlacklist();
shutdown = false;
/* Create a bunch of streams and put them into the stream pool. */
for( int i = 0; i < 32; ++i )
{
Alsa9Buf *newbuf;
try {
newbuf = new Alsa9Buf( Alsa9Buf::HW_HARDWARE, channels );
} catch(const RageException &e) {
/* If we didn't get at least 8, fail. */
if(i >= 8) break; /* OK */
/* Clean up; the dtor won't be called. */
for(int n = 0; n < i; ++n)
delete stream_pool[n];
if( !i )
RageException::ThrowNonfatal( "%s", e.what() );
/* We created at least one hardware buffer. */
LOG->Trace("Could only create %i buffers; need at least 8 (failed with %s). Hardware ALSA driver can't be used.", i, e.what());
RageException::ThrowNonfatal("Not enough substreams for hardware mixing, using software mixing");
}
stream *s = new stream;
s->pcm = newbuf;
stream_pool.push_back(s);
}
LOG->Info("ALSA: Got %i hardware buffers", stream_pool.size());
MixingThread.SetName( "RageSound_ALSA9" );
MixingThread.Create( MixerThread_start, this );
} catch(...) {
UnloadALSA();
throw;
}
}
RageSound_ALSA9::~RageSound_ALSA9()
{
/* Signal the mixing thread to quit. */
shutdown = true;
LOG->Trace("Shutting down mixer thread ...");
MixingThread.Wait();
LOG->Trace("Mixer thread shut down.");
for(unsigned i = 0; i < stream_pool.size(); ++i)
delete stream_pool[i];
UnloadALSA();
}
/*
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
*
* Glenn Maynard (RageSoundDriver_WaveOut)
*/