Browse Source

Added FMOD audio import
Implemented FMOD AudioClip

BearishSun 9 years ago
parent
commit
179e63a1eb

+ 0 - 14
Source/BansheeCore/Include/BsAudioClip.h

@@ -109,20 +109,6 @@ namespace BansheeEngine
 		/** Determines will the clip be played a spatial 3D sound, or as a normal sound (for example music). */
 		bool is3D() const { return mDesc.is3D; }
 
-		/** 
-		 * 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 offset, UINT32 count) const = 0;
-
 		/**
 		 * Creates a new AudioClip and populates it with provided samples.
 		 *

+ 13 - 2
Source/BansheeFMOD/Include/BsFMODAudioClip.h

@@ -4,6 +4,7 @@
 
 #include "BsFMODPrerequisites.h"
 #include "BsAudioClip.h"
+#include <fmod.hpp>
 
 namespace BansheeEngine
 {
@@ -18,8 +19,11 @@ namespace BansheeEngine
 		FMODAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
 		virtual ~FMODAudioClip();
 
-		/** @copydoc AudioClip::getSamples */
-		void getSamples(UINT8* samples, UINT32 offset, UINT32 count) const override;
+		/** 
+		 * Creates a new streaming sound. Only valid if the clip was created with AudioReadMode::Stream. Caller is
+		 * responsible for releasing the sound.
+		 */
+		FMOD::Sound* createStreamingSound() const;
 
 	protected:
 		/** @copydoc Resource::initialize */
@@ -27,6 +31,13 @@ namespace BansheeEngine
 
 		/** @copydoc AudioClip::getSourceFormatData */
 		SPtr<DataStream> getSourceStream(UINT32& size) override;
+
+		FMOD::Sound* mSound;
+
+		// These streams exist to save original audio data in case it's needed later (usually for saving with the editor, or
+		// manual data manipulation). In normal usage (in-game) these will be null so no memory is wasted.
+		SPtr<DataStream> mSourceStreamData;
+		UINT32 mSourceStreamSize;
 	};
 
 	/** @} */

+ 136 - 7
Source/BansheeFMOD/Source/BsFMODAudioClip.cpp

