2
0
Эх сурвалжийг харах

OpenAL audio source implementation finished (needs testing)
Register OpenAudio importer on OpenAudio startup
Add menu items for audio source and listener to the editor

BearishSun 9 жил өмнө
parent
commit
98598d5494

+ 6 - 16
Source/BansheeCore/Include/BsAudioClip.h

@@ -110,28 +110,18 @@ namespace BansheeEngine
 		bool is3D() const { return mDesc.is3D; }
 
 		/** 
-		 * Returns audio samples in PCM format, channel data interleaved. Sample read pointer is advanced by the read 
-		 * amount. Only available if the audio data has been created with AudioReadMode::Stream, 
-		 * AudioReadMode::LoadCompressed (and the format is compressed), or if @p keepSourceData was enabled on creation.
+		 * Returns audio samples in PCM format, channel data interleaved. Only available if the audio data has been created
+		 * with AudioReadMode::Stream, AudioReadMode::LoadCompressed (and the format is compressed), or if @p keepSourceData
+		 * was enabled on creation.
 		 *
 		 * @param[in]	samples		Previously allocated buffer to contain the samples.
+		 * @param[in]	offset		Offset in number of samples at which to start reading (should be a multiple of number
+		 *							of channels).
 		 * @param[in]	count		Number of samples to read (should be a multiple of number of channels).
 		 *
 		 * @note	Implementation must be thread safe as this will get called from audio streaming thread.
 		 */
-		virtual void getSamples(UINT8* samples, UINT32 count) const = 0;
-
-		/**
-		 * Moves the read location from which the getSamples method retrieves samples. Only available if the audio data
-		 * has been created with AudioReadMode::Stream, AudioReadMode::LoadCompressed (and the format is compressed), 
-		 * or if @p keepSourceData was enabled on creation.
-		 *
-		 * @param[in]	offset	Offset in number of samples at which to start reading (should be a multiple of number
-		 *						of channels).
-		 *
-		 * @note	Implementation must be thread safe as this will get called from audio streaming thread.
-		 */
-		virtual void seekSamples(UINT32 offset) = 0;
+		virtual void getSamples(UINT8* samples, UINT32 offset, UINT32 count) const = 0;
 
 		/**
 		 * Creates a new AudioClip and populates it with provided samples.

+ 9 - 2
Source/BansheeCore/Include/BsAudioSource.h

@@ -118,9 +118,16 @@ namespace BansheeEngine
 		/**
 		 * Sets the time at which playback will begin.
 		 *
-		 * @param[in]	position	Time in seconds, in range [0, clipLength].
+		 * @param[in]	time	Time in seconds, in range [0, clipLength].
 		 */
-		virtual void seek(float position) = 0;
+		virtual void setTime(float time) = 0;
+
+		/**
+		 * Returns the current playback time.
+		 *
+		 * @return	time	Time in seconds, in range [0, clipLength].
+		 */
+		virtual float getTime() const = 0;
 
 		/** Returns the current state of the audio playback (playing/paused/stopped). */
 		AudioSourceState getState() const { return mState; }

+ 6 - 3
Source/BansheeCore/Include/BsCAudioSource.h

@@ -65,6 +65,12 @@ namespace BansheeEngine
 		/** @copydoc AudioSource::getAttenuation */
 		float getAttenuation() const { return mAttenuation; }
 
+		/** @copydoc AudioSource::setTime */
+		void setTime(float time);
+
+		/** @copydoc AudioSource::getTime */
+		float getTime() const;
+
 		/** @copydoc AudioSource::play */
 		void play();
 
@@ -74,9 +80,6 @@ namespace BansheeEngine
 		/** @copydoc AudioSource::stop */
 		void stop();
 
-		/** @copydoc AudioSource::seek */
-		void seek(float position);
-
 		/** @copydoc AudioSource::getState */
 		AudioSourceState getState() const;
 

+ 18 - 2
Source/BansheeCore/Source/BsCAudioSource.cpp

@@ -112,10 +112,26 @@ namespace BansheeEngine
 			mInternal->stop();
 	}
 
-	void CAudioSource::seek(float position)
+	void CAudioSource::setTime(float position)
 	{
 		if (mInternal != nullptr)
-			mInternal->seek(position);
+			mInternal->setTime(position);
+	}
+
+	float CAudioSource::getTime() const
+	{
+		if (mInternal != nullptr)
+			return mInternal->getTime();
+
+		return 0.0f;
+	}
+
+	AudioSourceState CAudioSource::getState() const
+	{
+		if (mInternal != nullptr)
+			return mInternal->getState();
+
+		return AudioSourceState::Stopped;
 	}
 
 	void CAudioSource::onInitialized()

+ 17 - 20
Source/BansheeOpenAudio/Include/BsOAAudio.h

@@ -29,7 +29,7 @@ namespace BansheeEngine
 		void setPaused(bool paused) override;
 
 		/** @copydoc Audio::isPaused */
-		bool isPaused() const override;
+		bool isPaused() const override { return mIsPaused; }
 
 		/** @copydoc Audio::update */
 		void _update() override;
@@ -71,6 +71,18 @@ namespace BansheeEngine
 		/** Returns an OpenAL context assigned to the provided listener. */
 		ALCcontext* _getContext(const OAAudioListener* listener) const;
 
+		/** 
+		 * Returns optimal format for the provided number of channels and bit depth. It is assumed the user has checked if
+		 * extensions providing these formats are actually available.
+		 */
+		INT32 _getOpenALBufferFormat(UINT32 numChannels, UINT32 bitDepth);
+
+		/** 
+		 * Writes provided samples into the OpenAL buffer with the provided ID. If the provided format is not supported the
+		 * samples will first be converted into a valid format.
+		 */
+		void _writeToOpenALBuffer(UINT32 bufferId, UINT8* samples, const AudioFileInfo& info);
+
 		/** @} */
 
 	private:
