267 lines
7.0 KiB
C++
267 lines
7.0 KiB
C++
// Copyright (c) 2012-2024 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 "Engine/Core/Collections/Array.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
|