fork from github
This commit is contained in:
316
src/AudioGeneratorWAV.cpp
Normal file
316
src/AudioGeneratorWAV.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user