@@ -86,25 +98,8 @@ namespace BansheeEngine
 		/** Command queued for a streaming audio source. */
 		struct StreamingCommand
 		{
-			StreamingCommand()
-				:type((StreamingCommandType)0), source(nullptr), params()
-			{ }
-
 			StreamingCommandType type;
 			OAAudioSource* source;
-			UINT32 params[2];
-		};
-
-		/** Data required for maintaining a streaming audio source. */
-		struct StreamingData
-		{
-			StreamingData()
-				:streamBuffers()
-			{ }
-
-			static const UINT32 StreamBufferCount = 3;
-			UINT32 streamBuffers[StreamBufferCount];
-			Vector<UINT32> sourceIDs;
 		};
 
 		/** @copydoc Audio::createClip */
@@ -132,12 +127,13 @@ namespace BansheeEngine
 		void updateStreaming();
 
 		/** Starts data streaming for the provided source. */
-		void startStreaming(OAAudioSource* source, bool startPaused);
+		void startStreaming(OAAudioSource* source);
 
 		/** Stops data streaming for the provided source. */
 		void stopStreaming(OAAudioSource* source);
 
 		float mVolume;
+		bool mIsPaused;
 
 		ALCdevice* mDevice;
 		Vector<AudioDevice> mAllDevices;
@@ -150,7 +146,8 @@ namespace BansheeEngine
 
 		// Streaming thread
 		Vector<StreamingCommand> mStreamingCommandQueue;
-		UnorderedMap<OAAudioSource*, StreamingData> mStreamingSources;
+		UnorderedSet<OAAudioSource*> mStreamingSources;
+		UnorderedSet<OAAudioSource*> mDestroyedSources;
 		SPtr<Task> mStreamingTask;
 		mutable Mutex mMutex;
 	};

+ 1 - 4
Source/BansheeOpenAudio/Include/BsOAAudioClip.h

@@ -20,10 +20,7 @@ namespace BansheeEngine
 		virtual ~OAAudioClip();
 
 		/** @copydoc AudioClip::getSamples */
-		void getSamples(UINT8* samples, UINT32 count) const override;
-
-		/** @copydoc AudioClip::seekSamples */
-		void seekSamples(UINT32 offset) override;
+		void getSamples(UINT8* samples, UINT32 offset, UINT32 count) const override;
 
 		/** @name Internal
 		 *  @{

+ 32 - 6
Source/BansheeOpenAudio/Include/BsOAAudioSource.h

@@ -45,6 +45,12 @@ namespace BansheeEngine
 		/** @copydoc AudioSource::setAttenuation */
 		void setAttenuation(float attenuation) override;
 
+		/** @copydoc AudioSource::setTime */
+		void setTime(float setTime) override;
+
+		/** @copydoc AudioSource::getTime */
+		float getTime() const override;
+
 		/** @copydoc AudioSource::play */
 		void play() override;
 
@@ -54,9 +60,6 @@ namespace BansheeEngine
 		/** @copydoc AudioSource::stop */
 		void stop() override;
 
-		/** @copydoc AudioSource::seek */
-		void seek(float position) override;
-
 	private:
 		friend class OAAudio;
 
@@ -66,6 +69,18 @@ namespace BansheeEngine
 		/** Rebuilds the internal representation of an audio source. */
 		void rebuild();
 
+		/** Streams new data into the source audio buffer, if needed. */
+		void stream();
+
+		/** Starts data streaming from the currently attached audio clip. */
+		void startStreaming();
+
+		/** Stops streaming data from the currently attached audio clip. */
+		void stopStreaming();
+
+		/** Pauses or resumes audio playback due to the global pause setting. */
+		void setGlobalPause(bool pause);
+
 		/** 
 		 * Returns true if the sound source is three dimensional (volume and pitch varies based on listener distance
 		 * and velocity). 
@@ -76,11 +91,22 @@ namespace BansheeEngine
 		 * Returns true if the audio source is receiving audio data from a separate thread (as opposed to loading it all
 		 * at once. 
 		 */
-		bool isStreaming() const;
+		bool requiresStreaming() const;
+
+		/** Fills the provided buffer with streaming data. */
+		bool fillBuffer(UINT32 buffer, AudioFileInfo& info, UINT32 maxNumSamples);
 
 		Vector<UINT32> mSourceIDs;
-		UINT32 mSeekPosition;
-		bool mRequiresStreaming;
+		float mSavedTime;
+		bool mGloballyPaused;
+
+		static const UINT32 StreamBufferCount = 3;
+		UINT32 mStreamBuffers[StreamBufferCount];
+		bool mBusyBuffers[StreamBufferCount];
+		UINT32 mStreamProcessedPosition;
+		UINT32 mStreamQueuedPosition;
+		bool mIsStreaming;
+		mutable Mutex mMutex;
 	};
 
 	/** @} */

+ 143 - 48
Source/BansheeOpenAudio/Source/BsOAAudio.cpp

@@ -6,12 +6,13 @@
 #include "BsOAAudioSource.h"
 #include "BsMath.h"
 #include "BsTaskScheduler.h"
