sfxInternal.h

Engine/source/sfx/sfxInternal.h

Mostly internal definitions for sound stream handling.

More...

Classes:

class

An async stream queue that writes sound packets to SFXBuffers in sync to the playback of an SFXVoice.

class

Asynchronous sound data stream that delivers sound data in discrete packets.

class

Sound stream packets are raw byte buffers containing PCM sample data.

class
class

Wrapper around SFXVoice that yields the raw underlying sample position rather than the virtualized position returned by SFXVoice::getPosition().

class

Buffer that uses wrap-around packet buffering.

Namespaces:

namespace

Detailed Description

Mostly internal definitions for sound stream handling.

The code here is used by SFXBuffer for asynchronously loading sample data from sound files, both for streaming buffers as well as for "normal" buffers.

This is all pretty low-level code here.

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#ifndef _SFXINTERNAL_H_
 25#define _SFXINTERNAL_H_
 26
 27#ifndef _THREADPOOL_H_
 28   #include "platform/threads/threadPool.h"
 29#endif
 30#ifndef _ASYNCUPDATE_H_
 31   #include "platform/async/asyncUpdate.h"
 32#endif
 33#ifndef _ASYNCPACKETSTREAM_H_
 34   #include "platform/async/asyncPacketStream.h"
 35#endif
 36#ifndef _ASYNCPACKETQUEUE_H_
 37   #include "platform/async/asyncPacketQueue.h"
 38#endif
 39#ifndef _SFXSTREAM_H_
 40   #include "sfx/sfxStream.h"
 41#endif
 42#ifndef _SFXBUFFER_H_
 43   #include "sfx/sfxBuffer.h"
 44#endif
 45#ifndef _SFXVOICE_H_
 46   #include "sfx/sfxVoice.h"
 47#endif
 48#ifndef _CONSOLE_H_
 49   #include "console/console.h"
 50#endif
 51#ifndef _TSINGLETON_H_
 52   #include "core/util/tSingleton.h"
 53#endif
 54
 55
 56/// @file
 57/// Mostly internal definitions for sound stream handling.
 58/// The code here is used by SFXBuffer for asynchronously loading
 59/// sample data from sound files, both for streaming buffers
 60/// as well as for "normal" buffers.
 61///
 62/// This is all pretty low-level code here.
 63
 64
 65namespace SFXInternal {
 66
 67typedef AsyncUpdateThread SFXUpdateThread;
 68typedef AsyncUpdateList SFXBufferProcessList;
 69
 70//--------------------------------------------------------------------------
 71//    Async sound packets.
 72//--------------------------------------------------------------------------
 73
 74/// Sound stream packets are raw byte buffers containing PCM sample data.
 75class SFXStreamPacket : public AsyncPacket< U8 >
 76{
 77   public:
 78
 79      typedef AsyncPacket< U8> Parent;
 80
 81      SFXStreamPacket() {}
 82      SFXStreamPacket( U8* data, U32 size, bool ownMemory = false )
 83         : Parent( data, size, ownMemory ) {}
 84
 85      /// The format of the sound samples in the packet.
 86      SFXFormat mFormat;
 87
 88      /// @return the number of samples contained in the packet.
 89      U32 getSampleCount() const { return ( mSizeActual / mFormat.getBytesPerSample() ); }
 90};
 91
 92//--------------------------------------------------------------------------
 93//    Async SFXStream I/O.
 94//--------------------------------------------------------------------------
 95
 96/// Asynchronous sound data stream that delivers sound data
 97/// in discrete packets.
 98class SFXAsyncStream : public AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket >
 99{
100   public:
101
102      typedef AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket> Parent;
103
104      enum
105      {
106         /// The number of seconds of sample data to load per streaming packet by default.
107         /// Set this reasonably high to ensure the system is able to cope with latencies
108         /// in the buffer update chain.
109         DEFAULT_STREAM_PACKET_LENGTH = 8
110      };
111
112   protected:
113
114      /// If true, the stream reads one packet of silence beyond the
115      /// sound streams actual sound data.  This is to avoid wrap-around
116      /// playback queues running into old data when there is a delay
117      /// in playback being stopped.
118      ///
119      /// @note The silence packet is <em>not</em> counting towards stream
120      ///   playback time.
121      bool mReadSilenceAtEnd;
122
123      // AsyncPacketStream.
124      virtual SFXStreamPacket* _newPacket( U32 packetSize )
125      {
126         SFXStreamPacket* packet = Parent::_newPacket( packetSize );
127         packet->mFormat = getSourceStream()->getFormat();
128         return packet;
129      }
130      virtual void _requestNext();
131      virtual void _onArrival( SFXStreamPacket* const& packet );
132      virtual void _newReadItem( PacketReadItemRef& outRef, SFXStreamPacket* packet, U32 numElements )
133      {
134         if( !this->mNumRemainingSourceElements && mReadSilenceAtEnd )
135            packet->mIsLast = false;
136         Parent::_newReadItem( outRef, packet, numElements );
137      }
138
139   public:
140
141      /// Construct a new async sound stream reading data from "stream".
142      ///
143      /// @param stream The sound data source stream.
144      /// @param isIncremental If true, "stream" is read in packets of "streamPacketLength" size
145      ///   each; otherwise the stream is read in a single packet containing the entire stream.
146      /// @param streamPacketLength Seconds of sample data to read per streaming packet.  Only
147      ///   relevant if "isIncremental" is true.
148      /// @param numReadAhead Number of stream packets to read and buffer in advance.
149      /// @param isLooping If true, the packet stream infinitely loops over "stream".
150      SFXAsyncStream( const SFXStreamRef& stream,
151                      bool isIncremental,
152                      U32 streamPacketLength = DEFAULT_STREAM_PACKET_LENGTH,
153                      U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD,
154                      bool isLooping = false );
155
156      /// Returns true if the stream will read a packet of silence after the actual sound data.
157      U32 getReadSilenceAtEnd() const { return mReadSilenceAtEnd; }
158
159      /// Set whether the stream should read one packet of silence past the
160      /// actual sound data.  This is useful for situations where continued
161      /// playback may run into old data.
162      void setReadSilenceAtEnd( bool value ) { mReadSilenceAtEnd = value; }
163
164      /// Return the playback time of a single sound packet in milliseconds.
165      /// For non-incremental streams, this will be the duration of the
166      /// entire stream.
167      U32 getPacketDuration() const
168      {
169         const SFXFormat& format = getSourceStream()->getFormat();
170         return format.getDuration( mPacketSize / format.getBytesPerSample() );
171      }
172};
173
174//--------------------------------------------------------------------------
175//    Voice time source wrapper.
176//--------------------------------------------------------------------------
177
178/// Wrapper around SFXVoice that yields the raw underlying sample position
179/// rather than the virtualized position returned by SFXVoice::getPosition().
180class SFXVoiceTimeSource
181{
182   public:
183
184      typedef void Parent;
185
186   protected:
187
188      /// The voice to sample the position from.
189      SFXVoice* mVoice;
190
191      /// Last position returned by voice.
192      mutable U32 mLastPos;
193
194   public:
195
196      SFXVoiceTimeSource( SFXVoice* voice )
197         : mVoice( voice ), mLastPos( 0 ) {}
198
199      U32 getPosition() const
200      {
201         U32 samplePos = mVoice->_tell();
202
203         // The device playback cursor may snap back to an undefined value as soon
204         // as all the data has been consumed.  However, for us to be a reliable
205         // time source, we can't let that happen so as soon as the device play cursor
206         // goes back to a sample position we have already passed, we start reporting an
207         // end-of-stream position.
208
209         if( samplePos < mLastPos && mVoice->mBuffer != NULL )
210            samplePos = mVoice->mBuffer->getNumSamples();
211         else
212            mLastPos = samplePos;
213         
214         return samplePos;
215      }
216};
217
218//--------------------------------------------------------------------------
219//    Async sound packet queue.
220//--------------------------------------------------------------------------
221
222/// An async stream queue that writes sound packets to SFXBuffers in sync
223/// to the playback of an SFXVoice.
224///
225/// Sound packet queues use sample counts as tick counts.
226class SFXAsyncQueue : public AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer* >
227{
228   public:
229
230      typedef AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer*> Parent;
231
232      enum
233      {
234         /// The number of stream packets that the playback queue for streaming
235         /// sounds will be sliced into.  This should generally be left at
236         /// three since there is an overhead incurred for each additional
237         /// segment.  Having three segments gives one segment for current
238         /// immediate playback, one segment as intermediate buffer, and one segment
239         /// for stream writes.
240         DEFAULT_STREAM_QUEUE_LENGTH = 3,
241      };
242
243      /// Construct a new sound queue that pushes sound packets to "buffer" in sync
244      /// to the playback of "voice".
245      ///
246      /// @param voice The SFXVoice to synchronize to.
247      /// @param buffer The sound buffer to push sound packets to.
248      SFXAsyncQueue(    SFXVoice* voice,
249                        SFXBuffer* buffer,
250                        bool looping = false )
251         : Parent(   DEFAULT_STREAM_QUEUE_LENGTH,
252                        voice,
253                        buffer,
254                        ( looping
255                          ? 0
256                          : ( buffer->getDuration() * ( buffer->getFormat().getSamplesPerSecond() / 1000 ) ) - voice->mOffset ) ) {}
257};
258
259//--------------------------------------------------------------------------
260//    SFXBuffer with a wrap-around buffering scheme.
261//--------------------------------------------------------------------------
262
263/// Buffer that uses wrap-around packet buffering.
264///
265/// This class automatically coordinates retrieval and submitting of
266/// sound packets and also protects against play cursors running beyond
267/// the last packet by making sure some silence is submitted after the
268/// last packet (does not count towards playback time).
269///
270/// @note Note that the reaction times of this class depend on the SFXDevice
271///   triggering timely SFXBuffer:update() calls.
272class SFXWrapAroundBuffer : public SFXBuffer
273{
274   public:
275
276      typedef SFXBuffer Parent;
277
278   protected:
279
280      /// Absolute byte offset into the sound stream that the next packet write
281      /// will occur at.  This is <em>not</em> an offset into the device buffer
282      /// in order to allow us to track how far in the source stream we are.
283      U32 mWriteOffset;
284
285      /// Size of the device buffer in bytes.
286      U32 mBufferSize;
287
288      // SFXBuffer.
289      virtual void _flush()
290      {
291         mWriteOffset = 0;
292      }
293
294      /// Copy "length" bytes from "data" into the device at "offset".
295      virtual bool _copyData( U32 offset, const U8* data, U32 length ) = 0;
296
297      // SFXBuffer.
298      virtual void write( SFXStreamPacket* const* packets, U32 num );
299
300      /// @return the sample position in the sound stream as determined from the
301      ///   given buffer offset.
302      U32 getSamplePos( U32 bufferOffset ) const
303      {
304         if( !mBufferSize )
305            return ( bufferOffset / getFormat().getBytesPerSample() );
306            
307         const U32 writeOffset = mWriteOffset; // Concurrent writes on this one.
308         const U32 writeOffsetRelative = writeOffset % mBufferSize;
309         
310         U32 numBufferedBytes;
311         if( !writeOffset )
312            numBufferedBytes = 0;
313         else if( writeOffsetRelative > bufferOffset )
314            numBufferedBytes = writeOffsetRelative - bufferOffset;
315         else
316            // Wrap-around.
317            numBufferedBytes = mBufferSize - bufferOffset + writeOffsetRelative;
318
319         const U32 bytePos = writeOffset - numBufferedBytes;
320         
321         return ( bytePos / getFormat().getBytesPerSample() );
322      }
323
324   public:
325
326      SFXWrapAroundBuffer( const ThreadSafeRef< SFXStream>& stream, SFXDescription* description );
327      SFXWrapAroundBuffer( SFXDescription* description )
328         : Parent( description ), mBufferSize( 0 ), mWriteOffset(0) {}
329         
330      virtual U32 getMemoryUsed() const { return mBufferSize; }
331};
332
333//--------------------------------------------------------------------------
334//    Global state.
335//--------------------------------------------------------------------------
336
337enum
338{
339   /// Soft limit on milliseconds to spend on updating sound buffers
340   /// when doing buffer updates on the main thread.
341   MAIN_THREAD_PROCESS_TIMEOUT = 512,
342   
343   /// Default time interval between periodic sound updates in milliseconds.
344   /// Only relevant for devices that perform periodic updates.
345   DEFAULT_UPDATE_INTERVAL = 512,
346};
347
348/// Thread pool for sound I/O.
349///
350/// We are using a separate pool for sound packets in order to be
351/// able to submit packet items from different threads.  This would
352/// violate the invariant of the global thread pool that only the
353/// main thread is feeding the queues.
354///
355/// Note that this also means that only at certain very well-defined
356/// points is it possible to safely flush the work item queue on this
357/// pool.
358///
359/// @note Don't use this directly but rather use THREAD_POOL() instead.
360///   This way, the sound code may be easily switched to using a common
361///   pool later on.
362class SFXThreadPool : public ThreadPool, public ManagedSingleton< SFXThreadPool >
363{
364   public:
365   
366      typedef ThreadPool Parent;
367      
368      /// Create a ThreadPool called "SFX" with two threads.
369      SFXThreadPool()
370         : Parent( "SFX", 2 ) {}
371         
372      // For ManagedSingleton.
373      static const char* getSingletonName() { return "SFXThreadPool"; }
374};
375
376/// Dedicated thread that does sound buffer updates.
377/// May be NULL if sound API used does not do asynchronous buffer
378/// updates but rather uses per-frame polling.
379///
380/// @note SFXDevice automatically polls if this is NULL.
381extern ThreadSafeRef< AsyncUpdateThread> gUpdateThread;
382
383/// List of buffers that need updating.
384///
385/// It depends on the actual device whether this list is processed
386/// on a stream update thread or on the main thread.
387extern ThreadSafeRef< SFXBufferProcessList> gBufferUpdateList;
388
389/// List of buffers that are pending deletion.
390///
391/// This is a messy issue.  Buffers with live async states cannot be instantly
392/// deleted since they may still be running concurrent updates.  However, they
393/// also cannot be deleted on the update thread since the StrongRefBase stuff
394/// isn't thread-safe (i.e weak references kept by client code would cause trouble).
395///
396/// So, what we do is mark buffers for deletion, wait till they surface on the
397/// process list and then ping them back to this list to have them deleted by the
398/// SFXDevice itself on the main thread.  A bit of overhead but only a fraction of
399/// the buffers will ever undergo this procedure.
400extern ThreadSafeDeque< SFXBuffer*> gDeadBufferList;
401
402/// Return the thread pool used for SFX work.
403inline ThreadPool& THREAD_POOL()
404{
405   return *( SFXThreadPool::instance() );
406}
407
408/// Return the dedicated SFX update thread; NULL if updating on the main thread.
409inline ThreadSafeRef< SFXUpdateThread> UPDATE_THREAD()
410{
411   return gUpdateThread;
412}
413
414/// Return the processing list for SFXBuffers that need updating.
415inline SFXBufferProcessList& UPDATE_LIST()
416{
417   return *gBufferUpdateList;
418}
419
420/// Trigger an SFX update.
421inline bool TriggerUpdate()
422{
423   ThreadSafeRef< SFXUpdateThread> sfxThread = UPDATE_THREAD();
424   if( sfxThread != NULL )
425   {
426      sfxThread->triggerUpdate();
427      return true;
428   }
429   else
430      return false;
431}
432
433/// Delete all buffers currently on the dead buffer list.
434inline void PurgeDeadBuffers()
435{
436   SFXBuffer* buffer;
437   while( gDeadBufferList.tryPopFront( buffer ) )
438      delete buffer;
439}
440
441/// Return true if the current thread is the one responsible for doing SFX updates.
442inline bool isSFXThread()
443{
444   ThreadSafeRef< SFXUpdateThread> sfxThread = UPDATE_THREAD();
445
446   U32 threadId;
447   if( sfxThread != NULL )
448      threadId = sfxThread->getId();
449   else
450      threadId = ThreadManager::getMainThreadId();
451
452   return ThreadManager::compare( ThreadManager::getCurrentThreadId(), threadId );
453}
454
455} // namespace SFXInternal
456
457#endif // !_SFXINTERNAL_H_
458