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 | } |