+#include "BsAudioUtility.h"
 #include "AL\al.h"
 
 namespace BansheeEngine
 {
 	OAAudio::OAAudio()
-		:mVolume(1.0f)
+		:mVolume(1.0f), mIsPaused(false)
 	{
 		bool enumeratedDevices;
 		if(_isExtensionSupported("ALC_ENUMERATE_ALL_EXT"))
@@ -87,13 +88,13 @@ namespace BansheeEngine
 
 	void OAAudio::setPaused(bool paused)
 	{
-		// TODO
-	}
+		if (mIsPaused == paused)
+			return;
 
-	bool OAAudio::isPaused() const
-	{
-		// TODO
-		return false;
+		mIsPaused = paused;
+
+		for (auto& source : mSources)
+			source->setGlobalPause(paused);
 	}
 
 	void OAAudio::_update()
@@ -163,27 +164,19 @@ namespace BansheeEngine
 		mSources.erase(source);
 	}
 
-	void OAAudio::startStreaming(OAAudioSource* source, bool startPaused)
+	void OAAudio::startStreaming(OAAudioSource* source)
 	{
 		Lock(mMutex);
 
-		StreamingCommand command;
-		command.type = StreamingCommandType::Start;
-		command.source = source;
-		command.params[0] = startPaused;
-
-		mStreamingCommandQueue.push_back(command);
+		mStreamingCommandQueue.push_back({ StreamingCommandType::Start, source });
 	}
 
 	void OAAudio::stopStreaming(OAAudioSource* source)
 	{
 		Lock(mMutex);
 
-		StreamingCommand command;
-		command.type = StreamingCommandType::Stop;
-		command.source = source;
-
-		mStreamingCommandQueue.push_back(command);
+		mStreamingCommandQueue.push_back({ StreamingCommandType::Stop, source });
+		mDestroyedSources.insert(source);
 	}
 
 	ALCcontext* OAAudio::_getContext(const OAAudioListener* listener) const
@@ -269,37 +262,9 @@ namespace BansheeEngine
 				switch(command.type)
 				{
 				case StreamingCommandType::Start:
-				{
-					StreamingData data;
-					data.sourceIDs = command.source->mSourceIDs;
-					alGenBuffers(StreamingData::StreamBufferCount, data.streamBuffers);
-
-					mStreamingSources.insert(std::make_pair(command.source, data));
-
-					for(auto& )
-
-					// TODO - Store source IDs, start playback (possibly paused)
-				}
+					mStreamingSources.insert(command.source);
 					break;
 				case StreamingCommandType::Stop:
-					auto& contexts = gOAAudio()._getContexts(); // TODO - Don't use contexts here, use the source IDs directly
-					UINT32 numContexts = (UINT32)contexts.size();
-					for (UINT32 i = 0; i < numContexts; i++)
-					{
-						if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
-							alcMakeContextCurrent(contexts[i]);
-
-						INT32 numQueuedBuffers;
-						alGetSourcei(mSourceIDs[i], AL_BUFFERS_QUEUED, &numQueuedBuffers);
-
-						UINT32 buffer;
-						for (INT32 j = 0; j < numQueuedBuffers; j++)
-							alSourceUnqueueBuffers(mSourceIDs[i], 1, &buffer);
-					}
-
-					alDeleteBuffers(StreamBufferCount, mStreamBuffers);
-
-					command.source->destroyStreamBuffers();
 					mStreamingSources.erase(command.source);
 					break;
 				default:
@@ -308,10 +273,140 @@ namespace BansheeEngine
 			}
 
 			mStreamingCommandQueue.clear();
+			mDestroyedSources.clear();
 		}
 
 		for (auto& source : mStreamingSources)
+		{
+			// Check if the source got destroyed while streaming
+			{
+				Lock(mMutex);
+
+				auto iterFind = mDestroyedSources.find(source);
+				if (iterFind != mDestroyedSources.end())
+					continue;
+			}
+
 			source->stream();
+		}
+	}
+
+	ALenum OAAudio::_getOpenALBufferFormat(UINT32 numChannels, UINT32 bitDepth)
+	{
+		switch (bitDepth)
+		{
+		case 8:
+		{
+			switch (numChannels)
+			{
+			case 1:  return AL_FORMAT_MONO8;
+			case 2:  return AL_FORMAT_STEREO8;
+			case 4:  return alGetEnumValue("AL_FORMAT_QUAD8");
+			case 6:  return alGetEnumValue("AL_FORMAT_51CHN8");
+			case 7:  return alGetEnumValue("AL_FORMAT_61CHN8");
+			case 8:  return alGetEnumValue("AL_FORMAT_71CHN8");
+			default:
+				assert(false);
+				return 0;
+			}
+		}
+		case 16:
+		{
+			switch (numChannels)
+			{
+			case 1:  return AL_FORMAT_MONO16;
+			case 2:  return AL_FORMAT_STEREO16;
+			case 4:  return alGetEnumValue("AL_FORMAT_QUAD16");
+			case 6:  return alGetEnumValue("AL_FORMAT_51CHN16");
+			case 7:  return alGetEnumValue("AL_FORMAT_61CHN16");
+			case 8:  return alGetEnumValue("AL_FORMAT_71CHN16");
+			default:
+				assert(false);
+				return 0;
+			}
+		}
+		case 32:
+		{
+			switch (numChannels)
+			{
+			case 1:  return alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
+			case 2:  return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
+			case 4:  return alGetEnumValue("AL_FORMAT_QUAD32");
+			case 6:  return alGetEnumValue("AL_FORMAT_51CHN32");
+			case 7:  return alGetEnumValue("AL_FORMAT_61CHN32");
+			case 8:  return alGetEnumValue("AL_FORMAT_71CHN32");
+			default:
+				assert(false);
+				return 0;
+			}
+		}
+		default:
+			assert(false);
+			return 0;
+		}
+	}
+
+	void OAAudio::_writeToOpenALBuffer(UINT32 bufferId, UINT8* samples, const AudioFileInfo& info)
+	{
+		if (info.numChannels <= 2) // Mono or stereo
+		{
+			if (info.bitDepth > 16)
+			{
+				if (_isExtensionSupported("AL_EXT_float32"))
+				{
+					UINT32 bufferSize = info.numSamples * sizeof(float);
+					float* sampleBufferFloat = (float*)bs_stack_alloc(bufferSize);
+
+					AudioUtility::convertToFloat(samples, info.bitDepth, sampleBufferFloat, info.numSamples);
+
+					ALenum format = _getOpenALBufferFormat(info.numChannels, info.bitDepth);
+					alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.sampleRate);
+
+					bs_stack_free(sampleBufferFloat);
+				}
+				else
+				{
+					LOGWRN("OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated.");
+
+					UINT32 bufferSize = info.numSamples * 2;
+					UINT8* sampleBuffer16 = (UINT8*)bs_stack_alloc(bufferSize);
+
+					AudioUtility::convertBitDepth(samples, info.bitDepth, sampleBuffer16, 16, info.numSamples);
+
+					ALenum format = _getOpenALBufferFormat(info.numChannels, 16);
+					alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.sampleRate);
+
+					bs_stack_free(sampleBuffer16);
+				}
+			}
+			else
+			{
+				ALenum format = _getOpenALBufferFormat(info.numChannels, 16);
+				alBufferData(bufferId, format, samples, info.numSamples * (info.bitDepth / 8), info.sampleRate);
+			}
+		}
+		else // Multichannel
+		{
+			// Note: Assuming AL_EXT_MCFORMATS is supported. If it's not, channels should be reduced to mono or stereo.
+
+			if (info.bitDepth == 24) // 24-bit not supported, convert to 32-bit
+			{
+				UINT32 bufferSize = info.numSamples * sizeof(INT32);
+				UINT8* sampleBuffer32 = (UINT8*)bs_stack_alloc(bufferSize);
+
+				AudioUtility::convertBitDepth(samples, info.bitDepth, sampleBuffer32, 32, info.numSamples);
+
+				ALenum format = _getOpenALBufferFormat(info.numChannels, 32);
+				alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.sampleRate);
+
+				bs_stack_free(sampleBuffer32);
+			}
+			else
+			{
+				ALenum format = _getOpenALBufferFormat(info.numChannels, info.bitDepth);
+				alBufferData(bufferId, format, samples, info.numSamples * (info.bitDepth / 8), info.sampleRate);
+			}
+		}
 	}
 
 	OAAudio& gOAAudio()

