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 "BsOggVorbisEncoder.h" |
4 | #include "FileSystem/BsDataStream.h" |
5 | #include "Audio/BsAudioUtility.h" |
6 | |
7 | namespace bs |
8 | { |
9 | // Writes to the internal cached buffer and flushes it if needed |
10 | #define WRITE_TO_BUFFER(data, length) \ |
11 | if ((mBufferOffset + length) > BUFFER_SIZE) \ |
12 | flush(); \ |
13 | \ |
14 | if(length > BUFFER_SIZE) \ |
15 | mWriteCallback(data, length); \ |
16 | else \ |
17 | { \ |
18 | memcpy(mBuffer + mBufferOffset, data, length); \ |
19 | mBufferOffset += length; \ |
20 | } |
21 | |
22 | OggVorbisEncoder::~OggVorbisEncoder() |
23 | { |
24 | close(); |
25 | } |
26 | |
27 | bool OggVorbisEncoder::open(std::function<void(UINT8*, UINT32)> writeCallback, UINT32 sampleRate, UINT32 bitDepth, |
28 | UINT32 numChannels) |
29 | { |
30 | mNumChannels = numChannels; |
31 | mBitDepth = bitDepth; |
32 | mWriteCallback = writeCallback; |
33 | mClosed = false; |
34 | |
35 | ogg_stream_init(&mOggState, std::rand()); |
36 | vorbis_info_init(&mVorbisInfo); |
37 | |
38 | // Automatic bitrate management with quality 0.4 (~128 kbps for 44 KHz stereo sound) |
39 | INT32 status = vorbis_encode_init_vbr(&mVorbisInfo, numChannels, sampleRate, 0.4f); |
40 | if (status != 0) |
41 | { |
42 | LOGERR("Failed to write Ogg Vorbis file." ); |
43 | close(); |
44 | return false; |
45 | } |
46 | |
47 | vorbis_analysis_init(&mVorbisState, &mVorbisInfo); |
48 | vorbis_block_init(&mVorbisState, &mVorbisBlock); |
49 | |
50 | // Generate header |
51 | vorbis_comment ; |
52 | vorbis_comment_init(&comment); |
53 | |
54 | ogg_packet , , codePacket; |
55 | status = vorbis_analysis_headerout(&mVorbisState, &comment, &headerPacket, &commentPacket, &codePacket); |
56 | vorbis_comment_clear(&comment); |
57 | |
58 | if (status != 0) |
59 | { |
60 | LOGERR("Failed to write Ogg Vorbis file." ); |
61 | close(); |
62 | return false; |
63 | } |
64 | |
65 | // Write header |
66 | ogg_stream_packetin(&mOggState, &headerPacket); |
67 | ogg_stream_packetin(&mOggState, &commentPacket); |
68 | ogg_stream_packetin(&mOggState, &codePacket); |
69 | |
70 | ogg_page page; |
71 | while (ogg_stream_flush(&mOggState, &page) > 0) |
72 | { |
73 | WRITE_TO_BUFFER(page.header, page.header_len); |
74 | WRITE_TO_BUFFER(page.body, page.body_len); |
75 | } |
76 | |
77 | return true; |
78 | } |
79 | |
80 | void OggVorbisEncoder::write(UINT8* samples, UINT32 numSamples) |
81 | { |
82 | static const UINT32 WRITE_LENGTH = 1024; |
83 | |
84 | UINT32 numFrames = numSamples / mNumChannels; |
85 | while (numFrames > 0) |
86 | { |
87 | UINT32 numFramesToWrite = std::min(numFrames, WRITE_LENGTH); |
88 | float** buffer = vorbis_analysis_buffer(&mVorbisState, numFramesToWrite); |
89 | |
90 | if (mBitDepth == 8) |
91 | { |
92 | for (UINT32 i = 0; i < numFramesToWrite; i++) |
93 | { |
94 | for (UINT32 j = 0; j < mNumChannels; j++) |
95 | { |
96 | INT8 sample = *(INT8*)samples; |
97 | float encodedSample = sample / 127.0f; |
98 | buffer[j][i] = encodedSample; |
99 | |
100 | samples++; |
101 | } |
102 | } |
103 | } |
104 | else if (mBitDepth == 16) |
105 | { |
106 | for (UINT32 i = 0; i < numFramesToWrite; i++) |
107 | { |
108 | for (UINT32 j = 0; j < mNumChannels; j++) |
109 | { |
110 | INT16 sample = *(INT16*)samples; |
111 | float encodedSample = sample / 32767.0f; |
112 | buffer[j][i] = encodedSample; |
113 | |
114 | samples += 2; |
115 | } |
116 | } |
117 | } |
118 | else if (mBitDepth == 24) |
119 | { |
120 | for (UINT32 i = 0; i < numFramesToWrite; i++) |
121 | { |
122 | for (UINT32 j = 0; j < mNumChannels; j++) |
123 | { |
124 | INT32 sample = AudioUtility::convert24To32Bits(samples); |
125 | float encodedSample = sample / 2147483647.0f; |
126 | buffer[j][i] = encodedSample; |
127 | |
128 | samples += 3; |
129 | } |
130 | } |
131 | } |
132 | else if (mBitDepth == 32) |
133 | { |
134 | for (UINT32 i = 0; i < numFramesToWrite; i++) |
135 | { |
136 | for (UINT32 j = 0; j < mNumChannels; j++) |
137 | { |
138 | INT32 sample = *(INT32*)samples; |
139 | float encodedSample = sample / 2147483647.0f; |
140 | buffer[j][i] = encodedSample; |
141 | |
142 | samples += 4; |
143 | } |
144 | } |
145 | } |
146 | else |
147 | assert(false); |
148 | |
149 | // Signal how many frames were written |
150 | vorbis_analysis_wrote(&mVorbisState, numFramesToWrite); |
151 | writeBlocks(); |
152 | |
153 | numFrames -= numFramesToWrite; |
154 | } |
155 | } |
156 | |
157 | void OggVorbisEncoder::writeBlocks() |
158 | { |
159 | while (vorbis_analysis_blockout(&mVorbisState, &mVorbisBlock) == 1) |
160 | { |
161 | // Analyze and determine optimal bitrate |
162 | vorbis_analysis(&mVorbisBlock, nullptr); |
163 | vorbis_bitrate_addblock(&mVorbisBlock); |
164 | |
165 | // Write block into ogg packets |
166 | ogg_packet packet; |
167 | while (vorbis_bitrate_flushpacket(&mVorbisState, &packet)) |
168 | { |
169 | ogg_stream_packetin(&mOggState, &packet); |
170 | |
171 | // If new page, write it to the internal buffer |
172 | ogg_page page; |
173 | while (ogg_stream_flush(&mOggState, &page) > 0) |
174 | { |
175 | WRITE_TO_BUFFER(page.header, page.header_len); |
176 | WRITE_TO_BUFFER(page.body, page.body_len); |
177 | } |
178 | } |
179 | } |
180 | } |
181 | |
182 | void OggVorbisEncoder::flush() |
183 | { |
184 | if (mBufferOffset > 0 && mWriteCallback != nullptr) |
185 | mWriteCallback(mBuffer, mBufferOffset); |
186 | |
187 | mBufferOffset = 0; |
188 | } |
189 | |
190 | void OggVorbisEncoder::close() |
191 | { |
192 | if (mClosed) |
193 | return; |
194 | |
195 | // Mark end of data and flush any remaining data in the buffers |
196 | vorbis_analysis_wrote(&mVorbisState, 0); |
197 | writeBlocks(); |
198 | flush(); |
199 | |
200 | ogg_stream_clear(&mOggState); |
201 | vorbis_block_clear(&mVorbisBlock); |
202 | vorbis_dsp_clear(&mVorbisState); |
203 | vorbis_info_clear(&mVorbisInfo); |
204 | |
205 | mClosed = true; |
206 | } |
207 | |
208 | UINT8* OggVorbisEncoder::PCMToOggVorbis(UINT8* samples, const AudioDataInfo& info, UINT32& size) |
209 | { |
210 | struct EncodedBlock |
211 | { |
212 | UINT8* data; |
213 | UINT32 size; |
214 | }; |
215 | |
216 | Vector<EncodedBlock> blocks; |
217 | UINT32 totalEncodedSize = 0; |
218 | auto writeCallback = [&](UINT8* buffer, UINT32 size) |
219 | { |
220 | EncodedBlock newBlock; |
221 | newBlock.data = bs_frame_alloc(size); |
222 | newBlock.size = size; |
223 | |
224 | memcpy(newBlock.data, buffer, size); |
225 | blocks.push_back(newBlock); |
226 | totalEncodedSize += size; |
227 | }; |
228 | |
229 | bs_frame_mark(); |
230 | |
231 | OggVorbisEncoder writer; |
232 | writer.open(writeCallback, info.sampleRate, info.bitDepth, info.numChannels); |
233 | |
234 | writer.write(samples, info.numSamples); |
235 | writer.close(); |
236 | |
237 | UINT8* outSampleBuffer = (UINT8*)bs_alloc(totalEncodedSize); |
238 | UINT32 offset = 0; |
239 | for (auto& block : blocks) |
240 | { |
241 | memcpy(outSampleBuffer + offset, block.data, block.size); |
242 | offset += block.size; |
243 | |
244 | bs_frame_free(block.data); |
245 | } |
246 | |
247 | bs_frame_clear(); |
248 | |
249 | size = totalEncodedSize; |
250 | return outSampleBuffer; |
251 | } |
252 | |
253 | #undef WRITE_TO_BUFFER |
254 | } |
255 | |