- allow WavFileReader to read audio without resampling

This commit is contained in:
Dmytro Bogovych 2023-05-31 22:55:36 +03:00
parent 21fb865d80
commit 2aba79353a
2 changed files with 263 additions and 247 deletions

View File

@ -19,13 +19,13 @@
#endif #endif
typedef struct { typedef struct {
WORD wFormatTag; WORD wFormatTag;
WORD nChannels; WORD nChannels;
DWORD nSamplesPerSec; DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec; DWORD nAvgBytesPerSec;
WORD nBlockAlign; WORD nBlockAlign;
WORD wBitsPerSample; WORD wBitsPerSample;
WORD cbSize; WORD cbSize;
} }
WaveFormatEx; WaveFormatEx;
@ -39,9 +39,9 @@ using namespace Audio;
// ---------------------- WavFileReader ------------------------- // ---------------------- WavFileReader -------------------------
WavFileReader::WavFileReader() WavFileReader::WavFileReader()
:mHandle(nullptr), mRate(0), mLastError(0) :mHandle(nullptr), mSamplerate(0), mLastError(0)
{ {
mDataOffset = 0; mDataOffset = 0;
} }
WavFileReader::~WavFileReader() WavFileReader::~WavFileReader()
@ -52,207 +52,221 @@ WavFileReader::~WavFileReader()
std::string WavFileReader::readChunk() std::string WavFileReader::readChunk()
{ {
char name[5]; char name[5];
if (fread(name, 1, 4, mHandle) != 4) if (fread(name, 1, 4, mHandle) != 4)
THROW_READERROR; THROW_READERROR;
name[4] = 0; name[4] = 0;
std::string result = name; std::string result = name;
unsigned size; unsigned size;
if (fread(&size, 4, 1, mHandle) != 1) if (fread(&size, 4, 1, mHandle) != 1)
THROW_READERROR; THROW_READERROR;
if (result == "fact") if (result == "fact")
fread(&mDataLength, 4, 1, mHandle); fread(&mDataLength, 4, 1, mHandle);
else else
if (result != "data") if (result != "data")
fseek(mHandle, size, SEEK_CUR); fseek(mHandle, size, SEEK_CUR);
else else
mDataLength = size; mDataLength = size;
return result; return result;
} }
bool WavFileReader::open(const std::tstring& filename) bool WavFileReader::open(const std::tstring& filename)
{ {
LOCK; LOCK;
try try
{
#ifdef WIN32
mHandle = _wfopen(filename.c_str(), L"rb");
#else
mHandle = fopen(strx::makeUtf8(filename).c_str(), "rb");
#endif
if (NULL == mHandle)
{ {
#ifdef WIN32
mHandle = _wfopen(filename.c_str(), L"rb");
#else
mHandle = fopen(strx::makeUtf8(filename).c_str(), "rb");
#endif
if (NULL == mHandle)
{
#if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX) #if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX)
mLastError = errno; mLastError = errno;
#endif #endif
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
mLastError = GetLastError(); mLastError = GetLastError();
#endif #endif
return false; return false;
}
mLastError = 0;
// Read the .WAV header
char riff[4];
if (fread(riff, 4, 1, mHandle) < 1)
THROW_READERROR;
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
THROW_READERROR;
// Read the file size
unsigned int filesize = 0;
if (fread(&filesize, 4, 1, mHandle) < 1)
THROW_READERROR;
char wavefmt[9];
if (fread(wavefmt, 8, 1, mHandle) < 1)
THROW_READERROR;
wavefmt[8] = 0;
if (strcmp(wavefmt, "WAVEfmt ") != 0)
THROW_READERROR;
unsigned fmtSize = 0;
if (fread(&fmtSize, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned fmtStart = ftell(mHandle);
unsigned short formattag = 0;
if (fread(&formattag, 2, 1, mHandle) < 1)
THROW_READERROR;
if (formattag != 1/*WAVE_FORMAT_PCM*/)
THROW_READERROR;
mChannels = 0;
if (fread(&mChannels, 2, 1, mHandle) < 1)
THROW_READERROR;
mSamplerate = 0;
if (fread(&mSamplerate, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned int avgbytespersec = 0;
if (fread(&avgbytespersec, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned short blockalign = 0;
if (fread(&blockalign, 2, 1, mHandle) < 1)
THROW_READERROR;
mBits = 0;
if (fread(&mBits, 2, 1, mHandle) < 1)
THROW_READERROR;
if (mBits !=8 && mBits != 16)
THROW_READERROR;
// Read the "chunk"
fseek(mHandle, fmtStart + fmtSize, SEEK_SET);
//unsigned pos = ftell(mHandle);
mDataLength = 0;
while (readChunk() != "data")
;
mFileName = filename;
mDataOffset = ftell(mHandle);
mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE);
} }
mLastError = 0; catch(...)
{
// Read the .WAV header fclose(mHandle); mHandle = nullptr;
char riff[4]; mLastError = static_cast<unsigned>(-1);
if (fread(riff, 4, 1, mHandle) < 1) }
THROW_READERROR; return isOpened();
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
THROW_READERROR;
// Read the file size
unsigned int filesize = 0;
if (fread(&filesize, 4, 1, mHandle) < 1)
THROW_READERROR;
char wavefmt[9];
if (fread(wavefmt, 8, 1, mHandle) < 1)
THROW_READERROR;
wavefmt[8] = 0;
if (strcmp(wavefmt, "WAVEfmt ") != 0)
THROW_READERROR;
unsigned fmtSize = 0;
if (fread(&fmtSize, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned fmtStart = ftell(mHandle);
unsigned short formattag = 0;
if (fread(&formattag, 2, 1, mHandle) < 1)
THROW_READERROR;
if (formattag != 1/*WAVE_FORMAT_PCM*/)
THROW_READERROR;
mChannels = 0;
if (fread(&mChannels, 2, 1, mHandle) < 1)
THROW_READERROR;
mRate = 0;
if (fread(&mRate, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned int avgbytespersec = 0;
if (fread(&avgbytespersec, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned short blockalign = 0;
if (fread(&blockalign, 2, 1, mHandle) < 1)
THROW_READERROR;
mBits = 0;
if (fread(&mBits, 2, 1, mHandle) < 1)
THROW_READERROR;
if (mBits !=8 && mBits != 16)
THROW_READERROR;
// Read the "chunk"
fseek(mHandle, fmtStart + fmtSize, SEEK_SET);
//unsigned pos = ftell(mHandle);
mDataLength = 0;
while (readChunk() != "data")
;
mFileName = filename;
mDataOffset = ftell(mHandle);
mResampler.start(AUDIO_CHANNELS, mRate, AUDIO_SAMPLERATE);
}
catch(...)
{
fclose(mHandle); mHandle = nullptr;
mLastError = static_cast<unsigned>(-1);
}
return isOpened();
} }
void WavFileReader::close() void WavFileReader::close()
{ {
LOCK; LOCK;
if (nullptr != mHandle) if (nullptr != mHandle)
fclose(mHandle); fclose(mHandle);
mHandle = nullptr; mHandle = nullptr;
} }
int WavFileReader::rate() const int WavFileReader::samplerate() const
{ {
return mRate; return mSamplerate;
} }
unsigned WavFileReader::read(void* buffer, unsigned bytes) int WavFileReader::channels() const
{ {
return read((short*)buffer, bytes / (AUDIO_CHANNELS * 2)) * AUDIO_CHANNELS * 2; return mChannels;
} }
unsigned WavFileReader::read(short* buffer, unsigned samples) unsigned WavFileReader::read(void* buffer, unsigned bytes, bool resample)
{ {
LOCK; if (resample)
return read((short*)buffer, bytes / (AUDIO_CHANNELS * 2), true) * AUDIO_CHANNELS * 2;
else
return read((short*)buffer, bytes / channels() / sizeof(short), false) * channels() * sizeof(short);
}
if (!mHandle) unsigned WavFileReader::read(short* buffer, unsigned samples, bool resample)
return 0; {
LOCK;
// Get number of samples that must be read from source file if (!mHandle)
int requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8; return 0;
void* temp = alloca(requiredBytes);
memset(temp, 0, requiredBytes);
// Find required size of input buffer
if (mDataLength)
{
unsigned filePosition = ftell(mHandle);
// Check how much data we can read // Get number of samples that must be read from source file
unsigned fileAvailable = mDataLength + mDataOffset - filePosition; int requiredBytes = resample ? mResampler.getSourceLength(samples) * mChannels * mBits / 8 : samples * mChannels * sizeof(short);
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes; void* temp = alloca(requiredBytes);
} memset(temp, 0, requiredBytes);
/*int readSamples = */fread(temp, 1, requiredBytes, mHandle);// / mChannels / (mBits / 8); // Find required size of input buffer
size_t processedBytes = 0; if (mDataLength)
size_t result = mResampler.processBuffer(temp, requiredBytes, processedBytes, {
buffer, samples * 2 * AUDIO_CHANNELS); unsigned filePosition = ftell(mHandle);
return result / 2 / AUDIO_CHANNELS; // Check how much data we can read
unsigned fileAvailable = mDataLength + mDataOffset - filePosition;
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
}
/*int readSamples = */fread(temp, 1, requiredBytes, mHandle);// / mChannels / (mBits / 8);
if (resample)
{
size_t processedBytes = 0;
size_t result = mResampler.processBuffer(temp, requiredBytes, processedBytes,
buffer, samples * 2 * AUDIO_CHANNELS);
return result / 2 / AUDIO_CHANNELS;
}
else
return requiredBytes / channels() / sizeof(short);
} }
bool WavFileReader::isOpened() bool WavFileReader::isOpened()
{ {
LOCK; LOCK;
return (mHandle != 0); return (mHandle != 0);
} }
void WavFileReader::rewind() void WavFileReader::rewind()
{ {
LOCK; LOCK;
if (mHandle) if (mHandle)
fseek(mHandle, mDataOffset, SEEK_SET); fseek(mHandle, mDataOffset, SEEK_SET);
} }
std::tstring WavFileReader::filename() const std::tstring WavFileReader::filename() const
{ {
LOCK; LOCK;
return mFileName; return mFileName;
} }
unsigned WavFileReader::size() const unsigned WavFileReader::size() const
{ {
LOCK; LOCK;
return mDataLength; return mDataLength;
} }
unsigned WavFileReader::lastError() const unsigned WavFileReader::lastError() const
{ {
return mLastError; return mLastError;
} }
// ------------------------- WavFileWriter ------------------------- // ------------------------- WavFileWriter -------------------------
#define LOG_SUBSYTEM "WavFileWriter" #define LOG_SUBSYTEM "WavFileWriter"
@ -260,135 +274,135 @@ unsigned WavFileReader::lastError() const
#define BITS_PER_CHANNEL 16 #define BITS_PER_CHANNEL 16
WavFileWriter::WavFileWriter() WavFileWriter::WavFileWriter()
:mHandle(nullptr), mLengthOffset(0), mRate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0) :mHandle(nullptr), mLengthOffset(0), mRate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
{ {
} }
WavFileWriter::~WavFileWriter() WavFileWriter::~WavFileWriter()
{ {
close(); close();
} }
void WavFileWriter::checkWriteResult(int result) void WavFileWriter::checkWriteResult(int result)
{ {
if (result < 1) if (result < 1)
throw Exception(ERR_WAVFILE_FAILED, errno); throw Exception(ERR_WAVFILE_FAILED, errno);
} }
bool WavFileWriter::open(const std::tstring& filename, int rate, int channels) bool WavFileWriter::open(const std::tstring& filename, int rate, int channels)
{ {
LOCK; LOCK;
close(); close();
mRate = rate; mRate = rate;
mChannels = channels; mChannels = channels;
#ifdef WIN32 #ifdef WIN32
mHandle = _wfopen(filename.c_str(), L"wb"); mHandle = _wfopen(filename.c_str(), L"wb");
#else #else
mHandle = fopen(strx::makeUtf8(filename).c_str(), "wb"); mHandle = fopen(strx::makeUtf8(filename).c_str(), "wb");
#endif #endif
if (nullptr == mHandle) if (nullptr == mHandle)
{ {
ICELogError(<< "Failed to create .wav file: filename = " << strx::makeUtf8(filename) << " , error = " << errno); ICELogError(<< "Failed to create .wav file: filename = " << strx::makeUtf8(filename) << " , error = " << errno);
return false; return false;
} }
// Write the .WAV header // Write the .WAV header
const char* riff = "RIFF"; const char* riff = "RIFF";
checkWriteResult( fwrite(riff, 4, 1, mHandle) ); checkWriteResult( fwrite(riff, 4, 1, mHandle) );
// Write the file size
unsigned int filesize = 0;
checkWriteResult( fwrite(&filesize, 4, 1, mHandle) );
const char* wavefmt = "WAVEfmt "; // Write the file size
checkWriteResult( fwrite(wavefmt, 8, 1, mHandle) ); unsigned int filesize = 0;
checkWriteResult( fwrite(&filesize, 4, 1, mHandle) );
// Set the format description const char* wavefmt = "WAVEfmt ";
DWORD dwFmtSize = 16; /*= 16L*/; checkWriteResult( fwrite(wavefmt, 8, 1, mHandle) );
checkWriteResult( fwrite(&dwFmtSize, sizeof(dwFmtSize), 1, mHandle) );
WaveFormatEx format; // Set the format description
format.wFormatTag = WAVE_FORMAT_PCM; DWORD dwFmtSize = 16; /*= 16L*/;
checkWriteResult( fwrite(&format.wFormatTag, sizeof(format.wFormatTag), 1, mHandle) ); checkWriteResult( fwrite(&dwFmtSize, sizeof(dwFmtSize), 1, mHandle) );
format.nChannels = mChannels;
checkWriteResult( fwrite(&format.nChannels, sizeof(format.nChannels), 1, mHandle) );
format.nSamplesPerSec = mRate; WaveFormatEx format;
checkWriteResult( fwrite(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec), 1, mHandle) ); format.wFormatTag = WAVE_FORMAT_PCM;
checkWriteResult( fwrite(&format.wFormatTag, sizeof(format.wFormatTag), 1, mHandle) );
format.nAvgBytesPerSec = mRate * 2 * mChannels;
checkWriteResult( fwrite(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec), 1, mHandle) );
format.nBlockAlign = 2 * mChannels; format.nChannels = mChannels;
checkWriteResult( fwrite(&format.nBlockAlign, sizeof(format.nBlockAlign), 1, mHandle) ); checkWriteResult( fwrite(&format.nChannels, sizeof(format.nChannels), 1, mHandle) );
format.wBitsPerSample = BITS_PER_CHANNEL;
checkWriteResult( fwrite(&format.wBitsPerSample, sizeof(format.wBitsPerSample), 1, mHandle) );
const char* data = "data";
checkWriteResult( fwrite(data, 4, 1, mHandle));
mFileName = filename; format.nSamplesPerSec = mRate;
mWritten = 0; checkWriteResult( fwrite(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec), 1, mHandle) );
mLengthOffset = ftell(mHandle); format.nAvgBytesPerSec = mRate * 2 * mChannels;
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) ); checkWriteResult( fwrite(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec), 1, mHandle) );
return isOpened(); format.nBlockAlign = 2 * mChannels;
checkWriteResult( fwrite(&format.nBlockAlign, sizeof(format.nBlockAlign), 1, mHandle) );
format.wBitsPerSample = BITS_PER_CHANNEL;
checkWriteResult( fwrite(&format.wBitsPerSample, sizeof(format.wBitsPerSample), 1, mHandle) );
const char* data = "data";
checkWriteResult( fwrite(data, 4, 1, mHandle));
mFileName = filename;
mWritten = 0;
mLengthOffset = ftell(mHandle);
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
return isOpened();
} }
void WavFileWriter::close() void WavFileWriter::close()
{ {
LOCK; LOCK;
if (mHandle) if (mHandle)
{ {
fclose(mHandle); fclose(mHandle);
mHandle = nullptr; mHandle = nullptr;
} }
} }
size_t WavFileWriter::write(const void* buffer, size_t bytes) size_t WavFileWriter::write(const void* buffer, size_t bytes)
{ {
LOCK; LOCK;
if (!mHandle) if (!mHandle)
return 0; return 0;
// Seek the end of file // Seek the end of file
fseek(mHandle, 0, SEEK_END); fseek(mHandle, 0, SEEK_END);
mWritten += bytes; mWritten += bytes;
// Write the data // Write the data
fwrite(buffer, bytes, 1, mHandle); fwrite(buffer, bytes, 1, mHandle);
// Write file length // Write file length
fseek(mHandle, 4, SEEK_SET); fseek(mHandle, 4, SEEK_SET);
int32_t fl = mWritten + 36; int32_t fl = mWritten + 36;
fwrite(&fl, sizeof(fl), 1, mHandle); fwrite(&fl, sizeof(fl), 1, mHandle);
// Write data length
fseek(mHandle, static_cast<long>(mLengthOffset), SEEK_SET);
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
return bytes; // Write data length
fseek(mHandle, static_cast<long>(mLengthOffset), SEEK_SET);
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
return bytes;
} }
bool WavFileWriter::isOpened() bool WavFileWriter::isOpened()
{ {
LOCK; LOCK;
return (mHandle != nullptr); return (mHandle != nullptr);
} }
std::tstring WavFileWriter::filename() std::tstring WavFileWriter::filename()
{ {
LOCK; LOCK;
return mFileName; return mFileName;
} }

View File

@ -20,16 +20,17 @@ namespace Audio
class WavFileReader class WavFileReader
{ {
protected: protected:
FILE* mHandle; FILE* mHandle;
short mChannels; short mChannels;
short mBits; short mBits;
int mRate; int mSamplerate;
std::tstring mFileName; std::tstring mFileName;
mutable std::recursive_mutex mFileMtx; mutable std::recursive_mutex
unsigned mDataOffset; mFileMtx;
unsigned mDataLength; unsigned mDataOffset;
Resampler mResampler; unsigned mDataLength;
unsigned mLastError; Resampler mResampler;
unsigned mLastError;
std::string readChunk(); std::string readChunk();
public: public:
@ -40,13 +41,14 @@ namespace Audio
void close(); void close();
bool isOpened(); bool isOpened();
void rewind(); void rewind();
int rate() const; int samplerate() const;
int channels() const;
// This method returns number of read bytes // This method returns number of read bytes
unsigned read(void* buffer, unsigned bytes); unsigned read(void* buffer, unsigned bytes, bool resample = true);
// This method returns number of read samples // This method returns number of read samples
unsigned read(short* buffer, unsigned samples); unsigned read(short* buffer, unsigned samples, bool resample = true);
std::tstring filename() const; std::tstring filename() const;
unsigned size() const; unsigned size() const;