Skip to content

Commit

Permalink
add opus stream and file support for opus/aac (#22795)
Browse files Browse the repository at this point in the history
  • Loading branch information
Staars authored Jan 10, 2025
1 parent f42cb55 commit ed21cfd
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 61 deletions.
1 change: 1 addition & 0 deletions tasmota/my_user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,7 @@
#define MP3_MIC_STREAM
#define USE_I2S_AUDIO_BERRY
#define USE_I2S_AAC
#define USE_I2S_OPUS
#endif // USE_I2S_ALL

#endif // _MY_USER_CONFIG_H_
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ enum : int8_t {
enum : uint32_t {
AAC_DECODER = 0,
MP3_DECODER = 1,
OPUS_DECODER = 2,
};

#define I2S_SLOTS 2
Expand Down
102 changes: 70 additions & 32 deletions tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
#ifdef USE_I2S_AAC
#include "AudioGeneratorAAC.h"
#endif // USE_I2S_AAC
#ifdef USE_I2S_OPUS
#include "AudioGeneratorOpus.h"
#endif // USE_I2S_OPUS

#include <layer3.h>

Expand Down Expand Up @@ -75,15 +78,14 @@ void CmndI2SMP3Stream(void);

struct AUDIO_I2S_MP3_t {
#ifdef USE_I2S_MP3
AudioGeneratorMP3 *mp3 = nullptr;
AudioGenerator *decoder = nullptr;
AudioFileSourceFS *file = nullptr;
AudioFileSourceID3 *id3 = nullptr;

void *mp3ram = NULL;
void *preallocateCodec = NULL;
#endif // USE_I2S_MP3

#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) || defined(USE_SHINE) || defined(MP3_MIC_STREAM)
AudioGenerator *decoder = nullptr;
TaskHandle_t mp3_task_handle;
TaskHandle_t mic_task_handle;
#endif // defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
Expand Down Expand Up @@ -500,7 +502,7 @@ int32_t I2sRecordShine(char *path) {
AddLog(LOG_LEVEL_INFO, PSTR("I2S: accepted sample rate for MP3 encoding: %d"), audio_i2s.Settings->rx.sample_rate);

#ifdef USE_I2S_MP3
if (audio_i2s_mp3.decoder || audio_i2s_mp3.mp3) return 0;
if (audio_i2s_mp3.decoder) return 0;
#endif

strlcpy(audio_i2s_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path));
Expand All @@ -524,6 +526,7 @@ enum {
I2S_ERR_OUTPUT_NOT_CONFIGURED,
I2S_ERR_INPUT_NOT_CONFIGURED,
I2S_ERR_DECODER_IN_USE,
I2S_ERR_DECODER_FAILED_TO_INIT,
I2S_ERR_FILE_NOT_FOUND,
I2S_ERR_TX_FAILED,
};
Expand Down Expand Up @@ -771,11 +774,11 @@ void I2sInit(void) {
// audio_i2s.out->stopTx();
// }
#ifdef USE_I2S_MP3
audio_i2s_mp3.mp3ram = nullptr;
audio_i2s_mp3.preallocateCodec = nullptr;
if (audio_i2s.Settings->sys.mp3_preallocate == 1){
// if (UsePSRAM()) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder"));
audio_i2s_mp3.mp3ram = special_malloc(preallocateCodecSize);
audio_i2s_mp3.preallocateCodec = special_malloc(preallocateCodecSize);
}
#endif // USE_I2S_MP3
AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done");
Expand Down Expand Up @@ -826,14 +829,14 @@ int32_t I2SPrepareRx(void) {
#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
void I2sMp3Task(void *arg) {
audio_i2s_mp3.task_running = true;
while (audio_i2s_mp3.mp3->isRunning() && audio_i2s_mp3.task_running) {
if (!audio_i2s_mp3.mp3->loop()) {
while (audio_i2s_mp3.decoder->isRunning() && audio_i2s_mp3.task_running) {
if (!audio_i2s_mp3.decoder->loop()) {
audio_i2s_mp3.task_running = false;
}
vTaskDelay(pdMS_TO_TICKS(1));
}
audio_i2s.out->flush();
audio_i2s_mp3.mp3->stop();
audio_i2s_mp3.decoder->stop();
mp3_delete();
audio_i2s_mp3.mp3_task_handle = nullptr;
audio_i2s_mp3.task_has_ended = true;
Expand Down Expand Up @@ -877,7 +880,7 @@ void I2sStopPlaying() {
while(audio_i2s_mp3.task_has_ended == false){
delay(10);
}
while(audio_i2s_mp3.mp3){
while(audio_i2s_mp3.decoder){
delay(10);
}
}
Expand All @@ -890,13 +893,47 @@ void I2sStopPlaying() {
}

#ifdef USE_I2S_MP3
// Play_mp3 - Play a MP3 file from filesystem

bool I2SinitDecoder(uint32_t decoder_type){
switch(decoder_type){
case MP3_DECODER:
if (audio_i2s_mp3.preallocateCodec) {
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorMP3(audio_i2s_mp3.preallocateCodec, preallocateCodecSize));
} else {
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorMP3());
}
break;
#ifdef USE_I2S_AAC
case AAC_DECODER:
audio_i2s_mp3.preallocateCodec = special_realloc(audio_i2s_mp3.preallocateCodec, preallocateCodecSizeAAC);
if(audio_i2s_mp3.preallocateCodec == nullptr){
AddLog(LOG_LEVEL_ERROR, "I2S: could not alloc heap for AAC");
return false;
}
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorAAC(audio_i2s_mp3.preallocateCodec, preallocateCodecSizeAAC));
break;
#endif //USE_I2S_AAC
#ifdef USE_I2S_OPUS
case OPUS_DECODER:
free(audio_i2s_mp3.preallocateCodec);
audio_i2s_mp3.preallocateCodec = nullptr;
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorOpus());
break;
#endif //USE_I2S_OPUS
}
if(audio_i2s_mp3.decoder == nullptr){
return false;
}
return true;
}

// Play a audio file from filesystem
//
// Returns I2S_error_t
int32_t I2SPlayMp3(const char *path) {
int32_t I2SPlayFile(const char *path, uint32_t decoder_type) {
int32_t i2s_err = I2SPrepareTx();
if ((i2s_err) != I2S_OK) { return i2s_err; }
if (audio_i2s_mp3.mp3) return I2S_ERR_DECODER_IN_USE;
if (audio_i2s_mp3.decoder) return I2S_ERR_DECODER_IN_USE;

// check if the filename starts with '/', if not add it
char fname[64];
Expand All @@ -913,30 +950,28 @@ int32_t I2SPlayMp3(const char *path) {

audio_i2s_mp3.id3 = new AudioFileSourceID3(audio_i2s_mp3.file);

if (audio_i2s_mp3.mp3ram) {
audio_i2s_mp3.mp3 = new AudioGeneratorMP3(audio_i2s_mp3.mp3ram, preallocateCodecSize);
if(I2SinitDecoder(decoder_type)){
audio_i2s_mp3.decoder->begin(audio_i2s_mp3.id3, audio_i2s.out);
} else {
audio_i2s_mp3.mp3 = new AudioGeneratorMP3();
return I2S_ERR_DECODER_FAILED_TO_INIT;
}

size_t wr_tasksize = 8000; // suitable for ACC and MP3
if(decoder_type == 2){ // opus needs a ton of stack
wr_tasksize = 26000;
}
audio_i2s_mp3.mp3->begin(audio_i2s_mp3.id3, audio_i2s.out);

// Always use a task
xTaskCreatePinnedToCore(I2sMp3Task, "MP3", 8192, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1);
xTaskCreatePinnedToCore(I2sMp3Task, "PLAYFILE", wr_tasksize, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1);
return I2S_OK;
}

void mp3_delete(void) {
delete audio_i2s_mp3.file;
delete audio_i2s_mp3.id3;
delete audio_i2s_mp3.mp3;
audio_i2s_mp3.mp3 = nullptr;

// if (audio_i2s_mp3.decoder) {
// audio_i2s_mp3.decoder->stop();
// delete audio_i2s_mp3.decoder;
// audio_i2s_mp3.decoder = nullptr;
// AddLog(LOG_LEVEL_DEBUG, "I2S: audio_i2s_mp3.decoder = nullptr");
// }
delete audio_i2s_mp3.decoder;
audio_i2s_mp3.decoder = nullptr;

}
#endif // USE_I2S_MP3

Expand Down Expand Up @@ -985,7 +1020,7 @@ void CmndI2SStop(void) {
#ifdef USE_I2S_MP3
void CmndI2SPlay(void) {
if (XdrvMailbox.data_len > 0) {
int32_t err = I2SPlayMp3(XdrvMailbox.data);
int32_t err = I2SPlayFile(XdrvMailbox.data, XdrvMailbox.index);
// display return message
switch (err) {
case I2S_OK:
Expand All @@ -997,6 +1032,9 @@ void CmndI2SPlay(void) {
case I2S_ERR_DECODER_IN_USE:
ResponseCmndChar("Decoder already in use");
break;
case I2S_ERR_DECODER_FAILED_TO_INIT:
ResponseCmndChar("Decoder failed to init");
break;
case I2S_ERR_FILE_NOT_FOUND:
ResponseCmndChar("File not found");
break;
Expand Down Expand Up @@ -1056,11 +1094,11 @@ void CmndI2SMicRec(void) {
ResponseCmndChar("I2S Mic not configured");
return;
}
if (audio_i2s_mp3.mp3ram == nullptr){
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: try late buffer allocation for mp3 encoder"));
audio_i2s_mp3.mp3ram = special_malloc(preallocateCodecSize);
if (audio_i2s_mp3.preallocateCodec == nullptr){
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: try late codec buffer allocation"));
audio_i2s_mp3.preallocateCodec = special_malloc(preallocateCodecSize);
}
if (audio_i2s_mp3.mp3ram != nullptr) {
if (audio_i2s_mp3.preallocateCodec != nullptr) {
if (XdrvMailbox.data_len > 0) {
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
Response_P("{\"I2SREC-duration\":%d}", audio_i2s_mp3.recdur);
Expand Down
41 changes: 12 additions & 29 deletions tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ struct AUDIO_I2S_WEBRADIO_t {
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
} Audio_webradio;

void I2sMDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
Expand All @@ -46,46 +45,30 @@ void I2SWrStatusCB(void *cbData, int code, const char *str){
AddLog(LOG_LEVEL_INFO, "I2S: status: %s",str);
}

bool I2SinitDecoder(uint32_t decoder_type){
switch(decoder_type){
case MP3_DECODER:
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorMP3(Audio_webradio.preallocateCodec, preallocateCodecSize));
break;
#ifdef USE_I2S_AAC
case AAC_DECODER:
Audio_webradio.preallocateCodec = special_realloc(Audio_webradio.preallocateCodec, preallocateCodecSizeAAC);
if(Audio_webradio.preallocateCodec == nullptr){
AddLog(LOG_LEVEL_ERROR, "I2S: could not alloc heap for AAC");
return false;
}
audio_i2s_mp3.decoder = dynamic_cast<AudioGenerator *>(new AudioGeneratorAAC(Audio_webradio.preallocateCodec, preallocateCodecSizeAAC));
break;
#endif //USE_I2S_AAC
}
if(audio_i2s_mp3.decoder == nullptr){
return false;
bool I2SWebradio(const char *url, uint32_t decoder_type) {

size_t wr_tasksize = 8000; // suitable for ACC and MP3
if(decoder_type == 2){ // opus needs a ton of stack
wr_tasksize = 26000;
}
return true;
}

bool I2SWebradio(const char *url, uint32_t decoder_type) {
// allocate buffers if not already done
if (Audio_webradio.preallocateBuffer == NULL) {
Audio_webradio.preallocateBuffer = special_malloc(preallocateBufferSize);
}
if (Audio_webradio.preallocateCodec == NULL) {
Audio_webradio.preallocateCodec = special_malloc(preallocateCodecSize);
if (audio_i2s_mp3.preallocateCodec == NULL) {
audio_i2s_mp3.preallocateCodec = special_malloc(preallocateCodecSize);
}
// check if we have buffers
if (Audio_webradio.preallocateBuffer == NULL || Audio_webradio.preallocateCodec == NULL) {
if (Audio_webradio.preallocateBuffer == NULL || audio_i2s_mp3.preallocateCodec == NULL) {
AddLog(LOG_LEVEL_INFO, "I2S: cannot allocate buffers");
if (Audio_webradio.preallocateBuffer != NULL) {
free(Audio_webradio.preallocateBuffer);
Audio_webradio.preallocateBuffer = NULL;
}
if (Audio_webradio.preallocateCodec != NULL) {
free(Audio_webradio.preallocateCodec);
Audio_webradio.preallocateCodec = NULL;
if (audio_i2s_mp3.preallocateCodec != NULL) {
free(audio_i2s_mp3.preallocateCodec);
audio_i2s_mp3.preallocateCodec = NULL;
}
return false;
}
Expand Down Expand Up @@ -118,7 +101,7 @@ bool I2SWebradio(const char *url, uint32_t decoder_type) {
}

AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task with decoder type %u"), decoder_type);
xTaskCreatePinnedToCore(I2sMp3WrTask, "MP3-WR", 8192, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1);
xTaskCreatePinnedToCore(I2sMp3WrTask, "MP3-WR", wr_tasksize, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1);
return true;

i2swr_fail:
Expand Down

0 comments on commit ed21cfd

Please sign in to comment.