+ 9 - 163
Source/BansheeOpenAudio/Source/BsOAAudioClip.cpp

@@ -4,138 +4,11 @@
 #include "BsOAOggVorbisWriter.h"
 #include "BsOAOggVorbisReader.h"
 #include "BsDataStream.h"
-#include "BsAudioUtility.h"
 #include "BsOAAudio.h"
 #include "AL/al.h"
 
 namespace BansheeEngine
 {
-	/** 
-	 * Returns optimal format for the provided number of channels and bit depth. It is assumed the user has checked if
-	 * extensions providing these formats are actually available.
-	 */
-	ALenum getSoundBufferFormat(UINT32 numChannels, UINT32 bitDepth)
-	{
-		switch (bitDepth)
-		{
-		case 8:
-		{
-			switch (numChannels)
-			{
-			case 1:  return AL_FORMAT_MONO8;
-			case 2:  return AL_FORMAT_STEREO8;
-			case 4:  return alGetEnumValue("AL_FORMAT_QUAD8");
-			case 6:  return alGetEnumValue("AL_FORMAT_51CHN8");
-			case 7:  return alGetEnumValue("AL_FORMAT_61CHN8");
-			case 8:  return alGetEnumValue("AL_FORMAT_71CHN8");
-			default:
-				assert(false);
-				return 0;
-			}
-		}
-		case 16:
-		{
-			switch (numChannels)
-			{
-			case 1:  return AL_FORMAT_MONO16;
-			case 2:  return AL_FORMAT_STEREO16;
-			case 4:  return alGetEnumValue("AL_FORMAT_QUAD16");
-			case 6:  return alGetEnumValue("AL_FORMAT_51CHN16");
-			case 7:  return alGetEnumValue("AL_FORMAT_61CHN16");
-			case 8:  return alGetEnumValue("AL_FORMAT_71CHN16");
-			default:
-				assert(false);
-				return 0;
-			}
-		}
-		case 32:
-		{
-			switch (numChannels)
-			{
-			case 1:  return alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
-			case 2:  return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
-			case 4:  return alGetEnumValue("AL_FORMAT_QUAD32");
-			case 6:  return alGetEnumValue("AL_FORMAT_51CHN32");
-			case 7:  return alGetEnumValue("AL_FORMAT_61CHN32");
-			case 8:  return alGetEnumValue("AL_FORMAT_71CHN32");
-			default:
-				assert(false);
-				return 0;
-			}
-		}
-		default:
-			assert(false);
-			return 0;
-		}
-	}
-
-	/** 
-	 * Writes provided samples into the OpenAL buffer with the provided ID. If the supported format is not supported the
-	 * samples will first be converted into a valid format.
-	 */
-	void writeToSoundBuffer(UINT32 bufferId, UINT8* samples, const AudioFileInfo& info)
-	{
-		if (info.numChannels <= 2) // Mono or stereo
-		{
-			if (info.bitDepth > 16)
-			{
-				if (gOAAudio()._isExtensionSupported("AL_EXT_float32"))
-				{
-					UINT32 bufferSize = info.numSamples * sizeof(float);
-					float* sampleBufferFloat = (float*)bs_stack_alloc(bufferSize);
-
-					AudioUtility::convertToFloat(samples, info.bitDepth, sampleBufferFloat, info.numSamples);
-
-					ALenum format = getSoundBufferFormat(info.numChannels, info.bitDepth);
-					alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.sampleRate);
-
-					bs_stack_free(sampleBufferFloat);
-				}
-				else
-				{
-					LOGWRN("OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated.");
-
-					UINT32 bufferSize = info.numSamples * 2;
-					UINT8* sampleBuffer16 = (UINT8*)bs_stack_alloc(bufferSize);
-
-					AudioUtility::convertBitDepth(samples, info.bitDepth, sampleBuffer16, 16, info.numSamples);
-
-					ALenum format = getSoundBufferFormat(info.numChannels, 16);
-					alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.sampleRate);
-
-					bs_stack_free(sampleBuffer16);
-				}
-			}
-			else
-			{
-				ALenum format = getSoundBufferFormat(info.numChannels, 16);
-				alBufferData(bufferId, format, samples, info.numSamples * (info.bitDepth / 8), info.sampleRate);
-			}
-		}
-		else // Multichannel
-		{
-			// Note: Assuming AL_EXT_MCFORMATS is supported. If it's not, channels should be reduced to mono or stereo.
-
-			if (info.bitDepth == 24) // 24-bit not supported, convert to 32-bit
-			{
-				UINT32 bufferSize = info.numSamples * sizeof(INT32);
-				UINT8* sampleBuffer32 = (UINT8*)bs_stack_alloc(bufferSize);
-
-				AudioUtility::convertBitDepth(samples, info.bitDepth, sampleBuffer32, 32, info.numSamples);
-
-				ALenum format = getSoundBufferFormat(info.numChannels, 32);
-				alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.sampleRate);
-
-				bs_stack_free(sampleBuffer32);
-			}
-			else
-			{
-				ALenum format = getSoundBufferFormat(info.numChannels, info.bitDepth);
-				alBufferData(bufferId, format, samples, info.numSamples * (info.bitDepth / 8), info.sampleRate);
-			}
-		}
-	}
-
 	OAAudioClip::OAAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
 		:AudioClip(samples, streamSize, numSamples, desc), mNeedsDecompression(false), mBufferId((UINT32)-1), mSourceStreamSize(0)
 	{ }
@@ -195,7 +68,7 @@ namespace BansheeEngine
 					reader.read(sampleBuffer, info.numSamples);
 
 					alGenBuffers(1, &mBufferId);
-					writeToSoundBuffer(mBufferId, sampleBuffer, info);
+					gOAAudio()._writeToOpenALBuffer(mBufferId, sampleBuffer, info);
 
 					mStreamData = nullptr;
 					mStreamOffset = 0;
@@ -245,7 +118,7 @@ namespace BansheeEngine
 		AudioClip::initialize();
 	}
 
-	void OAAudioClip::getSamples(UINT8* samples, UINT32 count) const
+	void OAAudioClip::getSamples(UINT8* samples, UINT32 offset, UINT32 count) const
 	{
 		Lock lock(mMutex);
 
@@ -253,12 +126,17 @@ namespace BansheeEngine
 		if (mStreamData == nullptr)
 		{
 			if (mNeedsDecompression)
+			{
+				mVorbisReader.seek(offset);
 				mVorbisReader.read(samples, count);
+			}
 			else
 			{
 				UINT32 bytesPerSample = mDesc.bitDepth / 8;
 				UINT32 size = count * bytesPerSample;
+				UINT32 streamOffset = mStreamOffset + offset * bytesPerSample;
 
+				mStreamData->seek(streamOffset);
 				mStreamData->read(samples, size);
 			}
 
@@ -271,46 +149,14 @@ namespace BansheeEngine
 
 			UINT32 bytesPerSample = mDesc.bitDepth / 8;
 			UINT32 size = count * bytesPerSample;
-
-			mSourceStreamData->read(samples, size);
-			return;
-		}
-
-		LOGWRN("Attempting to read samples while sample data is not available.");
-	}
-
-	void OAAudioClip::seekSamples(UINT32 offset)
-	{
-		Lock lock(mMutex);
-
-		// Try to seek normal stream, and if that fails seek in-memory stream if it exists
-		if (mStreamData != nullptr)
-		{
-			if (mNeedsDecompression)
-				mVorbisReader.seek(offset);
-			else
-			{
-				UINT32 bytesPerSample = mDesc.bitDepth / 8;
-				UINT32 streamOffset = mStreamOffset + offset * bytesPerSample;
-
-				mStreamData->seek(streamOffset);
-			}
-
-			return;
-		}
-
-		if(mSourceStreamData != nullptr)
-		{
-			assert(!mNeedsDecompression); // Normal stream must exist if decompressing
-
-			UINT32 bytesPerSample = mDesc.bitDepth / 8;
 			UINT32 streamOffset = offset * bytesPerSample;
 
 			mSourceStreamData->seek(streamOffset);
+			mSourceStreamData->read(samples, size);
 			return;
 		}
 
-		LOGWRN("Seeking samples while sample data is not available.");
+		LOGWRN("Attempting to read samples while sample data is not available.");
 	}
 
 	SPtr<DataStream> OAAudioClip::getSourceStream(UINT32& size)

+ 288 - 94
Source/BansheeOpenAudio/Source/BsOAAudioSource.cpp

