mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-27 20:56:35 +00:00
add opus stream and file support for opus/aac (#22795)
This commit is contained in:
parent
f42cb555c3
commit
ed21cfd487
@ -1412,6 +1412,7 @@
|
|||||||
#define MP3_MIC_STREAM
|
#define MP3_MIC_STREAM
|
||||||
#define USE_I2S_AUDIO_BERRY
|
#define USE_I2S_AUDIO_BERRY
|
||||||
#define USE_I2S_AAC
|
#define USE_I2S_AAC
|
||||||
|
#define USE_I2S_OPUS
|
||||||
#endif // USE_I2S_ALL
|
#endif // USE_I2S_ALL
|
||||||
|
|
||||||
#endif // _MY_USER_CONFIG_H_
|
#endif // _MY_USER_CONFIG_H_
|
||||||
|
@ -56,6 +56,7 @@ enum : int8_t {
|
|||||||
enum : uint32_t {
|
enum : uint32_t {
|
||||||
AAC_DECODER = 0,
|
AAC_DECODER = 0,
|
||||||
MP3_DECODER = 1,
|
MP3_DECODER = 1,
|
||||||
|
OPUS_DECODER = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define I2S_SLOTS 2
|
#define I2S_SLOTS 2
|
||||||
|
@ -33,6 +33,9 @@
|
|||||||
#ifdef USE_I2S_AAC
|
#ifdef USE_I2S_AAC
|
||||||
#include "AudioGeneratorAAC.h"
|
#include "AudioGeneratorAAC.h"
|
||||||
#endif // USE_I2S_AAC
|
#endif // USE_I2S_AAC
|
||||||
|
#ifdef USE_I2S_OPUS
|
||||||
|
#include "AudioGeneratorOpus.h"
|
||||||
|
#endif // USE_I2S_OPUS
|
||||||
|
|
||||||
#include <layer3.h>
|
#include <layer3.h>
|
||||||
|
|
||||||
@ -75,15 +78,14 @@ void CmndI2SMP3Stream(void);
|
|||||||
|
|
||||||
struct AUDIO_I2S_MP3_t {
|
struct AUDIO_I2S_MP3_t {
|
||||||
#ifdef USE_I2S_MP3
|
#ifdef USE_I2S_MP3
|
||||||
AudioGeneratorMP3 *mp3 = nullptr;
|
AudioGenerator *decoder = nullptr;
|
||||||
AudioFileSourceFS *file = nullptr;
|
AudioFileSourceFS *file = nullptr;
|
||||||
AudioFileSourceID3 *id3 = nullptr;
|
AudioFileSourceID3 *id3 = nullptr;
|
||||||
|
|
||||||
void *mp3ram = NULL;
|
void *preallocateCodec = NULL;
|
||||||
#endif // USE_I2S_MP3
|
#endif // USE_I2S_MP3
|
||||||
|
|
||||||
#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) || defined(USE_SHINE) || defined(MP3_MIC_STREAM)
|
#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 mp3_task_handle;
|
||||||
TaskHandle_t mic_task_handle;
|
TaskHandle_t mic_task_handle;
|
||||||
#endif // defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
|
#endif // defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
|
||||||
@ -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);
|
AddLog(LOG_LEVEL_INFO, PSTR("I2S: accepted sample rate for MP3 encoding: %d"), audio_i2s.Settings->rx.sample_rate);
|
||||||
|
|
||||||
#ifdef USE_I2S_MP3
|
#ifdef USE_I2S_MP3
|
||||||
if (audio_i2s_mp3.decoder || audio_i2s_mp3.mp3) return 0;
|
if (audio_i2s_mp3.decoder) return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
strlcpy(audio_i2s_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path));
|
strlcpy(audio_i2s_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path));
|
||||||
@ -524,6 +526,7 @@ enum {
|
|||||||
I2S_ERR_OUTPUT_NOT_CONFIGURED,
|
I2S_ERR_OUTPUT_NOT_CONFIGURED,
|
||||||
I2S_ERR_INPUT_NOT_CONFIGURED,
|
I2S_ERR_INPUT_NOT_CONFIGURED,
|
||||||
I2S_ERR_DECODER_IN_USE,
|
I2S_ERR_DECODER_IN_USE,
|
||||||
|
I2S_ERR_DECODER_FAILED_TO_INIT,
|
||||||
I2S_ERR_FILE_NOT_FOUND,
|
I2S_ERR_FILE_NOT_FOUND,
|
||||||
I2S_ERR_TX_FAILED,
|
I2S_ERR_TX_FAILED,
|
||||||
};
|
};
|
||||||
@ -771,11 +774,11 @@ void I2sInit(void) {
|
|||||||
// audio_i2s.out->stopTx();
|
// audio_i2s.out->stopTx();
|
||||||
// }
|
// }
|
||||||
#ifdef USE_I2S_MP3
|
#ifdef USE_I2S_MP3
|
||||||
audio_i2s_mp3.mp3ram = nullptr;
|
audio_i2s_mp3.preallocateCodec = nullptr;
|
||||||
if (audio_i2s.Settings->sys.mp3_preallocate == 1){
|
if (audio_i2s.Settings->sys.mp3_preallocate == 1){
|
||||||
// if (UsePSRAM()) {
|
// if (UsePSRAM()) {
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder"));
|
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
|
#endif // USE_I2S_MP3
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done");
|
AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done");
|
||||||
@ -826,14 +829,14 @@ int32_t I2SPrepareRx(void) {
|
|||||||
#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
|
#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO)
|
||||||
void I2sMp3Task(void *arg) {
|
void I2sMp3Task(void *arg) {
|
||||||
audio_i2s_mp3.task_running = true;
|
audio_i2s_mp3.task_running = true;
|
||||||
while (audio_i2s_mp3.mp3->isRunning() && audio_i2s_mp3.task_running) {
|
while (audio_i2s_mp3.decoder->isRunning() && audio_i2s_mp3.task_running) {
|
||||||
if (!audio_i2s_mp3.mp3->loop()) {
|
if (!audio_i2s_mp3.decoder->loop()) {
|
||||||
audio_i2s_mp3.task_running = false;
|
audio_i2s_mp3.task_running = false;
|
||||||
}
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(1));
|
vTaskDelay(pdMS_TO_TICKS(1));
|
||||||
}
|
}
|
||||||
audio_i2s.out->flush();
|
audio_i2s.out->flush();
|
||||||
audio_i2s_mp3.mp3->stop();
|
audio_i2s_mp3.decoder->stop();
|
||||||
mp3_delete();
|
mp3_delete();
|
||||||
audio_i2s_mp3.mp3_task_handle = nullptr;
|
audio_i2s_mp3.mp3_task_handle = nullptr;
|
||||||
audio_i2s_mp3.task_has_ended = true;
|
audio_i2s_mp3.task_has_ended = true;
|
||||||
@ -877,7 +880,7 @@ void I2sStopPlaying() {
|
|||||||
while(audio_i2s_mp3.task_has_ended == false){
|
while(audio_i2s_mp3.task_has_ended == false){
|
||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
while(audio_i2s_mp3.mp3){
|
while(audio_i2s_mp3.decoder){
|
||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -890,13 +893,47 @@ void I2sStopPlaying() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_I2S_MP3
|
#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
|
// 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();
|
int32_t i2s_err = I2SPrepareTx();
|
||||||
if ((i2s_err) != I2S_OK) { return i2s_err; }
|
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
|
// check if the filename starts with '/', if not add it
|
||||||
char fname[64];
|
char fname[64];
|
||||||
@ -913,30 +950,28 @@ int32_t I2SPlayMp3(const char *path) {
|
|||||||
|
|
||||||
audio_i2s_mp3.id3 = new AudioFileSourceID3(audio_i2s_mp3.file);
|
audio_i2s_mp3.id3 = new AudioFileSourceID3(audio_i2s_mp3.file);
|
||||||
|
|
||||||
if (audio_i2s_mp3.mp3ram) {
|
if(I2SinitDecoder(decoder_type)){
|
||||||
audio_i2s_mp3.mp3 = new AudioGeneratorMP3(audio_i2s_mp3.mp3ram, preallocateCodecSize);
|
audio_i2s_mp3.decoder->begin(audio_i2s_mp3.id3, audio_i2s.out);
|
||||||
} else {
|
} 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
|
// 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;
|
return I2S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp3_delete(void) {
|
void mp3_delete(void) {
|
||||||
delete audio_i2s_mp3.file;
|
delete audio_i2s_mp3.file;
|
||||||
delete audio_i2s_mp3.id3;
|
delete audio_i2s_mp3.id3;
|
||||||
delete audio_i2s_mp3.mp3;
|
delete audio_i2s_mp3.decoder;
|
||||||
audio_i2s_mp3.mp3 = nullptr;
|
audio_i2s_mp3.decoder = 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");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
#endif // USE_I2S_MP3
|
#endif // USE_I2S_MP3
|
||||||
|
|
||||||
@ -985,7 +1020,7 @@ void CmndI2SStop(void) {
|
|||||||
#ifdef USE_I2S_MP3
|
#ifdef USE_I2S_MP3
|
||||||
void CmndI2SPlay(void) {
|
void CmndI2SPlay(void) {
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
int32_t err = I2SPlayFile(XdrvMailbox.data, XdrvMailbox.index);
|
||||||
// display return message
|
// display return message
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case I2S_OK:
|
case I2S_OK:
|
||||||
@ -997,6 +1032,9 @@ void CmndI2SPlay(void) {
|
|||||||
case I2S_ERR_DECODER_IN_USE:
|
case I2S_ERR_DECODER_IN_USE:
|
||||||
ResponseCmndChar("Decoder already in use");
|
ResponseCmndChar("Decoder already in use");
|
||||||
break;
|
break;
|
||||||
|
case I2S_ERR_DECODER_FAILED_TO_INIT:
|
||||||
|
ResponseCmndChar("Decoder failed to init");
|
||||||
|
break;
|
||||||
case I2S_ERR_FILE_NOT_FOUND:
|
case I2S_ERR_FILE_NOT_FOUND:
|
||||||
ResponseCmndChar("File not found");
|
ResponseCmndChar("File not found");
|
||||||
break;
|
break;
|
||||||
@ -1056,11 +1094,11 @@ void CmndI2SMicRec(void) {
|
|||||||
ResponseCmndChar("I2S Mic not configured");
|
ResponseCmndChar("I2S Mic not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (audio_i2s_mp3.mp3ram == nullptr){
|
if (audio_i2s_mp3.preallocateCodec == nullptr){
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: try late buffer allocation for mp3 encoder"));
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: try late codec buffer allocation"));
|
||||||
audio_i2s_mp3.mp3ram = special_malloc(preallocateCodecSize);
|
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 (XdrvMailbox.data_len > 0) {
|
||||||
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
|
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
|
||||||
Response_P("{\"I2SREC-duration\":%d}", audio_i2s_mp3.recdur);
|
Response_P("{\"I2SREC-duration\":%d}", audio_i2s_mp3.recdur);
|
||||||
|
@ -26,7 +26,6 @@ struct AUDIO_I2S_WEBRADIO_t {
|
|||||||
AudioFileSourceBuffer *buff = NULL;
|
AudioFileSourceBuffer *buff = NULL;
|
||||||
char wr_title[64];
|
char wr_title[64];
|
||||||
void *preallocateBuffer = NULL;
|
void *preallocateBuffer = NULL;
|
||||||
void *preallocateCodec = NULL;
|
|
||||||
} Audio_webradio;
|
} Audio_webradio;
|
||||||
|
|
||||||
void I2sMDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
|
void I2sMDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
|
||||||
@ -46,46 +45,30 @@ void I2SWrStatusCB(void *cbData, int code, const char *str){
|
|||||||
AddLog(LOG_LEVEL_INFO, "I2S: status: %s",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;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool I2SWebradio(const char *url, uint32_t decoder_type) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// allocate buffers if not already done
|
// allocate buffers if not already done
|
||||||
if (Audio_webradio.preallocateBuffer == NULL) {
|
if (Audio_webradio.preallocateBuffer == NULL) {
|
||||||
Audio_webradio.preallocateBuffer = special_malloc(preallocateBufferSize);
|
Audio_webradio.preallocateBuffer = special_malloc(preallocateBufferSize);
|
||||||
}
|
}
|
||||||
if (Audio_webradio.preallocateCodec == NULL) {
|
if (audio_i2s_mp3.preallocateCodec == NULL) {
|
||||||
Audio_webradio.preallocateCodec = special_malloc(preallocateCodecSize);
|
audio_i2s_mp3.preallocateCodec = special_malloc(preallocateCodecSize);
|
||||||
}
|
}
|
||||||
// check if we have buffers
|
// 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");
|
AddLog(LOG_LEVEL_INFO, "I2S: cannot allocate buffers");
|
||||||
if (Audio_webradio.preallocateBuffer != NULL) {
|
if (Audio_webradio.preallocateBuffer != NULL) {
|
||||||
free(Audio_webradio.preallocateBuffer);
|
free(Audio_webradio.preallocateBuffer);
|
||||||
Audio_webradio.preallocateBuffer = NULL;
|
Audio_webradio.preallocateBuffer = NULL;
|
||||||
}
|
}
|
||||||
if (Audio_webradio.preallocateCodec != NULL) {
|
if (audio_i2s_mp3.preallocateCodec != NULL) {
|
||||||
free(Audio_webradio.preallocateCodec);
|
free(audio_i2s_mp3.preallocateCodec);
|
||||||
Audio_webradio.preallocateCodec = NULL;
|
audio_i2s_mp3.preallocateCodec = NULL;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -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);
|
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;
|
return true;
|
||||||
|
|
||||||
i2swr_fail:
|
i2swr_fail:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user