Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2S: add opus stream and file support for opus/aac #22795

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading