Files
FlaxEngine/Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp
2021-01-02 14:28:49 +01:00

266 lines
7.0 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_OGG_VORBIS
#include "OggVorbisEncoder.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "AudioTool.h"
#include <ThirdParty/vorbis/vorbisenc.h>
// Writes to the internal cached buffer and flushes it if needed
#define WRITE_TO_BUFFER(data, length) \
if ((_bufferOffset + length) > BUFFER_SIZE) \
Flush(); \
if(length > BUFFER_SIZE) \
_writeCallback(data, length, _userData); \
else \
{ \
Platform::MemoryCopy(_buffer + _bufferOffset, data, length); \
_bufferOffset += length; \
}
OggVorbisEncoder::OggVorbisEncoder()
: _bufferOffset(0)
, _numChannels(0)
, _bitDepth(0)
, _closed(true)
{
}
OggVorbisEncoder::~OggVorbisEncoder()
{
Close();
}
bool OggVorbisEncoder::Open(WriteCallback writeCallback, uint32 sampleRate, uint32 bitDepth, uint32 numChannels, float quality, void* userData)
{
_numChannels = numChannels;
_bitDepth = bitDepth;
_writeCallback = writeCallback;
_userData = userData;
_closed = false;
ogg_stream_init(&_oggState, rand());
vorbis_info_init(&_vorbisInfo);
int32 status = vorbis_encode_init_vbr(&_vorbisInfo, numChannels, sampleRate, quality);
if (status != 0)
{
LOG(Warning, "Failed to write Ogg Vorbis file.");
Close();
return true;
}
vorbis_analysis_init(&_vorbisState, &_vorbisInfo);
vorbis_block_init(&_vorbisState, &_vorbisBlock);
// Generate header
vorbis_comment comment;
vorbis_comment_init(&comment);
ogg_packet headerPacket, commentPacket, codePacket;
status = vorbis_analysis_headerout(&_vorbisState, &comment, &headerPacket, &commentPacket, &codePacket);
vorbis_comment_clear(&comment);
if (status != 0)
{
LOG(Warning, "Failed to write Ogg Vorbis file.");
Close();
return true;
}
// Write header
ogg_stream_packetin(&_oggState, &headerPacket);
ogg_stream_packetin(&_oggState, &commentPacket);
ogg_stream_packetin(&_oggState, &codePacket);
ogg_page page;
while (ogg_stream_flush(&_oggState, &page) > 0)
{
WRITE_TO_BUFFER(page.header, page.header_len);
WRITE_TO_BUFFER(page.body, page.body_len);
}
return false;
}
void OggVorbisEncoder::Write(uint8* samples, uint32 numSamples)
{
static const uint32 WRITE_LENGTH = 1024;
uint32 numFrames = numSamples / _numChannels;
while (numFrames > 0)
{
const uint32 numFramesToWrite = Math::Min(numFrames, WRITE_LENGTH);
float** buffer = vorbis_analysis_buffer(&_vorbisState, numFramesToWrite);
if (_bitDepth == 8)
{
for (uint32 i = 0; i < numFramesToWrite; i++)
{
for (uint32 j = 0; j < _numChannels; j++)
{
const int8 sample = *(int8*)samples;
const float encodedSample = sample / 127.0f;
buffer[j][i] = encodedSample;
samples++;
}
}
}
else if (_bitDepth == 16)
{
for (uint32 i = 0; i < numFramesToWrite; i++)
{
for (uint32 j = 0; j < _numChannels; j++)
{
const int16 sample = *(int16*)samples;
const float encodedSample = sample / 32767.0f;
buffer[j][i] = encodedSample;
samples += 2;
}
}
}
else if (_bitDepth == 24)
{
for (uint32 i = 0; i < numFramesToWrite; i++)
{
for (uint32 j = 0; j < _numChannels; j++)
{
const int32 sample = AudioTool::Convert24To32Bits(samples);
const float encodedSample = sample / 2147483647.0f;
buffer[j][i] = encodedSample;
samples += 3;
}
}
}
else if (_bitDepth == 32)
{
for (uint32 i = 0; i < numFramesToWrite; i++)
{
for (uint32 j = 0; j < _numChannels; j++)
{
const int32 sample = *(int32*)samples;
const float encodedSample = sample / 2147483647.0f;
buffer[j][i] = encodedSample;
samples += 4;
}
}
}
else
{
CRASH;
}
// Signal how many frames were written
vorbis_analysis_wrote(&_vorbisState, numFramesToWrite);
WriteBlocks();
numFrames -= numFramesToWrite;
}
}
void OggVorbisEncoder::WriteBlocks()
{
while (vorbis_analysis_blockout(&_vorbisState, &_vorbisBlock) == 1)
{
// Analyze and determine optimal bit rate
vorbis_analysis(&_vorbisBlock, nullptr);
vorbis_bitrate_addblock(&_vorbisBlock);
// Write block into ogg packets
ogg_packet packet;
while (vorbis_bitrate_flushpacket(&_vorbisState, &packet))
{
ogg_stream_packetin(&_oggState, &packet);
// If new page, write it to the internal buffer
ogg_page page;
while (ogg_stream_flush(&_oggState, &page) > 0)
{
WRITE_TO_BUFFER(page.header, page.header_len);
WRITE_TO_BUFFER(page.body, page.body_len);
}
}
}
}
struct EncodedBlock
{
uint8* data;
uint32 size;
};
struct ConvertWriteCallbackData
{
Array<EncodedBlock> Blocks;
uint32 TotalEncodedSize = 0;
};
void ConvertWriteCallback(uint8* buffer, uint32 size, void* userData)
{
EncodedBlock newBlock;
newBlock.data = (uint8*)Allocator::Allocate(size);
newBlock.size = size;
Platform::MemoryCopy(newBlock.data, buffer, size);
auto data = (ConvertWriteCallbackData*)userData;
data->Blocks.Add(newBlock);
data->TotalEncodedSize += size;
};
bool OggVorbisEncoder::Convert(byte* samples, AudioDataInfo& info, BytesContainer& result, float quality)
{
ConvertWriteCallbackData data;
if (Open(ConvertWriteCallback, info.SampleRate, info.BitDepth, info.NumChannels, quality, &data))
return true;
Write(samples, info.NumSamples);
Close();
result.Allocate(data.TotalEncodedSize);
uint32 offset = 0;
for (auto& block : data.Blocks)
{
Platform::MemoryCopy(result.Get() + offset, block.data, block.size);
offset += block.size;
Allocator::Free(block.data);
}
return false;
}
void OggVorbisEncoder::Flush()
{
if (_bufferOffset > 0 && _writeCallback != nullptr)
_writeCallback(_buffer, _bufferOffset, _userData);
_bufferOffset = 0;
}
void OggVorbisEncoder::Close()
{
if (_closed)
return;
// Mark end of data and flush any remaining data in the buffers
vorbis_analysis_wrote(&_vorbisState, 0);
WriteBlocks();
Flush();
ogg_stream_clear(&_oggState);
vorbis_block_clear(&_vorbisBlock);
vorbis_dsp_clear(&_vorbisState);
vorbis_info_clear(&_vorbisInfo);
_closed = true;
}
#endif