mirror of https://github.com/arendst/Tasmota.git
215 lines
5.4 KiB
C++
215 lines
5.4 KiB
C++
/*
|
|
AudioGeneratorAAC
|
|
Audio output generator using the Helix AAC decoder
|
|
|
|
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/>.
|
|
*/
|
|
|
|
#pragma GCC optimize ("O3")
|
|
|
|
#include "AudioGeneratorAAC.h"
|
|
|
|
AudioGeneratorAAC::AudioGeneratorAAC()
|
|
{
|
|
preallocateSpace = NULL;
|
|
preallocateSize = 0;
|
|
|
|
running = false;
|
|
file = NULL;
|
|
output = NULL;
|
|
|
|
buff = (uint8_t*)malloc(buffLen);
|
|
outSample = (int16_t*)malloc(1024 * 2 * sizeof(uint16_t));
|
|
if (!buff || !outSample) {
|
|
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
|
|
Serial.flush();
|
|
}
|
|
|
|
hAACDecoder = AACInitDecoder();
|
|
if (!hAACDecoder) {
|
|
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
|
|
Serial.flush();
|
|
}
|
|
|
|
buffValid = 0;
|
|
lastFrameEnd = 0;
|
|
validSamples = 0;
|
|
curSample = 0;
|
|
lastRate = 0;
|
|
lastChannels = 0;
|
|
}
|
|
|
|
AudioGeneratorAAC::AudioGeneratorAAC(void *preallocateData, int preallocateSz)
|
|
{
|
|
preallocateSpace = preallocateData;
|
|
preallocateSize = preallocateSz;
|
|
|
|
running = false;
|
|
file = NULL;
|
|
output = NULL;
|
|
|
|
uint8_t *p = (uint8_t*)preallocateSpace;
|
|
buff = (uint8_t*) p;
|
|
p += (buffLen + 7) & ~7;
|
|
outSample = (int16_t*) p;
|
|
p += (1024 * 2 * sizeof(int16_t) + 7) & ~7;
|
|
int used = p - (uint8_t*)preallocateSpace;
|
|
int availSpace = preallocateSize - used;
|
|
if (availSpace < 0 ) {
|
|
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
|
|
}
|
|
|
|
hAACDecoder = AACInitDecoderPre(p, availSpace);
|
|
if (!hAACDecoder) {
|
|
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
|
|
Serial.flush();
|
|
}
|
|
buffValid = 0;
|
|
lastFrameEnd = 0;
|
|
validSamples = 0;
|
|
curSample = 0;
|
|
lastRate = 0;
|
|
lastChannels = 0;
|
|
}
|
|
|
|
|
|
|
|
AudioGeneratorAAC::~AudioGeneratorAAC()
|
|
{
|
|
if (!preallocateSpace) {
|
|
AACFreeDecoder(hAACDecoder);
|
|
free(buff);
|
|
free(outSample);
|
|
}
|
|
}
|
|
|
|
bool AudioGeneratorAAC::stop()
|
|
{
|
|
running = false;
|
|
output->stop();
|
|
return file->close();
|
|
}
|
|
|
|
bool AudioGeneratorAAC::isRunning()
|
|
{
|
|
return running;
|
|
}
|
|
|
|
bool AudioGeneratorAAC::FillBufferWithValidFrame()
|
|
{
|
|
buff[0] = 0; // Destroy any existing sync word @ 0
|
|
int nextSync;
|
|
do {
|
|
nextSync = AACFindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
|
|
if (nextSync >= 0) nextSync += lastFrameEnd;
|
|
lastFrameEnd = 0;
|
|
if (nextSync == -1) {
|
|
if (buffValid && buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
|
|
buff[0] = 0xff;
|
|
buffValid = file->read(buff+1, buffLen-1);
|
|
if (buffValid==0) return false; // No data available, EOF
|
|
} else { // Try a whole new buffer
|
|
buffValid = file->read(buff, buffLen-1);
|
|
if (buffValid==0) return false; // No data available, EOF
|
|
}
|
|
}
|
|
} while (nextSync == -1);
|
|
|
|
// Move the frame to start at offset 0 in the buffer
|
|
buffValid -= nextSync; // Throw out prior to nextSync
|
|
memmove(buff, buff+nextSync, buffValid);
|
|
|
|
// We have a sync word at 0 now, try and fill remainder of buffer
|
|
buffValid += file->read(buff + buffValid, buffLen - buffValid);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AudioGeneratorAAC::loop()
|
|
{
|
|
if (!running) goto done; // Nothing to do here!
|
|
|
|
// If we've got data, try and pump it out...
|
|
while (validSamples) {
|
|
lastSample[0] = outSample[curSample*2];
|
|
lastSample[1] = outSample[curSample*2 + 1];
|
|
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
|
validSamples--;
|
|
curSample++;
|
|
}
|
|
|
|
// No samples available, need to decode a new frame
|
|
if (FillBufferWithValidFrame()) {
|
|
// buff[0] start of frame, decode it...
|
|
unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
|
|
int bytesLeft = buffValid;
|
|
int ret = AACDecode(hAACDecoder, &inBuff, &bytesLeft, outSample);
|
|
if (ret) {
|
|
// Error, skip the frame...
|
|
char buff[48];
|
|
sprintf_P(buff, PSTR("AAC decode error %d"), ret);
|
|
cb.st(ret, buff);
|
|
} else {
|
|
lastFrameEnd = buffValid - bytesLeft;
|
|
AACFrameInfo fi;
|
|
AACGetLastFrameInfo(hAACDecoder, &fi);
|
|
if ((int)fi.sampRateOut != (int)lastRate) {
|
|
output->SetRate(fi.sampRateOut);
|
|
lastRate = fi.sampRateOut;
|
|
}
|
|
if (fi.nChans != lastChannels) {
|
|
output->SetChannels(fi.nChans);
|
|
lastChannels = fi.nChans;
|
|
}
|
|
curSample = 0;
|
|
validSamples = fi.outputSamps / lastChannels;
|
|
}
|
|
} else {
|
|
running = false; // No more data, we're done here...
|
|
}
|
|
|
|
done:
|
|
file->loop();
|
|
output->loop();
|
|
|
|
return running;
|
|
}
|
|
|
|
bool AudioGeneratorAAC::begin(AudioFileSource *source, AudioOutput *output)
|
|
{
|
|
if (!source) return false;
|
|
file = source;
|
|
if (!output) return false;
|
|
this->output = output;
|
|
if (!file->isOpen()) return false; // Error
|
|
|
|
output->begin();
|
|
|
|
// AAC always comes out at 16 bits
|
|
output->SetBitsPerSample(16);
|
|
|
|
|
|
memset(buff, 0, buffLen);
|
|
memset(outSample, 0, 1024*2*sizeof(int16_t));
|
|
|
|
|
|
running = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|