add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(audio-lyraT)
spiffs_create_partition_image(storage spiffs FLASH_IN_PROJECT)

View File

@@ -0,0 +1,71 @@
# ESP-DSP LyraT Board audio processing application
The demo applications are developed for the ESP32-LyraT development board and demonstrate the usage of IIR filters from the ESP-DSP library.
This example showcases how to use IIR filter functionality to process audio stream data.
To hear the audio please connect headphones or speakers to the ESP32-LyraT audio output.
The example performs the following steps:
1. Read samples from a file
* read samples from file
* write audio samples to the triple buffer
2. Process audio samples
* process volume/bass/treble
* apply digital limiter to the audio data
* control audio buffer overflow
3. Pass samples to the audio codec
To control the volume/bass/treble, please select the value by press 'Set' button and adjust the value by '+/-' buttons.
## The Triple Audio Buffer
In audion processing, it's possible to have situation when one task write data to the processing buffer (processing task), and another task read data from the same buffer, and still in reading process.
Triple buffering is a technique used in audio processing to minimize latency and ensure smooth playback. It involves using three buffers to store audio data. Here's a description of how it works:
* Buffer A: This buffer holds the audio data that is currently being processed by the audio system. It is typically filled with samples from the audio source and processed in real-time.
* Buffer B: When Buffer A is full, the audio system begins reading from Buffer A and starts processing the data. At the same time, Buffer B is filled with new audio samples from the source.
* Buffer C: Once Buffer B is full, the audio system switches its attention to Buffer B and continues processing data. Buffer C is then filled with the latest audio samples.
The cycle continues, with the audio system always processing the data from a buffer while the other two buffers are being filled. This approach helps ensure a continuous and uninterrupted audio playback, as there is always a buffer ready to be processed. It reduces the chances of audio glitches or dropouts caused by delays in reading or processing the audio data.
Triple buffering is particularly useful when working with real-time audio processing applications, where low latency and uninterrupted playback are crucial.
## Audio Processing Flow
The audio processing flow contains next blocks:
1. Audio processing task
* Read data from file and store to the triple buffer
* Read from triple buffer and concert data from int16_t to float
* Process bass
* Process treble
* Process volume
* Apply digital limiter
* Convert data from float to int16_t and store to the triple buffer
2. Audio output task
* Write data from triple buffer to audio codec
3. Buttons control task
* React on buttons and adjust the control values
* Calculates IIR filter coefficients
## How to use the example
### Hardware required
This example require LyraT development board.
### Configure the project
Under Component Config ---> DSP Library ---> DSP Optimization, it's possible to choose either the optimized or ANSI implementation, to compare them.
### Build and flash
Build the project and flash it to the board, then run monitor tool to view serial output (replace PORT with serial port name):
```
idf.py flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

View File

@@ -0,0 +1 @@
idf_component_register(SRCS "audio_amp_main.c")

View File

@@ -0,0 +1,382 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <inttypes.h>
#include <sdkconfig.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_dsp.h"
#include "bsp/esp-bsp.h"
#include <stdint.h>
#include <math.h>
// Buffer for reading/writing to I2S driver. Same length as SPIFFS buffer and I2S buffer, for optimal read/write performance.
// Recording audio data path:
// I2S peripheral -> I2S buffer (DMA) -> App buffer (RAM) -> SPIFFS buffer -> External SPI Flash.
// Vice versa for playback.
#define BUFFER_SIZE (64)
#define SAMPLE_RATE (16000) // For recording
#define DEFAULT_VOLUME (100)
// Globals
static const char *TAG = "example";
static QueueHandle_t audio_button_q = NULL;
static void btn_handler(void *button_handle, void *usr_data)
{
int button_pressed = (int)usr_data;
xQueueSend(audio_button_q, &button_pressed, 0);
}
// Very simple WAV header, ignores most fields
typedef struct __attribute__((packed))
{
uint8_t ignore_0[22];
uint16_t num_channels;
uint32_t sample_rate;
uint8_t ignore_1[6];
uint16_t bits_per_sample;
uint8_t ignore_2[4];
uint32_t data_size;
uint8_t data[];
} dumb_wav_header_t;
typedef enum audio_set {
AUDIO_VOLUME,
AUDIO_BASS,
AUDIO_TREBLE,
} audio_set_t;
static esp_codec_dev_handle_t spk_codec_dev = NULL;
static FILE *play_file = NULL;
// Pointer to a file that is going to be played
static const char play_filename[] = BSP_SPIFFS_MOUNT_POINT"/16bit_mono_44_1_khz.wav";
// Definition for all tasks
static void buttons_process_task(void *arg);
static void audio_read_task(void *arg);
static void audio_process_task(void *arg);
// Wave file header
static dumb_wav_header_t wav_header;
static audio_set_t current_set = AUDIO_VOLUME;
// Data for IIR filters
float iir_coeffs_lpf[5];
float iir_w_lpf[5] = {0, 0};
float iir_coeffs_hpf[5];
float iir_w_hpf[5] = {0, 0};
// IIR filters parameters
// Parameters for low-pass filter (LPF)
float lpf_gain = 0;
float lpf_qFactor = 0.5;
float lpf_freq = 0.01;
// Parameters for high-pass filter (HPF)
float hpf_gain = 0;
float hpf_qFactor = 1;
float hpf_freq = 0.15;
// Volume control definitions
float full_volume = 1;
// Volume in dB
int full_volume_db = -12;
// Digital limiter envelope value
float full_envelope = 0;
// processing audio buffer
float processing_audio_buffer[BUFFER_SIZE] = {0};
// The triple_audio_buffer contains tree data arrays sizeof BUFFER_SIZE, for writing audio data to the codec
int16_t triple_audio_buffer[3 * BUFFER_SIZE] = {0};
// The write index shows the audio buffer that will be used to write data to the codec
int audio_buffer_write_index = 0;
// The read index shows the audio buffer that will be used to fill data from the data source to the processing buffer
int audio_buffer_read_index = 0;
// Semaphore to synchronize the read and write
static SemaphoreHandle_t sync_read_task;
// Convert input int16_t Q15 values array to the float values array
static void convert_short2float(int16_t *int16_data, float *float_data, int len)
{
float multiplier = 1.0 / (float)(INT16_MAX + 1);
for (int i = 0 ; i < len ; i++) {
float_data[i] = (float)int16_data[i] * multiplier;
}
}
// Convert input float values to the int16_t Q15 values array
static void convert_float2short(float *float_data, int16_t *int16_data, int len)
{
float multiplier = (float)(INT16_MAX + 1);
for (int i = 0 ; i < len ; i++) {
int16_data[i] = (int16_t)((float)multiplier * (float)float_data[i]);
}
}
// In the audio processing the valid output audio values for the floating point format should be in range +/- 1.0,
// because these values will be converted to the 0x8000 and 0x7fff int16_t values, and will be accepted by the codec.
// With additional amplification, for example bass, treble, and for other processing, it is possible that the audio
// signal will reach the maximum values and will make an overflow at the DAC.
// To avoid this situation the digital limiter analyze the output values and control the full amplification gain.
//
void digitalLimiter(float *input_signal, float *output_signal, int signal_length, float threshold, float attack_value, float release_value, float *in_envelope)
{
float envelope = *in_envelope;
for (int i = 0; i < signal_length; i++) {
// Calculate envelope
float abs_input = fabsf(input_signal[i]);
if (abs_input > envelope) {
envelope = envelope * (1 - attack_value) + attack_value * abs_input;
} else {
envelope = envelope * (1 - release_value) + release_value * abs_input;
}
// Apply compression
if (envelope > threshold) {
output_signal[i] = input_signal[i] * (threshold / envelope);
} else {
output_signal[i] = input_signal[i];
}
}
*in_envelope = envelope;
}
static void audio_process_task(void *arg)
{
// Init codeac and apply the initial volume to maximum
spk_codec_dev = bsp_audio_codec_speaker_init();
assert(spk_codec_dev);
esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME);
// Open file and het the WAV header to set up the sample frequency, amount of channels and resolution
play_file = fopen(play_filename, "rb");
if (play_file == NULL) {
ESP_LOGW(TAG, "%s file does not exist!", play_filename);
}
// Read WAV header
if (fread((void *)&wav_header, 1, sizeof(wav_header), play_file) != sizeof(wav_header)) {
ESP_LOGW(TAG, "Error in reading file");
return;
}
ESP_LOGI(TAG, "Number of channels: %" PRIu16 "", wav_header.num_channels);
ESP_LOGI(TAG, "Bits per sample: %" PRIu16 "", wav_header.bits_per_sample);
ESP_LOGI(TAG, "Sample rate: %" PRIu32 "", wav_header.sample_rate);
ESP_LOGI(TAG, "Data size: %" PRIu32 "", wav_header.data_size);
esp_codec_dev_sample_info_t fs = {
.sample_rate = wav_header.sample_rate,
.channel = wav_header.num_channels,
.bits_per_sample = wav_header.bits_per_sample,
};
if (spk_codec_dev != NULL) {
int result = esp_codec_dev_open(spk_codec_dev, &fs);
}
// Calculate initial volume value
full_volume = exp10f((float)full_volume_db / 20);
// Calculate initial state for LPF
dsps_biquad_gen_lowShelf_f32(iir_coeffs_lpf, lpf_freq, lpf_gain, lpf_qFactor);
// Calculate initial state for HPF
dsps_biquad_gen_highShelf_f32(iir_coeffs_hpf, hpf_freq, hpf_gain, hpf_qFactor);
BaseType_t ret = xTaskCreate(buttons_process_task, "buttons_process_task", 4096, NULL, 4, NULL);
assert(ret == pdPASS);
sync_read_task = xSemaphoreCreateCounting(1, 0);
ret = xTaskCreate(audio_read_task, "audio_read_task", 4096, NULL, 7, NULL);
assert(ret == pdPASS);
vTaskDelay(1);
ESP_LOGW(TAG, "To select volume/bass/treble please use the 'Set' button. And adjust the value with +/- buttons.");
for (;;) {
/* Get data from SPIFFS and send it to codec */
int16_t *wav_bytes = &triple_audio_buffer[audio_buffer_write_index * BUFFER_SIZE];
// Write samples to audio codec
esp_codec_dev_write(spk_codec_dev, wav_bytes, BUFFER_SIZE * sizeof(int16_t));
audio_buffer_write_index++;
if (audio_buffer_write_index >= 3) {
audio_buffer_write_index = 0;
}
// Check the triple buffer overflow
if (audio_buffer_write_index == audio_buffer_read_index) {
// Call delay to switch the task to fill the buffer
vTaskDelay(1);
// Check and indicate overflow status
if (audio_buffer_write_index == audio_buffer_read_index) {
ESP_LOGW(TAG, "Audio buffer overflow!");
}
}
// Generate synt event to read task:
xSemaphoreGive(sync_read_task);
}
}
// The audio_read_task is responsible to read data from the file and fill it to the audio buffer.
// The audio buffer is places inside of triple buffer, to avoid overflow situation in case if
// the processing task busy for a while.
static void audio_read_task(void *arg)
{
while (1) {
// Wait the sync semaphore
if (xSemaphoreTake(sync_read_task, 100)) {
// Get the pointer to the current audio buffer
int16_t *wav_buffer = &triple_audio_buffer[audio_buffer_read_index * BUFFER_SIZE];
// Read the data from the file
uint32_t bytes_read_from_spiffs = fread(wav_buffer, sizeof(int16_t), BUFFER_SIZE, play_file);
// Convert input samples from int16 to float for processing
convert_short2float((int16_t *)wav_buffer, processing_audio_buffer, BUFFER_SIZE);
// Apply bass
dsps_biquad_f32(processing_audio_buffer, processing_audio_buffer, BUFFER_SIZE, iir_coeffs_lpf, iir_w_lpf);
// Apply treble
dsps_biquad_f32(processing_audio_buffer, processing_audio_buffer, BUFFER_SIZE, iir_coeffs_hpf, iir_w_hpf);
// Apply voluve
dsps_mulc_f32_ansi(processing_audio_buffer, processing_audio_buffer, BUFFER_SIZE, full_volume, 1, 1);
// Apply limiter
digitalLimiter(processing_audio_buffer, processing_audio_buffer, BUFFER_SIZE, 0.5, 0.5, 0.0001, &full_envelope);
// Convert from float to int16 for audio codec
convert_float2short(processing_audio_buffer, (int16_t *)wav_buffer, BUFFER_SIZE);
if (bytes_read_from_spiffs != BUFFER_SIZE) {
// Rewind the file and read the WAV header
rewind(play_file);
fread((void *)&wav_header, 1, sizeof(wav_header), play_file);
// Read data to the audio buffer
bytes_read_from_spiffs = fread(wav_buffer, sizeof(int16_t), BUFFER_SIZE, play_file);
}
audio_buffer_read_index++;
if (audio_buffer_read_index >= 3) {
audio_buffer_read_index = 0;
}
} else {
// Error in case of timeout
ESP_LOGE(TAG, "Audio timeout!");
}
}
}
static void buttons_process_task(void *arg)
{
while (1) {
int btn_index = 0;
if (xQueueReceive(audio_button_q, &btn_index, portMAX_DELAY) == pdTRUE) {
switch (btn_index) {
case BSP_BUTTON_SET: {
current_set += 1;
if (current_set > 2) {
current_set = AUDIO_VOLUME;
}
switch (current_set) {
case AUDIO_VOLUME:
ESP_LOGW(TAG, "Select volume");
break;
case AUDIO_BASS:
ESP_LOGW(TAG, "Select bass");
break;
case AUDIO_TREBLE:
ESP_LOGW(TAG, "Select treble");
break;
default:
break;
}
break;
}
case BSP_BUTTON_VOLDOWN: {
switch (current_set) {
case AUDIO_VOLUME:
full_volume_db -= 3;
if (full_volume_db < -36) {
full_volume_db = -36;
}
full_volume = exp10f((float)full_volume_db / 20);
ESP_LOGI(TAG, "Volume Down: %i dB", full_volume_db);
break;
case AUDIO_BASS:
lpf_gain -= 1;
if (lpf_gain < -12) {
lpf_gain = -12;
}
ESP_LOGI(TAG, "Bass Down: %i", (int)lpf_gain);
dsps_biquad_gen_lowShelf_f32(iir_coeffs_lpf, lpf_freq, lpf_gain, lpf_qFactor);
break;
case AUDIO_TREBLE:
hpf_gain -= 1;
if (hpf_gain < -12) {
hpf_gain = -12;
}
ESP_LOGI(TAG, "Treble Down: %i", (int)hpf_gain);
dsps_biquad_gen_highShelf_f32(iir_coeffs_hpf, hpf_freq, hpf_gain, hpf_qFactor);
break;
default:
break;
}
break;
}
case BSP_BUTTON_VOLUP: {
switch (current_set) {
case AUDIO_VOLUME:
full_volume_db += 3;
if (full_volume_db > 0) {
full_volume_db = 0;
}
full_volume = exp10f((float)full_volume_db / 20);
ESP_LOGI(TAG, "Volume Up: %i dB", full_volume_db);
break;
case AUDIO_BASS:
lpf_gain += 1;
if (lpf_gain > 12) {
lpf_gain = 12;
}
ESP_LOGI(TAG, "Bass Up: %i", (int)lpf_gain);
dsps_biquad_gen_lowShelf_f32(iir_coeffs_lpf, lpf_freq, lpf_gain, lpf_qFactor);
break;
case AUDIO_TREBLE:
hpf_gain += 1;
if (hpf_gain > 12) {
hpf_gain = 12;
}
ESP_LOGI(TAG, "Treble Up: %i", (int)hpf_gain);
dsps_biquad_gen_highShelf_f32(iir_coeffs_hpf, hpf_freq, hpf_gain, hpf_qFactor);
break;
default:
break;
}
break;
}
default:
ESP_LOGI(TAG, "No function for this button");
break;
}
}
}
}
void app_main(void)
{
ESP_ERROR_CHECK(bsp_spiffs_mount());
// Create FreeRTOS tasks and queues
audio_button_q = xQueueCreate(10, sizeof(int));
assert (audio_button_q != NULL);
BaseType_t ret = xTaskCreate(audio_process_task, "audio_process_task", 4096, NULL, 6, NULL);
assert(ret == pdPASS);
// Init audio buttons
button_handle_t btns[BSP_BUTTON_NUM];
ESP_ERROR_CHECK(bsp_iot_button_create(btns, NULL, BSP_BUTTON_NUM));
for (int i = 0; i < BSP_BUTTON_NUM; i++) {
ESP_ERROR_CHECK(iot_button_register_cb(btns[i], BUTTON_PRESS_DOWN, btn_handler, (void *) i));
}
}

View File

@@ -0,0 +1,5 @@
dependencies:
espressif/esp32_lyrat: "^1.0.0"
espressif/esp-dsp:
version: '*'
override_path: "../../../../esp-dsp"

View File

@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 0x2f0000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, spiffs, , 0x2f0000,

View File

@@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_USE_MEMMAP=y
CONFIG_SPIFFS_PAGE_SIZE=1024