sfxInternal.h
Engine/source/sfx/sfxInternal.h
Mostly internal definitions for sound stream handling.
Classes:
An async stream queue that writes sound packets to SFXBuffers in sync to the playback of an SFXVoice.
Asynchronous sound data stream that delivers sound data in discrete packets.
Sound stream packets are raw byte buffers containing PCM sample data.
Thread pool for sound I/O.
Wrapper around SFXVoice that yields the raw underlying sample position rather than the virtualized position returned by SFXVoice::getPosition().
Buffer that uses wrap-around packet buffering.
Namespaces:
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