| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "BsOAImporter.h" |
| 4 | #include "FileSystem/BsDataStream.h" |
| 5 | #include "FileSystem/BsFileSystem.h" |
| 6 | #include "BsWaveDecoder.h" |
| 7 | #include "BsFLACDecoder.h" |
| 8 | #include "BsOggVorbisDecoder.h" |
| 9 | #include "BsOggVorbisEncoder.h" |
| 10 | #include "Audio/BsAudioClipImportOptions.h" |
| 11 | #include "Audio/BsAudioUtility.h" |
| 12 | |
| 13 | namespace bs |
| 14 | { |
| 15 | OAImporter::OAImporter() |
| 16 | :SpecificImporter() |
| 17 | { |
| 18 | |
| 19 | } |
| 20 | |
| 21 | bool OAImporter::isExtensionSupported(const String& ext) const |
| 22 | { |
| 23 | String lowerCaseExt = ext; |
| 24 | StringUtil::toLowerCase(lowerCaseExt); |
| 25 | |
| 26 | return lowerCaseExt == u8"wav" || lowerCaseExt == u8"flac" || lowerCaseExt == u8"ogg" ; |
| 27 | } |
| 28 | |
| 29 | bool OAImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const |
| 30 | { |
| 31 | // Don't check for magic number, rely on extension |
| 32 | return true; |
| 33 | } |
| 34 | |
| 35 | SPtr<ImportOptions> OAImporter::createImportOptions() const |
| 36 | { |
| 37 | return bs_shared_ptr_new<AudioClipImportOptions>(); |
| 38 | } |
| 39 | |
| 40 | SPtr<Resource> OAImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions) |
| 41 | { |
| 42 | AudioDataInfo info; |
| 43 | UINT32 bytesPerSample; |
| 44 | UINT32 bufferSize; |
| 45 | UINT8* sampleBuffer; |
| 46 | { |
| 47 | Lock fileLock = FileScheduler::getLock(filePath); |
| 48 | SPtr<DataStream> stream = FileSystem::openFile(filePath); |
| 49 | |
| 50 | String extension = filePath.getExtension(); |
| 51 | StringUtil::toLowerCase(extension); |
| 52 | |
| 53 | UPtr<AudioDecoder> reader; |
| 54 | if (extension == u8".wav" ) |
| 55 | reader = bs_unique_ptr_new<WaveDecoder>(); |
| 56 | else if (extension == u8".flac" ) |
| 57 | reader = bs_unique_ptr_new<FLACDecoder>(); |
| 58 | else if (extension == u8".ogg" ) |
| 59 | reader = bs_unique_ptr_new<OggVorbisDecoder>(); |
| 60 | |
| 61 | if (reader == nullptr) |
| 62 | return nullptr; |
| 63 | |
| 64 | if (!reader->isValid(stream)) |
| 65 | return nullptr; |
| 66 | |
| 67 | if (!reader->open(stream, info)) |
| 68 | return nullptr; |
| 69 | |
| 70 | bytesPerSample = info.bitDepth / 8; |
| 71 | bufferSize = info.numSamples * bytesPerSample; |
| 72 | sampleBuffer = (UINT8*)bs_alloc(bufferSize); |
| 73 | |
| 74 | reader->read(sampleBuffer, info.numSamples); |
| 75 | } |
| 76 | |
| 77 | SPtr<const AudioClipImportOptions> clipIO = std::static_pointer_cast<const AudioClipImportOptions>(importOptions); |
| 78 | |
| 79 | // If 3D, convert to mono |
| 80 | if(clipIO->is3D && info.numChannels > 1) |
| 81 | { |
| 82 | UINT32 numSamplesPerChannel = info.numSamples / info.numChannels; |
| 83 | |
| 84 | UINT32 monoBufferSize = numSamplesPerChannel * bytesPerSample; |
| 85 | UINT8* monoBuffer = (UINT8*)bs_alloc(monoBufferSize); |
| 86 | |
| 87 | AudioUtility::convertToMono(sampleBuffer, monoBuffer, info.bitDepth, numSamplesPerChannel, info.numChannels); |
| 88 | |
| 89 | info.numSamples = numSamplesPerChannel; |
| 90 | info.numChannels = 1; |
| 91 | |
| 92 | bs_free(sampleBuffer); |
| 93 | |
| 94 | sampleBuffer = monoBuffer; |
| 95 | bufferSize = monoBufferSize; |
| 96 | } |
| 97 | |
| 98 | // Convert bit depth if needed |
| 99 | if(clipIO->bitDepth != info.bitDepth) |
| 100 | { |
| 101 | UINT32 outBufferSize = info.numSamples * (clipIO->bitDepth / 8); |
| 102 | UINT8* outBuffer = (UINT8*)bs_alloc(outBufferSize); |
| 103 | |
| 104 | AudioUtility::convertBitDepth(sampleBuffer, info.bitDepth, outBuffer, clipIO->bitDepth, info.numSamples); |
| 105 | |
| 106 | info.bitDepth = clipIO->bitDepth; |
| 107 | |
| 108 | bs_free(sampleBuffer); |
| 109 | |
| 110 | sampleBuffer = outBuffer; |
| 111 | bufferSize = outBufferSize; |
| 112 | } |
| 113 | |
| 114 | // Encode to Ogg Vorbis if needed |
| 115 | if(clipIO->format == AudioFormat::VORBIS) |
| 116 | { |
| 117 | // Note: If the original source was in Ogg Vorbis we could just copy it here, but instead we decode to PCM and |
| 118 | // then re-encode which is redundant. If later we decide to copy be aware that the engine encodes Ogg in a |
| 119 | // specific quality, and the the import source might have lower or higher bitrate/quality. |
| 120 | UINT8* encodedSamples = OggVorbisEncoder::PCMToOggVorbis(sampleBuffer, info, bufferSize); |
| 121 | |
| 122 | bs_free(sampleBuffer); |
| 123 | sampleBuffer = encodedSamples; |
| 124 | } |
| 125 | |
| 126 | SPtr<MemoryDataStream> sampleStream = bs_shared_ptr_new<MemoryDataStream>(sampleBuffer, bufferSize); |
| 127 | |
| 128 | AUDIO_CLIP_DESC clipDesc; |
| 129 | clipDesc.bitDepth = info.bitDepth; |
| 130 | clipDesc.format = clipIO->format; |
| 131 | clipDesc.frequency = info.sampleRate; |
| 132 | clipDesc.numChannels = info.numChannels; |
| 133 | clipDesc.readMode = clipIO->readMode; |
| 134 | clipDesc.is3D = clipIO->is3D; |
| 135 | |
| 136 | SPtr<AudioClip> clip = AudioClip::_createPtr(sampleStream, bufferSize, info.numSamples, clipDesc); |
| 137 | |
| 138 | const String fileName = filePath.getFilename(false); |
| 139 | clip->setName(fileName); |
| 140 | |
| 141 | return clip; |
| 142 | } |
| 143 | } |