Last active
January 14, 2025 13:59
-
-
Save Staars/57e7193fe99311cd0e80b83d98b13ece to your computer and use it in GitHub Desktop.
some tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
xdrv_42_i2s_audio.ino - Audio dac support for Tasmota | |
Copyright (C) 2021 Gerhard Mutz and Theo Arends | |
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/>. | |
*/ | |
#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | |
#ifdef USE_I2S_AUDIO | |
class AudioEncoder | |
{ | |
public: | |
AudioEncoder() { | |
inBuffer = nullptr; | |
outFrame = nullptr; | |
samplesPerPass = 0; | |
byteSize = 0; | |
}; | |
virtual ~AudioEncoder() {}; | |
virtual uint32_t begin(uint32_t samplingRate, uint32_t inputChannels) { return 0; }; | |
virtual size_t getHeader(uint8_t *header) {return 0;} | |
virtual size_t encode() { return 0; }; | |
virtual size_t stop() { return 0; }; | |
public: | |
int16_t *inBuffer; | |
uint8_t *outFrame; | |
uint32_t samplesPerPass; | |
uint32_t byteSize; | |
}; | |
class AudioEncoderShineMP3 : public AudioEncoder | |
{ | |
public: | |
AudioEncoderShineMP3() { | |
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: AudioEncoderShineMP3")); | |
}; | |
virtual ~AudioEncoderShineMP3() { | |
if (s) {shine_close(s);} | |
if(inBuffer){free(inBuffer); } | |
}; | |
virtual uint32_t begin(uint32_t samplingRate, uint32_t inputChannels) { | |
shine_set_config_mpeg_defaults(&config.mpeg); | |
if (inputChannels == 1) { | |
config.mpeg.mode = MONO; | |
} else { | |
config.mpeg.mode = STEREO; | |
} | |
config.wave.samplerate = samplingRate; | |
config.wave.channels = (channels)inputChannels; | |
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {return 3;} | |
s = shine_initialise(&config); | |
if (!s) {return 4; } | |
samplesPerPass = shine_samples_per_pass(s); | |
byteSize = samplesPerPass * 2 * inputChannels; | |
inBuffer = (int16_t*)malloc(byteSize); | |
if (!inBuffer) {return 5; } | |
return 0; | |
}; | |
virtual size_t stop() { | |
int written; | |
outFrame = shine_flush(s, &written); | |
return written; | |
} | |
virtual size_t encode() { | |
int written; | |
outFrame = shine_encode_buffer_interleaved(s, inBuffer, &written); | |
return written; | |
}; | |
protected: | |
shine_config_t config; | |
shine_t s; | |
}; | |
#include "libopus/opus.h" | |
class AudioEncoderOpus : public AudioEncoder | |
{ | |
public: | |
AudioEncoderOpus() { | |
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: AudioEncoderOpus")); | |
}; | |
virtual ~AudioEncoderOpus() { | |
if(inBuffer){ free(inBuffer); } | |
if(outFrame){ free(outFrame); } | |
opus_encoder_destroy(encoder); | |
}; | |
virtual uint32_t begin(uint32_t samplingRate, uint32_t inputChannels) { | |
int error; | |
encoder = opus_encoder_create(samplingRate, inputChannels, OPUS_APPLICATION_AUDIO, &error); | |
if (error != 0) { return error; } | |
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(complexity)); | |
constexpr uint32_t frameDuration = 20; //ms | |
samplesPerPass = samplingRate/(1000/frameDuration); | |
byteSize = samplesPerPass * 2 * inputChannels; | |
inBuffer = (int16_t*)malloc(byteSize); | |
if (!inBuffer) {return 5; } | |
outFrame = (uint8_t*)malloc(maxBytes); | |
if (!outFrame) {return 5; } | |
return 0; | |
}; | |
virtual size_t encode() { | |
opus_int32 i = opus_encode(encoder, inBuffer, samplesPerPass, outFrame, maxBytes); | |
return i; | |
} | |
virtual size_t getHeader(uint8_t *header) { | |
header = (uint8_t*)&opusCombinedHeader; | |
return sizeof(opusCombinedHeader); | |
} | |
protected: | |
OpusEncoder *encoder; | |
const uint32_t maxBytes = 1296; | |
const uint32_t complexity = 1; // 1-10 - low to high quality | |
struct __attribute__((packed)) { | |
char signatureHead[8] = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}; | |
uint8_t version = 1; | |
uint8_t channelCount = 0; | |
uint16_t preSkip = 3840; | |
uint32_t sampleRate = 0; | |
int16_t outputGain = 0; | |
uint8_t channelMappingFamily = 0; | |
char signatureTags[8] = {'O', 'p', 'u', 's', 'T', 'a', 'g', 's'}; | |
uint32_t vendorStringLength = 8; | |
char vendor[8] = "Tasmota"; | |
uint32_t userCommentListLength = 0; | |
} opusCombinedHeader; | |
}; | |
// micro to mp3 file or stream | |
void I2sMicTask_new(void *arg){ | |
int8_t error = 0; | |
int written; | |
AudioEncoder *mic_enc; | |
File rec_file = (File)nullptr; | |
uint8_t *_header; | |
size_t _headerSize; | |
uint16_t bwritten; | |
uint32_t ctime; | |
uint32_t gain = audio_i2s.Settings->rx.gain; | |
uint32_t timeForOneRead; | |
if(audio_i2s_mp3.encoder_type == MP3_ENCODER){ | |
mic_enc = new AudioEncoderShineMP3; | |
} else { | |
mic_enc = new AudioEncoderOpus; | |
} | |
if (!audio_i2s_mp3.use_stream) { | |
rec_file = ufsp->open(audio_i2s_mp3.mic_path, "w"); | |
if (!rec_file) { | |
error = 1; | |
goto exit; | |
} | |
} else { | |
if (!audio_i2s_mp3.stream_active) { | |
error = 2; | |
audio_i2s_mp3.use_stream = 0; | |
goto exit; | |
} | |
audio_i2s_mp3.client.flush(); | |
audio_i2s_mp3.client.setTimeout(3); | |
if(audio_i2s_mp3.encoder_type == MP3_ENCODER){ | |
audio_i2s_mp3.client.print("HTTP/1.1 200 OK\r\n" | |
"Content-Type: audio/mpeg;\r\n\r\n"); | |
} else if (audio_i2s_mp3.encoder_type == OPUS_ENCODER){ | |
audio_i2s_mp3.client.print("HTTP/1.1 200 OK\r\n" | |
"Content-Type: audio/opus;\r\n\r\n"); | |
} | |
} | |
error = mic_enc->begin(audio_i2s.Settings->rx.sample_rate, audio_i2s.Settings->rx.channels); | |
if(error != 0){ | |
goto exit; | |
} | |
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: mic_enc->begin %u"), error); | |
_headerSize = mic_enc->getHeader(_header); | |
if(_headerSize != 0){ | |
if (!audio_i2s_mp3.use_stream) { | |
rec_file.write(_header, written); | |
} else { | |
audio_i2s_mp3.client.write((const char*)_header, written); | |
} | |
} | |
ctime = TasmotaGlobal.uptime; | |
timeForOneRead = 1000 / ((audio_i2s.Settings->rx.sample_rate / (mic_enc->samplesPerPass * audio_i2s.Settings->rx.channels ))); | |
timeForOneRead -= 1; // be very in time | |
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: samples %u, bytesize %u, time: %u"),mic_enc->samplesPerPass, mic_enc->byteSize, timeForOneRead); | |
while (!audio_i2s_mp3.mic_stop) { | |
TickType_t xLastWakeTime = xTaskGetTickCount(); | |
size_t bytes_read; | |
// bytes_read = audio_i2s.in->readMic((uint8_t*)buffer, bytesize, true /*dc_block*/, false /*apply_gain*/, true /*lowpass*/, nullptr /*peak_ptr*/); | |
i2s_channel_read(audio_i2s.in->getRxHandle(), (void*)mic_enc->inBuffer, mic_enc->byteSize, &bytes_read, pdMS_TO_TICKS(5)); | |
if(bytes_read < mic_enc->byteSize) AddLog(LOG_LEVEL_DEBUG, PSTR("!! %u, %u"), bytes_read, mic_enc->byteSize); | |
if (gain > 1) { | |
// set gain the "old way" | |
int16_t _gain = gain / 16; | |
for (uint32_t cnt = 0; cnt < bytes_read / 2; cnt++) { | |
mic_enc->inBuffer[cnt] *= _gain; | |
} | |
} | |
written = mic_enc->encode(); | |
// AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: mic_enc->encode written %u"),written); | |
if (!audio_i2s_mp3.use_stream) { | |
bwritten = rec_file.write(mic_enc->outFrame, written); | |
if (bwritten != written) { | |
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: rec file write error")); | |
break; | |
} | |
} else { | |
audio_i2s_mp3.client.write((const char*)mic_enc->outFrame, written); | |
if (!audio_i2s_mp3.client.connected()) { | |
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: client.connected error")); | |
break; | |
} | |
} | |
audio_i2s_mp3.recdur = TasmotaGlobal.uptime - ctime; | |
vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS(timeForOneRead)); | |
} | |
written = mic_enc->stop(); | |
if (!audio_i2s_mp3.use_stream) { | |
rec_file.write(mic_enc->outFrame, written); | |
} else { | |
audio_i2s_mp3.client.write((const char*)mic_enc->outFrame, written); | |
} | |
exit: | |
delete mic_enc; | |
if (rec_file) { | |
rec_file.close(); | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: rec file closed")); | |
} | |
if (audio_i2s_mp3.use_stream) { | |
audio_i2s_mp3.client.stop(); | |
} | |
audio_i2s.in->stopRx(); | |
audio_i2s_mp3.mic_stop = 0; | |
audio_i2s_mp3.mic_error = error; | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: record task result code: %d"), error); | |
audio_i2s_mp3.mic_task_handle = 0; | |
audio_i2s_mp3.recdur = 0; | |
audio_i2s_mp3.stream_active = 0; | |
vTaskDelete(NULL); | |
} | |
int32_t I2sRecord(char *path, uint32_t encoder_type) { | |
esp_err_t err = ESP_OK; | |
uint32_t stack = 12000; | |
switch(encoder_type){ | |
case MP3_ENCODER: | |
switch(audio_i2s.Settings->rx.sample_rate){ | |
case 32000: case 48000: case 44100: | |
break; // supported | |
default: | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: unsupported sample rate for MP3 encoding: %d"), audio_i2s.Settings->rx.sample_rate); | |
return -1; | |
} | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: start MP3 encoding: %d"), audio_i2s.Settings->rx.sample_rate); | |
break; | |
case OPUS_ENCODER: | |
switch(audio_i2s.Settings->rx.sample_rate){ | |
case 48000: case 24000: case 16000: case 12000: case 8000: | |
break; | |
default: | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: unsupported sample rate for OPUS encoding: %d"), audio_i2s.Settings->rx.sample_rate); | |
return -1; | |
} | |
AddLog(LOG_LEVEL_INFO, PSTR("I2S: start OPUS encoding: %d Hz"), audio_i2s.Settings->rx.sample_rate); | |
stack = 25000; | |
} | |
audio_i2s_mp3.encoder_type = encoder_type; | |
#ifdef USE_I2S_MP3 | |
if (audio_i2s_mp3.decoder) return 0; | |
#endif | |
if(!audio_i2s_mp3.use_stream){ | |
strlcpy(audio_i2s_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path)); | |
} | |
audio_i2s_mp3.mic_stop = 0; | |
audio_i2s.in->startRx(); | |
err = xTaskCreatePinnedToCore(I2sMicTask_new, "MIC", stack, NULL, 3, &audio_i2s_mp3.mic_task_handle, 1); | |
return err; | |
} | |
#endif // USE_I2S_AUDIO | |
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment