📄 beaudio.cxx
字号:
}
BOOL PSound::PlayFile(const PFilePath & file, BOOL wait)
{
entry_ref ref;
status_t err; // can't use dwLastError because this function is static
// using pointers for these objects so that we don't have to
// construct them here but can nevertheless use the if(ok)'s
BEntry *pentry = NULL;
{
// Create BEntry from file name
pentry = new BEntry(file, true);
err = pentry->InitCheck();
}
if (err==B_OK)
{
// Create entry_ref from BEntry
err = pentry->GetRef(&ref);
}
if (err==B_OK)
{
// Play the sound. Return value is a handle or a negative value for errors
// Errors in BeOS are always negative values
err=play_sound(&ref, true, !wait, wait);
if (err>=0)
{
err=B_OK;
}
}
return (err==B_OK);
}
void PSound::Beep()
{
::beep();
}
////////////////////////////////////////////////////////////////////////////////
// CircularBuffer
class Guard
{
private:
sem_id mSem;
public:
Guard(sem_id sem) { acquire_sem(mSem=sem); }
~Guard() { release_sem(mSem); }
};
/*
This class represents a circular FIFO buffer.
The buffer has a head and a tail that chase each other.
The data is added to the buffer at the tail side by using Fill.
The data from the buffer can be read starting at the head side using
Drain.
It is possible to use two threads to fill and drain the buffer but
there should not be more than 2 threads doing draining and filling.
Resetting (flushing) or destroying from a third thread is allowed;
do make sure that any threads that operate on buffer data are stopped
before destroying a buffer.
Normally, filling and draining operations block the thread as short as
possible (i.e. only when the other thread needs to update the head and
tail pointers etc). If the filling thread tries to put data into a full
or almost full buffer, it just returns after filling as much data as
it can, and if the draining thread tries to get more data out than is
in the buffer, it will simply return with the data that is there.
In order to move all the data from an external buffer into an object
of this class, the caller would have to call Fill repeatedly until
all the data has been processed (similarly it would have to call Drain
until it receives sufficient data). But if the application has nothing
else to do in the mean time, this constitutes a Busy Waiting loop
on either the filling or draining side of the FIFO buffer that slurps
up as much CPU time as possible.
To improve this behaviour, it's possible to specify a threshold value
that is used to change the state to FullEnough and EmptyEnough. By using
these states (instead of Full and Empty), one thread can block until the
other thread has determined that there is enough data or enough room for
data.
*/
class CircularBuffer
{
public:
// Internal state for the buffer
// Note the nifty bit patterns for comparing the current state
// with a desired state
typedef enum
{ // Headspace Tailspace
Empty =1, // 0 size
Filled =2, // 0<h<size 0<t<size
NotFull =3, // 0<=h<size 0<t<=size (for comparing)
Full =4, // size 0
NotEmpty =6, // 0<h<=size 0<=t<size (for comparing)
Flushed =8, // (extra signal to threads waiting on full)
FullEnough =16, // h>=drainthreshold
EmptyEnough =32, // t>=fillthreshold
} State;
protected:
friend class ResamplingBuffer; // needed for one of their constructors
BYTE *mBuffer; // the buffer
PINDEX mSize; // size of the buffer in bytes
volatile PINDEX mHead; // index where to start reading
volatile PINDEX mTail; // index where to start writing
volatile PINDEX mHeadRoom; // consecutive space from head to end-of-buffer or tail
volatile PINDEX mTailRoom; // consecutive space from tail to end-of-buffer or head
volatile PINDEX mSizeUsed; // total bytes in use
volatile PINDEX mFillThreshold; // see above
volatile PINDEX mDrainThreshold; // see above
volatile State mState; // current state of the buffer
sem_id mSemInUse; // used to guard data integrity
sem_id mSemStateChange; // used to wait for state changes
protected:
// Check if the state changed. Private because it's not guarded by semaphore
void UpdateState(void)
{
// Determine current state
State newstate;
if (mSizeUsed==mSize)
{
PRINTCB(("State is FULL"));
newstate=Full;
}
else if (mSizeUsed==0)
{
PRINTCB(("State is EMPTY"));
newstate=Empty;
}
else
{
PRINTCB(("State is FILLED"));
newstate=Filled;
}
// Check thresholds
if (mSize-mSizeUsed>=mFillThreshold)
{
PRINTCB(("...and EMPTYENOUGH"));
newstate=(State)(newstate | EmptyEnough);
}
if (mSizeUsed>=mDrainThreshold)
{
PRINTCB(("...and FULLENOUGH"));
newstate=(State)(newstate | FullEnough);
}
// Check if the state changed
if (newstate!=mState)
{
PRINTCB(("Updating state from %X to %X", mState, newstate));
// Set the new state
mState=newstate;
// Signal state change
release_sem(mSemStateChange);
}
}
virtual size_t Write(
BYTE *dest, // destination
const BYTE **extbuf, // source, to be updated
size_t size, // space in destination
size_t *extsize) // data in source, to be updated
{
// This function is called to put data into the buffer
size_t todo=MIN(size, *extsize);
memcpy(dest, *extbuf, todo);
*extbuf +=todo; // The external pointer moves forward...
*extsize -=todo; // ... so the remaining size decreases
return todo;
}
virtual size_t Read(
BYTE **extbuf, // destination, to be updated
const BYTE *src, // source
size_t *extsize, // space in destination, to be updated
size_t size) // data in source
{
// This function is called to read data out of the buffer
size_t todo=MIN(size, *extsize);
memcpy(*extbuf, src, todo);
*extbuf +=todo; // The external pointer moves forward...
*extsize -=todo; // ... so the remaining size decreases
return todo;
}
public:
// Reset buffer so that it can be filled again
void Reset(void)
{
Guard _(mSemInUse); // guard data integrity
mHead=mHeadRoom=mTail=mSizeUsed=0;
mTailRoom=GetSize();
mState=(State)(Flushed|Empty|EmptyEnough);
}
// Constructor
CircularBuffer(
PINDEX size,
PINDEX fillthreshold = 0,
PINDEX drainthreshold = 0)
: mFillThreshold(fillthreshold), mDrainThreshold(drainthreshold), mState(Empty)
{
PAssert(size!=0, "Attempting to create a buffer with size 0");
mSemInUse=create_sem(1, "mSemInUse");
mSemStateChange=create_sem(0, "mSemStateChange");
PAssert(mSemInUse>=0 && mSemStateChange>=0, "Unable to create semaphores");
mBuffer=new BYTE[(mSize=size)];
Reset();
}
// Destructor
virtual ~CircularBuffer()
{
// make sure the in-use semaphore is free and stays free
while (acquire_sem_etc(mSemInUse,1,B_RELATIVE_TIMEOUT,0)==B_WOULD_BLOCK)
{
// nothing to do, just busy-wait
}
delete_sem(mSemInUse);
delete_sem(mSemStateChange);
Reset();
if(mBuffer)
delete[] mBuffer;
}
// Check if buffer is empty
bool IsEmpty() { return (mState==Empty); }
// Check if buffer is full
bool IsFull() { return (mState==Full); }
// Get the size of the buffer
PINDEX GetSize(void) { return mSize; }
// Wait asynchronously for a buffer state or one of a number of states
void WaitForState(State state)
{
PRINTCB(("Waiting for state %X, current state=%X this=%p", state, mState, this));
// reset the Flushed bit so it only stops the loop if the buffer
// is flushed DURING an operation
{
Guard _(mSemInUse);
mState=(State)(mState & ~Flushed);
}
for(;;)
{
if ((mState & (state|Flushed))!=0) // bit patterns allowed
{
PRINTCB(("Detected state %X, wanted %X, returning", mState, state));
return;
}
PRINTCB(("Waiting for %X; headroom=%u tailroom=%u this=%p",state,mHeadRoom,mTailRoom,this));
// To prevent a race condition here in case the state
// gets changed just after the GetState call, the next
// semaphore call has a timeout.
acquire_sem_etc(mSemStateChange,1,B_RELATIVE_TIMEOUT,1000000);
}
}
// Fill buffer with data.
void Fill(const BYTE **extbuf, size_t *extsize)
{
PRINTCB(("start: head %d tail %d headroom %d tailroom %d extsize %d buffer %p this %p", mHead, mTail, mHeadRoom, mTailRoom, *extsize, mBuffer, this));
// Make a local copy of the queue.
// This is ok because there is only one filler thread and
// one drainer thread. The drainer is not going to make the
// free area for the filler any smaller and the filler is not
// going to overwrite the drainer's data if we do this.
// This way we can keep the semaphore busy as short as possible.
PINDEX lTail;
PINDEX lTailRoom;
PINDEX lHead; // read only
{
Guard _(mSemInUse); // guard data integrity
lTail=mTail;
lTailRoom=mTailRoom;
lHead=mHead;
}
bool needhousekeeping=false;
PINDEX totaldone=0;
while (*extsize!=0 && lTailRoom!=0 && totaldone<mSize)
{
needhousekeeping=true;
PINDEX done=Write(
mBuffer+lTail,
extbuf,
lTailRoom,
extsize);
totaldone +=done;
lTail +=done; // The tail moves forward...
lTailRoom -=done; // ... so there will be less room at the tail
// Check if we should wrap around
if (lTail==mSize)
{
lTail=0;
lTailRoom=lHead;
}
}
if (needhousekeeping)
{
Guard _(mSemInUse);
// Copy the local values back
mTail=lTail;
mTailRoom=lTailRoom;
mSizeUsed+=totaldone;
// Recalculate headroom
if (mTail>mHead)
{
mHeadRoom=mTail-mHead;
}
else
{
mHeadRoom=mSize-mHead;
}
// Check if we need to change the state
UpdateState();
PRINTCB((" end: head %d tail %d headroom %d tailroom %d extsize %d", mHead, mTail, mHeadRoom, mTailRoom, *extsize));
}
}
// Empty data out of buffer
void Drain(BYTE **extbuf, size_t *extsize)
{
PTRACE(7, "Drain: head " << mHead
<< " tail " << mTail
<< " headroom " << mHeadRoom
<< " tailroom " << mTailRoom
<< " extsize " << *extsize
<< " buffer " << mBuffer
<< " this " << this);
// Make a local copy of the queue.
// This is ok because there is only one filler thread and
// one drainer thread. The drainer is not going to make the
// free area for the filler any smaller and the filler is not
// going to overwrite the drainer's data if we do this.
// This way we can keep the semaphore busy as short as possible.
PINDEX lHead;
PINDEX lHeadRoom;
PINDEX lTail; // read only
{
Guard _(mSemInUse); // guard data integrity
lHead=mHead;
lHeadRoom=mHeadRoom;
lTail=mTail;
}
bool needhousekeeping=false;
PINDEX totaldone=0;
while (*extsize!=0 && lHeadRoom!=0 && totaldone<mSize)
{
needhousekeeping=true;
size_t done=Read(
extbuf,
mBuffer+lHead,
extsize,
lHeadRoom);
totaldone +=done;
lHead +=done; // The head moves forward...
lHeadRoom -=done; // ... so there will be less room at the head
// Check if we should wrap around
if (lHead==mSize)
{
lHead=0;
lHeadRoom=mTail;
}
}
if (needhousekeeping)
{
Guard _(mSemInUse);
// Copy the local values back
mHead=lHead;
mHeadRoom=lHeadRoom;
mSizeUsed-=totaldone;
// Recalculate tailroom
if (mHead>mTail)
{
mTailRoom=mHead-mTail;
}
else
{
mTailRoom=GetSize()-mTail;
}
// Check if we need to change the state
UpdateState();
PRINTCB((" end: head %d tail %d headroom %d tailroom %d extsize %d", mHead, mTail, mHeadRoom, mTailRoom, *extsize));
}
}
};
////////////////////////////////////////////////////////////////////////////////
class ResamplingBuffer : public CircularBuffer
{
protected:
Resampler *mResampler;
protected:
virtual size_t Write(
BYTE *dest, // destination
const BYTE **extbuf, // source, to be updated
size_t size, // space in destination
size_t *extsize) // data in source, to be updated
{
size_t todo=*extsize/mResampler->InFrameSize();
size_t done=mResampler->InFrames(
(const short **)extbuf,
(short **)&dest,
&todo,
size/mResampler->OutFrameSize());
done*=mResampler->OutFrameSize();
*extsize=todo*mResampler->InFrameSize();
return done;
}
public:
void SetResampler(Resampler *resampler)
{
Guard _(mSemInUse); // guard data integrity
mResampler=resampler;
}
ResamplingBuffer(
Resampler *resampler,
PINDEX size,
PINDEX fillthreshold=0,
PINDEX drainthreshold=0)
: CircularBuffer(size, fillthreshold, drainthreshold), mResampler(NULL)
{
SetResampler(resampler);
}
ResamplingBuffer(
Resampler *resampler,
CircularBuffer *other)
: CircularBuffer(other->mSize, other->mFillThreshold, other->mDrainThreshold), mResampler(NULL)
{
SetResampler(resampler);
}
};
////////////////////////////////////////////////////////////////////////////////
static void PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format)
{
// This function is called by the BSoundPlayer object whenever it needs some more
// data to play.
DETECTVARS(buffer, size/2)
((CircularBuffer *)cookie)->Drain((BYTE **)&buffer, &size);
DETECTSOUND();
}
static void RecordBuffer(void *cookie, const void *buffer, size_t size, const media_header &header)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -