ESP8266Audio/src/AudioFileSourceBuffer.cpp

212 lines
5.3 KiB
C++

/*
AudioFileSourceBuffer
Double-buffered file source using system RAM
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 <Arduino.h>
#include "AudioFileSourceBuffer.h"
#pragma GCC optimize ("O3")
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, uint32_t buffSizeBytes)
{
buffSize = buffSizeBytes;
buffer = (uint8_t*)malloc(sizeof(uint8_t) * buffSize);
if (!buffer) audioLogger->printf_P(PSTR("Unable to allocate AudioFileSourceBuffer::buffer[]\n"));
deallocateBuffer = true;
writePtr = 0;
readPtr = 0;
src = source;
length = 0;
filled = false;
}
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, void *inBuff, uint32_t buffSizeBytes)
{
buffSize = buffSizeBytes;
buffer = (uint8_t*)inBuff;
deallocateBuffer = false;
writePtr = 0;
readPtr = 0;
src = source;
length = 0;
filled = false;
}
AudioFileSourceBuffer::~AudioFileSourceBuffer()
{
if (deallocateBuffer) free(buffer);
buffer = NULL;
}
bool AudioFileSourceBuffer::seek(int32_t pos, int dir)
{
if(dir == SEEK_CUR && (readPtr+pos) < length) {
readPtr += pos;
return true;
} else {
// Invalidate
readPtr = 0;
writePtr = 0;
length = 0;
return src->seek(pos, dir);
}
}
bool AudioFileSourceBuffer::close()
{
if (deallocateBuffer) free(buffer);
buffer = NULL;
return src->close();
}
bool AudioFileSourceBuffer::isOpen()
{
return src->isOpen();
}
uint32_t AudioFileSourceBuffer::getSize()
{
return src->getSize();
}
uint32_t AudioFileSourceBuffer::getPos()
{
return src->getPos();
}
uint32_t AudioFileSourceBuffer::getFillLevel()
{
return length;
}
uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len)
{
if (!buffer) return src->read(data, len);
uint32_t bytes = 0;
if (!filled) {
// Fill up completely before returning any data at all
cb.st(STATUS_FILLING, PSTR("Refilling buffer"));
length = src->read(buffer, buffSize);
writePtr = length % buffSize;
filled = true;
}
// Pull from buffer until we've got none left or we've satisfied the request
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
uint32_t toReadFromBuffer = (len < length) ? len : length;
if ( (toReadFromBuffer > 0) && (readPtr >= writePtr) ) {
uint32_t toReadToEnd = (toReadFromBuffer < (uint32_t)(buffSize - readPtr)) ? toReadFromBuffer : (buffSize - readPtr);
memcpy(ptr, &buffer[readPtr], toReadToEnd);
readPtr = (readPtr + toReadToEnd) % buffSize;
len -= toReadToEnd;
length -= toReadToEnd;
ptr += toReadToEnd;
bytes += toReadToEnd;
toReadFromBuffer -= toReadToEnd;
}
if (toReadFromBuffer > 0) { // We know RP < WP at this point
memcpy(ptr, &buffer[readPtr], toReadFromBuffer);
readPtr = (readPtr + toReadFromBuffer) % buffSize;
len -= toReadFromBuffer;
length -= toReadFromBuffer;
ptr += toReadFromBuffer;
bytes += toReadFromBuffer;
toReadFromBuffer -= toReadFromBuffer;
}
if (len) {
// Still need more, try direct read from src
bytes += src->read(ptr, len);
// We're out of buffered data, need to force a complete refill. Thanks, @armSeb
readPtr = 0;
writePtr = 0;
length = 0;
filled = false;
cb.st(STATUS_UNDERFLOW, PSTR("Buffer underflow"));
}
fill();
return bytes;
}
void AudioFileSourceBuffer::fill()
{
if (!buffer) return;
if (length < buffSize) {
// Now try and opportunistically fill the buffer
if (readPtr > writePtr) {
if (readPtr == writePtr+1) return;
uint32_t bytesAvailMid = readPtr - writePtr - 1;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailMid);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
return;
}
if (buffSize > writePtr) {
uint32_t bytesAvailEnd = buffSize - writePtr;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailEnd);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
if (cnt != (int)bytesAvailEnd) return;
}
if (readPtr > 1) {
uint32_t bytesAvailStart = readPtr - 1;
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailStart);
length += cnt;
writePtr = (writePtr + cnt) % buffSize;
}
}
}
bool AudioFileSourceBuffer::fill(uint32_t len, uint32_t max_delay)
{
if (!src->isOpen()) {
Serial.printf_P(PSTR("Source file not open\n"));
return false;
}
if (!src->loop()) return false;
fill();
uint32_t toFill = (len < buffSize) ? len : buffSize;
uint32_t startTimestamp = millis();
while (length < toFill && millis() - startTimestamp < max_delay) {
delay(100);
if (!src->loop()) return false;
fill();
}
filled = true;
if (length < len) return false;
return true;
}
bool AudioFileSourceBuffer::loop()
{
if (!src->loop()) return false;
fill();
return true;
}