Files
ESP8266Audio/src/AudioGeneratorWAV.cpp
2021-09-27 11:00:34 +02:00

317 lines
8.9 KiB
C++

/*
AudioGeneratorWAV
Audio output generator that reads 8 and 16-bit WAV files
Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorWAV.h"
AudioGeneratorWAV::AudioGeneratorWAV()
{
running = false;
file = NULL;
output = NULL;
buffSize = 128;
buff = NULL;
buffPtr = 0;
buffLen = 0;
}
AudioGeneratorWAV::~AudioGeneratorWAV()
{
free(buff);
buff = NULL;
}
bool AudioGeneratorWAV::stop()
{
if (!running) return true;
running = false;
free(buff);
buff = NULL;
output->stop();
return file->close();
}
bool AudioGeneratorWAV::isRunning()
{
return running;
}
// Handle buffered reading, reload each time we run out of data
bool AudioGeneratorWAV::GetBufferedData(int bytes, void *dest)
{
if (!running) return false; // Nothing to do here!
uint8_t *p = reinterpret_cast<uint8_t*>(dest);
while (bytes--) {
// Potentially load next batch of data...
if (buffPtr >= buffLen) {
buffPtr = 0;
uint32_t toRead = availBytes > buffSize ? buffSize : availBytes;
buffLen = file->read( buff, toRead );
availBytes -= buffLen;
}
if (buffPtr >= buffLen)
return false; // No data left!
*(p++) = buff[buffPtr++];
}
return true;
}
bool AudioGeneratorWAV::loop()
{
if (!running) goto done; // Nothing to do here!
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
// Try and stuff the buffer one sample at a time
do
{
if (bitsPerSample == 8) {
uint8_t l, r;
if (!GetBufferedData(1, &l)) stop();
if (channels == 2) {
if (!GetBufferedData(1, &r)) stop();
} else {
r = 0;
}
lastSample[AudioOutput::LEFTCHANNEL] = l;
lastSample[AudioOutput::RIGHTCHANNEL] = r;
} else if (bitsPerSample == 16) {
if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) stop();
if (channels == 2) {
if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) stop();
} else {
lastSample[AudioOutput::RIGHTCHANNEL] = 0;
}
}
} while (running && output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
return running;
}
bool AudioGeneratorWAV::ReadWAVInfo()
{
uint32_t u32;
uint16_t u16;
int toSkip;
// WAV specification document:
// https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
// Header == "RIFF"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 != 0x46464952) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: %08X \n"), (uint32_t) u32);
return false;
}
// Skip ChunkSize
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
// Format == "WAVE"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 != 0x45564157) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid WAVE header, got: %08X \n"), (uint32_t) u32);
return false;
}
// there might be JUNK or PAD - ignore it by continuing reading until we get to "fmt "
while (1) {
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 0x20746d66) break; // 'fmt '
};
// subchunk size
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 16) { toSkip = 0; }
else if (u32 == 18) { toSkip = 18 - 16; }
else if (u32 == 40) { toSkip = 40 - 16; }
else {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, appears not to be standard PCM \n"));
return false;
} // we only do standard PCM
// AudioFormat
if (!ReadU16(&u16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u16 != 1) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, AudioFormat appears not to be standard PCM \n"));
return false;
} // we only do standard PCM
// NumChannels
if (!ReadU16(&channels)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if ((channels<1) || (channels>2)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only mono and stereo are supported \n"));
return false;
} // Mono or stereo support only
// SampleRate
if (!ReadU32(&sampleRate)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (sampleRate < 1) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, unknown sample rate \n"));
return false;
} // Weird rate, punt. Will need to check w/DAC to see if supported
// Ignore byterate and blockalign
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (!ReadU16(&u16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
// Bits per sample
if (!ReadU16(&bitsPerSample)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if ((bitsPerSample!=8) && (bitsPerSample != 16)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only 8 or 16 bits is supported \n"));
return false;
} // Only 8 or 16 bits
// Skip any extra header
while (toSkip) {
uint8_t ign;
if (!ReadU8(&ign)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
toSkip--;
}
// look for data subchunk
do {
// id == "data"
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if (u32 == 0x61746164) break; // "data"
// Skip size, read until end of chunk
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
if(!file->seek(u32, SEEK_CUR)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data, seek failed\n"));
return false;
}
} while (1);
if (!file->isOpen()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, file is not open\n"));
return false;
};
// Skip size, read until end of file...
if (!ReadU32(&u32)) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
return false;
};
availBytes = u32;
// Now set up the buffer or fail
buff = reinterpret_cast<uint8_t *>(malloc(buffSize));
if (!buff) {
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, failed to set up buffer \n"));
return false;
};
buffPtr = 0;
buffLen = 0;
return true;
}
bool AudioGeneratorWAV::begin(AudioFileSource *source, AudioOutput *output)
{
if (!source) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed: invalid source\n"));
return false;
}
file = source;
if (!output) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: invalid output\n"));
return false;
}
this->output = output;
if (!file->isOpen()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: file not open\n"));
return false;
} // Error
if (!ReadWAVInfo()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed during ReadWAVInfo\n"));
return false;
}
if (!output->SetRate( sampleRate )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetRate in output\n"));
return false;
}
if (!output->SetBitsPerSample( bitsPerSample )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetBitsPerSample in output\n"));
return false;
}
if (!output->SetChannels( channels )) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetChannels in output\n"));
return false;
}
if (!output->begin()) {
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: output's begin did not return true\n"));
return false;
}
running = true;
return true;
}