@@ -8,7 +8,8 @@
 namespace BansheeEngine
 {
 	OAAudioSource::OAAudioSource()
-		:mSeekPosition(0), mRequiresStreaming(false)
+		: mSavedTime(0.0f), mGloballyPaused(false), mStreamBuffers(), mBusyBuffers(), mStreamProcessedPosition(0)
+		, mStreamQueuedPosition(0), mIsStreaming(false)
 	{
 		gOAAudio()._registerSource(this);
 		rebuild();
@@ -22,10 +23,10 @@ namespace BansheeEngine
 
 	void OAAudioSource::setClip(const HAudioClip& clip)
 	{
-		AudioSource::setClip(clip);
+		stop();
 
-		stop(); // TODO: I should wait until all buffers are unqueued before proceeding
-		mRequiresStreaming = isStreaming();
+		Lock(mMutex);
+		AudioSource::setClip(clip);
 
 		auto& contexts = gOAAudio()._getContexts();
 		UINT32 numContexts = (UINT32)contexts.size();
@@ -36,7 +37,7 @@ namespace BansheeEngine
 
 			alSourcei(mSourceIDs[i], AL_SOURCE_RELATIVE, !is3D());
 
-			if (!mRequiresStreaming)
+			if (!requiresStreaming())
 			{
 				UINT32 oaBuffer = 0;
 				if (clip.isLoaded())
@@ -170,38 +171,33 @@ namespace BansheeEngine
 
 	void OAAudioSource::play()
 	{
-		AudioSourceState state = getState();
 		AudioSource::play();
 
-		if (state == AudioSourceState::Playing)
+		if (mGloballyPaused)
 			return;
+
+		{
+			Lock(mMutex);
+			
+			if (!mIsStreaming)
+				startStreaming();
+		}
 		
-		if(!mRequiresStreaming || state == AudioSourceState::Paused)
+		auto& contexts = gOAAudio()._getContexts();
+		UINT32 numContexts = (UINT32)contexts.size();
+		for (UINT32 i = 0; i < numContexts; i++)
 		{
-			auto& contexts = gOAAudio()._getContexts();
-			UINT32 numContexts = (UINT32)contexts.size();
-			for (UINT32 i = 0; i < numContexts; i++)
-			{
-				if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
-					alcMakeContextCurrent(contexts[i]);
+			if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
+				alcMakeContextCurrent(contexts[i]);
 
-				alSourcePlay(mSourceIDs[i]);
-			}
-		} 
-		else // Streaming and currently stopped
-		{
-			gOAAudio().startStreaming(this, false);
+			alSourcePlay(mSourceIDs[i]);
 		}
 	}
 
 	void OAAudioSource::pause()
 	{
-		AudioSourceState state = getState();
 		AudioSource::pause();
 
-		if (state == AudioSourceState::Paused)
-			return;
-
 		auto& contexts = gOAAudio()._getContexts();
 		UINT32 numContexts = (UINT32)contexts.size();
 		for (UINT32 i = 0; i < numContexts; i++)
@@ -215,11 +211,15 @@ namespace BansheeEngine
 
 	void OAAudioSource::stop()
 	{
-		AudioSourceState state = getState();
 		AudioSource::stop();
 
-		if (state == AudioSourceState::Stopped)
-			return;
+		{
+			Lock(mMutex);
+
+			mStreamProcessedPosition = 0;
+			if (mIsStreaming)
+				stopStreaming();
+		}
 
 		auto& contexts = gOAAudio()._getContexts();
 		UINT32 numContexts = (UINT32)contexts.size();
@@ -229,74 +229,120 @@ namespace BansheeEngine
 				alcMakeContextCurrent(contexts[i]);
 
 			alSourceStop(mSourceIDs[i]);
+			alSourcef(mSourceIDs[i], AL_SEC_OFFSET, 0.0f);
 		}
-
-		if (mRequiresStreaming)
-			gOAAudio().stopStreaming(this);
 	}
 
-	void OAAudioSource::seek(float position)
+	void OAAudioSource::setGlobalPause(bool pause)
 	{
-		if (mAudioClip.isLoaded())
-			mSeekPosition = position * mAudioClip->getFrequency() * mAudioClip->getNumChannels();
-		else
-			mSeekPosition = 0;
+		if (mGloballyPaused == pause)
+			return;
+
+		mGloballyPaused = pause;
 
-		if (!mRequiresStreaming)
+		if (getState() == AudioSourceState::Playing)
 		{
-			auto& contexts = gOAAudio()._getContexts();
-			UINT32 numContexts = (UINT32)contexts.size();
-			for (UINT32 i = 0; i < numContexts; i++)
+			if (pause)
 			{
-				if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
-					alcMakeContextCurrent(contexts[i]);
+				auto& contexts = gOAAudio()._getContexts();
+				UINT32 numContexts = (UINT32)contexts.size();
+				for (UINT32 i = 0; i < numContexts; i++)
+				{
+					if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
+						alcMakeContextCurrent(contexts[i]);
 
-				alSourcef(mSourceIDs[i], AL_SEC_OFFSET, position);
+					alSourcePause(mSourceIDs[i]);
+				}
+			}
+			else
+			{
+				play();
 			}
 		}
-		else
+	}
+
+	void OAAudioSource::setTime(float time)
+	{
+		if (!mAudioClip.isLoaded())
+			return;
+
+		AudioSourceState state = getState();
+		stop();
+
+		float clipTime = 0.0f;
 		{
-			AudioSourceState state = getState();
-			if(state != AudioSourceState::Stopped)
+			Lock(mMutex);
+
+			if (!mIsStreaming)
+				clipTime = time;
+			else
 			{
-				gOAAudio().stopStreaming(this);
-				gOAAudio().startStreaming(this, state == AudioSourceState::Paused);
+				if (mAudioClip.isLoaded())
+					mStreamProcessedPosition = (UINT32)(time * mAudioClip->getFrequency() * mAudioClip->getNumChannels());
+				else
+					mStreamProcessedPosition = 0;
+
+				clipTime = 0.0f;
 			}
 		}
+
+		auto& contexts = gOAAudio()._getContexts();
+		UINT32 numContexts = (UINT32)contexts.size();
+		for (UINT32 i = 0; i < numContexts; i++)
+		{
+			if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
+				alcMakeContextCurrent(contexts[i]);
+
+			alSourcef(mSourceIDs[i], AL_SEC_OFFSET, clipTime);
+		}
+
+		if (state != AudioSourceState::Stopped)
+			play();
+		
+		if (state == AudioSourceState::Paused)
+			pause();
 	}
 
-	void OAAudioSource::clear()
+	float OAAudioSource::getTime() const
 	{
-		if (mAudioClip.isLoaded())
-			mSeekPosition = tell() * mAudioClip->getFrequency() * mAudioClip->getNumChannels();
-		else
-			mSeekPosition = 0;
+		Lock(mMutex);
 
-		auto destroySources = [this]()
-		{
-			for (auto& source : mSourceIDs)
-				alSourcei(source, AL_BUFFER, 0);
+		auto& contexts = gOAAudio()._getContexts();
 
-			alDeleteSources((UINT32)mSourceIDs.size(), mSourceIDs.data());
-			mSourceIDs.clear();
-		};
+		if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
+			alcMakeContextCurrent(contexts[0]);
 
-		if(mRequiresStreaming)
+		float time;
+		if (!mIsStreaming)
 		{
-			AudioSourceState state = getState();
-			if (state != AudioSourceState::Stopped)
-			{
-				gOAAudio().stopStreaming(this);
-				gOAAudio().queueCommand(destroySources);
-			}
+			alGetSourcef(mSourceIDs[0], AL_SEC_OFFSET, &time);
+			return time;
 		}
 		else
 		{
-			// Still used by the streaming thread
-			if (gOAAudio().isStreaming(this)) // Assuming stopStreaming was already called
-				gOAAudio().queueCommand(destroySources);
-			else
-				destroySources();
+			float timeOffset = 0.0f;
+			if (mAudioClip.isLoaded())
+				timeOffset = (float)mStreamProcessedPosition / mAudioClip->getFrequency() / mAudioClip->getNumChannels();
+
+			// When streaming, the returned offset is relative to the last queued buffer
+			alGetSourcef(mSourceIDs[0], AL_SEC_OFFSET, &time);
+			return timeOffset + time;
+		}
+	}
+
+	void OAAudioSource::clear()
+	{
+		mSavedTime = getTime();
+		stop();
+		
+		for (auto& source : mSourceIDs)
+			alSourcei(source, AL_BUFFER, 0);
+
+		{
+			Lock(mMutex);
+
+			alDeleteSources((UINT32)mSourceIDs.size(), mSourceIDs.data());
+			mSourceIDs.clear();
 		}
 	}
 
@@ -305,8 +351,12 @@ namespace BansheeEngine
 		AudioSourceState state = getState();
 		auto& contexts = gOAAudio()._getContexts();
 
-		mSourceIDs.resize(contexts.size());
-		alGenSources((UINT32)mSourceIDs.size(), mSourceIDs.data());
+		{
+			Lock(mMutex);
+
+			mSourceIDs.resize(contexts.size());
+			alGenSources((UINT32)mSourceIDs.size(), mSourceIDs.data());
+		}
 
 		UINT32 numContexts = (UINT32)contexts.size();
 		for (UINT32 i = 0; i < numContexts; i++)
@@ -318,7 +368,6 @@ namespace BansheeEngine
 			alSourcef(mSourceIDs[i], AL_LOOPING, mLoop);
 			alSourcef(mSourceIDs[i], AL_REFERENCE_DISTANCE, mMinDistance);
 			alSourcef(mSourceIDs[i], AL_ROLLOFF_FACTOR, mAttenuation);
-			alSourcef(mSourceIDs[i], AL_SEC_OFFSET, mSeekPosition);
 
 			if (is3D())
 			{
@@ -333,40 +382,185 @@ namespace BansheeEngine
 				alSource3f(mSourceIDs[i], AL_VELOCITY, 0.0f, 0.0f, 0.0f);
 			}
 
-			if (!mRequiresStreaming)
 			{
-				UINT32 oaBuffer = 0;
-				if (mAudioClip.isLoaded())
+				Lock(mMutex);
+
+				if (!mIsStreaming)
 				{
-					OAAudioClip* oaClip = static_cast<OAAudioClip*>(mAudioClip.get());
-					oaBuffer = oaClip->_getOpenALBuffer();
+					UINT32 oaBuffer = 0;
+					if (mAudioClip.isLoaded())
+					{
+						OAAudioClip* oaClip = static_cast<OAAudioClip*>(mAudioClip.get());
+						oaBuffer = oaClip->_getOpenALBuffer();
+					}
+
+					alSourcei(mSourceIDs[i], AL_BUFFER, oaBuffer);
 				}
+			}
+		}
 
-				alSourcei(mSourceIDs[i], AL_BUFFER, oaBuffer);
+		setTime(mSavedTime);
 
-				float offset = 0.0f;
-				if (mAudioClip.isLoaded())
-					offset = (float)mSeekPosition / mAudioClip->getFrequency() / mAudioClip->getNumChannels();
+		if (state != AudioSourceState::Stopped)
+			play();
+
+		if (state == AudioSourceState::Paused)
+			pause();
+	}
+
+	void OAAudioSource::startStreaming()
+	{
+		assert(!mIsStreaming);
+
+		alGenBuffers(StreamBufferCount, mStreamBuffers);
+		gOAAudio().startStreaming(this);
+
+		memset(&mBusyBuffers, 0, sizeof(mBusyBuffers));
+		mIsStreaming = true;
+	}
+
+	void OAAudioSource::stopStreaming()
+	{
+		assert(mIsStreaming);
+
+		mIsStreaming = false;
+		gOAAudio().stopStreaming(this);
+
+		auto& contexts = gOAAudio()._getContexts();
+		UINT32 numContexts = (UINT32)contexts.size();
+		for (UINT32 i = 0; i < numContexts; i++)
+		{
+			if (contexts.size() > 1) // If only one context is available it is guaranteed it is always active, so we can avoid setting it
+				alcMakeContextCurrent(contexts[i]);
+
+			INT32 numQueuedBuffers;
+			alGetSourcei(mSourceIDs[i], AL_BUFFERS_QUEUED, &numQueuedBuffers);
+
+			UINT32 buffer;
+			for (INT32 j = 0; j < numQueuedBuffers; j++)
+				alSourceUnqueueBuffers(mSourceIDs[i], 1, &buffer);
+		}
+
+		alDeleteBuffers(StreamBufferCount, mStreamBuffers);
+	}
+
+	void OAAudioSource::stream()
+	{
+		Lock(mMutex);
+
+		// Note: Not setting context here. This might be an issue when multiple contexts are used. If context setting
+		// ends up to be needed, then I'll need to lock every audio source operation to avoid other thread changing
+		// the context.
+
+		AudioFileInfo info;
+		info.bitDepth = mAudioClip->getBitDepth();
+		info.numChannels = mAudioClip->getNumChannels();
+		info.sampleRate = mAudioClip->getFrequency();
+		info.numSamples = 0;
+
+		UINT32 totalNumSamples = mAudioClip->getNumSamples();
 
-				alSourcef(mSourceIDs[i], AL_SEC_OFFSET, offset);
+		// Note: This code only uses the first source to determine the number of processed buffers. This will be an issue
+		// if other sources haven't yet processed those same buffers. No easy way to fix that situation other than to
+		// use different buffers for each source.
+
+		INT32 numProcessedBuffers = 0;
+		alGetSourcei(mSourceIDs[0], AL_BUFFERS_PROCESSED, &numProcessedBuffers);
+
+		for (INT32 i = numProcessedBuffers; i > 0; i--)
+		{
+			UINT32 buffer;
+			alSourceUnqueueBuffers(mSourceIDs[0], 1, &buffer);
+
+			UINT32 bufferIdx = 0;
+			for (UINT32 j = 0; j < StreamBufferCount; j++)
+			{
+				if (buffer == mStreamBuffers[j])
+				{
+					bufferIdx = j;
+					break;
+				}
+			}
+
+			mBusyBuffers[bufferIdx] = false;
+
+			INT32 bufferSize;
+			INT32 bufferBits;
+
+			alGetBufferi(buffer, AL_SIZE, &bufferSize);
+			alGetBufferi(buffer, AL_BITS, &bufferBits);
+
+			if (bufferBits == 0)
+			{
+				LOGERR("Error decoding stream.");
+				return;
 			}
 			else
-				alSourcef(mSourceIDs[i], AL_SEC_OFFSET, 0.0f); // Offset handled by the streaming system
+			{
+				UINT32 bytesPerSample = bufferBits / 8;
+				mStreamProcessedPosition = bufferSize / bytesPerSample;
+			}
 
-			if (state == AudioSourceState::Playing || state == AudioSourceState::Paused)
-				alSourcePlay(mSourceIDs[i]);
-			
-			if (state == AudioSourceState::Paused)
-				alSourcePause(mSourceIDs[i]);
+			if (mStreamProcessedPosition == totalNumSamples) // Reached the end
+			{
+				mStreamProcessedPosition = 0;
+
+				if (!mLoop) // Variable used on both threads and not thread safe, but it doesn't matter
+				{
+					stopStreaming();
+					return;
+				}
+			}
 		}
 
-		if(mRequiresStreaming)
+		for(UINT32 i = 0; i < StreamBufferCount; i++)
 		{
-			if (state != AudioSourceState::Stopped)
-				gOAAudio().startStreaming(this, state == AudioSourceState::Paused);
+			if (mBusyBuffers[i])
+				continue;
+
+			if (fillBuffer(mStreamBuffers[i], info, totalNumSamples))
+			{
+				for (auto& source : mSourceIDs)
+					alSourceQueueBuffers(source, 1, &mStreamBuffers[i]);
+
+				mBusyBuffers[i] = true;
+			}
+			else
+				break;
 		}
 	}
 
+	bool OAAudioSource::fillBuffer(UINT32 buffer, AudioFileInfo& info, UINT32 maxNumSamples)
+	{
+		UINT32 numRemainingSamples = maxNumSamples - mStreamQueuedPosition;
+		if (numRemainingSamples == 0) // Reached the end
+		{
+			if (mLoop)
+			{
+				mStreamQueuedPosition = 0;
+				numRemainingSamples = maxNumSamples;
+			}
+			else // If not looping, don't queue any more buffers, we're done
+				return false;
+		}
+
+		// Read audio data
+		UINT32 numSamples = std::min(numRemainingSamples, info.sampleRate * info.numChannels); // 1 second of data
+		UINT32 sampleBufferSize = numSamples * (info.bitDepth / 8);
+
+		UINT8* samples = (UINT8*)bs_stack_alloc(sampleBufferSize);
+
+		mAudioClip->getSamples(samples, mStreamQueuedPosition, numSamples);
+		mStreamQueuedPosition += numSamples;
+
+		info.numSamples = numSamples;
+		gOAAudio()._writeToOpenALBuffer(buffer, samples, info);
+
+		bs_stack_free(samples);
+
+		return true;
+	}
+
 	bool OAAudioSource::is3D() const
 	{
 		if (!mAudioClip.isLoaded())
@@ -375,7 +569,7 @@ namespace BansheeEngine
 		return mAudioClip->is3D();
 	}
 
-	bool OAAudioSource::isStreaming() const
+	bool OAAudioSource::requiresStreaming() const
 	{
 		if (!mAudioClip.isLoaded())
 			return false;

+ 5 - 0
Source/BansheeOpenAudio/Source/BsOAPlugin.cpp

@@ -3,6 +3,8 @@
 #include "BsOAPrerequisites.h"
 #include "BsAudioManager.h"
 #include "BsOAAudio.h"
+#include "BsOAImporter.h"
+#include "BsImporter.h"
 
 namespace BansheeEngine
 {
@@ -30,6 +32,9 @@ namespace BansheeEngine
 	/**	Entry point to the plugin. Called by the engine when the plugin is loaded. */
 	extern "C" BS_OA_EXPORT void* loadPlugin()
 	{
+		OAImporter* importer = bs_new<OAImporter>();
+		Importer::instance()._registerAssetImporter(importer);
+
 		return bs_new<OAFactory>();
 	}
 

+ 2 - 2
Source/SBansheeEngine/Source/BsScriptAudioSource.cpp

@@ -113,12 +113,12 @@ namespace BansheeEngine
 
 	void ScriptAudioSource::internal_SetTime(ScriptAudioSource* thisPtr, float position)
 	{
-		thisPtr->mSource->seek(position);
+		thisPtr->mSource->setTime(position);
 	}
 
 	float ScriptAudioSource::internal_GetTime(ScriptAudioSource* thisPtr)
 	{
-		return thisPtr->mSource->tell();
+		return thisPtr->mSource->getTime();
 	}
 
 	UINT32 ScriptAudioSource::internal_GetState(ScriptAudioSource* thisPtr)