- rewrite .wav audio reader/writer with standard C++ stream and filesystem's path

This commit is contained in:
Dmytro Bogovych 2025-12-05 12:45:38 +03:00
parent f90d64b279
commit ffd371d6e7
2 changed files with 148 additions and 155 deletions

View File

@ -40,7 +40,7 @@ using namespace Audio;
// ---------------------- WavFileReader ------------------------- // ---------------------- WavFileReader -------------------------
WavFileReader::WavFileReader() WavFileReader::WavFileReader()
:mHandle(nullptr), mSamplerate(0), mLastError(0), mChannels(0), mBits(0), mDataLength(0) :mSamplerate(0), mLastError(0), mChannels(0), mBits(0), mDataLength(0)
{ {
mDataOffset = 0; mDataOffset = 0;
} }
@ -53,38 +53,51 @@ WavFileReader::~WavFileReader()
std::string WavFileReader::readChunk() std::string WavFileReader::readChunk()
{ {
char name[5]; char name[5] = {0};
if (fread(name, 1, 4, mHandle) != 4) readBuffer(name, 4);
THROW_READERROR;
name[4] = 0;
std::string result = name; std::string result = name;
unsigned size; uint32_t size = 0;
if (fread(&size, 4, 1, mHandle) != 1) readBuffer(&size, 4);
THROW_READERROR;
if (result == "fact") if (result == "fact")
fread(&mDataLength, 4, 1, mHandle); {
uint32_t dataLength = 0;
readBuffer(&dataLength, sizeof dataLength);
mDataLength = dataLength;
}
else else
if (result != "data") if (result != "data")
fseek(mHandle, size, SEEK_CUR); mInput->seekg(size, std::ios_base::beg);
else else
mDataLength = size; mDataLength = size;
return result; return result;
} }
bool WavFileReader::open(const std::tstring& filename) void WavFileReader::readBuffer(void* buffer, size_t sz)
{
auto p = mInput->tellg();
mInput->read(reinterpret_cast<char*>(buffer), sz);
if (mInput->tellg() - p != sz)
throw Exception(ERR_WAVFILE_FAILED);
}
size_t WavFileReader::tryReadBuffer(void* buffer, size_t sz)
{
auto p = mInput->tellg();
mInput->read(reinterpret_cast<char*>(buffer), sz);
return mInput->tellg() - p;
}
bool WavFileReader::open(const std::filesystem::path& p)
{ {
LOCK; LOCK;
try try
{ {
#ifdef WIN32 mPath = p;
mHandle = _wfopen(filename.c_str(), L"rb"); mInput = std::make_unique<std::ifstream>(p, std::ios::binary | std::ios::in);
#else if (!mInput->is_open())
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;
@ -98,75 +111,62 @@ bool WavFileReader::open(const std::tstring& filename)
// Read the .WAV header // Read the .WAV header
char riff[4]; char riff[4];
if (fread(riff, 4, 1, mHandle) < 1) readBuffer(riff, sizeof riff);
THROW_READERROR;
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F')) if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
THROW_READERROR; THROW_READERROR;
// Read the file size // Read the file size
uint32_t filesize = 0; uint32_t filesize = 0;
if (fread(&filesize, 4, 1, mHandle) < 1) readBuffer(&filesize, sizeof(filesize));
THROW_READERROR;
char wavefmt[9]; char wavefmt[9] = {0};
if (fread(wavefmt, 8, 1, mHandle) < 1) readBuffer(wavefmt, 8);
THROW_READERROR;
wavefmt[8] = 0;
if (strcmp(wavefmt, "WAVEfmt ") != 0) if (strcmp(wavefmt, "WAVEfmt ") != 0)
THROW_READERROR; THROW_READERROR;
uint32_t fmtSize = 0; uint32_t fmtSize = 0;
if (fread(&fmtSize, 4, 1, mHandle) < 1) readBuffer(&fmtSize, sizeof(fmtSize));
THROW_READERROR;
uint32_t fmtStart = ftell(mHandle); auto fmtStart = mInput->tellg();
uint16_t formattag = 0; uint16_t formattag = 0;
if (fread(&formattag, 2, 1, mHandle) < 1) readBuffer(&formattag, sizeof(formattag));
THROW_READERROR;
if (formattag != 1/*WAVE_FORMAT_PCM*/) if (formattag != 1/*WAVE_FORMAT_PCM*/)
THROW_READERROR; THROW_READERROR;
if (fread(&mChannels, 2, 1, mHandle) < 1) mChannels = 0;
THROW_READERROR; readBuffer(&mChannels, sizeof(mChannels));
mSamplerate = 0; mSamplerate = 0;
if (fread(&mSamplerate, 4, 1, mHandle) < 1) readBuffer(&mSamplerate, sizeof(mSamplerate));
THROW_READERROR;
unsigned int avgbytespersec = 0; uint32_t avgbytespersec = 0;
if (fread(&avgbytespersec, 4, 1, mHandle) < 1) readBuffer(&avgbytespersec, sizeof(avgbytespersec));
THROW_READERROR;
unsigned short blockalign = 0; uint16_t blockalign = 0;
if (fread(&blockalign, 2, 1, mHandle) < 1) readBuffer(&blockalign, sizeof(blockalign));
THROW_READERROR;
mBits = 0; mBits = 0;
if (fread(&mBits, 2, 1, mHandle) < 1) readBuffer(&mBits, sizeof(mBits));
THROW_READERROR;
if (mBits !=8 && mBits != 16) if (mBits !=8 && mBits != 16)
THROW_READERROR; THROW_READERROR;
// Read the "chunk" // Look for the chunk 'data'
fseek(mHandle, fmtStart + fmtSize, SEEK_SET); mInput->seekg(fmtStart + std::streampos(fmtSize));
//unsigned pos = ftell(mHandle);
mDataLength = 0; mDataLength = 0;
while (readChunk() != "data") while (readChunk() != "data")
; ;
mFileName = filename; mDataOffset = mInput->tellg();
mDataOffset = ftell(mHandle);
mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE); mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE);
} }
catch(...) catch(...)
{ {
fclose(mHandle); mHandle = nullptr; mInput.reset();
mLastError = static_cast<unsigned>(-1); mLastError = static_cast<unsigned>(-1);
} }
return isOpened(); return isOpened();
@ -175,10 +175,7 @@ bool WavFileReader::open(const std::tstring& filename)
void WavFileReader::close() void WavFileReader::close()
{ {
LOCK; LOCK;
mInput.reset();
if (nullptr != mHandle)
fclose(mHandle);
mHandle = nullptr;
} }
int WavFileReader::samplerate() const int WavFileReader::samplerate() const
@ -205,7 +202,7 @@ size_t WavFileReader::read(short* buffer, size_t samples)
{ {
LOCK; LOCK;
if (!mHandle) if (!mInput)
return 0; return 0;
// Get number of samples that must be read from source file // Get number of samples that must be read from source file
@ -216,14 +213,14 @@ size_t WavFileReader::read(short* buffer, size_t samples)
// Find required size of input buffer // Find required size of input buffer
if (mDataLength) if (mDataLength)
{ {
unsigned filePosition = ftell(mHandle); auto filePosition = mInput->tellg();
// Check how much data we can read // Check how much data we can read
size_t fileAvailable = mDataLength + mDataOffset - filePosition; size_t fileAvailable = mDataLength + mDataOffset - filePosition;
requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes; requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes;
} }
size_t readBytes = fread(temp, 1, requiredBytes, mHandle); size_t readBytes = tryReadBuffer(temp, requiredBytes);
size_t processedBytes = 0; size_t processedBytes = 0;
size_t result = mResampler.processBuffer(temp, readBytes, processedBytes, size_t result = mResampler.processBuffer(temp, readBytes, processedBytes,
@ -237,54 +234,50 @@ size_t WavFileReader::readRaw(short* buffer, size_t samples)
{ {
LOCK; LOCK;
if (!mHandle) if (!mInput)
return 0; return 0;
// Get number of samples that must be read from source file // Get number of samples that must be read from source file
int requiredBytes = samples * channels() * sizeof(short); size_t requiredBytes = samples * channels() * sizeof(short);
// Find required size of input buffer // Find required size of input buffer
if (mDataLength) if (mDataLength)
{ {
unsigned filePosition = ftell(mHandle); auto filePosition = mInput->tellg();
// Check how much data we can read // Check how much data we can read
unsigned fileAvailable = mDataLength + mDataOffset - filePosition; size_t fileAvailable = mDataLength + mDataOffset - filePosition;
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes; requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
} }
size_t readBytes = fread(buffer, 1, requiredBytes, mHandle); size_t readBytes = tryReadBuffer(buffer, requiredBytes);
return readBytes / channels() / sizeof(short); return readBytes / channels() / sizeof(short);
} }
bool WavFileReader::isOpened() bool WavFileReader::isOpened()
{ {
LOCK; LOCK;
if (!mInput)
return (mHandle != 0); return false;
return mInput->is_open();
} }
void WavFileReader::rewind() void WavFileReader::rewind()
{ {
LOCK; LOCK;
if (mInput)
if (mHandle) mInput->seekg(mDataOffset);
fseek(mHandle, mDataOffset, SEEK_SET);
} }
std::tstring WavFileReader::filename() const std::filesystem::path WavFileReader::path() const
{ {
LOCK; LOCK;
return mPath;
return mFileName;
} }
size_t WavFileReader::size() const size_t WavFileReader::size() const
{ {
LOCK; LOCK;
return mDataLength; return mDataLength;
} }
@ -298,9 +291,8 @@ 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) :mLengthOffset(0), mSamplerate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
{ {}
}
WavFileWriter::~WavFileWriter() WavFileWriter::~WavFileWriter()
{ {
@ -313,70 +305,74 @@ void WavFileWriter::checkWriteResult(int result)
throw Exception(ERR_WAVFILE_FAILED, errno); throw Exception(ERR_WAVFILE_FAILED, errno);
} }
bool WavFileWriter::open(const std::tstring& filename, int rate, int channels) void WavFileWriter::writeBuffer(const void* buffer, size_t sz)
{
if (!mOutput)
return;
auto p = mOutput->tellp();
mOutput->write(reinterpret_cast<const char*>(buffer), sz);
if (mOutput->tellp() - p != sz)
throw Exception(ERR_WAVFILE_FAILED);
}
bool WavFileWriter::open(const std::filesystem::path& p, int samplerate, int channels)
{ {
LOCK; LOCK;
close(); close();
mSamplerate = samplerate;
mRate = rate;
mChannels = channels; mChannels = channels;
#ifdef WIN32 mOutput = std::make_unique<std::ofstream>(p, std::ios::binary | std::ios::trunc);
mHandle = _wfopen(filename.c_str(), L"wb"); if (!mOutput)
#else
auto filename_utf8 = strx::makeUtf8(filename);
mHandle = fopen(filename_utf8.c_str(), "wb");
#endif
if (nullptr == mHandle)
{ {
int errorcode = errno; int errorcode = errno;
ICELogError(<< "Failed to create .wav file: filename = " << strx::makeUtf8(filename) << " , error = " << errorcode); ICELogError(<< "Failed to create .wav file: filename = " << p << " , error = " << errorcode);
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) ); writeBuffer(riff, 4);
// Write the file size // Write the file size
unsigned int filesize = 0; uint32_t filesize = 0;
checkWriteResult( fwrite(&filesize, 4, 1, mHandle) ); writeBuffer(&filesize, sizeof filesize);
const char* wavefmt = "WAVEfmt "; const char* wavefmt = "WAVEfmt ";
checkWriteResult( fwrite(wavefmt, 8, 1, mHandle) ); writeBuffer(wavefmt, 8);
// Set the format description // Set the format description
DWORD dwFmtSize = 16; /*= 16L*/; uint32_t dwFmtSize = 16; /*= 16L*/;
checkWriteResult( fwrite(&dwFmtSize, sizeof(dwFmtSize), 1, mHandle) ); writeBuffer(&dwFmtSize, sizeof(dwFmtSize));
WaveFormatEx format; WaveFormatEx format;
format.wFormatTag = WAVE_FORMAT_PCM; format.wFormatTag = WAVE_FORMAT_PCM;
checkWriteResult( fwrite(&format.wFormatTag, sizeof(format.wFormatTag), 1, mHandle) ); writeBuffer(&format.wFormatTag, sizeof(format.wFormatTag));
format.nChannels = mChannels; format.nChannels = mChannels;
checkWriteResult( fwrite(&format.nChannels, sizeof(format.nChannels), 1, mHandle) ); writeBuffer(&format.nChannels, sizeof(format.nChannels));
format.nSamplesPerSec = mRate; format.nSamplesPerSec = mSamplerate;
checkWriteResult( fwrite(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec), 1, mHandle) ); writeBuffer(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec));
format.nAvgBytesPerSec = mRate * 2 * mChannels; format.nAvgBytesPerSec = mSamplerate * 2 * mChannels;
checkWriteResult( fwrite(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec), 1, mHandle) ); writeBuffer(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec));
format.nBlockAlign = 2 * mChannels; format.nBlockAlign = 2 * mChannels;
checkWriteResult( fwrite(&format.nBlockAlign, sizeof(format.nBlockAlign), 1, mHandle) ); writeBuffer(&format.nBlockAlign, sizeof(format.nBlockAlign));
format.wBitsPerSample = BITS_PER_CHANNEL; format.wBitsPerSample = BITS_PER_CHANNEL;
checkWriteResult( fwrite(&format.wBitsPerSample, sizeof(format.wBitsPerSample), 1, mHandle) ); writeBuffer(&format.wBitsPerSample, sizeof(format.wBitsPerSample));
const char* data = "data"; const char* data = "data";
checkWriteResult( fwrite(data, 4, 1, mHandle)); writeBuffer(data, 4);
mFileName = filename; mPath = p;
mWritten = 0; mWritten = 0;
mLengthOffset = ftell(mHandle); mLengthOffset = mOutput->tellp();
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) ); writeBuffer(&mWritten, sizeof mWritten);
return isOpened(); return isOpened();
} }
@ -384,51 +380,44 @@ bool WavFileWriter::open(const std::tstring& filename, int rate, int channels)
void WavFileWriter::close() void WavFileWriter::close()
{ {
LOCK; LOCK;
mOutput.reset();
if (mHandle)
{
fclose(mHandle);
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 (!mOutput)
return 0; return 0;
// Seek the end of file // Seek the end of file - here new data will be written
fseek(mHandle, 0, SEEK_END); mOutput->seekp(0, std::ios_base::end);
mWritten += bytes; mWritten += bytes;
// Write the data // Write the data
fwrite(buffer, bytes, 1, mHandle); writeBuffer(buffer, bytes);
// Write file length // Write file length
fseek(mHandle, 4, SEEK_SET); mOutput->seekp(4, std::ios_base::beg);
int32_t fl = mWritten + 36; uint32_t fl = mWritten + 36;
fwrite(&fl, sizeof(fl), 1, mHandle); writeBuffer(&fl, sizeof(fl));
// Write data length // Write data length
fseek(mHandle, static_cast<long>(mLengthOffset), SEEK_SET); mOutput->seekp(mLengthOffset, std::ios_base::beg);
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) ); writeBuffer(&mWritten, sizeof(mWritten));
return bytes; return bytes;
} }
bool WavFileWriter::isOpened() bool WavFileWriter::isOpened() const
{ {
LOCK; LOCK;
return mOutput.get();
return (mHandle != nullptr);
} }
std::tstring WavFileWriter::filename() std::filesystem::path WavFileWriter::path() const
{ {
LOCK; LOCK;
return mPath;
return mFileName;
} }

View File

@ -12,6 +12,8 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <filesystem>
#include <fstream>
namespace Audio namespace Audio
@ -20,24 +22,26 @@ namespace Audio
class WavFileReader class WavFileReader
{ {
protected: protected:
FILE* mHandle; uint16_t mChannels = 0;
uint16_t mChannels; uint16_t mBits = 0;
uint16_t mBits; int mSamplerate = 0;
int mSamplerate; std::filesystem::path mPath;
std::tstring mFileName; mutable std::recursive_mutex mFileMtx;
mutable std::recursive_mutex size_t mDataOffset = 0;
mFileMtx; size_t mDataLength = 0;
size_t mDataOffset; Resampler mResampler;
size_t mDataLength; unsigned mLastError = 0;
Resampler mResampler; std::unique_ptr<std::ifstream> mInput;
unsigned mLastError;
std::string readChunk();
void readBuffer(void* buffer, size_t sz); // This raises an exception if sz bytes are not read
size_t tryReadBuffer(void* buffer, size_t sz); // This doesn't raise an exception
std::string readChunk();
public: public:
WavFileReader(); WavFileReader();
~WavFileReader(); ~WavFileReader();
bool open(const std::tstring& filename); bool open(const std::filesystem::path& p);
void close(); void close();
bool isOpened(); bool isOpened();
void rewind(); void rewind();
@ -52,7 +56,7 @@ public:
size_t read(short* buffer, size_t samples); size_t read(short* buffer, size_t samples);
size_t readRaw(short* buffer, size_t samples); size_t readRaw(short* buffer, size_t samples);
std::tstring filename() const; std::filesystem::path path() const;
size_t size() const; size_t size() const;
unsigned lastError() const; unsigned lastError() const;
@ -63,25 +67,25 @@ typedef std::shared_ptr<WavFileReader> PWavFileReader;
class WavFileWriter class WavFileWriter
{ {
protected: protected:
FILE* mHandle; /// Handle of audio file. std::unique_ptr<std::ofstream> mOutput; /// Handle of audio file.
std::tstring mFileName; /// Path to requested audio file. std::filesystem::path mPath; /// Path to requested audio file.
std::recursive_mutex mFileMtx; /// Mutex to protect this instance. mutable std::recursive_mutex mFileMtx; /// Mutex to protect this instance.
size_t mWritten; /// Amount of written data (in bytes) size_t mWritten = 0; /// Amount of written data (in bytes)
size_t mLengthOffset; /// Position of length field. size_t mLengthOffset = 0; /// Position of length field.
int mRate, int mSamplerate = 0,
mChannels; mChannels = 0;
void checkWriteResult(int result); void checkWriteResult(int result);
void writeBuffer(const void* buffer, size_t sz);
public: public:
WavFileWriter(); WavFileWriter();
~WavFileWriter(); ~WavFileWriter();
bool open(const std::tstring& filename, int rate, int channels); bool open(const std::filesystem::path& p, int samplerate, int channels);
void close(); void close();
bool isOpened(); bool isOpened() const;
size_t write(const void* buffer, size_t bytes); size_t write(const void* buffer, size_t bytes);
std::tstring filename(); std::filesystem::path path() const;
}; };
typedef std::shared_ptr<WavFileWriter> PWavFileWriter; typedef std::shared_ptr<WavFileWriter> PWavFileWriter;