Browse Source

More work on OpenAudio AudioClip

BearishSun 9 years ago
parent
commit
a84c7b1115

+ 2 - 1
Source/BansheeCore/Include/BsAudio.h

@@ -21,11 +21,12 @@ namespace BansheeEngine
 		 * Creates a new audio clip.
 		 *
 		 * @param[in]	samples		Stream containing audio samples in format specified in @p desc.
+		 * @param[in]	streamSize	Size of the audio data in the provided stream, in bytes.
 		 * @param[in]	numSamples	Number of samples in @p samples stream.
 		 * @param[in]	desc		Descriptor describing the type of the audio stream (format, sample rate, etc.).
 		 * @return					Newly created AudioClip. Must be manually initialized.
 		 */
-		virtual SPtr<AudioClip> createClip(const SPtr<DataStream>& samples, UINT32 numSamples, 
+		virtual SPtr<AudioClip> createClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples,
 			const AUDIO_CLIP_DESC& desc) = 0;
 	};
 

+ 23 - 9
Source/BansheeCore/Include/BsAudioClip.h

@@ -62,8 +62,7 @@ namespace BansheeEngine
 	class BS_CORE_EXPORT AudioClip : public Resource
 	{
 	public:
-		static HAudioClip create(UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
-		static HAudioClip create(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+		virtual ~AudioClip() { }
 
 		UINT32 getBitDepth() const { return mDesc.bitDepth; }
 		UINT32 getFrequency() const { return mDesc.frequency; }
@@ -73,26 +72,39 @@ namespace BansheeEngine
 		UINT32 getLength() const { return mNumSamples / mDesc.frequency; }
 		UINT32 getNumSamples() const { return mNumSamples; }
 
-		virtual UINT32 getDataSize() const = 0;
-		virtual UINT8* getData() const = 0;
+		/** 
+		 * Returns audio samples in PCM format, channel data inerleaved. 
+		 *
+		 * @param[in]	samples		Previously allocated buffer to contain the samples.
+		 * @param[in]	count		Number of samples to read (should be a multiple of number of channels).
+		 * @param[in]	offset		Offset in number of samples at which to start reading (should be a multiple of number
+		 *							of channels).
+		 */
+		virtual void getSamples(UINT8* samples, UINT32 count, UINT32 offset = 0) const = 0;
 
-		// Note: This will convert internal audio read mode to LoadCompressed (if ogg) or LoadDecompressed (if PCM)
-		virtual void setData(UINT8* samples, UINT32 numSamples) = 0;
+		static HAudioClip create(UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+		static HAudioClip create(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples,
+			const AUDIO_CLIP_DESC& desc); // Note that ownership of stream is taken by the AudioClip
 
 	public: // ***** INTERNAL ******
 		/** @name Internal
 		 *  @{
 		 */
 
-		static SPtr<AudioClip> _createPtr(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+		static SPtr<AudioClip> _createPtr(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, 
+			const AUDIO_CLIP_DESC& desc);
 
 		/** @} */
-	private:
-		AudioClip(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+	protected:
+		AudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+
+		/** Returns audio data in the original source format. */
+		virtual SPtr<DataStream> getSourceFormatData(UINT32& size) = 0;
 
 	protected:
 		AUDIO_CLIP_DESC mDesc;
 		UINT32 mNumSamples;
+		UINT32 mStreamSize;
 		SPtr<DataStream> mStreamData;
 
 		/************************************************************************/
@@ -102,6 +114,8 @@ namespace BansheeEngine
 		friend class AudioClipRTTI;
 		static RTTITypeBase* getRTTIStatic();
 		RTTITypeBase* getRTTI() const override;
+
+		static SPtr<AudioClip> createEmpty();
 	};
 
 	/** @} */

+ 16 - 5
Source/BansheeCore/Include/BsAudioClipRTTI.h

@@ -24,17 +24,23 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_PLAIN_NAMED(bitDepth, mDesc.bitDepth, 3)
 			BS_RTTI_MEMBER_PLAIN_NAMED(numChannels, mDesc.numChannels, 4)
 			BS_RTTI_MEMBER_PLAIN(mNumSamples, 5)
+			BS_RTTI_MEMBER_PLAIN(mStreamSize, 7)
 		BS_END_RTTI_MEMBERS
 
 		SPtr<DataStream> getData(AudioClip* obj, UINT32& size)
 		{
-			size = obj->getDataSize();
-			return bs_shared_ptr_new<MemoryDataStream>(obj->getData(), size, false);
+			SPtr<DataStream> stream = obj->getSourceFormatData(size);
+
+			if (stream->isFile())
+				LOGWRN("Saving an AudioClip which uses streaming data. Streaming data might not be available if saving to the same file.");
+
+			return stream;
 		}
 
 		void setData(AudioClip* obj, const SPtr<DataStream>& val, UINT32 size)
 		{
-			// TODO - Load from steam
+			obj->mStreamData = val;
+			obj->mStreamSize = size;
 		}
 
 	public:
@@ -44,6 +50,12 @@ namespace BansheeEngine
 			addDataBlockField("mData", 6, &AudioClipRTTI::getData, &AudioClipRTTI::setData, 0);
 		}
 
+		void onDeserializationEnded(IReflectable* obj) override
+		{
+			AudioClip* clip = static_cast<AudioClip*>(obj);
+			clip->initialize();
+		}
+
 		const String& getRTTIName() override
 		{
 			static String name = "AudioClip";
@@ -57,8 +69,7 @@ namespace BansheeEngine
 
 		SPtr<IReflectable> newRTTIObject() override
 		{
-			AUDIO_CLIP_DESC desc;
-			return AudioClip::_createPtr(nullptr, 0, desc); // Initial values don't matter, they will get overwritten
+			return AudioClip::createEmpty();
 		}
 	};
 

+ 18 - 11
Source/BansheeCore/Source/BsAudioClip.cpp

@@ -7,28 +7,35 @@
 
 namespace BansheeEngine
 {
-	AudioClip::AudioClip(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
-		:Resource(false), mDesc(desc), mNumSamples(numSamples), mStreamData(samples)
+	AudioClip::AudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
+		:Resource(false), mDesc(desc), mNumSamples(numSamples), mStreamData(samples), mStreamSize(streamSize)
+	{ }
+
+	HAudioClip AudioClip::create(UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
 	{
-		if (mDesc.readMode != AudioReadMode::Stream)
-			mStreamData = nullptr;
+		return static_resource_cast<AudioClip>(gResources()._createResourceHandle(_createPtr(nullptr, streamSize, numSamples, desc)));
 	}
 
-	HAudioClip AudioClip::create(UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
+	HAudioClip AudioClip::create(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
 	{
-		return static_resource_cast<AudioClip>(gResources()._createResourceHandle(_createPtr(nullptr, numSamples, desc)));
+		return static_resource_cast<AudioClip>(gResources()._createResourceHandle(_createPtr(samples, streamSize, numSamples, desc)));
 	}
 
-	HAudioClip AudioClip::create(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
+	SPtr<AudioClip> AudioClip::_createPtr(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
 	{
-		return static_resource_cast<AudioClip>(gResources()._createResourceHandle(_createPtr(samples, numSamples, desc)));
+		SPtr<AudioClip> newClip = gAudio().createClip(samples, streamSize, numSamples, desc);
+		newClip->_setThisPtr(newClip);
+		newClip->initialize();
+
+		return newClip;
 	}
 
-	SPtr<AudioClip> AudioClip::_createPtr(const SPtr<DataStream>& samples, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
+	SPtr<AudioClip> AudioClip::createEmpty()
 	{
-		SPtr<AudioClip> newClip = gAudio().createClip(samples, numSamples, desc);
+		AUDIO_CLIP_DESC desc;
+
+		SPtr<AudioClip> newClip = gAudio().createClip(nullptr, 0, 0, desc);
 		newClip->_setThisPtr(newClip);
-		newClip->initialize();
 
 		return newClip;
 	}

+ 2 - 0
Source/BansheeOpenAudio/CMakeSources.cmake

@@ -6,6 +6,7 @@ set(BS_BANSHEEOPENAUDIO_INC_NOFILTER
 	"Include/BsOAFLACReader.h"
 	"Include/BsOAFileReader.h"
 	"Include/BsOAOggVorbisWriter.h"
+	"Include/BsOAAudioClip.h"
 )
 
 set(BS_BANSHEEOPENAUDIO_SRC_NOFILTER
@@ -15,6 +16,7 @@ set(BS_BANSHEEOPENAUDIO_SRC_NOFILTER
 	"Source/BsOAOggVorbisReader.cpp"
 	"Source/BsOAFLACReader.cpp"
 	"Source/BsOAOggVorbisWriter.cpp"
+	"Source/BsOAAudioClip.cpp"
 )
 
 source_group("Header Files" FILES ${BS_BANSHEEOPENAUDIO_INC_NOFILTER})

+ 31 - 0
Source/BansheeOpenAudio/Include/BsOAAudioClip.h

@@ -0,0 +1,31 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsOAPrerequisites.h"
+#include "BsAudioClip.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup OpenAudio
+	 *  @{
+	 */
+	
+	class BS_OA_EXPORT OAAudioClip : public AudioClip
+	{
+	public:
+		/** @copydoc AudioClip::getSamples */
+		virtual void getSamples(UINT8* samples, UINT32 count, UINT32 offset = 0) const = 0;
+
+	protected:
+		/** @copydoc Resource::initialize */
+		void initialize() override;
+
+		/** @copydoc AudioClip::getSourceFormatData */
+		virtual SPtr<DataStream> getSourceFormatData(UINT32& size) = 0;
+	private:
+		OAAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc);
+	};
+
+	/** @} */
+}

+ 2 - 0
Source/BansheeOpenAudio/Include/BsOAOggVorbisWriter.h

@@ -23,6 +23,8 @@ namespace BansheeEngine
 		void write(UINT8* samples, UINT32 numSamples); // Assumes 16-bit samples
 		void flush();
 		void close();
+
+		static UINT8* PCMToOggVorbis(UINT8* samples, const AudioFileInfo& info, UINT32& size);
 	private:
 		void writeBlocks();
 

+ 74 - 0
Source/BansheeOpenAudio/Source/BsOAAudioClip.cpp

@@ -0,0 +1,74 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsOAAudioClip.h"
+#include "BsOAOggVorbisWriter.h"
+#include "BsDataStream.h"
+
+namespace BansheeEngine
+{
+	OAAudioClip::OAAudioClip(const SPtr<DataStream>& samples, UINT32 streamSize, UINT32 numSamples, const AUDIO_CLIP_DESC& desc)
+		:AudioClip(samples, streamSize, numSamples, desc)
+	{ }
+
+	void OAAudioClip::initialize()
+	{
+		if (mDesc.readMode != AudioReadMode::Stream)
+		{
+			// If not streaming, but input stream is a file, read all of it into an internal memory stream
+			if (mStreamData != nullptr)
+			{
+				// Decompress if needed
+				if (mDesc.format == AudioFormat::VORBIS && mDesc.readMode == AudioReadMode::LoadDecompressed)
+				{
+					// TODO - Read entire stream and decompress into a new stream
+				}
+				else
+				{
+					if(mStreamData->isFile()) // If reading from file, make a copy of data in memory
+						mStreamData = bs_shared_ptr_new<MemoryDataStream>(mStreamData);
+				}
+			}
+		}
+
+		AudioClip::initialize();
+	}
+
+	void OAAudioClip::getSamples(UINT8* samples, UINT32 count, UINT32 offset) const
+	{
+		// TODO - Read from stream, and decompress as needed
+
+		// TODO - This must be thread safe as it will be called from other threads. Note this in the docs.
+	}
+
+	SPtr<DataStream> OAAudioClip::getSourceFormatData(UINT32& size)
+	{
+		if(mDesc.format == AudioFormat::PCM || mDesc.readMode == AudioReadMode::LoadCompressed || 
+			mDesc.readMode == AudioReadMode::Stream)
+		{
+			size = mStreamSize;
+			return mStreamData;
+		}
+		else // Data has been decompressed, we need to re-compress it to get the original stream
+		{
+			if (mStreamData == nullptr)
+			{
+				size = 0;
+				return nullptr;
+			}
+
+			assert(!mStreamData->isFile());
+			SPtr<MemoryDataStream> memStream = std::static_pointer_cast<MemoryDataStream>(mStreamData);
+
+			AudioFileInfo info;
+			info.bitDepth = mDesc.bitDepth;
+			info.numChannels = mDesc.numChannels;
+			info.numSamples = mNumSamples;
+			info.sampleRate = mDesc.frequency;
+
+			UINT32 bufferSize = 0;
+			UINT8* encodedSamples = OAOggVorbisWriter::PCMToOggVorbis(memStream->getPtr(), info, bufferSize);
+
+			return bs_shared_ptr_new<MemoryDataStream>(encodedSamples, bufferSize);
+		}
+	}
+}

+ 3 - 40
Source/BansheeOpenAudio/Source/BsOAImporter.cpp

@@ -70,50 +70,13 @@ namespace BansheeEngine
 		// Encode to Ogg Vorbis if needed
 		if(clipIO->getFormat() == AudioFormat::VORBIS)
 		{
-			struct EncodedBlock
-			{
-				UINT8* data;
-				UINT32 size;
-			};
-
-			Vector<EncodedBlock> blocks;
-			UINT32 totalEncodedSize = 0;
-			auto writeCallback = [&](UINT8* buffer, UINT32 size)
-			{
-				EncodedBlock newBlock;
-				newBlock.data = bs_frame_alloc(size);
-				newBlock.size = size;
-
-				memcpy(newBlock.data, buffer, size);
-				blocks.push_back(newBlock);
-				totalEncodedSize += size;
-			};
-
-			bs_frame_mark();
-
 			// Note: If the original source was in Ogg Vorbis we could just copy it here, but instead we decode to PCM and
 			// then re-encode which is redundant. If later we decide to copy be aware that the engine encodes Ogg in a
 			// specific quality, and the the import source might have lower or higher bitrate/quality.
-			OAOggVorbisWriter writer;
-			writer.open(writeCallback, info.sampleRate, info.bitDepth, info.numChannels);
-
-			writer.write(sampleBuffer, info.numSamples);
-			writer.close();
+			UINT8* encodedSamples = OAOggVorbisWriter::PCMToOggVorbis(sampleBuffer, info, bufferSize);
 
 			bs_free(sampleBuffer);
-			bufferSize = totalEncodedSize;
-
-			sampleBuffer = (UINT8*)bs_alloc(bufferSize);
-			UINT32 offset = 0;
-			for(auto& block : blocks)
-			{
-				memcpy(sampleBuffer + offset, block.data, block.size);
-				offset += block.size;
-
-				bs_frame_free(block.data);
-			}
-
-			bs_frame_clear();
+			sampleBuffer = encodedSamples;
 		}
 
 		SPtr<MemoryDataStream> sampleStream = bs_shared_ptr_new<MemoryDataStream>(sampleBuffer, bufferSize);
@@ -125,7 +88,7 @@ namespace BansheeEngine
 		clipDesc.numChannels = info.numChannels;
 		clipDesc.readMode = clipIO->getReadMode();
 
-		SPtr<AudioClip> clip = AudioClip::_createPtr(sampleStream, info.numSamples, clipDesc);
+		SPtr<AudioClip> clip = AudioClip::_createPtr(sampleStream, bufferSize, info.numSamples, clipDesc);
 
 		WString fileName = filePath.getWFilename(false);
 		clip->setName(fileName);

+ 46 - 0
Source/BansheeOpenAudio/Source/BsOAOggVorbisWriter.cpp

@@ -1,4 +1,5 @@
 #include "BsOAOggVorbisWriter.h"
+#include "BsDataStream.h"
 
 namespace BansheeEngine
 {
@@ -196,5 +197,50 @@ namespace BansheeEngine
 		vorbis_info_clear(&mVorbisInfo);
 	}
 
+	UINT8* OAOggVorbisWriter::PCMToOggVorbis(UINT8* samples, const AudioFileInfo& info, UINT32& size)
+	{
+		struct EncodedBlock
+		{
+			UINT8* data;
+			UINT32 size;
+		};
+
+		Vector<EncodedBlock> blocks;
+		UINT32 totalEncodedSize = 0;
+		auto writeCallback = [&](UINT8* buffer, UINT32 size)
+		{
+			EncodedBlock newBlock;
+			newBlock.data = bs_frame_alloc(size);
+			newBlock.size = size;
+
+			memcpy(newBlock.data, buffer, size);
+			blocks.push_back(newBlock);
+			totalEncodedSize += size;
+		};
+
+		bs_frame_mark();
+
+		OAOggVorbisWriter writer;
+		writer.open(writeCallback, info.sampleRate, info.bitDepth, info.numChannels);
+
+		writer.write(samples, info.numSamples);
+		writer.close();
+
+		UINT8* outSampleBuffer = (UINT8*)bs_alloc(totalEncodedSize);
+		UINT32 offset = 0;
+		for (auto& block : blocks)
+		{
+			memcpy(outSampleBuffer + offset, block.data, block.size);
+			offset += block.size;
+
+			bs_frame_free(block.data);
+		}
+
+		bs_frame_clear();
+		
+		size = totalEncodedSize;
+		return outSampleBuffer;
+	}
+
 #undef WRITE_TO_BUFFER
 }