Threadsafe fragment queue.

This commit is contained in:
Christian Speckner 2018-01-17 19:28:49 +01:00
parent 317f7391c2
commit be91e6ff21
3 changed files with 276 additions and 1 deletions

133
src/common/AudioQueue.cxx Normal file
View File

@ -0,0 +1,133 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AudioQueue.hxx"
using std::mutex;
using std::lock_guard;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate)
: myFragmentSize(fragmentSize),
myIsStereo(isStereo),
myFragmentQueue(capacity),
myAllFragments(capacity + 2),
mySize(0),
myNextFragment(0)
{
const uInt8 sampleSize = myIsStereo ? 2 : 1;
myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)];
for (uInt8 i = 0; i < capacity; i++)
myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize;
myAllFragments[capacity] = myFirstFragmentForEnqueue =
myFragmentBuffer + capacity * sampleSize * myFragmentSize;
myAllFragments[capacity + 1] = myFirstFragmentForDequeue =
myFragmentBuffer + (capacity + 1) * sampleSize * myFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioQueue::~AudioQueue()
{
delete[]myFragmentBuffer;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 AudioQueue::capacity() const
{
return myFragmentQueue.size();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 AudioQueue::size()
{
lock_guard<mutex> guard(myMutex);
return mySize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioQueue::isStereo() const
{
return myIsStereo;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioQueue::fragmentSize() const
{
return myFragmentSize;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt16 AudioQueue::sampleRate() const
{
return mySampleRate;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int16* AudioQueue::enqueue(Int16* fragment)
{
lock_guard<mutex> guard(myMutex);
Int16* newFragment;
if (!fragment) {
if (!myFirstFragmentForEnqueue) throw runtime_error("enqueue called empty");
newFragment = myFirstFragmentForEnqueue;
myFirstFragmentForEnqueue = 0;
return newFragment;
}
const uInt8 capacity = myFragmentQueue.size();
const uInt8 fragmentIndex = (myNextFragment + mySize) % capacity;
newFragment = myFragmentQueue.at(fragmentIndex);
myFragmentQueue.at(fragmentIndex) = fragment;
if (mySize < capacity) mySize++;
else myNextFragment = (myNextFragment + 1) % capacity;
return newFragment;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int16* AudioQueue::dequeue(Int16* fragment)
{
lock_guard<mutex> guard(myMutex);
if (mySize == 0) return 0;
if (!fragment) {
if (!myFirstFragmentForDequeue) throw runtime_error("dequeue called empty");
fragment = myFirstFragmentForDequeue;
myFirstFragmentForDequeue = 0;
}
Int16* nextFragment = myFragmentQueue.at(myNextFragment);
myFragmentQueue.at(myNextFragment) = fragment;
mySize--;
myNextFragment = (myNextFragment + 1) % myFragmentQueue.size();
return nextFragment;
}

141
src/common/AudioQueue.hxx Normal file
View File

@ -0,0 +1,141 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef AUDIO_QUEUE_HXX
#define AUDIO_QUEUE_HXX
#include <mutex>
#include "bspf.hxx"
/**
This class implements a an audio queue that acts both like a ring buffer
and a pool of audio fragments. The TIA emulation core fills a fragment
with samples and then returns it to the queue, receiving a new fragment
in return. The sound driver removes fragments for playback from the
queue and returns the used fragment in this process.
The queue needs to be threadsafe as the (SDL) audio driver runs on a
separate thread. Samples are stored as signed 16 bit integers
(platform endian).
*/
class AudioQueue
{
public:
/**
Create a new AudioQueue.
@param fragmentSize The size (in stereo / mono samples) of each fragment
@param capacaity The number of fragments that can be queued before wrapping.
@param isStereo Whether samples are stereo or mono.
@param sampleRate The sample rate. This is not used, but can be queried.
*/
AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate);
/**
We need a destructor to deallocate the individual fragment buffers.
*/
~AudioQueue();
/**
Capacity getter.
*/
uInt8 capacity() const;
/**
Size getter.
*/
uInt8 size();
/**
Stereo / mono getter.
*/
bool isStereo() const;
/**
Fragment size getter.
*/
uInt32 fragmentSize() const;
/**
Sample rate getter.
*/
uInt16 sampleRate() const;
/**
Enqueue a new fragment and get a new fragmen to fill.
@param fragment The returned fragment. This must be empty on the first call (when
there is nothing to return)
*/
Int16* enqueue(Int16* fragment = 0);
/**
* Dequeue a fragment for playback and return the played fragment. This may
* return 0 if there is no queued fragment to return (in this case, the returned
* fragment is not enqueued and must be passed in the next invocation).
*
* @param fragment The returned fragment. This must be empty on the first call (when
* there is nothing to return).
*/
Int16* dequeue(Int16* fragment = 0);
private:
// The size of an individual fragment (in stereo / mono samples)
uInt32 myFragmentSize;
// Are we using stereo samples?
bool myIsStereo;
// The sample rate
uInt16 mySampleRate;
// The fragment queue
vector<Int16*> myFragmentQueue;
// All fragments, including the two fragments that are in circulation.
vector<Int16*> myAllFragments;
// We allocate a consecutive slice of memory for the fragments.
Int16* myFragmentBuffer;
// The nubmer if queued fragments
uInt8 mySize;
// The next fragment.
uInt8 myNextFragment;
// We need a mutex for thread safety.
std::mutex myMutex;
// The first (empty) enqueue call returns this fragment.
Int16* myFirstFragmentForEnqueue;
// The first (empty) dequeue call replaces the returned fragment with this fragment.
Int16* myFirstFragmentForDequeue;
private:
AudioQueue() = delete;
AudioQueue(const AudioQueue&) = delete;
AudioQueue(AudioQueue&&) = delete;
AudioQueue& operator=(const AudioQueue&) = delete;
AudioQueue& operator=(AudioQueue&&) = delete;
};
#endif // AUDIO_QUEUE_HXX

View File

@ -12,7 +12,8 @@ MODULE_OBJS := \
src/common/MouseControl.o \
src/common/RewindManager.o \
src/common/StateManager.o \
src/common/ZipHandler.o
src/common/ZipHandler.o \
src/common/AudioQueue.o
MODULE_DIRS += \
src/common