You're breathtaking!
This commit is contained in:
84
Source/Engine/Tools/AudioTool/AudioDecoder.h
Normal file
84
Source/Engine/Tools/AudioTool/AudioDecoder.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Audio/Types.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
class ReadStream;
|
||||
|
||||
/// <summary>
|
||||
/// Interface used for implementations that parse audio formats into a set of PCM samples.
|
||||
/// </summary>
|
||||
class AudioDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="AudioDecoder"/> class.
|
||||
/// </summary>
|
||||
virtual ~AudioDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to open the specified stream with audio data and loads the whole audio data.
|
||||
/// </summary>
|
||||
/// <param name="stream">The data stream audio data is stored in. Must be valid until decoder usage end. Decoder may cache this pointer for the later usage.</param>
|
||||
/// <param name="info">The output information describing meta-data of the audio in the stream.</param>
|
||||
/// <param name="result">The output data.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>True if the data is invalid or conversion failed, otherwise false.</returns>
|
||||
virtual bool Convert(ReadStream* stream, AudioDataInfo& info, Array<byte>& result, uint32 offset = 0)
|
||||
{
|
||||
if (!IsValid(stream, offset))
|
||||
return true;
|
||||
if (!Open(stream, info, offset))
|
||||
return true;
|
||||
|
||||
// Load the whole audio data
|
||||
const int32 bytesPerSample = info.BitDepth / 8;
|
||||
const int32 bufferSize = info.NumSamples * bytesPerSample;
|
||||
result.Resize(bufferSize);
|
||||
Read(result.Get(), info.NumSamples);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to open the specified stream with audio data. Must be called before any reads or seeks.
|
||||
/// </summary>
|
||||
/// <param name="stream">The data stream audio data is stored in. Must be valid until decoder usage end. Decoder may cache this pointer for the later usage.</param>
|
||||
/// <param name="info">The output information describing meta-data of the audio in the stream.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>True if the data is invalid, otherwise false.</returns>
|
||||
virtual bool Open(ReadStream* stream, AudioDataInfo& info, uint32 offset = 0) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the read pointer to the specified offset. Any further Read() calls will read from this location. User must ensure not to seek past the end of the data.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset to move the pointer in. In number of samples.</param>
|
||||
virtual void Seek(uint32 offset) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a set of samples from the audio data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All values are returned as signed values.
|
||||
/// </remarks>
|
||||
/// <param name="samples">Pre-allocated buffer to store the samples in.</param>
|
||||
/// <param name="numSamples">The number of samples to read.</param>
|
||||
virtual void Read(byte* samples, uint32 numSamples) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the data in the provided stream valid audio data for the current format. You should check this before calling Open().
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to check.</param>
|
||||
/// <param name="offset">The offset at which audio data in the stream begins, in bytes.</param>
|
||||
/// <returns>True if the data is valid, otherwise false.</returns>
|
||||
virtual bool IsValid(ReadStream* stream, uint32 offset = 0) = 0;
|
||||
};
|
||||
33
Source/Engine/Tools/AudioTool/AudioEncoder.h
Normal file
33
Source/Engine/Tools/AudioTool/AudioEncoder.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Audio/Types.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
|
||||
/// <summary>
|
||||
/// Interface used for implementations that encodes set of PCM samples into a target audio format.
|
||||
/// </summary>
|
||||
class AudioEncoder
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="AudioEncoder"/> class.
|
||||
/// </summary>
|
||||
virtual ~AudioEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Converts the input PCM samples buffer into the encoder audio format.
|
||||
/// </summary>
|
||||
/// <param name="samples">The buffer containing samples in PCM format. All samples should be in signed integer format.</param>
|
||||
/// <param name="info">The input information describing meta-data of the audio in the samples buffer.</param>
|
||||
/// <param name="result">The output data.</param>
|
||||
/// <param name="quality">The output data compression quality (normalized in range [0;1]).</param>
|
||||
/// <returns>True if the data is invalid or conversion failed, otherwise false.</returns>
|
||||
virtual bool Convert(byte* samples, AudioDataInfo& info, BytesContainer& result, float quality = 0.5f) = 0;
|
||||
};
|
||||
30
Source/Engine/Tools/AudioTool/AudioTool.Build.cs
Normal file
30
Source/Engine/Tools/AudioTool/AudioTool.Build.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// Audio data utilities module.
|
||||
/// </summary>
|
||||
public class AudioTool : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
// TODO: convert into private deps
|
||||
options.PublicDependencies.Add("minimp3");
|
||||
options.PublicDependencies.Add("ogg");
|
||||
options.PublicDependencies.Add("vorbis");
|
||||
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_AUDIO_TOOL");
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_OGG_VORBIS");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GetFilesToDeploy(List<string> files)
|
||||
{
|
||||
}
|
||||
}
|
||||
263
Source/Engine/Tools/AudioTool/AudioTool.cpp
Normal file
263
Source/Engine/Tools/AudioTool/AudioTool.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AudioTool.h"
|
||||
#include "Engine/Core/Core.h"
|
||||
#include "Engine/Core/Memory/Allocation.h"
|
||||
|
||||
void ConvertToMono8(const int8* input, uint8* output, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
int16 sum = 0;
|
||||
for (uint32 j = 0; j < numChannels; j++)
|
||||
{
|
||||
sum += *input;
|
||||
++input;
|
||||
}
|
||||
|
||||
*output = sum / numChannels;
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToMono16(const int16* input, int16* output, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
int32 sum = 0;
|
||||
for (uint32 j = 0; j < numChannels; j++)
|
||||
{
|
||||
sum += *input;
|
||||
++input;
|
||||
}
|
||||
|
||||
*output = sum / numChannels;
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
void Convert32To24Bits(const int32 input, uint8* output)
|
||||
{
|
||||
const uint32 valToEncode = *(uint32*)&input;
|
||||
output[0] = (valToEncode >> 8) & 0x000000FF;
|
||||
output[1] = (valToEncode >> 16) & 0x000000FF;
|
||||
output[2] = (valToEncode >> 24) & 0x000000FF;
|
||||
}
|
||||
|
||||
void ConvertToMono24(const uint8* input, uint8* output, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
int64 sum = 0;
|
||||
for (uint32 j = 0; j < numChannels; j++)
|
||||
{
|
||||
sum += AudioTool::Convert24To32Bits(input);
|
||||
input += 3;
|
||||
}
|
||||
|
||||
const int32 avg = (int32)(sum / numChannels);
|
||||
Convert32To24Bits(avg, output);
|
||||
output += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToMono32(const int32* input, int32* output, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
int64 sum = 0;
|
||||
for (uint32 j = 0; j < numChannels; j++)
|
||||
{
|
||||
sum += *input;
|
||||
++input;
|
||||
}
|
||||
|
||||
*output = (int32)(sum / numChannels);
|
||||
++output;
|
||||
}
|
||||
}
|
||||
|
||||
void Convert8To32Bits(const int8* input, int32* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int8 val = input[i];
|
||||
output[i] = val << 24;
|
||||
}
|
||||
}
|
||||
|
||||
void Convert16To32Bits(const int16* input, int32* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
output[i] = input[i] << 16;
|
||||
}
|
||||
|
||||
void Convert24To32Bits(const uint8* input, int32* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
output[i] = AudioTool::Convert24To32Bits(input);
|
||||
input += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void Convert32To8Bits(const int32* input, uint8* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
output[i] = (int8)(input[i] >> 24);
|
||||
}
|
||||
|
||||
void Convert32To16Bits(const int32* input, int16* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
output[i] = (int16)(input[i] >> 16);
|
||||
}
|
||||
|
||||
void Convert32To24Bits(const int32* input, uint8* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
Convert32To24Bits(input[i], output);
|
||||
output += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTool::ConvertToMono(const byte* input, byte* output, uint32 bitDepth, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 8:
|
||||
ConvertToMono8((int8*)input, output, numSamples, numChannels);
|
||||
break;
|
||||
case 16:
|
||||
ConvertToMono16((int16*)input, (int16*)output, numSamples, numChannels);
|
||||
break;
|
||||
case 24:
|
||||
ConvertToMono24(input, output, numSamples, numChannels);
|
||||
break;
|
||||
case 32:
|
||||
ConvertToMono32((int32*)input, (int32*)output, numSamples, numChannels);
|
||||
break;
|
||||
default:
|
||||
CRASH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTool::ConvertBitDepth(const byte* input, uint32 inBitDepth, byte* output, uint32 outBitDepth, uint32 numSamples)
|
||||
{
|
||||
int32* srcBuffer = nullptr;
|
||||
|
||||
const bool needTempBuffer = inBitDepth != 32;
|
||||
if (needTempBuffer)
|
||||
srcBuffer = (int32*)Allocator::Allocate(numSamples * sizeof(int32));
|
||||
else
|
||||
srcBuffer = (int32*)input;
|
||||
|
||||
// Convert it to a temporary 32-bit buffer and then use that to convert to actual requested bit depth.
|
||||
// It could be more efficient to convert directly from source to requested depth without a temporary buffer,
|
||||
// at the cost of additional complexity. If this method ever becomes a performance issue consider that.
|
||||
|
||||
switch (inBitDepth)
|
||||
{
|
||||
case 8:
|
||||
Convert8To32Bits((int8*)input, srcBuffer, numSamples);
|
||||
break;
|
||||
case 16:
|
||||
Convert16To32Bits((int16*)input, srcBuffer, numSamples);
|
||||
break;
|
||||
case 24:
|
||||
::Convert24To32Bits(input, srcBuffer, numSamples);
|
||||
break;
|
||||
case 32:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
CRASH;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (outBitDepth)
|
||||
{
|
||||
case 8:
|
||||
Convert32To8Bits(srcBuffer, output, numSamples);
|
||||
break;
|
||||
case 16:
|
||||
Convert32To16Bits(srcBuffer, (int16*)output, numSamples);
|
||||
break;
|
||||
case 24:
|
||||
Convert32To24Bits(srcBuffer, output, numSamples);
|
||||
break;
|
||||
case 32:
|
||||
Platform::MemoryCopy(output, srcBuffer, numSamples * sizeof(int32));
|
||||
break;
|
||||
default:
|
||||
CRASH;
|
||||
break;
|
||||
}
|
||||
|
||||
if (needTempBuffer)
|
||||
{
|
||||
Allocator::Free(srcBuffer);
|
||||
srcBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* output, uint32 numSamples)
|
||||
{
|
||||
if (inBitDepth == 8)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int8 sample = *(int8*)input;
|
||||
output[i] = sample / 127.0f;
|
||||
|
||||
input++;
|
||||
}
|
||||
}
|
||||
else if (inBitDepth == 16)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int16 sample = *(int16*)input;
|
||||
output[i] = sample / 32767.0f;
|
||||
|
||||
input += 2;
|
||||
}
|
||||
}
|
||||
else if (inBitDepth == 24)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int32 sample = Convert24To32Bits(input);
|
||||
output[i] = sample / 2147483647.0f;
|
||||
|
||||
input += 3;
|
||||
}
|
||||
}
|
||||
else if (inBitDepth == 32)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int32 sample = *(int32*)input;
|
||||
output[i] = sample / 2147483647.0f;
|
||||
|
||||
input += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSamples)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const float sample = *(float*)input;
|
||||
output[i] = static_cast<int32>(sample * 2147483647.0f);
|
||||
|
||||
input++;
|
||||
}
|
||||
}
|
||||
61
Source/Engine/Tools/AudioTool/AudioTool.h
Normal file
61
Source/Engine/Tools/AudioTool/AudioTool.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config.h"
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
/// <summary>
|
||||
/// Audio data importing and processing utilities.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AudioTool
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Converts a set of audio samples using multiple channels into a set of mono samples.
|
||||
/// </summary>
|
||||
/// <param name="input">A set of input samples. Per-channels samples should be interleaved. Size of each sample is determined by bitDepth. Total size of the buffer should be (numSamples * numChannels * bitDepth / 8).</param>
|
||||
/// <param name="output">The pre-allocated buffer to store the mono samples. Should be of (numSamples * bitDepth / 8) size.</param>
|
||||
/// <param name="bitDepth">The size of a single sample in bits.</param>
|
||||
/// <param name="numSamples">The number of samples per a single channel.</param>
|
||||
/// <param name="numChannels">The number of channels in the input data.</param>
|
||||
static void ConvertToMono(const byte* input, byte* output, uint32 bitDepth, uint32 numSamples, uint32 numChannels);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a set of audio samples of a certain bit depth to a new bit depth.
|
||||
/// </summary>
|
||||
/// <param name="input">A set of input samples. Total size of the buffer should be *numSamples * inBitDepth / 8).</param>
|
||||
/// <param name="inBitDepth">The size of a single sample in the input array, in bits.</param>
|
||||
/// <param name="output">The pre-allocated buffer to store the output samples in. Total size of the buffer should be (numSamples * outBitDepth / 8).</param>
|
||||
/// <param name="outBitDepth">Size of a single sample in the output array, in bits.</param>
|
||||
/// <param name="numSamples">The total number of samples to process.</param>
|
||||
static void ConvertBitDepth(const byte* input, uint32 inBitDepth, byte* output, uint32 outBitDepth, uint32 numSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a set of audio samples of a certain bit depth to a set of floating point samples in range [-1, 1].
|
||||
/// </summary>
|
||||
/// <param name="input">A set of input samples. Total size of the buffer should be (numSamples * inBitDepth / 8). All input samples should be signed integers.</param>
|
||||
/// <param name="inBitDepth">The size of a single sample in the input array, in bits.</param>
|
||||
/// <param name="output">The pre-allocated buffer to store the output samples in. Total size of the buffer should be numSamples * sizeof(float).</param>
|
||||
/// <param name="numSamples">The total number of samples to process.</param>
|
||||
static void ConvertToFloat(const byte* input, uint32 inBitDepth, float* output, uint32 numSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a set of audio samples of floating point samples in range [-1, 1] to a 32-bit depth PCM data.
|
||||
/// </summary>
|
||||
/// <param name="input">A set of input samples. Total size of the buffer should be (numSamples * sizeof(float)). All input samples should be in range [-1, 1].</param>
|
||||
/// <param name="output">The pre-allocated buffer to store the output samples in. Total size of the buffer should be numSamples * sizeof(float).</param>
|
||||
/// <param name="numSamples">The total number of samples to process.</param>
|
||||
static void ConvertFromFloat(const float* input, int32* output, uint32 numSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a 24-bit signed integer into a 32-bit signed integer.
|
||||
/// </summary>
|
||||
/// <param name="input">The 24-bit signed integer as an array of 3 bytes.</param>
|
||||
/// <returns>The 32-bit signed integer.</returns>
|
||||
FORCE_INLINE static int32 Convert24To32Bits(const byte* input)
|
||||
{
|
||||
return (input[2] << 24) | (input[1] << 16) | (input[0] << 8);
|
||||
}
|
||||
};
|
||||
88
Source/Engine/Tools/AudioTool/MP3Decoder.cpp
Normal file
88
Source/Engine/Tools/AudioTool/MP3Decoder.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MP3Decoder.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#define MINIMP3_IMPLEMENTATION
|
||||
#include <minimp3/minimp3.h>
|
||||
|
||||
bool MP3Decoder::Convert(ReadStream* stream, AudioDataInfo& info, Array<byte>& result, uint32 offset)
|
||||
{
|
||||
ASSERT(stream);
|
||||
|
||||
mStream = stream;
|
||||
mStream->SetPosition(offset);
|
||||
|
||||
int32 dataSize = mStream->GetLength() - offset;
|
||||
Array<byte> dataBytes;
|
||||
dataBytes.Resize(dataSize);
|
||||
byte* data = dataBytes.Get();
|
||||
mStream->ReadBytes(data, dataSize);
|
||||
|
||||
info.NumSamples = 0;
|
||||
info.SampleRate = 0;
|
||||
info.NumChannels = 0;
|
||||
info.BitDepth = 16;
|
||||
|
||||
mp3dec_frame_info_t mp3Info;
|
||||
short pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
|
||||
|
||||
MemoryWriteStream output(Math::RoundUpToPowerOf2(dataSize));
|
||||
|
||||
do
|
||||
{
|
||||
const int32 samples = mp3dec_decode_frame(&mp3d, data, dataSize, pcm, &mp3Info);
|
||||
if (samples)
|
||||
{
|
||||
info.NumSamples += samples * mp3Info.channels;
|
||||
|
||||
output.WriteBytes(pcm, samples * 2 * mp3Info.channels);
|
||||
|
||||
if (!info.SampleRate)
|
||||
info.SampleRate = mp3Info.hz;
|
||||
if (!info.NumChannels)
|
||||
info.NumChannels = mp3Info.channels;
|
||||
if (info.SampleRate != mp3Info.hz || info.NumChannels != mp3Info.channels)
|
||||
break;
|
||||
}
|
||||
data += mp3Info.frame_bytes;
|
||||
dataSize -= mp3Info.frame_bytes;
|
||||
} while (mp3Info.frame_bytes);
|
||||
|
||||
if (info.SampleRate == 0)
|
||||
return true;
|
||||
|
||||
// Load the whole audio data
|
||||
const int32 bytesPerSample = info.BitDepth / 8;
|
||||
const int32 bufferSize = info.NumSamples * bytesPerSample;
|
||||
result.Set(output.GetHandle(), bufferSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MP3Decoder::Open(ReadStream* stream, AudioDataInfo& info, uint32 offset)
|
||||
{
|
||||
CRASH;
|
||||
// TODO: open MP3
|
||||
return true;
|
||||
}
|
||||
|
||||
void MP3Decoder::Seek(uint32 offset)
|
||||
{
|
||||
// TODO: seek MP3
|
||||
CRASH;
|
||||
}
|
||||
|
||||
void MP3Decoder::Read(byte* samples, uint32 numSamples)
|
||||
{
|
||||
// TODO: load MP3 format
|
||||
CRASH;
|
||||
}
|
||||
|
||||
bool MP3Decoder::IsValid(ReadStream* stream, uint32 offset)
|
||||
{
|
||||
// TODO: check MP3 format
|
||||
CRASH;
|
||||
return false;
|
||||
}
|
||||
43
Source/Engine/Tools/AudioTool/MP3Decoder.h
Normal file
43
Source/Engine/Tools/AudioTool/MP3Decoder.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
#include "Engine/Serialization/ReadStream.h"
|
||||
#include <minimp3/minimp3.h>
|
||||
|
||||
/// <summary>
|
||||
/// Decodes .mp3 audio data into raw PCM format.
|
||||
/// </summary>
|
||||
/// <seealso cref="AudioDecoder" />
|
||||
class MP3Decoder : public AudioDecoder
|
||||
{
|
||||
private:
|
||||
|
||||
ReadStream* mStream;
|
||||
mp3dec_t mp3d;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MP3Decoder"/> class.
|
||||
/// </summary>
|
||||
MP3Decoder()
|
||||
{
|
||||
mStream = nullptr;
|
||||
mp3dec_init(&mp3d);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// [AudioDecoder]
|
||||
bool Convert(ReadStream* stream, AudioDataInfo& info, Array<byte>& result, uint32 offset = 0) override;
|
||||
bool Open(ReadStream* stream, AudioDataInfo& info, uint32 offset = 0) override;
|
||||
void Seek(uint32 offset) override;
|
||||
void Read(byte* samples, uint32 numSamples) override;
|
||||
bool IsValid(ReadStream* stream, uint32 offset = 0) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
114
Source/Engine/Tools/AudioTool/OggVorbisDecoder.cpp
Normal file
114
Source/Engine/Tools/AudioTool/OggVorbisDecoder.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if COMPILE_WITH_OGG_VORBIS
|
||||
|
||||
#include "OggVorbisDecoder.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include <vorbis/codec.h>
|
||||
|
||||
size_t oggRead(void* ptr, size_t size, size_t nmemb, void* data)
|
||||
{
|
||||
OggVorbisDecoder* decoderData = static_cast<OggVorbisDecoder*>(data);
|
||||
const auto len = Math::Min<uint32>(static_cast<uint32>(size * nmemb), decoderData->Stream->GetLength() - decoderData->Stream->GetPosition());
|
||||
decoderData->Stream->ReadBytes(ptr, len);
|
||||
return static_cast<std::size_t>(len);
|
||||
}
|
||||
|
||||
int oggSeek(void* data, ogg_int64_t offset, int whence)
|
||||
{
|
||||
OggVorbisDecoder* decoderData = static_cast<OggVorbisDecoder*>(data);
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
offset += decoderData->Offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
offset += decoderData->Stream->GetPosition();
|
||||
break;
|
||||
case SEEK_END:
|
||||
offset = Math::Max<ogg_int64_t>(0, decoderData->Stream->GetLength() - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
decoderData->Stream->SetPosition(static_cast<uint32>(offset));
|
||||
return static_cast<int>(decoderData->Stream->GetPosition() - decoderData->Offset);
|
||||
}
|
||||
|
||||
long oggTell(void* data)
|
||||
{
|
||||
OggVorbisDecoder* decoderData = static_cast<OggVorbisDecoder*>(data);
|
||||
return static_cast<long>(decoderData->Stream->GetPosition() - decoderData->Offset);
|
||||
}
|
||||
|
||||
bool OggVorbisDecoder::Open(ReadStream* stream, AudioDataInfo& info, uint32 offset)
|
||||
{
|
||||
if (stream == nullptr)
|
||||
return false;
|
||||
|
||||
stream->SetPosition(offset);
|
||||
Stream = stream;
|
||||
Offset = offset;
|
||||
|
||||
const ov_callbacks callbacks = { &oggRead, &oggSeek, nullptr, &oggTell };
|
||||
const int status = ov_open_callbacks(this, &OggVorbisFile, nullptr, 0, callbacks);
|
||||
if (status < 0)
|
||||
{
|
||||
LOG(Warning, "Failed to open Ogg Vorbis file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
vorbis_info* vorbisInfo = ov_info(&OggVorbisFile, -1);
|
||||
info.NumChannels = vorbisInfo->channels;
|
||||
info.SampleRate = vorbisInfo->rate;
|
||||
info.NumSamples = static_cast<uint32>(ov_pcm_total(&OggVorbisFile, -1) * vorbisInfo->channels);
|
||||
info.BitDepth = 16;
|
||||
|
||||
ChannelCount = info.NumChannels;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OggVorbisDecoder::Seek(uint32 offset)
|
||||
{
|
||||
ov_pcm_seek(&OggVorbisFile, offset / ChannelCount);
|
||||
}
|
||||
|
||||
void OggVorbisDecoder::Read(byte* samples, uint32 numSamples)
|
||||
{
|
||||
uint32 numReadSamples = 0;
|
||||
while (numReadSamples < numSamples)
|
||||
{
|
||||
const int32 bytesToRead = static_cast<int32>(numSamples - numReadSamples) * sizeof(int16);
|
||||
const uint32 bytesRead = ov_read(&OggVorbisFile, (char*)samples, bytesToRead, 0, 2, 1, nullptr);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
const uint32 samplesRead = bytesRead / sizeof(int16);
|
||||
numReadSamples += samplesRead;
|
||||
samples += samplesRead * sizeof(int16);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OggVorbisDecoder::IsValid(ReadStream* stream, uint32 offset)
|
||||
{
|
||||
stream->SetPosition(offset);
|
||||
Stream = stream;
|
||||
Offset = offset;
|
||||
|
||||
OggVorbis_File file;
|
||||
const ov_callbacks callbacks = { &oggRead, &oggSeek, nullptr, &oggTell };
|
||||
if (ov_test_callbacks(this, &file, nullptr, 0, callbacks) == 0)
|
||||
{
|
||||
ov_clear(&file);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
55
Source/Engine/Tools/AudioTool/OggVorbisDecoder.h
Normal file
55
Source/Engine/Tools/AudioTool/OggVorbisDecoder.h
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_OGG_VORBIS
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
#include "Engine/Serialization/ReadStream.h"
|
||||
#include <ThirdParty/vorbis/vorbisfile.h>
|
||||
|
||||
/// <summary>
|
||||
/// Decodes .ogg audio data into raw PCM format.
|
||||
/// </summary>
|
||||
/// <seealso cref="AudioDecoder" />
|
||||
class OggVorbisDecoder : public AudioDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
ReadStream* Stream;
|
||||
uint32 Offset;
|
||||
uint32 ChannelCount;
|
||||
OggVorbis_File OggVorbisFile;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OggVorbisDecoder"/> class.
|
||||
/// </summary>
|
||||
OggVorbisDecoder()
|
||||
{
|
||||
Stream = nullptr;
|
||||
Offset = 0;
|
||||
ChannelCount = 0;
|
||||
OggVorbisFile.datasource = nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="OggVorbisDecoder"/> class.
|
||||
/// </summary>
|
||||
~OggVorbisDecoder()
|
||||
{
|
||||
if (OggVorbisFile.datasource != nullptr)
|
||||
ov_clear(&OggVorbisFile);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// [AudioDecoder]
|
||||
bool Open(ReadStream* stream, AudioDataInfo& info, uint32 offset = 0) override;
|
||||
void Seek(uint32 offset) override;
|
||||
void Read(byte* samples, uint32 numSamples) override;
|
||||
bool IsValid(ReadStream* stream, uint32 offset = 0) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
265
Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp
Normal file
265
Source/Engine/Tools/AudioTool/OggVorbisEncoder.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2012-2020 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
|
||||
95
Source/Engine/Tools/AudioTool/OggVorbisEncoder.h
Normal file
95
Source/Engine/Tools/AudioTool/OggVorbisEncoder.h
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioEncoder.h"
|
||||
#include "Engine/Audio/Config.h"
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL && COMPILE_WITH_OGG_VORBIS
|
||||
|
||||
#include <ThirdParty/vorbis/vorbisfile.h>
|
||||
|
||||
/// <summary>
|
||||
/// Raw PCM data encoder to Ogg Vorbis audio format.
|
||||
/// </summary>
|
||||
/// <seealso cref="AudioEncoder" />
|
||||
class OggVorbisEncoder : public AudioEncoder
|
||||
{
|
||||
public:
|
||||
|
||||
typedef void (*WriteCallback)(byte*, uint32, void*);
|
||||
|
||||
private:
|
||||
|
||||
static const uint32 BUFFER_SIZE = 4096;
|
||||
|
||||
WriteCallback _writeCallback;
|
||||
void* _userData;
|
||||
byte _buffer[BUFFER_SIZE];
|
||||
uint32 _bufferOffset;
|
||||
uint32 _numChannels;
|
||||
uint32 _bitDepth;
|
||||
bool _closed;
|
||||
|
||||
ogg_stream_state _oggState;
|
||||
vorbis_info _vorbisInfo;
|
||||
vorbis_dsp_state _vorbisState;
|
||||
vorbis_block _vorbisBlock;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OggVorbisEncoder"/> class.
|
||||
/// </summary>
|
||||
OggVorbisEncoder();
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="OggVorbisEncoder"/> class.
|
||||
/// </summary>
|
||||
~OggVorbisEncoder();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the writer. Should be called before calling Write().
|
||||
/// </summary>
|
||||
/// <param name="writeCallback">Callback that will be triggered when the writer is ready to output some data. The callback should copy the provided data into its own buffer.</param>
|
||||
/// <param name="sampleRate">Determines how many samples per second the written data will have.</param>
|
||||
/// <param name="bitDepth">Determines the size of a single sample, in bits.</param>
|
||||
/// <param name="numChannels">Determines the number of audio channels. Channel data will be output interleaved in the output buffer.</param>
|
||||
/// <param name="quality">The output data compression quality (normalized in range [0;1]).</param>
|
||||
/// <param name="userData">The custom used data passed to the write callback.</param>
|
||||
/// <returns>True if failed to open the data, otherwise false.</returns>
|
||||
bool Open(WriteCallback writeCallback, uint32 sampleRate, uint32 bitDepth, uint32 numChannels, float quality, void* userData = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a new set of samples and converts them to Ogg Vorbis.
|
||||
/// </summary>
|
||||
/// <param name="samples">The samples in PCM format. 8-bit samples should be unsigned, but higher bit depths signed. Each sample is assumed to be the bit depth that was provided to the Open() method.</param>
|
||||
/// <param name="numSamples">The number of samples to encode.</param>
|
||||
void Write(byte* samples, uint32 numSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the last of the data into the write buffer (triggers the write callback). This is called automatically when the writer is closed or goes out of scope.
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Closes the encoder and flushes the last of the data into the write buffer (triggers the write callback). This is called automatically when the writer goes out of scope.
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Writes Vorbis blocks into Ogg packets.
|
||||
/// </summary>
|
||||
void WriteBlocks();
|
||||
|
||||
public:
|
||||
|
||||
// [AudioEncoder]
|
||||
bool Convert(byte* samples, AudioDataInfo& info, BytesContainer& result, float quality = 0.5f) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
168
Source/Engine/Tools/AudioTool/WaveDecoder.cpp
Normal file
168
Source/Engine/Tools/AudioTool/WaveDecoder.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "WaveDecoder.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "AudioTool.h"
|
||||
|
||||
#define WAVE_FORMAT_PCM 0x0001
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#define WAVE_FORMAT_ALAW 0x0006
|
||||
#define WAVE_FORMAT_MULAW 0x0007
|
||||
#define WAVE_FORMAT_EXTENDED 0xFFFE
|
||||
#define MAIN_CHUNK_SIZE 12
|
||||
|
||||
bool WaveDecoder::ParseHeader(AudioDataInfo& info)
|
||||
{
|
||||
bool foundData = false;
|
||||
while (!foundData)
|
||||
{
|
||||
// Get sub-chunk ID and size
|
||||
uint8 subChunkId[4];
|
||||
mStream->Read(subChunkId, sizeof(subChunkId));
|
||||
|
||||
uint32 subChunkSize = 0;
|
||||
mStream->ReadUint32(&subChunkSize);
|
||||
|
||||
// FMT chunk
|
||||
if (subChunkId[0] == 'f' && subChunkId[1] == 'm' && subChunkId[2] == 't' && subChunkId[3] == ' ')
|
||||
{
|
||||
uint16 format;
|
||||
mStream->ReadUint16(&format);
|
||||
|
||||
if (format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT && format != WAVE_FORMAT_EXTENDED)
|
||||
{
|
||||
LOG(Warning, "Wave file doesn't contain raw PCM data. Not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 numChannels = 0;
|
||||
mStream->ReadUint16(&numChannels);
|
||||
|
||||
uint32 sampleRate = 0;
|
||||
mStream->ReadUint32(&sampleRate);
|
||||
|
||||
uint32 byteRate = 0;
|
||||
mStream->ReadUint32(&byteRate);
|
||||
|
||||
uint16 blockAlign = 0;
|
||||
mStream->ReadUint16(&blockAlign);
|
||||
|
||||
uint16 bitDepth = 0;
|
||||
mStream->ReadUint16(&bitDepth);
|
||||
|
||||
if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32)
|
||||
{
|
||||
LOG(Warning, "Unsupported number of bits per sample: {0}", bitDepth);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.NumChannels = numChannels;
|
||||
info.SampleRate = sampleRate;
|
||||
info.BitDepth = bitDepth;
|
||||
|
||||
// Read extension data, and get the actual format
|
||||
if (format == WAVE_FORMAT_EXTENDED)
|
||||
{
|
||||
uint16 extensionSize = 0;
|
||||
mStream->ReadUint16(&extensionSize);
|
||||
|
||||
if (extensionSize != 22)
|
||||
{
|
||||
LOG(Warning, "Wave file doesn't contain raw PCM data. Not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 validBitDepth = 0;
|
||||
mStream->ReadUint16(&validBitDepth);
|
||||
|
||||
uint32 channelMask = 0;
|
||||
mStream->ReadUint32(&channelMask);
|
||||
|
||||
uint8 subFormat[16];
|
||||
mStream->Read(subFormat, sizeof(subFormat));
|
||||
|
||||
Platform::MemoryCopy(&format, subFormat, sizeof(format));
|
||||
if (format != WAVE_FORMAT_PCM)
|
||||
{
|
||||
LOG(Warning, "Wave file doesn't contain raw PCM data. Not supported.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mBytesPerSample = bitDepth / 8;
|
||||
mFormat = format;
|
||||
}
|
||||
// DATA chunk
|
||||
else if (subChunkId[0] == 'd' && subChunkId[1] == 'a' && subChunkId[2] == 't' && subChunkId[3] == 'a')
|
||||
{
|
||||
info.NumSamples = subChunkSize / mBytesPerSample;
|
||||
mDataOffset = (uint32)mStream->GetPosition();
|
||||
|
||||
foundData = true;
|
||||
}
|
||||
// Unsupported chunk type
|
||||
else
|
||||
{
|
||||
if (mStream->GetPosition() + subChunkSize >= mStream->GetLength())
|
||||
return false;
|
||||
mStream->SetPosition(mStream->GetPosition() + subChunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaveDecoder::Open(ReadStream* stream, AudioDataInfo& info, uint32 offset)
|
||||
{
|
||||
ASSERT(stream);
|
||||
|
||||
mStream = stream;
|
||||
mStream->SetPosition(offset + MAIN_CHUNK_SIZE);
|
||||
|
||||
if (!ParseHeader(info))
|
||||
{
|
||||
LOG(Warning, "Provided file is not a valid WAVE file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaveDecoder::Seek(uint32 offset)
|
||||
{
|
||||
mStream->SetPosition(mDataOffset + offset * mBytesPerSample);
|
||||
}
|
||||
|
||||
void WaveDecoder::Read(byte* samples, uint32 numSamples)
|
||||
{
|
||||
const uint32 numRead = numSamples * mBytesPerSample;
|
||||
mStream->Read(samples, numRead);
|
||||
|
||||
// 8-bit samples are stored as unsigned, but engine convention is to store all bit depths as signed
|
||||
if (mBytesPerSample == 1)
|
||||
{
|
||||
for (uint32 i = 0; i < numRead; i++)
|
||||
{
|
||||
int8 val = samples[i] - 128;
|
||||
samples[i] = *((uint8*)&val);
|
||||
}
|
||||
}
|
||||
// IEEE float need to be converted into signed PCM data
|
||||
else if (mFormat == WAVE_FORMAT_IEEE_FLOAT)
|
||||
{
|
||||
AudioTool::ConvertFromFloat((const float*)samples, (int32*)samples, numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
bool WaveDecoder::IsValid(ReadStream* stream, uint32 offset)
|
||||
{
|
||||
ASSERT(stream);
|
||||
|
||||
stream->SetPosition(offset);
|
||||
|
||||
byte header[MAIN_CHUNK_SIZE];
|
||||
stream->ReadBytes(header, sizeof(header));
|
||||
|
||||
return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F')
|
||||
&& (header[8] == 'W') && (header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E');
|
||||
}
|
||||
54
Source/Engine/Tools/AudioTool/WaveDecoder.h
Normal file
54
Source/Engine/Tools/AudioTool/WaveDecoder.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
#include "Engine/Serialization/ReadStream.h"
|
||||
|
||||
/// <summary>
|
||||
/// Decodes .wav audio data into raw PCM format.
|
||||
/// </summary>
|
||||
/// <seealso cref="AudioDecoder" />
|
||||
class WaveDecoder : public AudioDecoder
|
||||
{
|
||||
private:
|
||||
|
||||
ReadStream* mStream;
|
||||
uint16 mFormat;
|
||||
uint32 mDataOffset;
|
||||
uint32 mBytesPerSample;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaveDecoder"/> class.
|
||||
/// </summary>
|
||||
WaveDecoder()
|
||||
{
|
||||
mStream = nullptr;
|
||||
mFormat = 0;
|
||||
mDataOffset = 0;
|
||||
mBytesPerSample = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Parses the WAVE header and output audio file meta-data. Returns false if the header is not valid.
|
||||
/// </summary>
|
||||
/// <param name="info">The output information.</param>
|
||||
/// <returns>True if header is valid, otherwise false.</returns>
|
||||
bool ParseHeader(AudioDataInfo& info);
|
||||
|
||||
public:
|
||||
|
||||
// [AudioDecoder]
|
||||
bool Open(ReadStream* stream, AudioDataInfo& info, uint32 offset = 0) override;
|
||||
void Seek(uint32 offset) override;
|
||||
void Read(byte* samples, uint32 numSamples) override;
|
||||
bool IsValid(ReadStream* stream, uint32 offset = 0) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user