@@ -1,34 +1,163 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsFMODAudioClip.h"
+#include "BsFMODAudio.h"
 #include "BsDataStream.h"
 
 namespace BansheeEngine
 {
 	FMODAudioClip::FMODAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
-		:AudioClip(samples, streamSize, numSamples, desc)
+		:AudioClip(samples, streamSize, numSamples, desc), mSound(nullptr), mSourceStreamSize(0)
 	{ }
 
 	FMODAudioClip::~FMODAudioClip()
 	{
-		// TODO
+		if(mSound != nullptr)
+			mSound->release();
 	}
 
 	void FMODAudioClip::initialize()
 	{
-		// TODO
+		AudioFileInfo info;
+		info.bitDepth = mDesc.bitDepth;
+		info.numChannels = mDesc.numChannels;
+		info.numSamples = mNumSamples;
+		info.sampleRate = mDesc.frequency;
+
+		// If we need to keep source data, read everything into memory and keep a copy
+		if (mKeepSourceData)
+		{
+			mStreamData->seek(mStreamOffset);
+
+			UINT8* sampleBuffer = (UINT8*)bs_alloc(mStreamSize);
+			mStreamData->read(sampleBuffer, mStreamSize);
+
+			mSourceStreamData = bs_shared_ptr_new<MemoryDataStream>(sampleBuffer, mStreamSize);
+			mSourceStreamSize = mStreamSize;
+		}
+
+		FMOD::System* fmod = gFMODAudio()._getFMOD();
+		FMOD_MODE flags = FMOD_OPENMEMORY;
+
+		if (is3D())
+			flags |= FMOD_3D;
+		else
+			flags |= FMOD_2D;
+
+		// Load decompressed data into a sound buffer
+		if(mDesc.readMode == AudioReadMode::LoadDecompressed || mDesc.readMode == AudioReadMode::LoadCompressed)
+		{
+			// Read all data into memory
+			SPtr<DataStream> stream;
+			UINT32 offset = 0;
+			if (mSourceStreamData != nullptr) // If it's already loaded in memory, use it directly
+				stream = mSourceStreamData;
+			else
+			{
+				stream = mStreamData;
+				offset = mStreamOffset;
+			}
+
+			UINT32 bufferSize = info.numSamples * (info.bitDepth / 8);
+			UINT8* sampleBuffer = (UINT8*)bs_stack_alloc(bufferSize);
+
+			stream->seek(offset);
+			stream->read(sampleBuffer, bufferSize);
+
+			FMOD_CREATESOUNDEXINFO exInfo;
+			memset(&exInfo, 0, sizeof(exInfo));
+			exInfo.cbsize = sizeof(exInfo);
+			exInfo.length = bufferSize;
+
+			// TODO - Vorbis cannot be decompressed from memory by FMOD. Instead we force AudioReadMode::Stream for it.
+			bool loadCompressed =
+				mDesc.readMode == AudioReadMode::LoadCompressed && mDesc.format != AudioFormat::VORBIS;
+
+			if (loadCompressed)
+				flags |= FMOD_CREATECOMPRESSEDSAMPLE;
+			else
+				flags |= FMOD_CREATESAMPLE;
+
+			if(fmod->createSound((const char*)sampleBuffer, flags, &exInfo, &mSound) != FMOD_OK)
+			{
+				LOGERR("Failed playing sound.");
+			}
+			else
+			{
+				mSound->setMode(FMOD_LOOP_OFF);
+			}
+
+			mStreamData = nullptr;
+			mStreamOffset = 0;
+			mStreamSize = 0;
+
+			bs_stack_free(sampleBuffer);
+		}
+		else // Streaming
+		{
+			// Do nothing, we rely on AudioSource from creating sounds as only one streaming sound can ever be playing
+		}
 
 		AudioClip::initialize();
 	}
 
-	void FMODAudioClip::getSamples(UINT8* samples, UINT32 offset, UINT32 count) const
+	FMOD::Sound* FMODAudioClip::createStreamingSound() const
 	{
-		// TODO
+		if(getReadMode() != AudioReadMode::Stream || mStreamData == nullptr)
+		{
+			LOGERR("Invalid audio stream data.");
+			return nullptr;
+		}
+
+		FMOD_MODE flags = FMOD_CREATESTREAM;
+		const char* streamData;
+
+		FMOD_CREATESOUNDEXINFO exInfo;
+		memset(&exInfo, 0, sizeof(exInfo));
+		exInfo.cbsize = sizeof(exInfo);
+		
+		String pathStr;
+		if(mStreamData->isFile())
+		{
+			SPtr<FileDataStream> fileStream = std::static_pointer_cast<FileDataStream>(mStreamData);
+			pathStr = fileStream->getPath().toString();
+
+			streamData = pathStr.c_str();
+		}
+		else
+		{
+			SPtr<MemoryDataStream> memStream = std::static_pointer_cast<MemoryDataStream>(mStreamData);
+
+			flags |= FMOD_OPENMEMORY_POINT;
+			exInfo.length = (UINT32)memStream->size();
+
+			memStream->seek(mStreamOffset);
+			streamData = (const char*)memStream->getCurrentPtr();
+			
+		}
+
+		if (is3D())
+			flags |= FMOD_3D;
+		else
+			flags |= FMOD_2D;
+
+		FMOD::Sound* sound = nullptr;
+		FMOD::System* fmod = gFMODAudio()._getFMOD();
+		if (fmod->createSound(streamData, flags, &exInfo, &sound) != FMOD_OK)
+		{
+			LOGERR("Failed creating a streaming sound.");
+			return nullptr;
+		}
+
+		sound->setMode(FMOD_LOOP_OFF);
+		return sound;
 	}
 
 	SPtr<DataStream> FMODAudioClip::getSourceStream(UINT32& size)
 	{
-		// TODO
-		return nullptr;
+		size = mSourceStreamSize;
+		mSourceStreamData->seek(0);
+
+		return mSourceStreamData;
 	}
 }

+ 56 - 7
Source/BansheeFMOD/Source/BsFMODImporter.cpp

@@ -5,6 +5,9 @@
 #include "BsFileSystem.h"
 #include "BsAudioClipImportOptions.h"
 #include "BsAudioUtility.h"
+#include "BsFMODAudio.h"
+
+#include <fmod.hpp>
 
 namespace BansheeEngine
 {
@@ -24,7 +27,9 @@ namespace BansheeEngine
 		WString lowerCaseExt = ext;
 		StringUtil::toLowerCase(lowerCaseExt);
 
-		return lowerCaseExt == L"wav" || lowerCaseExt == L"flac" || lowerCaseExt == L"ogg";
+		return lowerCaseExt == L"wav" || lowerCaseExt == L"flac" || lowerCaseExt == L"ogg" || lowerCaseExt == L"mp3" ||
+			lowerCaseExt == L"wma" || lowerCaseExt == L"asf" || lowerCaseExt == L"wmv" || lowerCaseExt == L"midi" || 
+			lowerCaseExt == L"fsb" || lowerCaseExt == L"aif" || lowerCaseExt == L"aiff";
 	}
 
 	bool FMODImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const
