project source files from ~60 messier squashed commits
parent
c65a33fe6a
commit
c4bceacc5e
@ -0,0 +1,88 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
|
||||
#include "WavDecoder.h"
|
||||
#include "WavPackDecoder.h"
|
||||
#include "FlacDecoder.h"
|
||||
#include "VorbisDecoder.h"
|
||||
#include "OpusDecoder.h"
|
||||
#include "CafDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
NyquistIO::NyquistIO()
|
||||
{
|
||||
BuildDecoderTable();
|
||||
}
|
||||
|
||||
NyquistIO::~NyquistIO()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int NyquistIO::Load(AudioData * data, const std::string & path)
|
||||
{
|
||||
if (IsFileSupported(path))
|
||||
{
|
||||
if (decoderTable.size() > 0)
|
||||
{
|
||||
auto fileExtension = ParsePathForExtension(path);
|
||||
auto decoder = GetDecoderForExtension(fileExtension);
|
||||
|
||||
try
|
||||
{
|
||||
return decoder->LoadFromPath(data, path);
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
std::cerr << "Caught fatal exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
return IOError::NoDecodersLoaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IOError::ExtensionNotSupported;
|
||||
}
|
||||
|
||||
// Should never be reached
|
||||
return IOError::UnknownError;
|
||||
}
|
||||
|
||||
bool NyquistIO::IsFileSupported(const std::string path) const
|
||||
{
|
||||
auto fileExtension = ParsePathForExtension(path);
|
||||
if (decoderTable.find(fileExtension) == decoderTable.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string NyquistIO::ParsePathForExtension(const std::string & path) const
|
||||
{
|
||||
if (path.find_last_of(".") != std::string::npos)
|
||||
return path.substr(path.find_last_of(".") + 1);
|
||||
|
||||
return std::string("");
|
||||
}
|
||||
|
||||
std::shared_ptr<BaseDecoder> NyquistIO::GetDecoderForExtension(const std::string ext)
|
||||
{
|
||||
return decoderTable[ext];
|
||||
}
|
||||
|
||||
void NyquistIO::BuildDecoderTable()
|
||||
{
|
||||
AddDecoderToTable(std::make_shared<WavDecoder>());
|
||||
AddDecoderToTable(std::make_shared<WavPackDecoder>());
|
||||
AddDecoderToTable(std::make_shared<FlacDecoder>());
|
||||
AddDecoderToTable(std::make_shared<VorbisDecoder>());
|
||||
AddDecoderToTable(std::make_shared<OpusDecoder>());
|
||||
AddDecoderToTable(std::make_shared<CAFDecoder>());
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "AudioDevice.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
static RingBufferT<float> buffer(BUFFER_LENGTH);
|
||||
|
||||
static int rt_callback(void * output_buffer, void * input_buffer, unsigned int num_bufferframes, double stream_time, RtAudioStreamStatus status, void * user_data)
|
||||
{
|
||||
if (status) std::cerr << "[rtaudio] Buffer over or underflow" << std::endl;
|
||||
|
||||
if (buffer.getAvailableRead())
|
||||
{
|
||||
buffer.read((float*) output_buffer, BUFFER_LENGTH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(output_buffer, 0, BUFFER_LENGTH * sizeof(float));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AudioDevice::Open(const int deviceId)
|
||||
{
|
||||
if (!rtaudio) throw std::runtime_error("rtaudio not created yet");
|
||||
|
||||
RtAudio::StreamParameters parameters;
|
||||
parameters.deviceId = info.id;
|
||||
parameters.nChannels = info.numChannels;
|
||||
parameters.firstChannel = 0;
|
||||
|
||||
rtaudio->openStream(¶meters, NULL, RTAUDIO_FLOAT32, info.sampleRate, &info.frameSize, &rt_callback, (void*) & buffer);
|
||||
|
||||
if (rtaudio->isStreamOpen())
|
||||
{
|
||||
rtaudio->startStream();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioDevice::ListAudioDevices()
|
||||
{
|
||||
std::unique_ptr<RtAudio> tempDevice(new RtAudio);
|
||||
|
||||
RtAudio::DeviceInfo info;
|
||||
unsigned int devices = tempDevice->getDeviceCount();
|
||||
|
||||
std::cout << "[rtaudio] Found: " << devices << " device(s)\n";
|
||||
|
||||
for (unsigned int i = 0; i < devices; ++i)
|
||||
{
|
||||
info = tempDevice->getDeviceInfo(i);
|
||||
std::cout << "\tDevice: " << i << " - " << info.name << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
bool AudioDevice::Play(const std::vector<float> & data)
|
||||
{
|
||||
if (!rtaudio->isStreamOpen()) return false;
|
||||
|
||||
// Each frame is the (size/2) cause interleaved channels!
|
||||
int sizeInFrames = ((int) data.size()) / (BUFFER_LENGTH);
|
||||
|
||||
int writeCount = 0;
|
||||
|
||||
while(writeCount < sizeInFrames)
|
||||
{
|
||||
bool status = buffer.write((data.data() + (writeCount * BUFFER_LENGTH)), BUFFER_LENGTH);
|
||||
if (status) writeCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "CafDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int CAFDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
return IOError::LoadPathNotImplemented;
|
||||
}
|
||||
|
||||
int CAFDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
return IOError::LoadBufferNotImplemented;
|
||||
}
|
||||
|
||||
std::vector<std::string> CAFDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
#include "Common.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
ChunkHeaderInfo nqr::ScanForChunk(const std::vector<uint8_t> & fileData, uint32_t chunkMarker)
|
||||
{
|
||||
// D[n] aligned to 16 bytes now
|
||||
const uint16_t * d = reinterpret_cast<const uint16_t *>(fileData.data());
|
||||
|
||||
for (size_t i = 0; i < fileData.size() / sizeof(uint16_t); i++)
|
||||
{
|
||||
// This will be in machine endianess
|
||||
uint32_t m = Pack(Read16(d[i]), Read16(d[i+1]));
|
||||
|
||||
if (m == chunkMarker)
|
||||
{
|
||||
uint32_t cSz = Pack(Read16(d[i+2]), Read16(d[i+3]));
|
||||
return {(uint32_t (i * sizeof(uint16_t))), cSz}; // return i in bytes to the start of the data
|
||||
}
|
||||
else continue;
|
||||
}
|
||||
return {0, 0};
|
||||
};
|
||||
|
||||
NyquistFileBuffer nqr::ReadFile(std::string pathToFile)
|
||||
{
|
||||
std::cout << "[Debug] Open: " << pathToFile << std::endl;
|
||||
|
||||
FILE * audioFile = fopen(pathToFile.c_str(), "rb");
|
||||
|
||||
if (!audioFile)
|
||||
{
|
||||
throw std::runtime_error("file not found");
|
||||
}
|
||||
|
||||
fseek(audioFile, 0, SEEK_END);
|
||||
size_t lengthInBytes = ftell(audioFile);
|
||||
fseek(audioFile, 0, SEEK_SET);
|
||||
|
||||
// Allocate temporary buffer
|
||||
std::vector<uint8_t> fileBuffer(lengthInBytes);
|
||||
|
||||
size_t elementsRead = fread(fileBuffer.data(), 1, lengthInBytes, audioFile);
|
||||
|
||||
if (elementsRead == 0 || fileBuffer.size() < 64)
|
||||
{
|
||||
throw std::runtime_error("error reading file or file too small");
|
||||
}
|
||||
|
||||
NyquistFileBuffer data = {std::move(fileBuffer), elementsRead};
|
||||
|
||||
fclose(audioFile);
|
||||
|
||||
// Copy out to user
|
||||
return data;
|
||||
}
|
||||
@ -0,0 +1,177 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "FlacDecoder.h"
|
||||
#include "flac/all.h"
|
||||
#include "AudioDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
class FlacDecoderInternal
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
// N.B.: FLAC is a big-endian format. All values are unsigned.
|
||||
FlacDecoderInternal(AudioData * d, std::string filepath) : d(d)
|
||||
{
|
||||
|
||||
/////////////////////////////
|
||||
// Initialize FLAC library //
|
||||
/////////////////////////////
|
||||
|
||||
decoderInternal = FLAC__stream_decoder_new();
|
||||
|
||||
FLAC__stream_decoder_set_metadata_respond(decoderInternal, FLAC__METADATA_TYPE_STREAMINFO);
|
||||
|
||||
//@todo: check if OGG flac
|
||||
bool initialized = FLAC__stream_decoder_init_file(decoderInternal,
|
||||
filepath.c_str(),
|
||||
s_writeCallback,
|
||||
s_metadataCallback,
|
||||
s_errorCallback,
|
||||
this) == FLAC__STREAM_DECODER_INIT_STATUS_OK;
|
||||
|
||||
FLAC__stream_decoder_set_md5_checking(decoderInternal, true);
|
||||
|
||||
//////////////////////
|
||||
// Read Stream Data //
|
||||
/////////////////////
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
// Find the size and allocate memory
|
||||
FLAC__stream_decoder_process_until_end_of_metadata(decoderInternal);
|
||||
|
||||
// Read memory out into our temporary internalBuffer
|
||||
FLAC__stream_decoder_process_until_end_of_stream(decoderInternal);
|
||||
|
||||
// Presently unneeded, but useful for reference
|
||||
// FLAC__ChannelAssignment channelAssignment = FLAC__stream_decoder_get_channel_assignment(decoderInternal);
|
||||
|
||||
// Fill out remaining user data
|
||||
d->lengthSeconds = (float) numSamples / (float) d->sampleRate;
|
||||
|
||||
auto totalSamples = numSamples * d->channelCount;
|
||||
|
||||
// N.B.: "Currently the reference encoder and decoders only support up to 24 bits per sample."
|
||||
|
||||
PCMFormat internalFmt = PCMFormat::PCM_END;
|
||||
|
||||
switch (d->bitDepth)
|
||||
{
|
||||
case 8:
|
||||
internalFmt = PCMFormat::PCM_S8;
|
||||
break;
|
||||
case 16:
|
||||
internalFmt = PCMFormat::PCM_16;
|
||||
break;
|
||||
case 24:
|
||||
internalFmt = PCMFormat::PCM_24;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("unsupported FLAC bit depth");
|
||||
break;
|
||||
}
|
||||
|
||||
// Next, process internal buffer into the user-visible samples array
|
||||
ConvertToFloat32(d->samples.data(), internalBuffer.data(), totalSamples, internalFmt);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Unable to initialize FLAC decoder");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
~FlacDecoderInternal()
|
||||
{
|
||||
if (decoderInternal)
|
||||
{
|
||||
FLAC__stream_decoder_finish(decoderInternal);
|
||||
FLAC__stream_decoder_delete(decoderInternal);
|
||||
}
|
||||
}
|
||||
|
||||
void processMetadata(const FLAC__StreamMetadata_StreamInfo & info)
|
||||
{
|
||||
d->sampleRate = info.sample_rate;
|
||||
d->channelCount = info.channels; // Assert 1 to 8
|
||||
d->bitDepth = info.bits_per_sample; // Assert 4 to 32
|
||||
d->frameSize = info.channels * info.bits_per_sample;
|
||||
|
||||
const size_t bytesPerSample = d->bitDepth / 8;
|
||||
|
||||
numSamples = (size_t) info.total_samples;
|
||||
|
||||
internalBuffer.resize(numSamples * info.channels * bytesPerSample); // as array of bytes
|
||||
d->samples.resize(numSamples * info.channels); // as audio samples in float32
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// libflab callbacks //
|
||||
///////////////////////
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus s_writeCallback(const FLAC__StreamDecoder*, const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* userPtr)
|
||||
{
|
||||
FlacDecoderInternal * decoder = reinterpret_cast<FlacDecoderInternal *>(userPtr);
|
||||
|
||||
const size_t bytesPerSample = decoder->d->bitDepth / 8;
|
||||
|
||||
auto dataPtr = decoder->internalBuffer.data();
|
||||
|
||||
for(unsigned int i = 0; i < frame->header.blocksize; i++)
|
||||
{
|
||||
for(int j = 0; j < decoder->d->channelCount; j++)
|
||||
{
|
||||
memcpy(dataPtr + decoder->bufferPosition, &buffer[j][i], bytesPerSample);
|
||||
decoder->bufferPosition += bytesPerSample;
|
||||
}
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static void s_metadataCallback (const FLAC__StreamDecoder*, const FLAC__StreamMetadata* metadata, void* userPtr)
|
||||
{
|
||||
static_cast<FlacDecoderInternal*>(userPtr)->processMetadata(metadata->data.stream_info);
|
||||
}
|
||||
|
||||
static void s_errorCallback (const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void*)
|
||||
{
|
||||
std::cerr << "FLAC Decoder Error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
NO_COPY(FlacDecoderInternal);
|
||||
|
||||
FLAC__StreamDecoder * decoderInternal;
|
||||
|
||||
size_t bufferPosition = 0;
|
||||
size_t numSamples = 0;
|
||||
|
||||
AudioData * d;
|
||||
|
||||
std::vector<uint8_t> internalBuffer;
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int FlacDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
FlacDecoderInternal decoder(data, path);
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
int FlacDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
return IOError::LoadBufferNotImplemented;
|
||||
}
|
||||
|
||||
std::vector<std::string> FlacDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {"flac"};
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#undef VERSION
|
||||
#define VERSION "1.3.1"
|
||||
|
||||
#define FLAC__NO_DLL 1
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334)
|
||||
#ifndef _WIN32
|
||||
#define _WIN32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define FLAC__SYS_DARWIN 1
|
||||
#endif
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX (size_t) (-1)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wconversion"
|
||||
#pragma clang diagnostic ignored "-Wshadow"
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-register"
|
||||
#endif
|
||||
|
||||
#if CPU_X86
|
||||
#ifdef __i386__
|
||||
#define FLAC__CPU_IA32 1
|
||||
#endif
|
||||
#ifdef __x86_64__
|
||||
#define FLAC__CPU_X86_64 1
|
||||
#endif
|
||||
#define FLAC__HAS_X86INTRIN 1
|
||||
#endif
|
||||
|
||||
// Ensure libflac can use non-standard <stdint> types
|
||||
#undef __STDC_LIMIT_MACROS
|
||||
#define __STDC_LIMIT_MACROS 1
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define flac_max(a,b) ((a) > (b) ? a : b)
|
||||
#define flac_min(a,b) ((a) < (b) ? a : b)
|
||||
#elif defined(_MSC_VER)
|
||||
#include <stdlib.h>
|
||||
#define flac_max(a,b) __max(a,b)
|
||||
#define flac_min(a,b) __min(a,b)
|
||||
#endif
|
||||
|
||||
#define HAVE_LROUND 1
|
||||
|
||||
#include "flac/all.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include "flac/src/win_utf8_io.c"
|
||||
#endif
|
||||
|
||||
#include "flac/src/bitmath.c"
|
||||
#include "flac/src/bitreader.c"
|
||||
#include "flac/src/bitwriter.c"
|
||||
#include "flac/src/cpu.c"
|
||||
#include "flac/src/crc.c"
|
||||
#include "flac/src/fixed.c"
|
||||
#include "flac/src/float.c"
|
||||
#include "flac/src/format.c"
|
||||
#include "flac/src/lpc.c"
|
||||
#include "flac/src/md5.c"
|
||||
#include "flac/src/memory.c"
|
||||
#include "flac/src/stream_decoder.c"
|
||||
#include "flac/src/window.c"
|
||||
|
||||
#undef VERSION
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
@ -0,0 +1,96 @@
|
||||
#if defined(_MSC_VER)
|
||||
#pragma comment(lib, "dsound.lib")
|
||||
#endif
|
||||
|
||||
#include "AudioDevice.h"
|
||||
#include "AudioDecoder.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
int main()
|
||||
{
|
||||
AudioDevice::ListAudioDevices();
|
||||
|
||||
int desiredSampleRate = 44100;
|
||||
AudioDevice myDevice(2, desiredSampleRate);
|
||||
myDevice.Open(myDevice.info.id);
|
||||
|
||||
AudioData * fileData = new AudioData();
|
||||
|
||||
NyquistIO loader;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/1ch/44100/8/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/1ch/44100/16/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/1ch/44100/24/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/1ch/44100/32/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/1ch/44100/64/test.wav");
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/2ch/44100/8/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/2ch/44100/16/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/2ch/44100/24/test.wav");
|
||||
auto result = loader.Load(fileData, "test_data/2ch/44100/32/test.wav");
|
||||
//auto result = loader.Load(fileData, "test_data/2ch/44100/64/test.wav");
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/6_channel_44k_16b.wav");
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/LR_Stereo.ogg");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestLaugh_44k.ogg");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat.ogg");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeatMono.ogg");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/BlockWoosh_Stereo.ogg");
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr8_Stereo_Dithered.flac");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr16_Stereo.flac");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr16_Mono.flac");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr24_Stereo.flac");
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/detodos.opus"); // "Firefox: From All, To All"
|
||||
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Float32.wv");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Float32_Mono.wv");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int16.wv");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int24.wv");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int32.wv");
|
||||
//auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat_Int24_Mono.wv");
|
||||
|
||||
std::cout << "[Debug] Loader Status: " << result << std::endl;
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
std::cerr << "Caught: " << e.what() << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// Libnyquist does not do sample rate conversion
|
||||
if (fileData->sampleRate != desiredSampleRate)
|
||||
{
|
||||
std::cout << "[Warning - Sample Rate Mismatch] - file is sampled at " << fileData->sampleRate << " and output is " << desiredSampleRate << std::endl;
|
||||
}
|
||||
|
||||
// Convert mono to stereo for testing playback
|
||||
if (fileData->channelCount == 1)
|
||||
{
|
||||
std::vector<float> stereoCopy(fileData->samples.size() * 2);
|
||||
|
||||
int m = 0;
|
||||
for (size_t i = 0; i < stereoCopy.size(); i+=2)
|
||||
{
|
||||
stereoCopy[i] = fileData->samples[m];
|
||||
stereoCopy[i+1] = fileData->samples[m];
|
||||
m++;
|
||||
}
|
||||
myDevice.Play(stereoCopy);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Playing for: " << fileData->lengthSeconds << " seconds..." << std::endl;
|
||||
myDevice.Play(fileData->samples);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "OpusDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
static const int OPUS_SAMPLE_RATE = 48000;
|
||||
|
||||
class OpusDecoderInternal
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
OpusDecoderInternal(AudioData * d, const std::vector<uint8_t> & fileData) : d(d)
|
||||
{
|
||||
/* @todo proper steaming support + classes
|
||||
const opus_callbacks = {
|
||||
.read = s_readCallback,
|
||||
.seek = s_seekCallback,
|
||||
.tell = s_tellCallback,
|
||||
.close = nullptr
|
||||
};
|
||||
*/
|
||||
|
||||
int err;
|
||||
|
||||
fileHandle = op_test_memory(fileData.data(), fileData.size(), &err);
|
||||
|
||||
if (!fileHandle)
|
||||
{
|
||||
std::cerr << errorAsString(err) << std::endl;
|
||||
throw std::runtime_error("File is not a valid ogg vorbis file");
|
||||
}
|
||||
|
||||
if (auto r = op_test_open(fileHandle) != 0)
|
||||
{
|
||||
std::cerr << errorAsString(r) << std::endl;
|
||||
throw std::runtime_error("Could not open file");
|
||||
}
|
||||
|
||||
const OpusHead *header = op_head(fileHandle, 0);
|
||||
|
||||
int originalSampleRate = header->input_sample_rate;
|
||||
|
||||
std::cout << "Original Sample Rate: " << originalSampleRate << std::endl;
|
||||
|
||||
d->sampleRate = OPUS_SAMPLE_RATE;
|
||||
d->channelCount = (uint32_t) header->channel_count;
|
||||
d->bitDepth = 32;
|
||||
d->lengthSeconds = double(getLengthInSeconds());
|
||||
d->frameSize = (uint32_t) header->channel_count * d->bitDepth;
|
||||
|
||||
// Samples in a single channel
|
||||
auto totalSamples = size_t(getTotalSamples());
|
||||
|
||||
d->samples.resize(totalSamples * d->channelCount);
|
||||
|
||||
auto r = readInternal(totalSamples);
|
||||
}
|
||||
|
||||
~OpusDecoderInternal()
|
||||
{
|
||||
op_free(fileHandle);
|
||||
}
|
||||
|
||||
size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0)
|
||||
{
|
||||
float *buffer = (float *) d->samples.data();
|
||||
size_t framesRemaining = requestedFrameCount;
|
||||
size_t totalFramesRead = 0;
|
||||
|
||||
while(0 < framesRemaining)
|
||||
{
|
||||
int64_t framesRead = op_read_float(fileHandle, buffer, (int)(framesRemaining * d->channelCount), nullptr);
|
||||
|
||||
// EOF
|
||||
if(!framesRead)
|
||||
break;
|
||||
|
||||
if (framesRead < 0)
|
||||
{
|
||||
std::cerr << "Opus decode error: " << framesRead << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffer += framesRead * d->channelCount;
|
||||
|
||||
totalFramesRead += framesRead;
|
||||
framesRemaining -= framesRead;
|
||||
}
|
||||
|
||||
return totalFramesRead;
|
||||
}
|
||||
|
||||
std::string errorAsString(int opusErrorCode)
|
||||
{
|
||||
switch(opusErrorCode)
|
||||
{
|
||||
case OP_FALSE: return "A request did not succeed";
|
||||
case OP_EOF: return "End of File Reached";
|
||||
case OP_HOLE: return "There was a hole in the page sequence numbers (e.g., a page was corrupt or missing).";
|
||||
case OP_EREAD: return "An underlying read, seek, or tell operation failed when it should have succeeded.";
|
||||
case OP_EFAULT: return "A NULL pointer was passed where one was unexpected, or an internal memory allocation failed, or an internal library error was encountered.";
|
||||
case OP_EIMPL: return "The stream used a feature that is not implemented, such as an unsupported channel family. ";
|
||||
case OP_EINVAL: return "One or more parameters to a function were invalid. ";
|
||||
case OP_ENOTFORMAT: return "A purported Ogg Opus stream did not begin with an Ogg page, a purported header packet did not start with one of the required strings";
|
||||
case OP_EBADHEADER: return "A required header packet was not properly formatted, contained illegal values, or was missing altogether.";
|
||||
case OP_EVERSION: return "The ID header contained an unrecognized version number.";
|
||||
case OP_ENOTAUDIO: return "Not Audio";
|
||||
case OP_EBADPACKET: return "An audio packet failed to decode properly.";
|
||||
case OP_EBADLINK: return "We failed to find data we had seen before, or the bitstream structure was sufficiently malformed that seeking to the target destination was impossible.";
|
||||
case OP_ENOSEEK: return "An operation that requires seeking was requested on an unseekable stream.";
|
||||
case OP_EBADTIMESTAMP: return "The first or last granule position of a link failed basic validity checks.";
|
||||
default: return "Unknown Error";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// opus callbacks //
|
||||
////////////////////
|
||||
|
||||
private:
|
||||
|
||||
NO_MOVE(OpusDecoderInternal);
|
||||
|
||||
OggOpusFile * fileHandle;
|
||||
|
||||
AudioData * d;
|
||||
|
||||
inline int64_t getTotalSamples() const { return int64_t(op_pcm_total(const_cast<OggOpusFile *>(fileHandle), -1)); }
|
||||
inline int64_t getLengthInSeconds() const { return uint64_t(getTotalSamples() / OPUS_SAMPLE_RATE); }
|
||||
inline int64_t getCurrentSample() const { return int64_t(op_pcm_tell(const_cast<OggOpusFile *>(fileHandle))); }
|
||||
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int nqr::OpusDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
auto fileBuffer = nqr::ReadFile(path);
|
||||
OpusDecoderInternal decoder(data, fileBuffer.buffer);
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
int nqr::OpusDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
OpusDecoderInternal decoder(data, memory);
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
std::vector<std::string> nqr::OpusDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {"opus"};
|
||||
}
|
||||
@ -0,0 +1,258 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
// https://dxr.mozilla.org/mozilla-central/source/media/libopus
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334 4703)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wconversion"
|
||||
#pragma clang diagnostic ignored "-Wshadow"
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-register"
|
||||
#endif
|
||||
|
||||
#undef HAVE_CONFIG_H
|
||||
|
||||
#define USE_ALLOCA 1
|
||||
#define OPUS_BUILD 1
|
||||
|
||||
/* Enable SSE functions, if compiled with SSE/SSE2 (note that AMD64 implies SSE2) */
|
||||
#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))
|
||||
#define __SSE__ 1
|
||||
#endif
|
||||
|
||||
//////////
|
||||
// CELT //
|
||||
//////////
|
||||
|
||||
#include "opus/celt/arch.h"
|
||||
#include "opus/celt/bands.h"
|
||||
#include "opus/celt/celt.h"
|
||||
#include "opus/celt/celt_lpc.h"
|
||||
#include "opus/celt/cwrs.h"
|
||||
#include "opus/celt/ecintrin.h"
|
||||
#include "opus/celt/entcode.h"
|
||||
#include "opus/celt/entdec.h"
|
||||
#include "opus/celt/entenc.h"
|
||||
#include "opus/celt/float_cast.h"
|
||||
#include "opus/celt/kiss_fft.h"
|
||||
#include "opus/celt/laplace.h"
|
||||
#include "opus/celt/mathops.h"
|
||||
#include "opus/celt/mdct.h"
|
||||
#include "opus/celt/mfrngcod.h"
|
||||
#include "opus/celt/modes.h"
|
||||
#include "opus/celt/os_support.h"
|
||||
#include "opus/celt/pitch.h"
|
||||
#include "opus/celt/quant_bands.h"
|
||||
#include "opus/celt/rate.h"
|
||||
#include "opus/celt/stack_alloc.h"
|
||||
#include "opus/celt/vq.h"
|
||||
#include "opus/celt/_kiss_fft_guts.h"
|
||||
|
||||
#include "opus/celt/bands.c"
|
||||
#include "opus/celt/celt.c"
|
||||
#include "opus/celt/celt_lpc.c"
|
||||
#include "opus/celt/cwrs.c"
|
||||
#include "opus/celt/entcode.c"
|
||||
#include "opus/celt/entdec.c"
|
||||
#include "opus/celt/entenc.c"
|
||||
#include "opus/celt/kiss_fft.c"
|
||||
#include "opus/celt/laplace.c"
|
||||
#include "opus/celt/mathops.c"
|
||||
#include "opus/celt/mdct.c"
|
||||
#include "opus/celt/modes.c"
|
||||
#include "opus/celt/pitch.c"
|
||||
#include "opus/celt/quant_bands.c"
|
||||
#include "opus/celt/rate.c"
|
||||
#include "opus/celt/vq.c"
|
||||
|
||||
// Disabled inline because of name clash of opus_custom_encoder_get_size.
|
||||
//#include "opus/celt/celt_decoder.c"
|
||||
//#include "opus/celt/celt_encoder.c"
|
||||
|
||||
/*
|
||||
See celt/celt_decoder.c + celt/celt_encoder.c in the project browser.
|
||||
These files need to be in separate translation units due to name clashes,
|
||||
unfortunately.
|
||||
*/
|
||||
|
||||
/////////////////
|
||||
// SILK Common //
|
||||
/////////////////
|
||||
|
||||
#include "opus_types.h"
|
||||
|
||||
#include "opus/silk/control.h"
|
||||
#include "opus/silk/debug.h"
|
||||
#include "opus/silk/define.h"
|
||||
#include "opus/silk/errors.h"
|
||||
#include "opus/silk/MacroCount.h"
|
||||
#include "opus/silk/MacroDebug.h"
|
||||
#include "opus/silk/macros.h"
|
||||
#include "opus/silk/main.h"
|
||||
#include "opus/silk/pitch_est_defines.h"
|
||||
#include "opus/silk/PLC.h"
|
||||
#include "opus/silk/resampler_private.h"
|
||||
#include "opus/silk/resampler_rom.h"
|
||||
#include "opus/silk/resampler_structs.h"
|
||||
#include "opus/silk/API.h"
|
||||
#include "opus/silk/SigProc_FIX.h"
|
||||
#include "opus/silk/structs.h"
|
||||
#include "opus/silk/tables.h"
|
||||
#include "opus/silk/tuning_parameters.h"
|
||||
#include "opus/silk/typedef.h"
|
||||
|
||||
#include "opus/silk/A2NLSF.c"
|
||||
#include "opus/silk/ana_filt_bank_1.c"
|
||||
#include "opus/silk/biquad_alt.c"
|
||||
#include "opus/silk/bwexpander.c"
|
||||
#include "opus/silk/bwexpander_32.c"
|
||||
#include "opus/silk/check_control_input.c"
|
||||
#include "opus/silk/CNG.c"
|
||||
#include "opus/silk/code_signs.c"
|
||||
#include "opus/silk/control_audio_bandwidth.c"
|
||||
#include "opus/silk/control_codec.c"
|
||||
#include "opus/silk/control_SNR.c"
|
||||
#include "opus/silk/debug.c"
|
||||
#include "opus/silk/decoder_set_fs.c"
|
||||
#include "opus/silk/decode_core.c"
|
||||
#include "opus/silk/decode_frame.c"
|
||||
#include "opus/silk/decode_indices.c"
|
||||
#include "opus/silk/decode_parameters.c"
|
||||
#include "opus/silk/decode_pitch.c"
|
||||
#include "opus/silk/decode_pulses.c"
|
||||
#include "opus/silk/dec_API.c"
|
||||
#include "opus/silk/encode_indices.c"
|
||||
#include "opus/silk/encode_pulses.c"
|
||||
#include "opus/silk/enc_API.c"
|
||||
#include "opus/silk/gain_quant.c"
|
||||
#include "opus/silk/HP_variable_cutoff.c"
|
||||
#include "opus/silk/init_decoder.c"
|
||||
#include "opus/silk/init_encoder.c"
|
||||
#include "opus/silk/inner_prod_aligned.c"
|
||||
#include "opus/silk/interpolate.c"
|
||||
#include "opus/silk/lin2log.c"
|
||||
#include "opus/silk/log2lin.c"
|
||||
#include "opus/silk/LPC_analysis_filter.c"
|
||||
#include "opus/silk/LPC_inv_pred_gain.c"
|
||||
#include "opus/silk/LP_variable_cutoff.c"
|
||||
#include "opus/silk/NLSF2A.c"
|
||||
#include "opus/silk/NLSF_decode.c"
|
||||
#include "opus/silk/NLSF_del_dec_quant.c"
|
||||
#include "opus/silk/NLSF_encode.c"
|
||||
#include "opus/silk/NLSF_stabilize.c"
|
||||
#include "opus/silk/NLSF_unpack.c"
|
||||
#include "opus/silk/NLSF_VQ.c"
|
||||
#include "opus/silk/NLSF_VQ_weights_laroia.c"
|
||||
#include "opus/silk/NSQ.c"
|
||||
#include "opus/silk/NSQ_del_dec.c"
|
||||
#include "opus/silk/pitch_est_tables.c"
|
||||
#include "opus/silk/PLC.c"
|
||||
#include "opus/silk/process_NLSFs.c"
|
||||
#include "opus/silk/quant_LTP_gains.c"
|
||||
#include "opus/silk/resampler.c"
|
||||
#include "opus/silk/resampler_down2.c"
|
||||
#include "opus/silk/resampler_down2_3.c"
|
||||
#include "opus/silk/resampler_private_AR2.c"
|
||||
#include "opus/silk/resampler_private_down_FIR.c"
|
||||
#include "opus/silk/resampler_private_IIR_FIR.c"
|
||||
#include "opus/silk/resampler_private_up2_HQ.c"
|
||||
#include "opus/silk/resampler_rom.c"
|
||||
#include "opus/silk/shell_coder.c"
|
||||
#include "opus/silk/sigm_Q15.c"
|
||||
#include "opus/silk/sort.c"
|
||||
#include "opus/silk/stereo_decode_pred.c"
|
||||
#include "opus/silk/stereo_encode_pred.c"
|
||||
#include "opus/silk/stereo_find_predictor.c"
|
||||
#include "opus/silk/stereo_LR_to_MS.c"
|
||||
#include "opus/silk/stereo_MS_to_LR.c"
|
||||
#include "opus/silk/stereo_quant_pred.c"
|
||||
#include "opus/silk/sum_sqr_shift.c"
|
||||
#include "opus/silk/tables_gain.c"
|
||||
#include "opus/silk/tables_LTP.c"
|
||||
#include "opus/silk/tables_NLSF_CB_NB_MB.c"
|
||||
#include "opus/silk/tables_NLSF_CB_WB.c"
|
||||
#include "opus/silk/tables_other.c"
|
||||
#include "opus/silk/tables_pitch_lag.c"
|
||||
#include "opus/silk/tables_pulses_per_block.c"
|
||||
#include "opus/silk/table_LSF_cos.c"
|
||||
#include "opus/silk/VAD.c"
|
||||
#include "opus/silk/VQ_WMat_EC.c"
|
||||
|
||||
////////////////
|
||||
// SILK Float //
|
||||
////////////////
|
||||
|
||||
#include "opus/silk/float/main_FLP.h"
|
||||
#include "opus/silk/float/SigProc_FLP.h"
|
||||
#include "opus/silk/float/structs_FLP.h"
|
||||
|
||||
#include "opus/silk/float/apply_sine_window_FLP.c"
|
||||
#include "opus/silk/float/autocorrelation_FLP.c"
|
||||
#include "opus/silk/float/burg_modified_FLP.c"
|
||||
#include "opus/silk/float/bwexpander_FLP.c"
|
||||
#include "opus/silk/float/corrMatrix_FLP.c"
|
||||
#include "opus/silk/float/encode_frame_FLP.c"
|
||||
#include "opus/silk/float/energy_FLP.c"
|
||||
#include "opus/silk/float/find_LPC_FLP.c"
|
||||
#include "opus/silk/float/find_LTP_FLP.c"
|
||||
#include "opus/silk/float/find_pitch_lags_FLP.c"
|
||||
#include "opus/silk/float/find_pred_coefs_FLP.c"
|
||||
#include "opus/silk/float/inner_product_FLP.c"
|
||||
#include "opus/silk/float/k2a_FLP.c"
|
||||
#include "opus/silk/float/levinsondurbin_FLP.c"
|
||||
#include "opus/silk/float/LPC_analysis_filter_FLP.c"
|
||||
#include "opus/silk/float/LPC_inv_pred_gain_FLP.c"
|
||||
#include "opus/silk/float/LTP_analysis_filter_FLP.c"
|
||||
#include "opus/silk/float/LTP_scale_ctrl_FLP.c"
|
||||
#include "opus/silk/float/noise_shape_analysis_FLP.c"
|
||||
#include "opus/silk/float/pitch_analysis_core_FLP.c"
|
||||
#include "opus/silk/float/prefilter_FLP.c"
|
||||
#include "opus/silk/float/process_gains_FLP.c"
|
||||
#include "opus/silk/float/regularize_correlations_FLP.c"
|
||||
#include "opus/silk/float/residual_energy_FLP.c"
|
||||
#include "opus/silk/float/scale_copy_vector_FLP.c"
|
||||
#include "opus/silk/float/scale_vector_FLP.c"
|
||||
#include "opus/silk/float/schur_FLP.c"
|
||||
#include "opus/silk/float/solve_LS_FLP.c"
|
||||
#include "opus/silk/float/sort_FLP.c"
|
||||
#include "opus/silk/float/warped_autocorrelation_FLP.c"
|
||||
#include "opus/silk/float/wrappers_FLP.c"
|
||||
|
||||
/////////////
|
||||
// LibOpus //
|
||||
/////////////
|
||||
|
||||
#include "opus/libopus/src/opus.c"
|
||||
#include "opus/libopus/src/opus_decoder.c"
|
||||
#include "opus/libopus/src/opus_encoder.c"
|
||||
#include "opus/libopus/src/opus_multistream.c"
|
||||
#include "opus/libopus/src/opus_multistream_decoder.c"
|
||||
#include "opus/libopus/src/opus_multistream_encoder.c"
|
||||
#include "opus/libopus/src/repacketizer.c"
|
||||
|
||||
#include "opus/libopus/src/analysis.c"
|
||||
#include "opus/libopus/src/mlp.c"
|
||||
#include "opus/libopus/src/mlp_data.c"
|
||||
|
||||
//////////////
|
||||
// Opusfile //
|
||||
//////////////
|
||||
|
||||
#include "opus/opusfile/src/http.c"
|
||||
#include "opus/opusfile/src/info.c"
|
||||
#include "opus/opusfile/src/internal.c"
|
||||
#include "opus/opusfile/src/opusfile.c"
|
||||
#include "opus/opusfile/src/stream.c"
|
||||
#include "opus/opusfile/src/wincerts.c"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
@ -0,0 +1,174 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "VorbisDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
//@todo: implement decoding from memory (c.f. http://stackoverflow.com/questions/13437422/libvorbis-audio-decode-from-memory-in-c)
|
||||
class VorbisDecoderInternal
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
VorbisDecoderInternal(AudioData * d, std::string filepath) : d(d)
|
||||
{
|
||||
fileHandle = new OggVorbis_File();
|
||||
|
||||
/* @todo proper steaming support + classes
|
||||
const ov_callbacks callbacks =
|
||||
{
|
||||
.read_func = s_readCallback,
|
||||
.seek_func = s_seekCallback,
|
||||
.tell_func = s_tellCallback,
|
||||
.close_func = nullptr
|
||||
};
|
||||
*/
|
||||
|
||||
FILE * f = fopen(filepath.c_str(), "rb");
|
||||
|
||||
if (!f)
|
||||
throw std::runtime_error("Can't open file");
|
||||
|
||||
if (auto r = ov_test_callbacks(f, fileHandle, nullptr, 0, OV_CALLBACKS_DEFAULT) != 0)
|
||||
{
|
||||
std::cerr << errorAsString(r) << std::endl;
|
||||
throw std::runtime_error("File is not a valid ogg vorbis file");
|
||||
}
|
||||
|
||||
if (auto r = ov_test_open(fileHandle) != 0)
|
||||
{
|
||||
std::cerr << errorAsString(r) << std::endl;
|
||||
throw std::runtime_error("ov_test_open failed");
|
||||
}
|
||||
// N.B.: Don't need to fclose() after an open -- vorbis does this internally
|
||||
|
||||
vorbis_info *ovInfo = ov_info(fileHandle, -1);
|
||||
|
||||
if (ovInfo == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Reading metadata failed");
|
||||
}
|
||||
|
||||
if (auto r = ov_streams(fileHandle) != 1)
|
||||
{
|
||||
std::cerr << errorAsString(r) << std::endl;
|
||||
throw std::runtime_error( "Unsupported: file contains multiple bitstreams");
|
||||
}
|
||||
|
||||
d->sampleRate = int(ovInfo->rate);
|
||||
d->channelCount = ovInfo->channels;
|
||||
d->bitDepth = 32; // float
|
||||
d->lengthSeconds = double(getLengthInSeconds());
|
||||
d->frameSize = ovInfo->channels * d->bitDepth;
|
||||
|
||||
// Samples in a single channel
|
||||
auto totalSamples = size_t(getTotalSamples());
|
||||
|
||||
d->samples.resize(totalSamples * d->channelCount);
|
||||
|
||||
auto r = readInternal(totalSamples);
|
||||
}
|
||||
|
||||
~VorbisDecoderInternal()
|
||||
{
|
||||
ov_clear(fileHandle);
|
||||
}
|
||||
|
||||
size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0)
|
||||
{
|
||||
|
||||
//@todo support offset
|
||||
|
||||
float **buffer = nullptr;
|
||||
size_t framesRemaining = requestedFrameCount;
|
||||
size_t totalFramesRead = 0;
|
||||
int bitstream = 0;
|
||||
|
||||
while(0 < framesRemaining)
|
||||
{
|
||||
int64_t framesRead = ov_read_float(fileHandle, &buffer, std::min(2048, (int) framesRemaining), &bitstream);
|
||||
|
||||
// EOF
|
||||
if(!framesRead)
|
||||
break;
|
||||
|
||||
if (framesRead < 0)
|
||||
{
|
||||
// Probably OV_HOLE, OV_EBADLINK, OV_EINVAL. Log warning here.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < framesRead; ++i)
|
||||
{
|
||||
for(int ch = 0; ch < d->channelCount; ch++)
|
||||
{
|
||||
d->samples[totalFramesRead] = buffer[ch][i];
|
||||
totalFramesRead++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return totalFramesRead;
|
||||
}
|
||||
|
||||
std::string errorAsString(int ovErrorCode)
|
||||
{
|
||||
switch(ovErrorCode)
|
||||
{
|
||||
case OV_FALSE: return "OV_FALSE";
|
||||
case OV_EOF: return "OV_EOF";
|
||||
case OV_HOLE: return "OV_HOLE";
|
||||
case OV_EREAD: return "OV_EREAD";
|
||||
case OV_EFAULT: return "OV_EFAULT";
|
||||
case OV_EIMPL: return "OV_EIMPL";
|
||||
case OV_EINVAL: return "OV_EINVAL";
|
||||
case OV_ENOTVORBIS: return "OV_ENOTVORBIS";
|
||||
case OV_EBADHEADER: return "OV_EBADHEADER";
|
||||
case OV_EVERSION: return "OV_EVERSION";
|
||||
case OV_ENOTAUDIO: return "OV_ENOTAUDIO";
|
||||
case OV_EBADPACKET: return "OV_EBADPACKET";
|
||||
case OV_EBADLINK: return "OV_EBADLINK";
|
||||
case OV_ENOSEEK: return "OV_ENOSEEK";
|
||||
default: return "OV_UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// vorbis callbacks //
|
||||
//////////////////////
|
||||
|
||||
//@todo: implement streaming support
|
||||
|
||||
private:
|
||||
|
||||
NO_COPY(VorbisDecoderInternal);
|
||||
|
||||
OggVorbis_File * fileHandle;
|
||||
AudioData * d;
|
||||
|
||||
inline int64_t getTotalSamples() const { return int64_t(ov_pcm_total(const_cast<OggVorbis_File *>(fileHandle), -1)); }
|
||||
inline int64_t getLengthInSeconds() const { return int64_t(ov_time_total(const_cast<OggVorbis_File *>(fileHandle), -1)); }
|
||||
inline int64_t getCurrentSample() const { return int64_t(ov_pcm_tell(const_cast<OggVorbis_File *>(fileHandle))); }
|
||||
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int VorbisDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
VorbisDecoderInternal decoder(data, path);
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
int VorbisDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
return IOError::LoadBufferNotImplemented;
|
||||
}
|
||||
|
||||
std::vector<std::string> VorbisDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {"ogg"};
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 181 111 4267 4996 4244 4701 4702 4133 4100 4127 4206 4312 4505 4365 4005 4013 4334)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wconversion"
|
||||
#pragma clang diagnostic ignored "-Wshadow"
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-register"
|
||||
#endif
|
||||
|
||||
#include "libvorbis/include/vorbis/vorbisenc.h"
|
||||
#include "libvorbis/include/vorbis/codec.h"
|
||||
#include "libvorbis/include/vorbis/vorbisfile.h"
|
||||
|
||||
#include "libogg/src/bitwise.c"
|
||||
#include "libogg/src/framing.c"
|
||||
|
||||
#include "libvorbis/src/analysis.c"
|
||||
#include "libvorbis/src/bitrate.c"
|
||||
#include "libvorbis/src/block.c"
|
||||
#include "libvorbis/src/codebook.c"
|
||||
#include "libvorbis/src/envelope.c"
|
||||
#include "libvorbis/src/floor0.c"
|
||||
#include "libvorbis/src/floor1.c"
|
||||
#include "libvorbis/src/info.c"
|
||||
#include "libvorbis/src/lpc.c"
|
||||
#include "libvorbis/src/lsp.c"
|
||||
#include "libvorbis/src/mapping0.c"
|
||||
#include "libvorbis/src/psy.c"
|
||||
#include "libvorbis/src/registry.c"
|
||||
#include "libvorbis/src/res0.c"
|
||||
#include "libvorbis/src/sharedbook.c"
|
||||
#include "libvorbis/src/smallft.c"
|
||||
#include "libvorbis/src/synthesis.c"
|
||||
#include "libvorbis/src/vorbisenc.c"
|
||||
#include "libvorbis/src/vorbisfile.c"
|
||||
#include "libvorbis/src/window.c"
|
||||
#include "libvorbis/src/mdct.c"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if (_MSC_VER)
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
@ -0,0 +1,185 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "WavDecoder.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int WavDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
auto fileBuffer = nqr::ReadFile(path);
|
||||
return LoadFromBuffer(data, fileBuffer.buffer);
|
||||
}
|
||||
|
||||
int WavDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
//////////////////////
|
||||
// Read RIFF Header //
|
||||
//////////////////////
|
||||
|
||||
//@todo swap methods for rifx
|
||||
|
||||
RiffChunkHeader riffHeader = {};
|
||||
memcpy(&riffHeader, memory.data(), 12);
|
||||
|
||||
// Files should be 2-byte aligned
|
||||
//@tofix: enforce this
|
||||
bool usePaddingShort = ((riffHeader.file_size % sizeof(uint16_t)) == 1) ? true : false;
|
||||
|
||||
// Check RIFF
|
||||
if (riffHeader.id_riff != GenerateChunkCode('R', 'I', 'F', 'F'))
|
||||
{
|
||||
// Check RIFX + FFIR
|
||||
if (riffHeader.id_riff == GenerateChunkCode('R', 'I', 'F', 'X') || riffHeader.id_riff == GenerateChunkCode('F', 'F', 'I', 'R'))
|
||||
{
|
||||
// We're not RIFF, and we don't match RIFX or FFIR either
|
||||
throw std::runtime_error("libnyquist doesn't support big endian files");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("bad RIFF/RIFX/FFIR file header");
|
||||
}
|
||||
}
|
||||
|
||||
if (riffHeader.id_wave != GenerateChunkCode('W', 'A', 'V', 'E')) throw std::runtime_error("bad WAVE header");
|
||||
|
||||
if ((memory.size() - riffHeader.file_size) != sizeof(uint32_t) * 2)
|
||||
{
|
||||
throw std::runtime_error("declared size of file less than file size"); //@todo warning instead of runtime_error
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Read WAVE Header //
|
||||
//////////////////////
|
||||
|
||||
auto WaveChunkInfo = ScanForChunk(memory, GenerateChunkCode('f', 'm', 't', ' '));
|
||||
|
||||
if (WaveChunkInfo.offset == 0) throw std::runtime_error("couldn't find fmt chunk");
|
||||
|
||||
assert(WaveChunkInfo.size == 16 || WaveChunkInfo.size == 18 || WaveChunkInfo.size == 40);
|
||||
|
||||
WaveChunkHeader wavHeader = {};
|
||||
memcpy(&wavHeader, memory.data() + WaveChunkInfo.offset, sizeof(WaveChunkHeader));
|
||||
|
||||
if (wavHeader.chunk_size < 16)
|
||||
throw std::runtime_error("format chunk too small");
|
||||
|
||||
//@todo validate wav header (sane sample rate, bit depth, etc)
|
||||
|
||||
data->channelCount = wavHeader.channel_count;
|
||||
data->sampleRate = wavHeader.sample_rate;
|
||||
data->frameSize = wavHeader.frame_size;
|
||||
|
||||
std::cout << wavHeader << std::endl;
|
||||
|
||||
bool scanForFact = false;
|
||||
bool grabExtensibleData = false;
|
||||
|
||||
if (wavHeader.format == WaveFormatCode::FORMAT_PCM)
|
||||
{
|
||||
std::cout << "[format id] pcm" << std::endl;
|
||||
}
|
||||
else if (wavHeader.format == WaveFormatCode::FORMAT_IEEE)
|
||||
{
|
||||
std::cout << "[format id] ieee" << std::endl;
|
||||
scanForFact = true;
|
||||
}
|
||||
else if (wavHeader.format == WaveFormatCode::FORMAT_EXT)
|
||||
{
|
||||
// Used when (1) PCM data has more than 16 bits; (2) channels > 2; (3) bits/sample !== container size; (4) channel/speaker mapping specified
|
||||
std::cout << "[format id] extended" << std::endl;
|
||||
scanForFact = true;
|
||||
grabExtensibleData = true;
|
||||
}
|
||||
else if (wavHeader.format == WaveFormatCode::FORMAT_UNKNOWN)
|
||||
{
|
||||
throw std::runtime_error("unknown wave format");
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Read Additional Chunks //
|
||||
////////////////////////////
|
||||
|
||||
if (scanForFact)
|
||||
{
|
||||
auto FactChunkInfo = ScanForChunk(memory, GenerateChunkCode('f', 'a', 'c', 't'));
|
||||
FactChunk factChunk = {};
|
||||
|
||||
if (FactChunkInfo.size)
|
||||
{
|
||||
memcpy(&factChunk, memory.data() + FactChunkInfo.offset, sizeof(FactChunk));
|
||||
}
|
||||
}
|
||||
|
||||
if (grabExtensibleData)
|
||||
{
|
||||
ExtensibleData extData = {};
|
||||
memcpy(&extData, memory.data() + WaveChunkInfo.offset + sizeof(WaveChunkHeader), sizeof(ExtensibleData));
|
||||
// extData can be compared against the multi-channel masks defined in the header
|
||||
// eg. extData.channel_mask == SPEAKER_5POINT1
|
||||
}
|
||||
|
||||
//@todo smpl chunk could be useful
|
||||
|
||||
/////////////////////
|
||||
// Read Bext Chunk //
|
||||
/////////////////////
|
||||
|
||||
auto BextChunkInfo = ScanForChunk(memory, GenerateChunkCode('b', 'e', 'x', 't'));
|
||||
BextChunk bextChunk = {};
|
||||
|
||||
if (BextChunkInfo.size)
|
||||
{
|
||||
memcpy(&bextChunk, memory.data() + BextChunkInfo.offset, sizeof(BextChunk));
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Read DATA Chunk //
|
||||
/////////////////////
|
||||
|
||||
auto DataChunkInfo = ScanForChunk(memory, GenerateChunkCode('d', 'a', 't', 'a'));
|
||||
|
||||
if (DataChunkInfo.offset == 0) throw std::runtime_error("couldn't find data chunk");
|
||||
|
||||
DataChunkInfo.offset += 8; // ignore the header and size fields (2 uint32s)
|
||||
|
||||
data->lengthSeconds = ((float) DataChunkInfo.size / (float) wavHeader.sample_rate) / wavHeader.frame_size;
|
||||
|
||||
auto bit_depth = wavHeader.bit_depth;
|
||||
|
||||
size_t totalSamples = (DataChunkInfo.size / wavHeader.frame_size) * wavHeader.channel_count;
|
||||
data->samples.resize(totalSamples);
|
||||
|
||||
PCMFormat internalFmt = PCMFormat::PCM_END;
|
||||
|
||||
switch (bit_depth)
|
||||
{
|
||||
case 8:
|
||||
internalFmt = PCMFormat::PCM_U8;
|
||||
break;
|
||||
case 16:
|
||||
internalFmt = PCMFormat::PCM_16;
|
||||
break;
|
||||
case 24:
|
||||
internalFmt = PCMFormat::PCM_24;
|
||||
break;
|
||||
case 32:
|
||||
internalFmt = (wavHeader.format == WaveFormatCode::FORMAT_IEEE) ? PCMFormat::PCM_FLT : PCMFormat::PCM_32;
|
||||
break;
|
||||
case 64:
|
||||
internalFmt = (wavHeader.format == WaveFormatCode::FORMAT_IEEE) ? PCMFormat::PCM_DBL : PCMFormat::PCM_64;
|
||||
break;
|
||||
}
|
||||
|
||||
ConvertToFloat32(data->samples.data(), memory.data() + DataChunkInfo.offset, totalSamples, internalFmt);
|
||||
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
std::vector<std::string> WavDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {"wav", "wave"};
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
#pragma comment(user, "license")
|
||||
|
||||
#include "WavPackDecoder.h"
|
||||
#include "wavpack.h"
|
||||
|
||||
using namespace nqr;
|
||||
|
||||
class WavPackInternal
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
WavPackInternal(AudioData * d, const std::string path) : d(d)
|
||||
{
|
||||
char errorStr[128];
|
||||
context = WavpackOpenFileInput(path.c_str(), errorStr, OPEN_WVC | OPEN_NORMALIZE, 0);
|
||||
|
||||
if (!context)
|
||||
{
|
||||
throw std::runtime_error("Not a WavPack file");
|
||||
}
|
||||
|
||||
d->sampleRate = WavpackGetSampleRate(context);
|
||||
d->channelCount = WavpackGetNumChannels(context);
|
||||
d->bitDepth = WavpackGetBitsPerSample(context);
|
||||
d->lengthSeconds = double(getLengthInSeconds());
|
||||
d->frameSize = d->channelCount * d->bitDepth;
|
||||
|
||||
//@todo support channel masks
|
||||
// WavpackGetChannelMask
|
||||
|
||||
auto totalSamples = size_t(getTotalSamples());
|
||||
|
||||
PCMFormat internalFmt = PCMFormat::PCM_END;
|
||||
|
||||
int mode = WavpackGetMode(context);
|
||||
int isFloatingPoint = (MODE_FLOAT & mode);
|
||||
|
||||
switch (d->bitDepth)
|
||||
{
|
||||
case 16:
|
||||
internalFmt = PCMFormat::PCM_16;
|
||||
break;
|
||||
case 24:
|
||||
internalFmt = PCMFormat::PCM_24;
|
||||
break;
|
||||
case 32:
|
||||
internalFmt = isFloatingPoint ? PCMFormat::PCM_FLT : PCMFormat::PCM_32;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("unsupported WavPack bit depth");
|
||||
break;
|
||||
}
|
||||
|
||||
/* From the docs:
|
||||
"... required memory at "buffer" is 4 * samples * num_channels bytes. The
|
||||
audio data is returned right-justified in 32-bit longs in the endian
|
||||
mode native to the executing processor."
|
||||
*/
|
||||
d->samples.resize(totalSamples * d->channelCount);
|
||||
|
||||
if (!isFloatingPoint)
|
||||
internalBuffer.resize(totalSamples * d->channelCount);
|
||||
|
||||
auto r = readInternal(totalSamples);
|
||||
|
||||
// Next, process internal buffer into the user-visible samples array
|
||||
if (!isFloatingPoint)
|
||||
ConvertToFloat32(d->samples.data(), internalBuffer.data(), totalSamples * d->channelCount, internalFmt);
|
||||
|
||||
}
|
||||
|
||||
~WavPackInternal()
|
||||
{
|
||||
WavpackCloseFile(context);
|
||||
}
|
||||
|
||||
size_t readInternal(size_t requestedFrameCount, size_t frameOffset = 0)
|
||||
{
|
||||
size_t framesRemaining = requestedFrameCount;
|
||||
size_t totalFramesRead = 0;
|
||||
|
||||
// int frameSize = d->channelCount * WavpackGetBitsPerSample(context);
|
||||
|
||||
// The samples returned are handled differently based on the file's mode
|
||||
int mode = WavpackGetMode(context);
|
||||
|
||||
while (0 < framesRemaining)
|
||||
{
|
||||
uint32_t framesRead = -1;
|
||||
|
||||
if (MODE_FLOAT & mode)
|
||||
{
|
||||
// Since it's float, we can decode directly into our buffer as a huge blob
|
||||
framesRead = WavpackUnpackSamples(context, reinterpret_cast<int32_t*>(&d->samples.data()[0]), uint32_t(d->samples.size() / d->channelCount));
|
||||
}
|
||||
|
||||
else if(MODE_LOSSLESS & mode)
|
||||
{
|
||||
// Lossless files will be handed off as integers
|
||||
framesRead = WavpackUnpackSamples(context, internalBuffer.data(), uint32_t(internalBuffer.size() / d->channelCount));
|
||||
}
|
||||
|
||||
// EOF
|
||||
//if (framesRead == 0)
|
||||
// break;
|
||||
|
||||
std::cout << framesRead << std::endl;
|
||||
|
||||
totalFramesRead += framesRead;
|
||||
framesRemaining -= framesRead;
|
||||
}
|
||||
|
||||
return totalFramesRead;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
NO_MOVE(WavPackInternal);
|
||||
|
||||
//WavpackStreamReader streamReader; //@todo: streaming support
|
||||
|
||||
WavpackContext * context; //@todo unique_ptr
|
||||
|
||||
AudioData * d;
|
||||
|
||||
std::vector<int32_t> internalBuffer;
|
||||
|
||||
inline int64_t getTotalSamples() const { return WavpackGetNumSamples(context); }
|
||||
inline int64_t getLengthInSeconds() const { return getTotalSamples() / WavpackGetSampleRate(context); }
|
||||
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
// Public Interface //
|
||||
//////////////////////
|
||||
|
||||
int WavPackDecoder::LoadFromPath(AudioData * data, const std::string & path)
|
||||
{
|
||||
WavPackInternal decoder(data, path);
|
||||
return IOError::NoError;
|
||||
}
|
||||
|
||||
int WavPackDecoder::LoadFromBuffer(AudioData * data, const std::vector<uint8_t> & memory)
|
||||
{
|
||||
return IOError::LoadBufferNotImplemented;
|
||||
}
|
||||
|
||||
std::vector<std::string> WavPackDecoder::GetSupportedFileExtensions()
|
||||
{
|
||||
return {"wv"};
|
||||
}
|
||||
Loading…
Reference in New Issue