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
7namespace 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 comment;
52 vorbis_comment_init(&comment);
53
54 ogg_packet headerPacket, commentPacket, 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