@@ -40,29 +45,73 @@ namespace BansheeEngine
 
 	SPtr<Resource> FMODImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions)
 	{
-		SPtr<DataStream> stream = FileSystem::openFile(filePath);
-
 		WString extension = filePath.getWExtension();
 		StringUtil::toLowerCase(extension);
 
 		AudioFileInfo info;
 
+		FMOD::Sound* sound;
+		String pathStr = filePath.toString();
+		if(gFMODAudio()._getFMOD()->createSound(pathStr.c_str(), FMOD_CREATESAMPLE, nullptr, &sound) != FMOD_OK)
+		{
+			LOGERR("Failed importing audio file: " + pathStr);
+			return nullptr;
+		}
 
+		FMOD_SOUND_FORMAT format;
+		INT32 numChannels = 0;
+		INT32 numBits = 0;
 
+		sound->getFormat(nullptr, &format, &numChannels, &numBits);
 
-		// TODO - Parse audio meta-data
-
+		if(format != FMOD_SOUND_FORMAT_PCM8 && format != FMOD_SOUND_FORMAT_PCM16 && format != FMOD_SOUND_FORMAT_PCM24 
+			&& format != FMOD_SOUND_FORMAT_PCM32 && format != FMOD_SOUND_FORMAT_PCMFLOAT)
+		{
+			LOGERR("Failed importing audio file, invalid imported format: " + pathStr);
+			return nullptr;
+		}
 
+		float frequency = 0.0f;
+		sound->getDefaults(&frequency, nullptr);
 
+		UINT32 size;
+		sound->getLength(&size, FMOD_TIMEUNIT_PCMBYTES);
+		
+		info.bitDepth = numBits;
+		info.numChannels = numChannels;
+		info.sampleRate = (UINT32)frequency;
+		info.numSamples = size / (info.bitDepth / 8);
 
 		UINT32 bytesPerSample = info.bitDepth / 8;
 		UINT32 bufferSize = info.numSamples * bytesPerSample;
 		UINT8* sampleBuffer = (UINT8*)bs_alloc(bufferSize);
+		assert(bufferSize == size);
 		
+		UINT8* startData = nullptr;
+		UINT8* endData = nullptr;
+		UINT32 startSize = 0;
+		UINT32 endSize = 0;
+		sound->lock(0, size, (void**)&startData, (void**)&endData, &startSize, &endSize);
 
+		if(format == FMOD_SOUND_FORMAT_PCMFLOAT)
+		{
+			assert(info.bitDepth == 32);
+
+			UINT32* output = (UINT32*)sampleBuffer;
+			for(UINT32 i = 0; i < info.numSamples; i++)
+			{
+				float value = *(((float*)startData) + i);
+				*output = (UINT32)(value * 2147483647.0f);
+				output++;
+			}
+		}
+		else
+		{
+			memcpy(sampleBuffer, startData, bufferSize);
+		}
 
-		// TODO - Read audio data
-
+		sound->unlock((void**)&startData, (void**)&endData, startSize, endSize);
+		sound->release();
 
 		SPtr<const AudioClipImportOptions> clipIO = std::static_pointer_cast<const AudioClipImportOptions>(importOptions);
 

+ 13 - 2
Source/BansheeOpenAudio/Include/BsOAAudioClip.h

@@ -19,8 +19,19 @@ namespace BansheeEngine
 		OAAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
 		virtual ~OAAudioClip();
 
-		/** @copydoc AudioClip::getSamples */
-		void getSamples(UINT8* samples, UINT32 offset, UINT32 count) const override;
+		/** 
+		 * 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.
+		 */
+		void getSamples(UINT8* samples, UINT32 offset, UINT32 count) const;
 
 		/** @name Internal
 		 *  @{

+ 3 - 1
Source/BansheeOpenAudio/Source/BsOAAudioSource.cpp

@@ -571,7 +571,9 @@ namespace BansheeEngine
 
 		UINT8* samples = (UINT8*)bs_stack_alloc(sampleBufferSize);
 
-		mAudioClip->getSamples(samples, mStreamQueuedPosition, numSamples);
+		OAAudioClip* audioClip = static_cast<OAAudioClip*>(mAudioClip.get());
+
+		audioClip->getSamples(samples, mStreamQueuedPosition, numSamples);
 		mStreamQueuedPosition += numSamples;
 
 		info.numSamples = numSamples;

+ 3 - 0
Source/BansheeUtility/Include/BsDataStream.h

@@ -263,6 +263,9 @@ namespace BansheeEngine
         /** @copydoc DataStream::close */
 		void close() override;
 
+		/** Returns the path of the file opened by the stream. */
+		const Path& getPath() const { return mPath; }
+
 	protected:
 		Path mPath;
 		SPtr<std::istream> mInStream;